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;
 }

Reply via email to