X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=strace.c;h=aae7505f4e47c4d3f71b7030feb2f6bdb5fcf33e;hb=8224758b339b23f7b4388d84b68dd032045af5e6;hp=ad29bb40003bfe9355032820ed0d16235da7f4bc;hpb=235067525cc064a9a466c245fa8a6265ae136306;p=strace diff --git a/strace.c b/strace.c index ad29bb40..aae7505f 100644 --- a/strace.c +++ b/strace.c @@ -39,6 +39,9 @@ #include #include #include +#ifdef HAVE_PRCTL +# include +#endif #if defined(IA64) # include #endif @@ -47,6 +50,10 @@ extern char **environ; extern int optind; extern char *optarg; +#ifdef USE_LIBUNWIND +/* if this is true do the stack trace for every system call */ +bool stack_trace_enabled = false; +#endif #if defined __NR_tkill # define my_tkill(tid, sig) syscall(__NR_tkill, (tid), (sig)) @@ -54,24 +61,32 @@ extern char *optarg; /* kill() may choose arbitrarily the target task of the process group while we later wait on a that specific TID. PID process waits become TID task specific waits for a process under ptrace(2). */ -# warning "Neither tkill(2) nor tgkill(2) available, risk of strace hangs!" +# warning "tkill(2) not available, risk of strace hangs!" # define my_tkill(tid, sig) kill((tid), (sig)) #endif -#undef KERNEL_VERSION -#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) +/* Glue for systems without a MMU that cannot provide fork() */ +#if !defined(HAVE_FORK) +# undef NOMMU_SYSTEM +# define NOMMU_SYSTEM 1 +#endif +#if NOMMU_SYSTEM +# define fork() vfork() +#endif cflag_t cflag = CFLAG_NONE; unsigned int followfork = 0; unsigned int ptrace_setoptions = 0; unsigned int xflag = 0; +bool need_fork_exec_workarounds = 0; bool debug_flag = 0; bool Tflag = 0; -bool qflag = 0; +bool iflag = 0; +bool count_wallclock = 0; +unsigned int qflag = 0; /* Which WSTOPSIG(status) value marks syscall traps? */ static unsigned int syscall_trap_sig = SIGTRAP; static unsigned int tflag = 0; -static bool iflag = 0; static bool rflag = 0; static bool print_pid_pfx = 0; @@ -102,7 +117,7 @@ static int opt_intr; */ static bool daemonized_tracer = 0; -#ifdef USE_SEIZE +#if USE_SEIZE static int post_attach_sigstop = TCB_IGNORE_ONE_SIGSTOP; # define use_seize (post_attach_sigstop == 0) #else @@ -114,13 +129,13 @@ static int post_attach_sigstop = TCB_IGNORE_ONE_SIGSTOP; bool not_failing_only = 0; /* Show path associated with fd arguments */ -bool show_fd_path = 0; - -/* are we filtering traces based on paths? */ -bool tracing_paths = 0; +unsigned int show_fd_path = 0; static bool detach_on_execve = 0; -static bool skip_startup_execve = 0; +/* Are we "strace PROG" and need to skip detach on first execve? */ +static bool skip_one_b_execve = 0; +/* Are we "strace PROG" and need to hide everything until execve? */ +bool hide_log_until_execve = 0; static int exit_code = 0; static int strace_child = 0; @@ -131,20 +146,23 @@ static uid_t run_uid; static gid_t run_gid; unsigned int max_strlen = DEFAULT_STRLEN; -static unsigned int acolumn = DEFAULT_ACOLUMN; +static int acolumn = DEFAULT_ACOLUMN; static char *acolumn_spaces; + static char *outfname = NULL; -static FILE *outf; +/* If -ff, points to stderr. Else, it's our common output log */ +static FILE *shared_log; + struct tcb *printing_tcp = NULL; -static unsigned int curcol; +static struct tcb *current_tcp; + static struct tcb **tcbtab; static unsigned int nprocs, tcbtabsize; static const char *progname; -static unsigned os_release; /* generated from uname()'s u.release */ +unsigned os_release; /* generated from uname()'s u.release */ -static int detach(struct tcb *tcp); -static int trace(void); +static void detach(struct tcb *tcp); static void cleanup(void); static void interrupt(int sig); static sigset_t empty_set, blocked_set; @@ -180,35 +198,36 @@ static void usage(FILE *ofp, int exitval) { fprintf(ofp, "\ -usage: strace [-CdDffhiqrtttTvVxxy] [-I n] [-a column] [-e expr]... [-o file]\n\ - [-p pid]... [-s strsize] [-u username] [-E var=val]...\n\ - [-P path] [PROG [ARGS]]\n\ - or: strace -c [-D] [-I n] [-e expr]... [-O overhead] [-S sortby] [-E var=val]...\n\ - [PROG [ARGS]]\n\ +usage: strace [-CdffhiqrtttTvVxxy] [-I n] [-e expr]...\n\ + [-a column] [-o file] [-s strsize] [-P path]...\n\ + -p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]\n\ + or: strace -c[df] [-I n] [-e expr]... [-O overhead] [-S sortby]\n\ + -p pid... / [-D] [-E var=val]... [-u username] PROG [ARGS]\n\ -c -- count time, calls, and errors for each syscall and report summary\n\ --C -- like -c but also print regular output while processes are running\n\ +-C -- like -c but also print regular output\n\ +-w -- summarise syscall latency (default is system time)\n\ -d -- enable debug output to stderr\n\ -D -- run tracer process as a detached grandchild, not as parent\n\ -f -- follow forks, -ff -- with output into separate files\n\ --F -- attempt to follow vforks (deprecated, use -f)\n\ -i -- print instruction pointer at time of syscall\n\ --I interruptible\n\ - 1: no signals are blocked\n\ - 2: fatal signals are blocked while decoding syscall (default)\n\ - 3: fatal signals are always blocked (default if '-o FILE PROG')\n\ - 4: fatal signals and SIGTSTP (^Z) are always blocked\n\ - (useful to make 'strace -o FILE PROG' not stop on ^Z)\n\ -q -- suppress messages about attaching, detaching, etc.\n\ -r -- print relative timestamp, -t -- absolute timestamp, -tt -- with usecs\n\ -T -- print time spent in each syscall\n\ -v -- verbose mode: print unabbreviated argv, stat, termios, etc. args\n\ -x -- print non-ascii strings in hex, -xx -- print all strings in hex\n\ -y -- print paths associated with file descriptor arguments\n\ --h -- print help message\n\ --V -- print version\n\ +-yy -- print ip:port pairs associated with socket file descriptors\n\ +-h -- print help message, -V -- print version\n\ -a column -- alignment COLUMN for printing syscall results (default %d)\n\ +-b execve -- detach on this syscall\n\ -e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\ - options: trace, abbrev, verbose, raw, signal, read, or write\n\ + options: trace, abbrev, verbose, raw, signal, read, write\n\ +-I interruptible --\n\ + 1: no signals are blocked\n\ + 2: fatal signals are blocked while decoding syscall (default)\n\ + 3: fatal signals are always blocked (default if '-o FILE PROG')\n\ + 4: fatal signals and SIGTSTP (^Z) are always blocked\n\ + (useful to make 'strace -o FILE PROG' not stop on ^Z)\n\ -o file -- send trace output to FILE instead of stderr\n\ -O overhead -- set overhead for tracing syscalls to OVERHEAD usecs\n\ -p pid -- trace process with process id PID, may be repeated\n\ @@ -219,12 +238,16 @@ usage: strace [-CdDffhiqrtttTvVxxy] [-I n] [-a column] [-e expr]... [-o file]\n\ -E var -- remove var from the environment for command\n\ -P path -- trace accesses to path\n\ " +#ifdef USE_LIBUNWIND +"-k obtain stack trace between each syscall (experimental)\n\ +" +#endif +/* ancient, no one should use it +-F -- attempt to follow vforks (deprecated, use -f)\n\ + */ /* this is broken, so don't document it -z -- print only succeeding syscalls\n\ */ -/* experimental, don't document it yet (option letter may change in the future!) --b -- detach on successful execve\n\ - */ , DEFAULT_ACOLUMN, DEFAULT_STRLEN, DEFAULT_SORTBY); exit(exitval); } @@ -311,25 +334,23 @@ void die_out_of_memory(void) error_msg_and_die("Out of memory"); } -/* Glue for systems without a MMU that cannot provide fork() */ -#ifdef HAVE_FORK -# define strace_vforked 0 -#else -# define strace_vforked 1 -# define fork() vfork() -#endif +static void +error_opt_arg(int opt, const char *arg) +{ + error_msg_and_die("Invalid -%c argument: '%s'", opt, arg); +} -#ifdef USE_SEIZE +#if USE_SEIZE static int ptrace_attach_or_seize(int pid) { int r; if (!use_seize) - return ptrace(PTRACE_ATTACH, pid, 0, 0); - r = ptrace(PTRACE_SEIZE, pid, 0, PTRACE_SEIZE_DEVEL); + return ptrace(PTRACE_ATTACH, pid, 0L, 0L); + r = ptrace(PTRACE_SEIZE, pid, 0L, (unsigned long)ptrace_setoptions); if (r) return r; - r = ptrace(PTRACE_INTERRUPT, pid, 0, 0); + r = ptrace(PTRACE_INTERRUPT, pid, 0L, 0L); return r; } #else @@ -373,7 +394,7 @@ ptrace_restart(int op, struct tcb *tcp, int sig) * 10252 died after we retrieved syscall exit data, * but before we tried to restart it. Log looks ugly. */ - if (curcol != 0) { + if (current_tcp && current_tcp->curcol != 0) { tprintf(" \n", msg, strerror(err)); line_ended(); } @@ -428,10 +449,26 @@ swap_uid(void) } } -#if _LFS64_LARGEFILE -# define fopen_for_output fopen64 +#ifdef _LARGEFILE64_SOURCE +# ifdef HAVE_FOPEN64 +# define fopen_for_output fopen64 +# else +# define fopen_for_output fopen +# endif +# define struct_stat struct stat64 +# define stat_file stat64 +# define struct_dirent struct dirent64 +# define read_dir readdir64 +# define struct_rlimit struct rlimit64 +# define set_rlimit setrlimit64 #else # define fopen_for_output fopen +# define struct_stat struct stat +# define stat_file stat +# define struct_dirent struct dirent +# define read_dir readdir +# define struct_rlimit struct rlimit +# define set_rlimit setrlimit #endif static FILE * @@ -463,6 +500,7 @@ static FILE * strace_popen(const char *command) { FILE *fp; + int pid; int fds[2]; swap_uid(); @@ -471,11 +509,11 @@ strace_popen(const char *command) set_cloexec_flag(fds[1]); /* never fails */ - popen_pid = vfork(); - if (popen_pid == -1) + pid = vfork(); + if (pid < 0) perror_msg_and_die("vfork"); - if (popen_pid == 0) { + if (pid == 0) { /* child */ close(fds[1]); if (fds[0] != 0) { @@ -488,6 +526,7 @@ strace_popen(const char *command) } /* parent */ + popen_pid = pid; close(fds[0]); swap_uid(); fp = fdopen(fds[1], "w"); @@ -502,42 +541,46 @@ tprintf(const char *fmt, ...) va_list args; va_start(args, fmt); - if (outf) { - int n = vfprintf(outf, fmt, args); + if (current_tcp) { + int n = strace_vfprintf(current_tcp->outf, fmt, args); if (n < 0) { - if (outf != stderr) - perror(outfname == NULL - ? "" : outfname); + if (current_tcp->outf != stderr) + perror_msg("%s", outfname); } else - curcol += n; + current_tcp->curcol += n; } va_end(args); } +#ifndef HAVE_FPUTS_UNLOCKED +# define fputs_unlocked fputs +#endif + void tprints(const char *str) { - if (outf) { - int n = fputs(str, outf); + if (current_tcp) { + int n = fputs_unlocked(str, current_tcp->outf); if (n >= 0) { - curcol += strlen(str); + current_tcp->curcol += strlen(str); return; } - if (outf != stderr) - perror(outfname == NULL - ? "" : outfname); + if (current_tcp->outf != stderr) + perror_msg("%s", outfname); } } void line_ended(void) { - curcol = 0; - fflush(outf); - if (!printing_tcp) - return; - printing_tcp->curcol = 0; - printing_tcp = NULL; + if (current_tcp) { + current_tcp->curcol = 0; + fflush(current_tcp->outf); + } + if (printing_tcp) { + printing_tcp->curcol = 0; + printing_tcp = NULL; + } } void @@ -550,8 +593,7 @@ printleader(struct tcb *tcp) printing_tcp = tcp; if (printing_tcp) { - outf = printing_tcp->outf; - curcol = printing_tcp->curcol; + current_tcp = printing_tcp; if (printing_tcp->curcol != 0 && (followfork < 2 || printing_tcp == tcp)) { /* * case 1: we have a shared log (i.e. not -ff), and last line @@ -560,14 +602,13 @@ printleader(struct tcb *tcp) * didn't finish ("SIGKILL nuked us after syscall entry" etc). */ tprints(" \n"); - printing_tcp->flags |= TCB_REPRINT; printing_tcp->curcol = 0; } } printing_tcp = tcp; - outf = tcp->outf; - curcol = 0; + current_tcp = tcp; + current_tcp->curcol = 0; if (print_pid_pfx) tprintf("%-5d ", tcp->pid); @@ -602,14 +643,14 @@ printleader(struct tcb *tcp) } } if (iflag) - printcall(tcp); + print_pc(tcp); } void tabto(void) { - if (curcol < acolumn) - tprints(acolumn_spaces + curcol); + if (current_tcp->curcol < acolumn) + tprints(acolumn_spaces + current_tcp->curcol); } /* Should be only called directly *after successful attach* to a tracee. @@ -619,7 +660,7 @@ tabto(void) static void newoutf(struct tcb *tcp) { - tcp->outf = outf; /* if not -ff mode, the same file is for all */ + tcp->outf = shared_log; /* if not -ff mode, the same file is for all */ if (followfork >= 2) { char name[520 + sizeof(int) * 3]; sprintf(name, "%.512s.%u", outfname, tcp->pid); @@ -635,7 +676,7 @@ expand_tcbtab(void) callers have pointers and it would be a pain. So tcbtab is a table of pointers. Since we never free the TCBs, we allocate a single chunk of many. */ - int i = tcbtabsize; + unsigned int i = tcbtabsize; struct tcb *newtcbs = calloc(tcbtabsize, sizeof(newtcbs[0])); struct tcb **newtab = realloc(tcbtab, tcbtabsize * 2 * sizeof(tcbtab[0])); if (!newtab || !newtcbs) @@ -649,7 +690,7 @@ expand_tcbtab(void) static struct tcb * alloctcb(int pid) { - int i; + unsigned int i; struct tcb *tcp; if (nprocs == tcbtabsize) @@ -657,14 +698,18 @@ alloctcb(int pid) for (i = 0; i < tcbtabsize; i++) { tcp = tcbtab[i]; - if ((tcp->flags & TCB_INUSE) == 0) { + if (!tcp->pid) { memset(tcp, 0, sizeof(*tcp)); tcp->pid = pid; - tcp->flags = TCB_INUSE; - /* tcp->outf = outf; - not needed? */ #if SUPPORTED_PERSONALITIES > 1 tcp->currpers = current_personality; #endif + +#ifdef USE_LIBUNWIND + if (stack_trace_enabled) + unwind_tcb_init(tcp); +#endif + nprocs++; if (debug_flag) fprintf(stderr, "new tcb for pid %d, active tcbs:%d\n", tcp->pid, nprocs); @@ -680,17 +725,21 @@ droptcb(struct tcb *tcp) if (tcp->pid == 0) return; +#ifdef USE_LIBUNWIND + if (stack_trace_enabled) { + unwind_tcb_fin(tcp); + } +#endif + nprocs--; if (debug_flag) fprintf(stderr, "dropped tcb for pid %d, %d remain\n", tcp->pid, nprocs); if (tcp->outf) { - if (outfname && followfork >= 2) { + if (followfork >= 2) { if (tcp->curcol != 0) fprintf(tcp->outf, " \n"); fclose(tcp->outf); - if (outf == tcp->outf) - outf = NULL; } else { if (printing_tcp == tcp && tcp->curcol != 0) fprintf(tcp->outf, " \n"); @@ -698,22 +747,24 @@ droptcb(struct tcb *tcp) } } + if (current_tcp == tcp) + current_tcp = NULL; if (printing_tcp == tcp) printing_tcp = NULL; memset(tcp, 0, sizeof(*tcp)); } -/* detach traced process; continue with sig +/* Detach traced process. * Never call DETACH twice on the same process as both unattached and * attached-unstopped processes give the same ESRCH. For unattached process we * would SIGSTOP it and wait for its SIGSTOP notification forever. */ -static int +static void detach(struct tcb *tcp) { int error; - int status, sigstop_expected; + int status; if (tcp->flags & TCB_BPTSET) clearbpt(tcp); @@ -724,90 +775,153 @@ detach(struct tcb *tcp) * to make a clean break of things. */ #if defined(SPARC) -#undef PTRACE_DETACH -#define PTRACE_DETACH PTRACE_SUNDETACH +# undef PTRACE_DETACH +# define PTRACE_DETACH PTRACE_SUNDETACH #endif - error = 0; - sigstop_expected = 0; - if (tcp->flags & TCB_ATTACHED) { + if (!(tcp->flags & TCB_ATTACHED)) + goto drop; + + /* We attached but possibly didn't see the expected SIGSTOP. + * We must catch exactly one as otherwise the detached process + * would be left stopped (process state T). + */ + if (tcp->flags & TCB_IGNORE_ONE_SIGSTOP) + goto wait_loop; + + error = ptrace(PTRACE_DETACH, tcp->pid, 0, 0); + if (!error) { + /* On a clear day, you can see forever. */ + goto drop; + } + if (errno != ESRCH) { + /* Shouldn't happen. */ + perror_msg("detach: ptrace(PTRACE_DETACH,%u)", tcp->pid); + goto drop; + } + /* ESRCH: process is either not stopped or doesn't exist. */ + if (my_tkill(tcp->pid, 0) < 0) { + if (errno != ESRCH) + /* Shouldn't happen. */ + perror_msg("detach: tkill(%u,0)", tcp->pid); + /* else: process doesn't exist. */ + goto drop; + } + /* Process is not stopped, need to stop it. */ + if (use_seize) { /* - * We attached but possibly didn't see the expected SIGSTOP. - * We must catch exactly one as otherwise the detached process - * would be left stopped (process state T). + * With SEIZE, tracee can be in group-stop already. + * In this state sending it another SIGSTOP does nothing. + * Need to use INTERRUPT. + * Testcase: trying to ^C a "strace -p ". */ - sigstop_expected = (tcp->flags & TCB_IGNORE_ONE_SIGSTOP); - error = ptrace(PTRACE_DETACH, tcp->pid, (char *) 1, 0); - if (error == 0) { - /* On a clear day, you can see forever. */ + error = ptrace(PTRACE_INTERRUPT, tcp->pid, 0, 0); + if (!error) + goto wait_loop; + if (errno != ESRCH) + perror_msg("detach: ptrace(PTRACE_INTERRUPT,%u)", tcp->pid); + } + else { + error = my_tkill(tcp->pid, SIGSTOP); + if (!error) + goto wait_loop; + if (errno != ESRCH) + perror_msg("detach: tkill(%u,SIGSTOP)", tcp->pid); + } + /* Either process doesn't exist, or some weird error. */ + goto drop; + + wait_loop: + /* We end up here in three cases: + * 1. We sent PTRACE_INTERRUPT (use_seize case) + * 2. We sent SIGSTOP (!use_seize) + * 3. Attach SIGSTOP was already pending (TCB_IGNORE_ONE_SIGSTOP set) + */ + for (;;) { + unsigned int sig; + if (waitpid(tcp->pid, &status, __WALL) < 0) { + if (errno == EINTR) + continue; + /* + * if (errno == ECHILD) break; + * ^^^ WRONG! We expect this PID to exist, + * and want to emit a message otherwise: + */ + perror_msg("detach: waitpid(%u)", tcp->pid); + break; } - else if (errno != ESRCH) { - /* Shouldn't happen. */ - perror("detach: ptrace(PTRACE_DETACH, ...)"); + if (!WIFSTOPPED(status)) { + /* + * Tracee exited or was killed by signal. + * We shouldn't normally reach this place: + * we don't want to consume exit status. + * Consider "strace -p PID" being ^C-ed: + * we want merely to detach from PID. + * + * However, we _can_ end up here if tracee + * was SIGKILLed. + */ + break; } - else if (my_tkill(tcp->pid, 0) < 0) { - if (errno != ESRCH) - perror("detach: checking sanity"); + sig = WSTOPSIG(status); + if (debug_flag) + fprintf(stderr, "detach wait: event:%d sig:%d\n", + (unsigned)status >> 16, sig); + if (use_seize) { + unsigned event = (unsigned)status >> 16; + if (event == PTRACE_EVENT_STOP /*&& sig == SIGTRAP*/) { + /* + * sig == SIGTRAP: PTRACE_INTERRUPT stop. + * sig == other: process was already stopped + * with this stopping sig (see tests/detach-stopped). + * Looks like re-injecting this sig is not necessary + * in DETACH for the tracee to remain stopped. + */ + sig = 0; + } + /* + * PTRACE_INTERRUPT is not guaranteed to produce + * the above event if other ptrace-stop is pending. + * See tests/detach-sleeping testcase: + * strace got SIGINT while tracee is sleeping. + * We sent PTRACE_INTERRUPT. + * We see syscall exit, not PTRACE_INTERRUPT stop. + * We won't get PTRACE_INTERRUPT stop + * if we would CONT now. Need to DETACH. + */ + if (sig == syscall_trap_sig) + sig = 0; + /* else: not sure in which case we can be here. + * Signal stop? Inject it while detaching. + */ + ptrace_restart(PTRACE_DETACH, tcp, sig); + break; } - else if (!sigstop_expected && my_tkill(tcp->pid, SIGSTOP) < 0) { - if (errno != ESRCH) - perror("detach: stopping child"); + /* Note: this check has to be after use_seize check */ + /* (else, in use_seize case SIGSTOP will be mistreated) */ + if (sig == SIGSTOP) { + /* Detach, suppressing SIGSTOP */ + ptrace_restart(PTRACE_DETACH, tcp, 0); + break; } - else - sigstop_expected = 1; - } - - if (sigstop_expected) { - for (;;) { -#ifdef __WALL - if (waitpid(tcp->pid, &status, __WALL) < 0) { - if (errno == ECHILD) /* Already gone. */ - break; - if (errno != EINVAL) { - perror("detach: waiting"); - break; - } -#endif /* __WALL */ - /* No __WALL here. */ - if (waitpid(tcp->pid, &status, 0) < 0) { - if (errno != ECHILD) { - perror("detach: waiting"); - break; - } -#ifdef __WCLONE - /* If no processes, try clones. */ - if (waitpid(tcp->pid, &status, __WCLONE) < 0) { - if (errno != ECHILD) - perror("detach: waiting"); - break; - } -#endif /* __WCLONE */ - } -#ifdef __WALL - } -#endif - if (!WIFSTOPPED(status)) { - /* Au revoir, mon ami. */ - break; - } - if (WSTOPSIG(status) == SIGSTOP) { - ptrace_restart(PTRACE_DETACH, tcp, 0); - break; - } - error = ptrace_restart(PTRACE_CONT, tcp, - WSTOPSIG(status) == syscall_trap_sig ? 0 - : WSTOPSIG(status)); - if (error < 0) - break; + if (sig == syscall_trap_sig) + sig = 0; + /* Can't detach just yet, may need to wait for SIGSTOP */ + error = ptrace_restart(PTRACE_CONT, tcp, sig); + if (error < 0) { + /* Should not happen. + * Note: ptrace_restart returns 0 on ESRCH, so it's not it. + * ptrace_restart already emitted error message. + */ + break; } } + drop: if (!qflag && (tcp->flags & TCB_ATTACHED)) fprintf(stderr, "Process %u detached\n", tcp->pid); droptcb(tcp); - - return error; } static void @@ -823,16 +937,12 @@ process_opt_p_list(char *opt) char c = *delim; *delim = '\0'; - pid = atoi(opt); /* TODO: stricter parsing of the number? */ + pid = string_to_uint(opt); if (pid <= 0) { - error_msg("Invalid process id: '%s'", opt); - *delim = c; - return; + error_msg_and_die("Invalid process id: '%s'", opt); } if (pid == strace_tracer_pid) { - error_msg("I'm sorry, I can't let you do that, Dave."); - *delim = c; - return; + error_msg_and_die("I'm sorry, I can't let you do that, Dave."); } *delim = c; alloctcb(pid); @@ -845,7 +955,7 @@ process_opt_p_list(char *opt) static void startup_attach(void) { - int tcbi; + unsigned int tcbi; struct tcb *tcp; /* @@ -880,7 +990,7 @@ startup_attach(void) for (tcbi = 0; tcbi < tcbtabsize; tcbi++) { tcp = tcbtab[tcbi]; - if (!(tcp->flags & TCB_INUSE)) + if (!tcp->pid) continue; /* Is this a process we should attach to, but not yet attached? */ @@ -895,14 +1005,15 @@ startup_attach(void) dir = opendir(procdir); if (dir != NULL) { unsigned int ntid = 0, nerr = 0; - struct dirent *de; + struct_dirent *de; - while ((de = readdir(dir)) != NULL) { + while ((de = read_dir(dir)) != NULL) { struct tcb *cur_tcp; int tid; if (de->d_fileno == 0) continue; + /* we trust /proc filesystem */ tid = atoi(de->d_name); if (tid <= 0) continue; @@ -930,7 +1041,7 @@ startup_attach(void) } ntid -= nerr; if (ntid == 0) { - perror("attach: ptrace(PTRACE_ATTACH, ...)"); + perror_msg("attach: ptrace(PTRACE_ATTACH, ...)"); droptcb(tcp); continue; } @@ -951,7 +1062,7 @@ startup_attach(void) } /* if (opendir worked) */ } /* if (-f) */ if (ptrace_attach_or_seize(tcp->pid) < 0) { - perror("attach: ptrace(PTRACE_ATTACH, ...)"); + perror_msg("attach: ptrace(PTRACE_ATTACH, ...)"); droptcb(tcp); continue; } @@ -979,13 +1090,79 @@ startup_attach(void) sigprocmask(SIG_SETMASK, &empty_set, NULL); } +/* Stack-o-phobic exec helper, in the hope to work around + * NOMMU + "daemonized tracer" difficulty. + */ +struct exec_params { + int fd_to_close; + uid_t run_euid; + gid_t run_egid; + char **argv; + char *pathname; +}; +static struct exec_params params_for_tracee; +static void __attribute__ ((noinline, noreturn)) +exec_or_die(void) +{ + struct exec_params *params = ¶ms_for_tracee; + + if (params->fd_to_close >= 0) + close(params->fd_to_close); + if (!daemonized_tracer && !use_seize) { + if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) { + perror_msg_and_die("ptrace(PTRACE_TRACEME, ...)"); + } + } + + if (username != NULL) { + /* + * It is important to set groups before we + * lose privileges on setuid. + */ + if (initgroups(username, run_gid) < 0) { + perror_msg_and_die("initgroups"); + } + if (setregid(run_gid, params->run_egid) < 0) { + perror_msg_and_die("setregid"); + } + if (setreuid(run_uid, params->run_euid) < 0) { + perror_msg_and_die("setreuid"); + } + } + else if (geteuid() != 0) + if (setreuid(run_uid, run_uid) < 0) { + perror_msg_and_die("setreuid"); + } + + if (!daemonized_tracer) { + /* + * Induce a ptrace stop. Tracer (our parent) + * will resume us with PTRACE_SYSCALL and display + * the immediately following execve syscall. + * Can't do this on NOMMU systems, we are after + * vfork: parent is blocked, stopping would deadlock. + */ + if (!NOMMU_SYSTEM) + kill(getpid(), SIGSTOP); + } else { + alarm(3); + /* we depend on SIGCHLD set to SIG_DFL by init code */ + /* if it happens to be SIG_IGN'ed, wait won't block */ + wait(NULL); + alarm(0); + } + + execv(params->pathname, params->argv); + perror_msg_and_die("exec"); +} + static void startup_child(char **argv) { - struct stat statbuf; + struct_stat statbuf; const char *filename; - char pathname[MAXPATHLEN]; - int pid = 0; + char pathname[PATH_MAX]; + int pid; struct tcb *tcp; filename = argv[0]; @@ -1002,12 +1179,12 @@ startup_child(char **argv) * first regardless of the path but doing that gives * security geeks a panic attack. */ - else if (stat(filename, &statbuf) == 0) + else if (stat_file(filename, &statbuf) == 0) strcpy(pathname, filename); #endif /* USE_DEBUGGING_EXEC */ else { const char *path; - int m, n, len; + size_t m, n, len; for (path = getenv("PATH"); path && *path; path += m) { const char *colon = strchr(path, ':'); @@ -1018,7 +1195,7 @@ startup_child(char **argv) else m = n = strlen(path); if (n == 0) { - if (!getcwd(pathname, MAXPATHLEN)) + if (!getcwd(pathname, PATH_MAX)) continue; len = strlen(pathname); } @@ -1031,7 +1208,7 @@ startup_child(char **argv) if (len && pathname[len - 1] != '/') pathname[len++] = '/'; strcpy(pathname + len, filename); - if (stat(pathname, &statbuf) == 0 && + if (stat_file(pathname, &statbuf) == 0 && /* Accept only regular files with some execute bits set. XXX not perfect, might still fail */ @@ -1040,79 +1217,47 @@ startup_child(char **argv) break; } } - if (stat(pathname, &statbuf) < 0) { + if (stat_file(pathname, &statbuf) < 0) { perror_msg_and_die("Can't stat '%s'", filename); } - strace_child = pid = fork(); + + params_for_tracee.fd_to_close = (shared_log != stderr) ? fileno(shared_log) : -1; + params_for_tracee.run_euid = (statbuf.st_mode & S_ISUID) ? statbuf.st_uid : run_uid; + params_for_tracee.run_egid = (statbuf.st_mode & S_ISGID) ? statbuf.st_gid : run_gid; + params_for_tracee.argv = argv; + /* + * On NOMMU, can be safely freed only after execve in tracee. + * It's hard to know when that happens, so we just leak it. + */ + params_for_tracee.pathname = NOMMU_SYSTEM ? strdup(pathname) : pathname; + +#if defined HAVE_PRCTL && defined PR_SET_PTRACER && defined PR_SET_PTRACER_ANY + if (daemonized_tracer) + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY); +#endif + + pid = fork(); if (pid < 0) { perror_msg_and_die("fork"); } - if ((pid != 0 && daemonized_tracer) /* -D: parent to become a traced process */ - || (pid == 0 && !daemonized_tracer) /* not -D: child to become a traced process */ + if ((pid != 0 && daemonized_tracer) + || (pid == 0 && !daemonized_tracer) ) { - pid = getpid(); - if (outf != stderr) - close(fileno(outf)); - if (!daemonized_tracer && !use_seize) { - if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) { - perror_msg_and_die("ptrace(PTRACE_TRACEME, ...)"); - } - } - - if (username != NULL) { - uid_t run_euid = run_uid; - gid_t run_egid = run_gid; - - if (statbuf.st_mode & S_ISUID) - run_euid = statbuf.st_uid; - if (statbuf.st_mode & S_ISGID) - run_egid = statbuf.st_gid; - /* - * It is important to set groups before we - * lose privileges on setuid. - */ - if (initgroups(username, run_gid) < 0) { - perror_msg_and_die("initgroups"); - } - if (setregid(run_gid, run_egid) < 0) { - perror_msg_and_die("setregid"); - } - if (setreuid(run_uid, run_euid) < 0) { - perror_msg_and_die("setreuid"); - } - } - else if (geteuid() != 0) - setreuid(run_uid, run_uid); - - if (!daemonized_tracer) { - /* - * Induce a ptrace stop. Tracer (our parent) - * will resume us with PTRACE_SYSCALL and display - * the immediately following execve syscall. - * Can't do this on NOMMU systems, we are after - * vfork: parent is blocked, stopping would deadlock. - */ - if (!strace_vforked) - kill(pid, SIGSTOP); - } else { - alarm(3); - /* we depend on SIGCHLD set to SIG_DFL by init code */ - /* if it happens to be SIG_IGN'ed, wait won't block */ - wait(NULL); - alarm(0); - } - - execv(pathname, argv); - perror_msg_and_die("exec"); + /* We are to become the tracee. Two cases: + * -D: we are parent + * not -D: we are child + */ + exec_or_die(); } /* We are the tracer */ if (!daemonized_tracer) { + strace_child = pid; if (!use_seize) { /* child did PTRACE_TRACEME, nothing to do in parent */ } else { - if (!strace_vforked) { + if (!NOMMU_SYSTEM) { /* Wait until child stopped itself */ int status; while (waitpid(pid, &status, WSTOPPED) < 0) { @@ -1125,7 +1270,7 @@ startup_child(char **argv) perror_msg_and_die("Unexpected wait status %x", status); } } - /* Else: vforked case, we have no way to sync. + /* Else: NOMMU case, we have no way to sync. * Just attach to it as soon as possible. * This means that we may miss a few first syscalls... */ @@ -1134,24 +1279,45 @@ startup_child(char **argv) kill_save_errno(pid, SIGKILL); perror_msg_and_die("Can't attach to %d", pid); } - if (!strace_vforked) + if (!NOMMU_SYSTEM) kill(pid, SIGCONT); } tcp = alloctcb(pid); - if (!strace_vforked) - tcp->flags |= TCB_ATTACHED | TCB_STRACE_CHILD | TCB_STARTUP | post_attach_sigstop; + if (!NOMMU_SYSTEM) + tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop; else - tcp->flags |= TCB_ATTACHED | TCB_STRACE_CHILD | TCB_STARTUP; + tcp->flags |= TCB_ATTACHED | TCB_STARTUP; newoutf(tcp); } else { - /* With -D, *we* are child here, IOW: different pid. Fetch it: */ + /* With -D, we are *child* here, IOW: different pid. Fetch it: */ strace_tracer_pid = getpid(); /* The tracee is our parent: */ pid = getppid(); alloctcb(pid); /* attaching will be done later, by startup_attach */ /* note: we don't do newoutf(tcp) here either! */ + + /* NOMMU BUG! -D mode is active, we (child) return, + * and we will scribble over parent's stack! + * When parent later unpauses, it segfaults. + * + * We work around it + * (1) by declaring exec_or_die() NORETURN, + * hopefully compiler will just jump to it + * instead of call (won't push anything to stack), + * (2) by trying very hard in exec_or_die() + * to not use any stack, + * (3) having a really big (PATH_MAX) stack object + * in this function, which creates a "buffer" between + * child's and parent's stack pointers. + * This may save us if (1) and (2) failed + * and compiler decided to use stack in exec_or_die() anyway + * (happens on i386 because of stack parameter passing). + * + * A cleaner solution is to use makecontext + setcontext + * to create a genuine separate stack and execute on it. + */ } } @@ -1160,7 +1326,7 @@ startup_child(char **argv) * First fork a new child, call ptrace with PTRACE_SETOPTIONS on it, * and then see which options are supported by the kernel. */ -static void +static int test_ptrace_setoptions_followfork(void) { int pid, expected_grandchild = 0, found_grandchild = 0; @@ -1168,6 +1334,10 @@ test_ptrace_setoptions_followfork(void) PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK; + /* Need fork for test. NOMMU has no forks */ + if (NOMMU_SYSTEM) + goto worked; /* be bold, and pretend that test succeeded */ + pid = fork(); if (pid < 0) perror_msg_and_die("fork"); @@ -1249,14 +1419,16 @@ test_ptrace_setoptions_followfork(void) } } if (expected_grandchild && expected_grandchild == found_grandchild) { + worked: ptrace_setoptions |= test_options; if (debug_flag) fprintf(stderr, "ptrace_setoptions = %#x\n", ptrace_setoptions); - return; + return 0; } error_msg("Test for PTRACE_O_TRACECLONE failed, " "giving up using this feature."); + return 1; } /* @@ -1266,14 +1438,15 @@ test_ptrace_setoptions_followfork(void) * * Use of this option enables correct handling of user-generated SIGTRAPs, * and SIGTRAPs generated by special instructions such as int3 on x86: - * _start: .globl _start - * int3 - * movl $42, %ebx - * movl $1, %eax - * int $0x80 - * (compile with: "gcc -nostartfiles -nostdlib -o int3 int3.S") + +# compile with: gcc -nostartfiles -nostdlib -o int3 int3.S +_start: .globl _start + int3 + movl $42, %ebx + movl $1, %eax + int $0x80 */ -static void +static int test_ptrace_setoptions_for_all(void) { const unsigned int test_options = PTRACE_O_TRACESYSGOOD | @@ -1281,6 +1454,10 @@ test_ptrace_setoptions_for_all(void) int pid; int it_worked = 0; + /* Need fork for test. NOMMU has no forks */ + if (NOMMU_SYSTEM) + goto worked; /* be bold, and pretend that test succeeded */ + pid = fork(); if (pid < 0) perror_msg_and_die("fork"); @@ -1343,24 +1520,32 @@ test_ptrace_setoptions_for_all(void) } if (it_worked) { + worked: syscall_trap_sig = (SIGTRAP | 0x80); ptrace_setoptions |= test_options; if (debug_flag) fprintf(stderr, "ptrace_setoptions = %#x\n", ptrace_setoptions); - return; + return 0; } error_msg("Test for PTRACE_O_TRACESYSGOOD failed, " "giving up using this feature."); + return 1; } -# ifdef USE_SEIZE +#if USE_SEIZE static void test_ptrace_seize(void) { int pid; + /* Need fork for test. NOMMU has no forks */ + if (NOMMU_SYSTEM) { + post_attach_sigstop = 0; /* this sets use_seize to 1 */ + return; + } + pid = fork(); if (pid < 0) perror_msg_and_die("fork"); @@ -1374,7 +1559,7 @@ test_ptrace_seize(void) * attaching tracee continues to run unless a trap condition occurs. * PTRACE_SEIZE doesn't affect signal or group stop state. */ - if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_SEIZE_DEVEL) == 0) { + if (ptrace(PTRACE_SEIZE, pid, 0, 0) == 0) { post_attach_sigstop = 0; /* this sets use_seize to 1 */ } else if (debug_flag) { fprintf(stderr, "PTRACE_SEIZE doesn't work\n"); @@ -1400,9 +1585,9 @@ test_ptrace_seize(void) __func__, status); } } -# else /* !USE_SEIZE */ -# define test_ptrace_seize() ((void)0) -# endif +#else /* !USE_SEIZE */ +# define test_ptrace_seize() ((void)0) +#endif static unsigned get_os_release(void) @@ -1424,8 +1609,14 @@ get_os_release(void) break; while (*p >= '0' && *p <= '9') p++; - if (*p != '.') + if (*p != '.') { + if (rel >= KERNEL_VERSION(0,1,0)) { + /* "X.Y-something" means "X.Y.0" */ + rel <<= 8; + break; + } error_msg_and_die("Bad OS release string: '%s'", u.release); + } p++; } return rel; @@ -1443,8 +1634,9 @@ static void __attribute__ ((noinline)) init(int argc, char *argv[]) { struct tcb *tcp; - int c; + int c, i; int optF = 0; + unsigned int tcbi; struct sigaction sa; progname = argv[0] ? argv[0] : "strace"; @@ -1468,33 +1660,42 @@ init(int argc, char *argv[]) tcp = calloc(tcbtabsize, sizeof(*tcp)); if (!tcp) die_out_of_memory(); - for (c = 0; c < tcbtabsize; c++) - tcbtab[c] = tcp++; + for (tcbi = 0; tcbi < tcbtabsize; tcbi++) + tcbtab[tcbi] = tcp++; - outf = stderr; + shared_log = stderr; set_sortby(DEFAULT_SORTBY); set_personality(DEFAULT_PERSONALITY); qualify("trace=all"); qualify("abbrev=all"); qualify("verbose=all"); +#if DEFAULT_QUAL_FLAGS != (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE) +# error Bug in DEFAULT_QUAL_FLAGS +#endif qualify("signal=all"); while ((c = getopt(argc, argv, - "+bcCdfFhiqrtTvVxyz" + "+b:cCdfFhiqrtTvVwxyz" +#ifdef USE_LIBUNWIND + "k" +#endif "D" "a:e:o:O:p:s:S:u:E:P:I:")) != EOF) { switch (c) { case 'b': + if (strcmp(optarg, "execve") != 0) + error_msg_and_die("Syscall '%s' for -b isn't supported", + optarg); detach_on_execve = 1; break; case 'c': if (cflag == CFLAG_BOTH) { - error_msg_and_die("-c and -C are mutually exclusive options"); + error_msg_and_die("-c and -C are mutually exclusive"); } cflag = CFLAG_ONLY_STATS; break; case 'C': if (cflag == CFLAG_ONLY_STATS) { - error_msg_and_die("-c and -C are mutually exclusive options"); + error_msg_and_die("-c and -C are mutually exclusive"); } cflag = CFLAG_BOTH; break; @@ -1517,7 +1718,7 @@ init(int argc, char *argv[]) iflag = 1; break; case 'q': - qflag = 1; + qflag++; break; case 'r': rflag = 1; @@ -1528,11 +1729,14 @@ init(int argc, char *argv[]) case 'T': Tflag = 1; break; + case 'w': + count_wallclock = 1; + break; case 'x': xflag++; break; case 'y': - show_fd_path = 1; + show_fd_path++; break; case 'v': qualify("abbrev=none"); @@ -1545,9 +1749,9 @@ init(int argc, char *argv[]) not_failing_only = 1; break; case 'a': - acolumn = atoi(optarg); + acolumn = string_to_uint(optarg); if (acolumn < 0) - error_msg_and_die("Bad column width '%s'", optarg); + error_opt_arg(c, optarg); break; case 'e': qualify(optarg); @@ -1556,22 +1760,22 @@ init(int argc, char *argv[]) outfname = strdup(optarg); break; case 'O': - set_overhead(atoi(optarg)); + i = string_to_uint(optarg); + if (i < 0) + error_opt_arg(c, optarg); + set_overhead(i); break; case 'p': process_opt_p_list(optarg); break; case 'P': - tracing_paths = 1; - if (pathtrace_select(optarg)) { - error_msg_and_die("Failed to select path '%s'", optarg); - } + pathtrace_select(optarg); break; case 's': - max_strlen = atoi(optarg); - if (max_strlen < 0) { - error_msg_and_die("Invalid -%c argument: '%s'", c, optarg); - } + i = string_to_uint(optarg); + if (i < 0) + error_opt_arg(c, optarg); + max_strlen = i; break; case 'S': set_sortby(optarg); @@ -1579,15 +1783,19 @@ init(int argc, char *argv[]) case 'u': username = strdup(optarg); break; +#ifdef USE_LIBUNWIND + case 'k': + stack_trace_enabled = true; + break; +#endif case 'E': if (putenv(optarg) < 0) die_out_of_memory(); break; case 'I': - opt_intr = atoi(optarg); - if (opt_intr <= 0 || opt_intr >= NUM_INTR_OPTS) { - error_msg_and_die("Invalid -%c argument: '%s'", c, optarg); - } + opt_intr = string_to_uint(optarg); + if (opt_intr <= 0 || opt_intr >= NUM_INTR_OPTS) + error_opt_arg(c, optarg); break; default: usage(stderr, 1); @@ -1608,16 +1816,42 @@ init(int argc, char *argv[]) usage(stderr, 1); if (nprocs != 0 && daemonized_tracer) { - error_msg_and_die("-D and -p are mutually exclusive options"); + error_msg_and_die("-D and -p are mutually exclusive"); } if (!followfork) followfork = optF; if (followfork >= 2 && cflag) { - error_msg_and_die("(-c or -C) and -ff are mutually exclusive options"); + error_msg_and_die("(-c or -C) and -ff are mutually exclusive"); + } + + if (count_wallclock && !cflag) { + error_msg_and_die("-w must be given with (-c or -C)"); + } + + if (cflag == CFLAG_ONLY_STATS) { + if (iflag) + error_msg("-%c has no effect with -c", 'i'); +#ifdef USE_LIBUNWIND + if (stack_trace_enabled) + error_msg("-%c has no effect with -c", 'k'); +#endif + if (rflag) + error_msg("-%c has no effect with -c", 'r'); + if (tflag) + error_msg("-%c has no effect with -c", 't'); + if (Tflag) + error_msg("-%c has no effect with -c", 'T'); + if (show_fd_path) + error_msg("-%c has no effect with -c", 'y'); } +#ifdef USE_LIBUNWIND + if (stack_trace_enabled) + unwind_init(); +#endif + /* See if they want to run as another user. */ if (username != NULL) { struct passwd *pent; @@ -1637,9 +1871,14 @@ init(int argc, char *argv[]) run_gid = getgid(); } + /* + * On any reasonably recent Linux kernel (circa about 2.5.46) + * need_fork_exec_workarounds should stay 0 after these tests: + */ + /*need_fork_exec_workarounds = 0; - already is */ if (followfork) - test_ptrace_setoptions_followfork(); - test_ptrace_setoptions_for_all(); + need_fork_exec_workarounds = test_ptrace_setoptions_followfork(); + need_fork_exec_workarounds |= test_ptrace_setoptions_for_all(); test_ptrace_seize(); /* Check if they want to redirect the output. */ @@ -1652,10 +1891,10 @@ init(int argc, char *argv[]) */ if (followfork >= 2) error_msg_and_die("Piping the output and -ff are mutually exclusive"); - outf = strace_popen(outfname + 1); + shared_log = strace_popen(outfname + 1); } else if (followfork < 2) - outf = strace_fopen(outfname); + shared_log = strace_fopen(outfname); } else { /* -ff without -o FILE is the same as single -f */ if (followfork >= 2) @@ -1666,7 +1905,7 @@ init(int argc, char *argv[]) char *buf = malloc(BUFSIZ); if (!buf) die_out_of_memory(); - setvbuf(outf, buf, _IOLBF, BUFSIZ); + setvbuf(shared_log, buf, _IOLBF, BUFSIZ); } if (outfname && argv[0]) { if (!opt_intr) @@ -1683,17 +1922,21 @@ init(int argc, char *argv[]) * no 1 1 INTR_WHILE_WAIT */ - /* STARTUP_CHILD must be called before the signal handlers get - installed below as they are inherited into the spawned process. - Also we do not need to be protected by them as during interruption - in the STARTUP_CHILD mode we kill the spawned process anyway. */ + sigemptyset(&empty_set); + sigemptyset(&blocked_set); + + /* startup_child() must be called before the signal handlers get + * installed below as they are inherited into the spawned process. + * Also we do not need to be protected by them as during interruption + * in the startup_child() mode we kill the spawned process anyway. + */ if (argv[0]) { - skip_startup_execve = 1; + if (!NOMMU_SYSTEM || daemonized_tracer) + hide_log_until_execve = 1; + skip_one_b_execve = 1; startup_child(argv); } - sigemptyset(&empty_set); - sigemptyset(&blocked_set); sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; @@ -1737,14 +1980,14 @@ init(int argc, char *argv[]) static struct tcb * pid2tcb(int pid) { - int i; + unsigned int i; if (pid <= 0) return NULL; for (i = 0; i < tcbtabsize; i++) { struct tcb *tcp = tcbtab[i]; - if (tcp->pid == pid && (tcp->flags & TCB_INUSE)) + if (tcp->pid == pid) return tcp; } @@ -1754,7 +1997,7 @@ pid2tcb(int pid) static void cleanup(void) { - int i; + unsigned int i; struct tcb *tcp; int fatal_sig; @@ -1765,19 +2008,19 @@ cleanup(void) for (i = 0; i < tcbtabsize; i++) { tcp = tcbtab[i]; - if (!(tcp->flags & TCB_INUSE)) + if (!tcp->pid) continue; if (debug_flag) fprintf(stderr, "cleanup: looking at pid %u\n", tcp->pid); - if (tcp->flags & TCB_STRACE_CHILD) { + if (tcp->pid == strace_child) { kill(tcp->pid, SIGCONT); kill(tcp->pid, fatal_sig); } detach(tcp); } if (cflag) - call_summary(outf); + call_summary(shared_log); } static void @@ -1786,68 +2029,61 @@ interrupt(int sig) interrupted = sig; } -static int +static void trace(void) { struct rusage ru; - struct rusage *rup = cflag ? &ru : NULL; -# ifdef __WALL - static int wait4_options = __WALL; -# endif - while (nprocs != 0) { + /* Used to be "while (nprocs != 0)", but in this testcase: + * int main() { _exit(!!fork()); } + * under strace -f, parent sometimes (rarely) manages + * to exit before we see the first stop of the child, + * and we are losing track of it: + * 19923 clone(...) = 19924 + * 19923 exit_group(1) = ? + * 19923 +++ exited with 1 +++ + * Waiting for ECHILD works better. + * (However, if -o|logger is in use, we can't do that. + * Can work around that by double-forking the logger, + * but that loses the ability to wait for its completion on exit. + * Oh well...) + */ + while (1) { int pid; int wait_errno; - int status, sig; + int status; int stopped; - struct tcb *tcp; + unsigned int sig; unsigned event; + struct tcb *tcp; if (interrupted) - return 0; + return; + + if (popen_pid != 0 && nprocs == 0) + return; + if (interactive) sigprocmask(SIG_SETMASK, &empty_set, NULL); -# ifdef __WALL - pid = wait4(-1, &status, wait4_options, rup); - if (pid < 0 && (wait4_options & __WALL) && errno == EINVAL) { - /* this kernel does not support __WALL */ - wait4_options &= ~__WALL; - pid = wait4(-1, &status, wait4_options, rup); - } - if (pid < 0 && !(wait4_options & __WALL) && errno == ECHILD) { - /* most likely a "cloned" process */ - pid = wait4(-1, &status, __WCLONE, rup); - if (pid < 0) { - perror_msg("wait4(__WCLONE) failed"); - } - } -# else - pid = wait4(-1, &status, 0, rup); -# endif /* __WALL */ + pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL)); wait_errno = errno; if (interactive) sigprocmask(SIG_BLOCK, &blocked_set, NULL); if (pid < 0) { - switch (wait_errno) { - case EINTR: + if (wait_errno == EINTR) continue; - case ECHILD: - /* - * We would like to verify this case - * but sometimes a race in Solbourne's - * version of SunOS sometimes reports - * ECHILD before sending us SIGCHILD. - */ - return 0; - default: - errno = wait_errno; - perror_msg("wait"); - return -1; - } + if (nprocs == 0 && wait_errno == ECHILD) + return; + /* If nprocs > 0, ECHILD is not expected, + * treat it as any other error here: + */ + errno = wait_errno; + perror_msg_and_die("wait4(__WALL)"); } + if (pid == popen_pid) { - if (WIFEXITED(status) || WIFSIGNALED(status)) + if (!WIFSTOPPED(status)) popen_pid = 0; continue; } @@ -1855,7 +2091,7 @@ trace(void) event = ((unsigned)status >> 16); if (debug_flag) { char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16]; - char evbuf[sizeof(",PTRACE_EVENT_?? (%u)") + sizeof(int)*3 /*paranoia:*/ + 16]; + char evbuf[sizeof(",EVENT_VFORK_DONE (%u)") + sizeof(int)*3 /*paranoia:*/ + 16]; strcpy(buf, "???"); if (WIFSIGNALED(status)) #ifdef WCOREDUMP @@ -1871,6 +2107,7 @@ trace(void) if (WIFSTOPPED(status)) sprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status))); #ifdef WIFCONTINUED + /* Should never be seen */ if (WIFCONTINUED(status)) strcpy(buf, "WIFCONTINUED"); #endif @@ -1883,22 +2120,52 @@ trace(void) [PTRACE_EVENT_VFORK_DONE] = "VFORK_DONE", [PTRACE_EVENT_EXEC] = "EXEC", [PTRACE_EVENT_EXIT] = "EXIT", + /* [PTRACE_EVENT_STOP (=128)] would make biggish array */ }; - const char *e; + const char *e = "??"; if (event < ARRAY_SIZE(event_names)) e = event_names[event]; - else { - sprintf(buf, "?? (%u)", event); - e = buf; - } - sprintf(evbuf, ",PTRACE_EVENT_%s", e); + else if (event == PTRACE_EVENT_STOP) + e = "STOP"; + sprintf(evbuf, ",EVENT_%s (%u)", e, event); } - fprintf(stderr, " [wait(0x%04x) = %u] %s%s\n", status, pid, buf, evbuf); + fprintf(stderr, " [wait(0x%06x) = %u] %s%s\n", status, pid, buf, evbuf); } /* Look up 'pid' in our table. */ tcp = pid2tcb(pid); + if (!tcp) { + if (!WIFSTOPPED(status)) { + /* This can happen if we inherited + * an unknown child. Example: + * (sleep 1 & exec strace sleep 2) + */ + error_msg("Exit of unknown pid %u seen", pid); + continue; + } + if (followfork) { + /* We assume it's a fork/vfork/clone child */ + tcp = alloctcb(pid); + tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop; + newoutf(tcp); + if (!qflag) + fprintf(stderr, "Process %d attached\n", + pid); + } else { + /* This can happen if a clone call used + * CLONE_PTRACE itself. + */ + ptrace(PTRACE_CONT, pid, (char *) 0, 0); + error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid); + continue; + } + } + + clear_regs(); + if (WIFSTOPPED(status)) + get_regs(pid); + /* Under Linux, execve changes pid to thread leader's pid, * and we see this changed pid on EVENT_EXEC and later, * execve sysexit. Leader "disappears" without exit @@ -1914,83 +2181,59 @@ trace(void) * On 2.6 and earlier, it can return garbage. */ if (event == PTRACE_EVENT_EXEC && os_release >= KERNEL_VERSION(3,0,0)) { + FILE *fp; + struct tcb *execve_thread; long old_pid = 0; - if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) >= 0 - && old_pid > 0 - && old_pid != pid - ) { - struct tcb *execve_thread = pid2tcb(old_pid); - if (tcp) { - outf = tcp->outf; - curcol = tcp->curcol; - if (execve_thread) { - if (execve_thread->curcol != 0) { - /* - * One case we are here is -ff: - * try "strace -oLOG -ff test/threaded_execve" - */ - fprintf(execve_thread->outf, " \n", pid); - execve_thread->curcol = 0; - } - /* swap output FILEs (needed for -ff) */ - tcp->outf = execve_thread->outf; - tcp->curcol = execve_thread->curcol; - execve_thread->outf = outf; - execve_thread->curcol = curcol; - } - droptcb(tcp); - } - tcp = execve_thread; - if (tcp) { - tcp->pid = pid; - tcp->flags |= TCB_REPRINT; - if (!cflag) { - printleader(tcp); - tprintf("+++ superseded by execve in pid %lu +++\n", old_pid); - line_ended(); - } - } - } - } - - if (event == PTRACE_EVENT_EXEC && detach_on_execve) { - if (!skip_startup_execve) - detach(tcp); - /* This was initial execve for "strace PROG". Skip. */ - skip_startup_execve = 0; - } - if (tcp == NULL) { - if (followfork) { - /* This is needed to go with the CLONE_PTRACE - changes in process.c/util.c: we might see - the child's initial trap before we see the - parent return from the clone syscall. - Leave the child suspended until the parent - returns from its system call. Only then - will we have the association of parent and - child so that we know how to do clearbpt - in the child. */ - tcp = alloctcb(pid); - tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop; - newoutf(tcp); - if (!qflag) - fprintf(stderr, "Process %d attached\n", - pid); + if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0) + goto dont_switch_tcbs; + /* Avoid truncation in pid2tcb() param passing */ + if (old_pid <= 0 || old_pid == pid) + goto dont_switch_tcbs; + if ((unsigned long) old_pid > UINT_MAX) + goto dont_switch_tcbs; + execve_thread = pid2tcb(old_pid); + /* It should be !NULL, but I feel paranoid */ + if (!execve_thread) + goto dont_switch_tcbs; + + if (execve_thread->curcol != 0) { + /* + * One case we are here is -ff: + * try "strace -oLOG -ff test/threaded_execve" + */ + fprintf(execve_thread->outf, " \n", pid); + /*execve_thread->curcol = 0; - no need, see code below */ } - else - /* This can happen if a clone call used - CLONE_PTRACE itself. */ - { - if (WIFSTOPPED(status)) - ptrace(PTRACE_CONT, pid, (char *) 0, 0); - error_msg_and_die("Unknown pid: %u", pid); + /* Swap output FILEs (needed for -ff) */ + fp = execve_thread->outf; + execve_thread->outf = tcp->outf; + tcp->outf = fp; + /* And their column positions */ + execve_thread->curcol = tcp->curcol; + tcp->curcol = 0; + /* Drop leader, but close execve'd thread outfile (if -ff) */ + droptcb(tcp); + /* Switch to the thread, reusing leader's outfile and pid */ + tcp = execve_thread; + tcp->pid = pid; + if (cflag != CFLAG_ONLY_STATS) { + printleader(tcp); + tprintf("+++ superseded by execve in pid %lu +++\n", old_pid); + line_ended(); + tcp->flags |= TCB_REPRINT; } } + dont_switch_tcbs: + + if (event == PTRACE_EVENT_EXEC) { + if (detach_on_execve && !skip_one_b_execve) + detach(tcp); /* do "-b execve" thingy */ + skip_one_b_execve = 0; + } /* Set current output file */ - outf = tcp->outf; - curcol = tcp->curcol; + current_tcp = tcp; if (cflag) { tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime); @@ -2001,7 +2244,8 @@ trace(void) if (pid == strace_child) exit_code = 0x100 | WTERMSIG(status); if (cflag != CFLAG_ONLY_STATS - && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)) { + && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL) + ) { printleader(tcp); #ifdef WCOREDUMP tprintf("+++ killed by %s %s+++\n", @@ -2019,7 +2263,8 @@ trace(void) if (WIFEXITED(status)) { if (pid == strace_child) exit_code = WEXITSTATUS(status); - if (!cflag /* && (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL) */ ) { + if (cflag != CFLAG_ONLY_STATS && + qflag < 2) { printleader(tcp); tprintf("+++ exited with %d +++\n", WEXITSTATUS(status)); line_ended(); @@ -2046,13 +2291,13 @@ trace(void) if (clearbpt(tcp) < 0) { /* Pretty fatal */ droptcb(tcp); - cleanup(); - return -1; + exit_code = 1; + return; } } - if (ptrace_setoptions) { + if (!use_seize && ptrace_setoptions) { if (debug_flag) - fprintf(stderr, "setting opts %x on pid %d\n", ptrace_setoptions, tcp->pid); + fprintf(stderr, "setting opts 0x%x on pid %d\n", ptrace_setoptions, tcp->pid); if (ptrace(PTRACE_SETOPTIONS, tcp->pid, NULL, ptrace_setoptions) < 0) { if (errno != ESRCH) { /* Should never happen, really */ @@ -2066,8 +2311,8 @@ trace(void) if (event != 0) { /* Ptrace event */ -#ifdef USE_SEIZE - if (event == PTRACE_EVENT_STOP || event == PTRACE_EVENT_STOP1) { +#if USE_SEIZE + if (event == PTRACE_EVENT_STOP) { /* * PTRACE_INTERRUPT-stop or group-stop. * PTRACE_INTERRUPT-stop has sig == SIGTRAP here. @@ -2103,38 +2348,25 @@ trace(void) /* Nonzero (true) if tracee is stopped by signal * (as opposed to "tracee received signal"). + * TODO: shouldn't we check for errno == EINVAL too? + * We can get ESRCH instead, you know... */ stopped = (ptrace(PTRACE_GETSIGINFO, pid, 0, (long) &si) < 0); -#ifdef USE_SEIZE +#if USE_SEIZE show_stopsig: #endif if (cflag != CFLAG_ONLY_STATS - && (qual_flags[sig] & QUAL_SIGNAL)) { -#if defined(PT_CR_IPSR) && defined(PT_CR_IIP) - long pc = 0; - long psr = 0; - - upeek(tcp, PT_CR_IPSR, &psr); - upeek(tcp, PT_CR_IIP, &pc); - -# define PSR_RI 41 - pc += (psr >> PSR_RI) & 0x3; -# define PC_FORMAT_STR " @ %lx" -# define PC_FORMAT_ARG , pc -#else -# define PC_FORMAT_STR "" -# define PC_FORMAT_ARG /* nothing */ -#endif + && !hide_log_until_execve + && (qual_flags[sig] & QUAL_SIGNAL) + ) { printleader(tcp); if (!stopped) { tprintf("--- %s ", signame(sig)); printsiginfo(&si, verbose(tcp)); - tprintf(PC_FORMAT_STR " ---\n" - PC_FORMAT_ARG); + tprints(" ---\n"); } else - tprintf("--- stopped by %s" PC_FORMAT_STR " ---\n", - signame(sig) - PC_FORMAT_ARG); + tprintf("--- stopped by %s ---\n", + signame(sig)); line_ended(); } @@ -2143,7 +2375,6 @@ trace(void) goto restart_tracee; /* It's group-stop */ -#ifdef USE_SEIZE if (use_seize) { /* * This ends ptrace-stop, but does *not* end group-stop. @@ -2151,20 +2382,19 @@ trace(void) * (that is, process really stops. It used to continue to run). */ if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) { - cleanup(); - return -1; + /* Note: ptrace_restart emitted error message */ + exit_code = 1; + return; } - tcp->curcol = curcol; continue; } /* We don't have PTRACE_LISTEN support... */ -#endif goto restart_tracee; } /* We handled quick cases, we are permitted to interrupt now. */ if (interrupted) - return 0; + return; /* This should be syscall entry or exit. * (Or it still can be that pesky post-execve SIGTRAP!) @@ -2181,21 +2411,17 @@ trace(void) * we can let this process to report its death to us * normally, via WIFEXITED or WIFSIGNALED wait status. */ - tcp->curcol = curcol; continue; } restart_tracee_with_sig_0: sig = 0; restart_tracee: - /* Remember current print column before continuing. */ if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) { - tcp->curcol = curcol; - cleanup(); - return -1; + /* Note: ptrace_restart emitted error message */ + exit_code = 1; + return; } - tcp->curcol = curcol; - } - return 0; + } /* while (1) */ } int @@ -2204,15 +2430,20 @@ main(int argc, char *argv[]) init(argc, argv); /* Run main tracing loop */ - if (trace() < 0) - return 1; + trace(); cleanup(); fflush(NULL); + if (shared_log != stderr) + fclose(shared_log); + if (popen_pid) { + while (waitpid(popen_pid, NULL, 0) < 0 && errno == EINTR) + ; + } if (exit_code > 0xff) { /* Avoid potential core file clobbering. */ - struct rlimit rlim = {0, 0}; - setrlimit(RLIMIT_CORE, &rlim); + struct_rlimit rlim = {0, 0}; + set_rlimit(RLIMIT_CORE, &rlim); /* Child was killed by a signal, mimic that. */ exit_code &= 0xff;