/* See LICENSE file for copyright and license details. */ #include "common.h" char *argv0; static unsigned long int trace_options = PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT; _Noreturn static void usage(void) { fprintf(stderr, "usage: %s [-a byte-count] [-o trace-output-file] [-ft]" " (command | -0 command argv0) [argument] ...\n", argv0); exit(1); } static void fetch_systemcall(struct process *proc, struct user_regs_struct *regs) { if (ptrace(PTRACE_GETREGS, proc->pid, REGARGS(NULL, regs))) eprintf("ptrace PTRACE_GETREGS %ju NULL :", (uintmax_t)proc->pid); proc->scall = regs->SYSCALL_NUM_REG; #ifdef CHECK_ARCHITECTURE CHECK_ARCHITECTURE(proc, regs); proc->scall ^= proc->scall_xor; #endif GET_SYSCALL_ARGUMENTS(proc, regs); memset(proc->save, 0, sizeof(proc->save)); } static void fetch_systemcall_result(struct process *proc, struct user_regs_struct *regs) { if (ptrace(PTRACE_GETREGS, proc->pid, REGARGS(NULL, regs))) eprintf("ptrace PTRACE_GETREGS %ju NULL :", (uintmax_t)proc->pid); proc->ret = regs->SYSCALL_RET_REG; } static void enter_systemcall(struct process *proc) { if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, 0)) eprintf("ptrace PTRACE_SYSCALL %ju NULL 0:", (uintmax_t)proc->pid); } static void leave_systemcall(struct process *proc) { if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, 0)) eprintf("ptrace PTRACE_SYSCALL %ju NULL 0:", (uintmax_t)proc->pid); } static void restart_systemcall(struct process *proc, int sig) { if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, sig)) eprintf("ptrace PTRACE_SYSCALL %ju NULL %i:", (uintmax_t)proc->pid, sig); } static void vfork_start(struct process *proc) { tprintf(proc, "\nProcess stopped by vfork until child exits or exec(2)s\n"); } static void vfork_done(struct process *proc, const char *reason) { tprintf(proc, "\nProcess continues due to %s of vfork child\n", reason); } static void process_exiting(struct process *proc, int status) { (void) proc; (void) status; } static void process_exited(struct process *proc, int status) { if (WIFEXITED(status)) { tprintf(proc, "\nProcess exited%s with value %i%s\n", proc->state == Zombie ? "" : ", without knell", WEXITSTATUS(status), WCOREDUMP(status) ? ", core dumped" : ""); } else { tprintf(proc, "\nProcess terminated%s by signal %i (%s: %s)%s\n", proc->state == Zombie ? "" : ", without knell", WTERMSIG(status), get_signum_name(WTERMSIG(status)), strsignal(WTERMSIG(status)), WCOREDUMP(status) ? ", core dumped" : ""); } } static void process_signalled(struct process *proc, int sig, int stopped) { tprintf(proc, "\nProcess %s signal %i (%s: %s)\n", stopped ? "stopped by" : "received", sig, get_signum_name(sig), strsignal(sig)); } static void process_continued(struct process *proc, int status) { tprintf(proc, "\n%s continued, presumably by signal %i (SIGCONT: %s)\n", proc->state == Zombie ? "Zombie process" : "Process", SIGCONT, strsignal(SIGCONT)); (void) status; } static void restart_process(struct process *proc, int cmd, int sig) { if (ptrace(cmd, proc->pid, NULL, sig)) { eprintf("ptrace %s %ju NULL %i:", cmd == PTRACE_CONT ? "PTRACE_CONT" : cmd == PTRACE_LISTEN ? "PTRACE_LISTEN" : cmd == PTRACE_SYSEMU ? "PTRACE_SYSEMU" : cmd == PTRACE_SYSCALL ? "PTRACE_SYSCALL" : "???", (uintmax_t)proc->pid, sig); } } static void process_created(struct process *proc, struct process *parent) { tprintf(proc, "\nTracing new process with parent %ju\n", (uintmax_t)parent->pid); tprintf(proc, "= 0\n"); } static void zombie_stopped(struct process *proc, int status) { tprintf(proc, "\nReceived unexpected event on zombie process\n"); (void) status; } static void handle_syscall(struct process *proc) { struct user_regs_struct regs; switch ((int)proc->state) { case UserSpace: fetch_systemcall(proc, ®s); print_systemcall(proc); enter_systemcall(proc); proc->state = KernelSpace; break; case KernelSpace: fetch_systemcall_result(proc, ®s); if (!proc->ignore_until_execed) print_systemcall_exit(proc); else proc->ignore_until_execed -= (proc->ignore_until_execed == 1); leave_systemcall(proc); proc->state = UserSpace; break; default: abort(); } } static unsigned long int get_event_msg(struct process *proc) { unsigned long int event; if (ptrace(PTRACE_GETEVENTMSG, proc->pid, NULL, &event)) eprintf("ptrace PTRACE_GETEVENTMSG %ju NULL :", (uintmax_t)proc->pid); return event; } static void handle_event(struct process *proc, int status) { struct process *proc2; int sig = WSTOPSIG(status); int trace_event = status >> 16; switch (trace_event) { case PTRACE_EVENT_VFORK: case PTRACE_EVENT_FORK: case PTRACE_EVENT_CLONE: proc2 = add_process((pid_t)get_event_msg(proc), trace_event == PTRACE_EVENT_CLONE ? proc->pid : 0, trace_options); if (trace_event == PTRACE_EVENT_VFORK) { vfork_start(proc); proc2->continue_on_exit = proc; proc->vfork_waiting_on = proc2; } process_created(proc2, proc); restart_systemcall(proc2, 0); restart_systemcall(proc, 0); break; case PTRACE_EVENT_EXEC: proc->ignore_until_execed -= (proc->ignore_until_execed == 2); restart_systemcall(proc, 0); proc2 = proc->continue_on_exit; if (proc2) { proc->continue_on_exit = NULL; proc2->vfork_waiting_on = NULL; vfork_done(proc2, "exec(2)"); } break; case PTRACE_EVENT_STOP: switch (sig) { case SIGSTOP: case SIGTSTP: case SIGTTIN: case SIGTTOU: process_signalled(proc, sig, 1); restart_process(proc, PTRACE_LISTEN, 0); /* TODO LISTEN only forks if SEIZED (original tracee) */ break; default: tprintf(proc, "\nTRACE_EVENT_STOP with signal %i (%s: %s)\n", WTERMSIG(status), get_signum_name(WTERMSIG(status)), strsignal(WTERMSIG(status))); restart_systemcall(proc, 0); break; } break; case PTRACE_EVENT_EXIT: process_exiting(proc, (int)get_event_msg(proc)); proc->state = Zombie; restart_process(proc, PTRACE_CONT, 0); break; case PTRACE_EVENT_VFORK_DONE: restart_systemcall(proc, 0); break; default: abort(); case 0: process_signalled(proc, sig, 0); restart_systemcall(proc, sig); break; } } int main(int argc, char **argv) { pid_t pid, orig_pid; char *outfile = NULL; FILE *outfp = stderr; int status, exit_code = 0, with_argv0 = 0, multiprocess = 0, i; char *arg; struct process *proc; struct sigaction sa; sigset_t sm; /* TODO add option to trace signals with siginfo (-s) */ ARGBEGIN { case '0': with_argv0 = 1; break; case 'a': arg = EARGF(usage()); if (!strcmp(arg, "inf")) { abbreviate_memory = SIZE_MAX; break; } if (!isdigit(arg[0])) usage(); errno = 0; abbreviate_memory = (size_t)strtoul(arg, &arg, 10); if ((errno && errno != ERANGE) || *arg) usage(); break; case 'o': if (outfile) usage(); outfile = EARGF(usage()); break; case 'f': trace_options |= PTRACE_O_TRACEFORK; trace_options |= PTRACE_O_TRACEVFORK; trace_options |= PTRACE_O_TRACEVFORKDONE; FALL_THROUGH /* fall through */ case 't': trace_options |= PTRACE_O_TRACECLONE; multiprocess = 1; break; default: usage(); } ARGEND; if (!argc) usage(); if (prctl(PR_SET_CHILD_SUBREAPER, 1)) weprintf("prctl PR_SET_CHILD_SUBREAPER 1:"); orig_pid = fork(); switch (orig_pid) { case -1: eprintf("fork:"); case 0: if (raise(SIGSTOP)) eprintf_and_kill(getppid(), "raise SIGSTOP:"); execvp(*argv, &argv[with_argv0]); eprintf_and_kill(getppid(), "execvp %s:", *argv); default: break; } memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; for (i = 1; i <= _NSIG; i++) sigaction(i, &sa, NULL); sigemptyset(&sm); if (sigprocmask(SIG_SETMASK, &sm, NULL)) eprintf_and_kill(orig_pid, "sigprocmask SIG_SETMASK NULL:"); outfp = outfile ? xfopen(outfile, "wb") : stderr; setup_trace_output(outfp, multiprocess); init_process_list(); proc = add_process(orig_pid, 0, trace_options); proc->ignore_until_execed = 2; restart_systemcall(proc, 0); for (;;) { pid = waitpid(-1, &status, __WALL | WCONTINUED); /* TODO WCONTINUED should require waitid */ if (pid < 0) { if (errno == ECHILD) break; if (errno == EINTR) continue; eprintf("waitpid -1 __WALL|WCONTINUED:"); } proc = find_process(pid); if (!proc) continue; if (WIFSTOPPED(status)) { if (proc->state == Zombie) zombie_stopped(proc, status); else if (WSTOPSIG(status) == (SIGTRAP | 0x80)) handle_syscall(proc); else handle_event(proc, status); } else if (WIFCONTINUED(status)) { process_continued(proc, status); } else { if (pid == orig_pid) exit_code = status; process_exited(proc, status); if (proc->continue_on_exit) vfork_done(proc->continue_on_exit, WIFEXITED(status) ? "exit" : "abnormal termination"); remove_process(proc); } fflush(outfp); } fflush(outfp); if (outfp != stderr) fclose(outfp); weprintf("Copying exit from %s\n", multiprocess ? "original tracee" : "tracee"); if (WIFSIGNALED(exit_code)) { prctl(PR_SET_DUMPABLE, 0); exit_code = WTERMSIG(exit_code); raise(exit_code); return exit_code + 128; } return WEXITSTATUS(exit_code); }