TODO: 1. CLEANUP !!!!
2. CLONE_UNTRACE check is wrong 3. child->pid is wrong, we must use tracer's namespace Note: Ugly, but we have to add context->o_did_clone_vfork bolean, it is true if the last do_fork() had CLONE_VFORK. We can't figure out whether we need PTRACE_O_TRACEVFORKDONE at ptrace_report_clone() time, the tracer can change options after we report PTRACE_EVENT_VFORK. Passes 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); 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 | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) --- PU/kernel/ptrace.c~31_TRACEVFORKDONE 2009-09-12 01:04:59.000000000 +0200 +++ PU/kernel/ptrace.c 2009-09-13 21:22:24.000000000 +0200 @@ -32,6 +32,8 @@ struct ptrace_context { int stopped_code; resume_stopped_t resume_stopped; + + bool o_did_clone_vfork; // XXX: move into ->options }; static inline struct ptrace_context * @@ -56,6 +58,8 @@ void __ptrace_link(struct task_struct *c static const struct utrace_engine_ops ptrace_utrace_ops; /* forward decl */ static int ptrace_attach_task(struct task_struct *tracee, int options); static void ptrace_abort_attach(struct task_struct *tracee); +static void ptrace_wake_up(struct utrace_engine *engine, + struct task_struct *tracee, enum utrace_resume_action action); static void ptrace_detach_task(struct task_struct *child, int sig) { @@ -221,6 +225,35 @@ static void ptrace_clone_attach(struct t set_tsk_thread_flag(child, TIF_SIGPENDING); } +static void ptrace_resume_vfork_done(struct utrace_engine *engine, + struct task_struct *tracee, long data) +{ + ptrace_wake_up(engine, tracee, UTRACE_RESUME); +} + +static bool ck_o_tracevforkdone(struct utrace_engine *engine, + struct task_struct *tracee) +{ + struct ptrace_context *context = ptrace_context(engine); + + if (!(context->o_did_clone_vfork && + (context->options & PTRACE_O_TRACEVFORKDONE))) + return false; + + context->resume_stopped = ptrace_resume_vfork_done; + context->stopped_code = (PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP; + + utrace_control(tracee, engine, UTRACE_REPORT); + return true; +} + +static void ptrace_resume_clone(struct utrace_engine *engine, + struct task_struct *tracee, long data) +{ + ck_o_tracevforkdone(engine, tracee); + ptrace_wake_up(engine, tracee, UTRACE_RESUME); +} + static u32 ptrace_report_clone(enum utrace_resume_action action, struct utrace_engine *engine, struct task_struct *parent, @@ -230,11 +263,16 @@ static u32 ptrace_report_clone(enum utra struct ptrace_context *context = ptrace_context(engine); int event; + WARN_ON(context->resume_stopped); + WARN_ON(context->stopped_code); + if (clone_flags & CLONE_UNTRACED) return UTRACE_RESUME; event = 0; + context->o_did_clone_vfork = false; if (clone_flags & CLONE_VFORK) { + context->o_did_clone_vfork = true; if (context->options & PTRACE_O_TRACEVFORK) event = PTRACE_EVENT_VFORK; } else if ((clone_flags & CSIGNAL) != SIGCHLD) { @@ -251,8 +289,16 @@ static u32 ptrace_report_clone(enum utra if (event || (clone_flags & CLONE_PTRACE)) ptrace_clone_attach(parent, child, context->options); - if (event) - return utrace_ptrace_event(parent, event, child->pid); + // XXX: child->pid is wrong! use tracer's pid_ns + if (event) { + parent->ptrace_message = child->pid; + context->resume_stopped = ptrace_resume_clone; + context->stopped_code = (event << 8) | SIGTRAP; + + return UTRACE_STOP; + } else if (ck_o_tracevforkdone(engine, parent)) { + parent->ptrace_message = child->pid; + } return UTRACE_RESUME; }