Thank you for your feedback.

> The real issue with debuggers is that ptrace is a pretty broken API.

Please note that this article focuses on NetBSD and FreeBSD first.

As your comment describes only one OS (Linux) please do not generalize as your comment seems to use truth sparingly.

The ptrace(2)/NetBSD API design and implementation is free from all of the difficulties you mentioned in your post.

> Supporting things like spawning threads, forking processes, fork+exec, etc. is difficult, and full of race conditions that are difficult to code correctly.

The difficulty of catching LWP creation events:

ptrace_event_t event = {};
event.pe_set_event = PTRACE_LWP_CREATE;
ptrace(PT_SET_EVENT_MASK, child, &event, sizeof(event))

Then whenever a debuggee creates a child, it's fully stopped (so called all-stop mode from GDB) and reported to the debugger by sending a signal that is wait(2)ed.

Then, investigate the debuggee event through checking the signal passed (SIGTRAP) and investigating siginfo_t that contains new thread identifier.

Then, you can resume the whole process with a single PT_CONTINUE.

> forking processes

Same for forking, use PT_SET_EVENT_MASK+PTRACE_FORK. Fork events are reported for the forking parent and forked child. As you poll on events on a single PID only (for all events for all threads within a process), you have the deterministic order of reporting the forked parent first always, followed by polling for the forked child (you know its PID from SIGTRAP + siginfo_t submitted to the parent).

> fork+exec

This is a matter of catching EXEC and FORK events separately. All exec() events are reported as SIGTRAP + siginfo_t specifying TRAP_EXEC. No big deal.

> is difficult, and full of race conditions that are difficult to code correctly

I push this comment to the free market of opinions of the readers.

> Attaching to running multithreaded processes is another challenge.

It's 1-liner always:

ptrace(PT_ATTACH, pid, NULL, 0);

No matter whether this is a single-threaded or multi-threaded process.

> Writing a debugger that can correctly handle multithreaded applications is challenging,

Again, I defer this question to the free market of opinions.

> the documentation gives you zero insight into what the potential pitfalls are,

Please list the pitfails so we can improve the documentation!

> and almost all examples are similarly uninformative, being too complex for their use case.

There are a few hundreds of ptrace programs in NetBSD executing each small feature in minimal code. This is embedded into the regression test framework (ATF). This code can be reused (good license + simple) in 3rd party software.

For external examples, I recommend the most minimal event tracker of debuggers, that I wrote here:

https://github.com/krytarowski/picotrace

In particular, you can trace all events possible in all types of programs (at least in the current version of ptrace(2)) in around 300 LOC, as noted here:

https://github.com/krytarowski/picotrace/blob/master/common/...

FreeBSD has a distinct ptrace(2) API, but not far from NetBSD and is relatively comparable and quickly portable from one BSD to another.

If you have got any more questions or comments, do not hesitate to ask!