From: Wang Chao Date: Fri, 12 Nov 2010 09:26:08 +0000 (+0800) Subject: Handle followfork using ptrace_setoptions if available X-Git-Tag: v4.6~76 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ca8ab8d2958f86297a6574a73cc1b9759d59c9b1;p=strace Handle followfork using ptrace_setoptions if available If PTRACE_O_TRACECLONE et al options are supported by kernel, use them to do followfork rather than the original setbpt method that changes registers ourselves. * defs.h [LINUX] (handle_new_child): New function prototype. * process.c [LINUX] (handle_new_child): New function based on the code from internal_fork(), with a trivial change: do reparent only for sys_clone. [LINUX] (internal_fork): Use handle_new_child(). Do nothing if ptrace_setoptions is in effect. * strace.c [LINUX] (handle_ptrace_event): New function. [LINUX] (trace): If ptrace_setoptions is in effect, then call the new function to handle PTRACE_EVENT_* status, and set PTRACE_SETOPTIONS when we see the initial stop of tracee. Signed-off-by: Wang Chao --- diff --git a/defs.h b/defs.h index eee47109..7a7a071b 100644 --- a/defs.h +++ b/defs.h @@ -571,6 +571,9 @@ extern int internal_fork(struct tcb *); extern int internal_exec(struct tcb *); extern int internal_wait(struct tcb *, int); extern int internal_exit(struct tcb *); +#ifdef LINUX +extern int handle_new_child(struct tcb *, int, int); +#endif extern const struct ioctlent *ioctl_lookup(long); extern const struct ioctlent *ioctl_next_match(const struct ioctlent *); diff --git a/process.c b/process.c index 2366d1f3..7b3eda1d 100644 --- a/process.c +++ b/process.c @@ -792,9 +792,127 @@ change_syscall(struct tcb *tcp, int new) } #ifdef LINUX +int +handle_new_child(struct tcb *tcp, int pid, int bpt) +{ + struct tcb *tcpchild; + +#ifdef CLONE_PTRACE /* See new setbpt code. */ + tcpchild = pid2tcb(pid); + if (tcpchild != NULL) { + /* The child already reported its startup trap + before the parent reported its syscall return. */ + if ((tcpchild->flags + & (TCB_STARTUP|TCB_ATTACHED|TCB_SUSPENDED)) + != (TCB_STARTUP|TCB_ATTACHED|TCB_SUSPENDED)) + fprintf(stderr, "\ +[preattached child %d of %d in weird state!]\n", + pid, tcp->pid); + } + else +#endif /* CLONE_PTRACE */ + { + fork_tcb(tcp); + tcpchild = alloctcb(pid); + } + +#ifndef CLONE_PTRACE + /* Attach to the new child */ + if (ptrace(PTRACE_ATTACH, pid, (char *) 1, 0) < 0) { + if (bpt) + clearbpt(tcp); + perror("PTRACE_ATTACH"); + fprintf(stderr, "Too late?\n"); + droptcb(tcpchild); + return 0; + } +#endif /* !CLONE_PTRACE */ + + if (bpt) + clearbpt(tcp); + + tcpchild->flags |= TCB_ATTACHED; + /* Child has BPT too, must be removed on first occasion. */ + if (bpt) { + tcpchild->flags |= TCB_BPTSET; + tcpchild->baddr = tcp->baddr; + memcpy(tcpchild->inst, tcp->inst, + sizeof tcpchild->inst); + } + tcpchild->parent = tcp; + tcp->nchildren++; + if (tcpchild->flags & TCB_SUSPENDED) { + /* The child was born suspended, due to our having + forced CLONE_PTRACE. */ + if (bpt) + clearbpt(tcpchild); + + tcpchild->flags &= ~(TCB_SUSPENDED|TCB_STARTUP); + if (ptrace_restart(PTRACE_SYSCALL, tcpchild, 0) < 0) + return -1; + + if (!qflag) + fprintf(stderr, "\ +Process %u resumed (parent %d ready)\n", + pid, tcp->pid); + } + else { + if (!qflag) + fprintf(stderr, "Process %d attached\n", pid); + } + +#ifdef TCB_CLONE_THREAD + if (sysent[tcp->scno].sys_func == sys_clone) + { + /* + * Save the flags used in this call, + * in case we point TCP to our parent below. + */ + int call_flags = tcp->u_arg[ARG_FLAGS]; + if ((tcp->flags & TCB_CLONE_THREAD) && + tcp->parent != NULL) { + /* The parent in this clone is itself a + thread belonging to another process. + There is no meaning to the parentage + relationship of the new child with the + thread, only with the process. We + associate the new thread with our + parent. Since this is done for every + new thread, there will never be a + TCB_CLONE_THREAD process that has + children. */ + --tcp->nchildren; + tcp = tcp->parent; + tcpchild->parent = tcp; + ++tcp->nchildren; + } + if (call_flags & CLONE_THREAD) { + tcpchild->flags |= TCB_CLONE_THREAD; + ++tcp->nclone_threads; + } + if ((call_flags & CLONE_PARENT) && + !(call_flags & CLONE_THREAD)) { + --tcp->nchildren; + tcpchild->parent = NULL; + if (tcp->parent != NULL) { + tcp = tcp->parent; + tcpchild->parent = tcp; + ++tcp->nchildren; + } + } + } +#endif /* TCB_CLONE_THREAD */ + return 0; +} + int internal_fork(struct tcb *tcp) { + if ((ptrace_setoptions + & (PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK)) + == (PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK)) + return 0; + if (entering(tcp)) { tcp->flags &= ~TCB_FOLLOWFORK; if (!followfork) @@ -811,7 +929,6 @@ internal_fork(struct tcb *tcp) if (setbpt(tcp) < 0) return 0; } else { - struct tcb *tcpchild; int pid; int bpt; @@ -828,110 +945,7 @@ internal_fork(struct tcb *tcp) pid = tcp->u_rval; -#ifdef CLONE_PTRACE /* See new setbpt code. */ - tcpchild = pid2tcb(pid); - if (tcpchild != NULL) { - /* The child already reported its startup trap - before the parent reported its syscall return. */ - if ((tcpchild->flags - & (TCB_STARTUP|TCB_ATTACHED|TCB_SUSPENDED)) - != (TCB_STARTUP|TCB_ATTACHED|TCB_SUSPENDED)) - fprintf(stderr, "\ -[preattached child %d of %d in weird state!]\n", - pid, tcp->pid); - } - else -#endif /* CLONE_PTRACE */ - { - fork_tcb(tcp); - tcpchild = alloctcb(pid); - } - -#ifndef CLONE_PTRACE - /* Attach to the new child */ - if (ptrace(PTRACE_ATTACH, pid, (char *) 1, 0) < 0) { - if (bpt) - clearbpt(tcp); - perror("PTRACE_ATTACH"); - fprintf(stderr, "Too late?\n"); - droptcb(tcpchild); - return 0; - } -#endif /* !CLONE_PTRACE */ - - if (bpt) - clearbpt(tcp); - - tcpchild->flags |= TCB_ATTACHED; - /* Child has BPT too, must be removed on first occasion. */ - if (bpt) { - tcpchild->flags |= TCB_BPTSET; - tcpchild->baddr = tcp->baddr; - memcpy(tcpchild->inst, tcp->inst, - sizeof tcpchild->inst); - } - tcpchild->parent = tcp; - tcp->nchildren++; - if (tcpchild->flags & TCB_SUSPENDED) { - /* The child was born suspended, due to our having - forced CLONE_PTRACE. */ - if (bpt) - clearbpt(tcpchild); - - tcpchild->flags &= ~(TCB_SUSPENDED|TCB_STARTUP); - if (ptrace_restart(PTRACE_SYSCALL, tcpchild, 0) < 0) - return -1; - - if (!qflag) - fprintf(stderr, "\ -Process %u resumed (parent %d ready)\n", - pid, tcp->pid); - } - else { - if (!qflag) - fprintf(stderr, "Process %d attached\n", pid); - } - -#ifdef TCB_CLONE_THREAD - { - /* - * Save the flags used in this call, - * in case we point TCP to our parent below. - */ - int call_flags = tcp->u_arg[ARG_FLAGS]; - if ((tcp->flags & TCB_CLONE_THREAD) && - tcp->parent != NULL) { - /* The parent in this clone is itself a - thread belonging to another process. - There is no meaning to the parentage - relationship of the new child with the - thread, only with the process. We - associate the new thread with our - parent. Since this is done for every - new thread, there will never be a - TCB_CLONE_THREAD process that has - children. */ - --tcp->nchildren; - tcp = tcp->parent; - tcpchild->parent = tcp; - ++tcp->nchildren; - } - if (call_flags & CLONE_THREAD) { - tcpchild->flags |= TCB_CLONE_THREAD; - ++tcp->nclone_threads; - } - if ((call_flags & CLONE_PARENT) && - !(call_flags & CLONE_THREAD)) { - --tcp->nchildren; - tcpchild->parent = NULL; - if (tcp->parent != NULL) { - tcp = tcp->parent; - tcpchild->parent = tcp; - ++tcp->nchildren; - } - } - } -#endif /* TCB_CLONE_THREAD */ + return handle_new_child(tcp, pid, bpt); } return 0; } diff --git a/strace.c b/strace.c index 09aedacd..6f63ee9a 100644 --- a/strace.c +++ b/strace.c @@ -2363,6 +2363,31 @@ handle_group_exit(struct tcb *tcp, int sig) } #endif +#ifdef LINUX +static int +handle_ptrace_event(int status, struct tcb *tcp) +{ + if (status >> 16 == PTRACE_EVENT_VFORK || + status >> 16 == PTRACE_EVENT_CLONE || + status >> 16 == PTRACE_EVENT_FORK) { + int childpid; + + if (do_ptrace(PTRACE_GETEVENTMSG, tcp, NULL, &childpid) < 0) { + if (errno != ESRCH) { + fprintf(stderr, "\ +%s: handle_ptrace_event: ptrace cannot get new child's pid\n", + progname); + cleanup(); + exit(1); + } + return -1; + } + return handle_new_child(tcp, childpid, 0); + } + return 1; +} +#endif + static int trace() { @@ -2548,6 +2573,11 @@ Process %d attached (waiting for parent)\n", fprintf(stderr, "pid %u stopped, [%s]\n", pid, signame(WSTOPSIG(status))); + if (ptrace_setoptions && (status >> 16)) { + if (handle_ptrace_event(status, tcp) != 1) + goto tracing; + } + /* * Interestingly, the process may stop * with STOPSIG equal to some other signal @@ -2575,6 +2605,13 @@ Process %d attached (waiting for parent)\n", return -1; } } +#ifdef LINUX + if (followfork && (tcp->parent == NULL) && ptrace_setoptions) + if (ptrace(PTRACE_SETOPTIONS, tcp->pid, + NULL, ptrace_setoptions) < 0 && + errno != ESRCH) + ptrace_setoptions = 0; +#endif goto tracing; }