I was working on something similar recently. I suspect you've solved your problem long ago or gave up, but let's write an answer here for posterity.
The various events you register with PTRACE_SETOPTIONS
generate messages different from the normal ptrace
events. But the normal events are still generated. One normal event is that a newly forked process starts stopped and has to be continued from the tracer.
This means that if you have registered events you watch with PTRACE_O_TRACEFORK
(or VFORK) waitpid
will trigger twice for the same process after a fork.
One will be with a status that is:
WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == SIGSTOP)
The other one will be with:
WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == 0) &&
((status >> 16) == PTRACE_EVENT_FORK) /* or VFORK */
There does not seem to be any guarantee from the kernel in which order they will arrive. I found it being close to 50/50 on my system.
To handle this my code looks something like this:
static void
proc_register(struct magic *pwi, pid_t pid, bool fork) {
/*
* When a new process starts two things happen:
* - We get a wait with STOPPED, SIGTRAP, PTRACE_EVENT_{CLONE,FORK,VFORK}
* - We get a wait with STOPPED, SIGSTOP
*
* Those can come in any order, so to get the proc in the right
* state this function should be called twice on every new proc. If
* it's called with fork first, we set the state to NEW_FORKED, if
* it's called with STOP first, we set NEW_STOPPED. Then when the
* other call comes, we set the state to TRACED and continue the
* process.
*/
if ((p = find_proc(pwi, pid)) == NULL) {
p = calloc(1, sizeof(*p));
p->pid = pid;
TAILQ_INSERT_TAIL(&pwi->procs, p, list);
if (fork) {
p->state = NEW_FORKED;
} else {
p->state = NEW_STOPPED;
}
} else {
assert((fork && p->state == NEW_STOPPED) || (!fork && p->state == NEW_FORKED));
p->state = TRACED;
int flags = PTRACE_O_TRACEEXEC|PTRACE_O_TRACEEXIT|PTRACE_O_TRACEFORK|PTRACE_O_TRACEVFORK;
if (ptrace(PTRACE_SETOPTIONS, pid, NULL, flags))
err(1, "ptrace(SETOPTIONS, %d)", pid);
if (ptrace(PTRACE_CONT, pid, NULL, signal) == -1)
err(1, "ptrace(CONT, %d, %d)", pid, signal);
}
}
[...]
pid = waitpid(-1, &status, __WALL);
if (WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == SIGSTOP)) {
proc_register(magic, pid, false);
} else if (WIFSTOPPED(status) && (WSTOPSIG(status) & 0xff == 0) && ((status >> 16) == PTRACE_EVENT_FORK)) {
proc_register(magic, pid, true);
} else {
/* ... */
}
The key to making this work was to not send PTRACE_CONT
until we receive both events. When figuring out how this works I was sending PTRACE_CONT
way too much and the kernel happily accepted them which sometimes even led to my processes exiting long before PTRACE_EVENT_FORK
arrived. This made it quite hard to debug.
N.B. I haven't found any documentation about this or anything saying that this is the way it should be. I just found out that this makes things work as things are today. YMMV.