If the tracee sets PTRACE_O_TRACEVFORK and the tracee does vfork(), the tracee stops inside do_fork() and reports PTRACE_EVENT_VFORK.
If we need to report PTRACE_EVENT_VFORK_DONE after that, ptrace_resume() can use ptrace_wake_up() or just report the stacked event. We need to wake up the tracee, it should pass wait_for_completion() and then stop and report before return to user-mode. So. change ptrace_resume() to do set_stop_code() + utrace_control(REPORT) like ptrace_report_clone() does, we rely on ->report_quiesce() which must notice ptrace_event_pending() and return UTRACE_STOP. And inow we pass this test: #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h> #include <errno.h> #include <sys/wait.h> #include <sys/ptrace.h> #include <assert.h> #define WEVENT(s) ((s & 0xFF0000) >> 16) static unsigned long __step(int line, int pid, int o, int e) { int stat, cont = PTRACE_CONT; unsigned long msg; if (o == PTRACE_SYSCALL) { cont = o; } else if (o) { assert(0 == ptrace(PTRACE_SETOPTIONS, pid, 0, o)); } assert(0 == ptrace(cont, pid, 0, 0)); assert(waitpid(pid, &stat, __WALL) == pid); // assert(WIFSTOPPED(stat)); // assert(WSTOPSIG(stat) == SIGTRAP); if (!WIFSTOPPED(stat) || WSTOPSIG(stat) != SIGTRAP) { printf("ERR!! %3d: %06X\n", line, stat); exit(1); } assert(WEVENT(stat) == e); assert(0 == ptrace(PTRACE_GETEVENTMSG, pid, 0, &msg)); return msg; } #define step(pid, o, e) __step(__LINE__, pid, o, e) #define T_FORK_A 0x01 #define T_FORK_Z 0x02 #define T_SYSCALL 0x04 #define T_SIGNAL 0x08 #define T_ALL (T_FORK_A | T_FORK_Z | T_SYSCALL | T_SIGNAL) static void p_todo(int todo) { printf("todo: "); #define P(n) if (todo & n) printf(#n " ") P(T_FORK_A); P(T_FORK_Z); P(T_SYSCALL); P(T_SIGNAL); #undef P if (todo & ~T_ALL) printf("ERR!!!"); printf("\n"); } static void tst(int todo) { int pid, cpid=-1, stat; long msg; p_todo(todo); pid = fork(); if (!pid) { int vp; if (!(todo & T_SIGNAL)) signal(SIGCHLD, SIG_IGN); assert(0 == ptrace(PTRACE_TRACEME, 0,0,0)); kill(getpid(), SIGSTOP); vp = vfork(); exit(0x43); } assert(wait(&stat) == pid); assert(WIFSTOPPED(stat) && WSTOPSIG(stat) == SIGSTOP); if (todo & T_SYSCALL) { msg = step(pid, PTRACE_SYSCALL, 0); assert(msg == 0); } if (todo & T_FORK_A) { cpid = step(pid, PTRACE_O_TRACEVFORK, PTRACE_EVENT_VFORK); assert(waitpid(cpid, &stat, __WALL) == cpid); assert(WIFSTOPPED(stat)); assert(WSTOPSIG(stat) == SIGSTOP); assert(0 == ptrace(PTRACE_CONT, cpid, 0, 0)); assert(waitpid(cpid, &stat, __WALL) == cpid); assert(WIFEXITED(stat)); } if (todo & T_FORK_Z) { msg = step(pid, PTRACE_O_TRACEVFORKDONE, PTRACE_EVENT_VFORK_DONE); if (todo & T_FORK_A) assert(msg == cpid); } if (todo & T_SIGNAL) { assert(0 == ptrace(PTRACE_CONT, pid, 0, 0)); assert(waitpid(pid, &stat, __WALL) == pid); if (!(todo & T_FORK_A) && stat == 0x4300) goto parent_exited_first; assert(WIFSTOPPED(stat)); assert(WSTOPSIG(stat) == SIGCHLD); } if (todo & T_SYSCALL) { msg = step(pid, PTRACE_SYSCALL, 0); if (todo & T_FORK_A) assert(msg == cpid); } assert(0 == ptrace(PTRACE_CONT, pid, 0, 0)); assert(waitpid(pid, &stat, __WALL) == pid); assert(WIFEXITED(stat)); parent_exited_first: assert(waitpid(-1, &stat, __WALL) == -1); assert(errno == ECHILD); } int main(int argc, char* argv[]) { int todo; if (argc > 1) { todo = atoi(argv[1]); tst(todo); } else { int todo; for (todo = 0; todo <= T_ALL; ++todo) tst(todo); } return 0; } --- kernel/ptrace.c | 10 ++++++++++ 1 file changed, 10 insertions(+) --- PU/kernel/ptrace.c~99_VFORK_THEN_VFORKDONE 2009-10-17 19:46:42.000000000 +0200 +++ PU/kernel/ptrace.c 2009-10-17 19:50:54.000000000 +0200 @@ -975,11 +975,21 @@ static void do_ptrace_resume(struct utra case 0: // XXX: JCTL stop break; + + case PTRACE_EVENT_VFORK: + if (context->options & PTRACE_O_TRACEVFORKDONE) { + set_stop_code(context, PTRACE_EVENT_VFORK_DONE); + utrace_control(tracee, engine, UTRACE_REPORT); + return; + } + break; + case PTRACE_EVENT_SYSCALL_ENTRY: case PTRACE_EVENT_SYSCALL_EXIT: if (data) send_sig_info(data, SEND_SIG_PRIV, tracee); break; + case PTRACE_EVENT_SIGNAL: context->signr = data; break;