/* See LICENSE file for copyright and license details. */ #include "common.h" char *argv0; static void usage(void) { fprintf(stderr, "usage: %s [-f trace-output-file] [-CFV] command ...\n", argv0); exit(1); } static void handle_syscall(struct process *proc) { struct user_regs_struct regs; switch (proc->state) { default: /* Get systemcall arguments */ if (ptrace(PTRACE_GETREGS, proc->pid, NULL, ®s)) eprintf("ptrace PTRACE_GETREGS %ju NULL :", (uintmax_t)proc->pid); proc->scall = regs.orig_rax; proc->args[0] = regs.rdi; proc->args[1] = regs.rsi; proc->args[2] = regs.rdx; proc->args[3] = regs.r10; proc->args[4] = regs.r8; proc->args[5] = regs.r9; /* Print system call */ print_systemcall(proc); /* Run system call */ if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, 0)) eprintf("ptrace PTRACE_SYSCALL %ju NULL 0:", (uintmax_t)proc->pid); proc->state = Syscall; break; case Syscall: /* Get system call result */ if (ptrace(PTRACE_GETREGS, proc->pid, NULL, ®s)) eprintf("ptrace PTRACE_GETREGS %ju NULL :", (uintmax_t)proc->pid); proc->ret = regs.rax; /* Print system call result */ print_systemcall_exit(proc); /* Make process continue and stop at next syscall */ if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, 0)) eprintf("ptrace PTRACE_SYSCALL %ju NULL 0", (uintmax_t)proc->pid); proc->state = Normal; break; } } int main(int argc, char **argv) { pid_t pid, orig_pid; long int tmp; char *outfile = NULL; FILE *outfp = stderr; const char *num = NULL; int status, exit_value = 0; unsigned long int trace_options = PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD; struct process *proc; unsigned long int event; /* TODO add support for exec */ /* TODO add option to trace threads */ /* TODO add option to trace vforks */ /* TODO add option to trace signals */ /* TODO add option to specify argv[0] */ ARGBEGIN { case 'f': if (outfile) usage(); outfile = EARGF(usage()); break; case 'F': trace_options |= PTRACE_O_TRACEFORK; break; default: usage(); } ARGEND; if (!argc) usage(); init_process_list(); /* Start program to trace */ pid = fork(); switch (pid) { case -1: eprintf("fork:"); return 1; case 0: if (ptrace(PTRACE_TRACEME, 0, NULL, 0)) { eprintf("ptrace PTRACE_TRACEME 0 NULL 0:"); return 1; } /* exec will block until parent attaches */ execvp(*argv, argv); eprintf("execvp %s:", *argv); default: orig_pid = pid; add_process(pid, trace_options); break; } /* Open trace output file */ if (outfile) { if (!strncmp(outfile, "/dev/fd/", sizeof("/dev/fd/") - 1)) num = &outfile[sizeof("/dev/fd/") - 1]; else if (!strncmp(outfile, "/proc/self/fd/", sizeof("/proc/self/fd/") - 1)) num = &outfile[sizeof("/proc/self/fd/") - 1]; else if (!strcmp(outfile, "/dev/stdin")) num = "0"; else if (!strcmp(outfile, "/dev/stdout")) num = "1"; else if (!strcmp(outfile, "/dev/stderr")) num = "2"; if (num && isdigit(*num)) { errno = 0; tmp = strtol(num, (void *)&num, 10); if (!errno && tmp >= 0 && #if INT_MAX < LONG_MAX tmp < INT_MAX && #endif !*num) { outfp = fdopen((int)tmp, "wb"); if (!outfp) { eprintf("fdopen %li wb:", tmp); return 1; } goto have_outfp; } } outfp = fopen(outfile, "wb"); if (!outfp) { eprintf("fopen %s wb:", outfile); return 1; } } have_outfp: set_trace_output(outfp); for (;;) { /* Wait for next syscall enter/exit */ pid = wait(&status); if (pid < 0) { if (errno == ECHILD) return exit_value; eprintf("wait :"); return 1; } proc = find_process(pid); if (!proc) continue; if (WIFEXITED(status)) { if (pid == orig_pid) exit_value = WEXITSTATUS(status); tprintf(proc, "\nProcess exited with value %i\n", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { tprintf(proc, "\nProcess terminated by signal %i (%s)\n", WTERMSIG(status), strsignal(WTERMSIG(status))); /* TODO print signal name */ } else if (WIFSTOPPED(status)) { if (WSTOPSIG(status) == (SIGTRAP | 0x80)) { handle_syscall(proc); } else if (WSTOPSIG(status) == SIGTRAP) { switch (((status >> 8) ^ SIGTRAP) >> 8) { case PTRACE_EVENT_FORK: if (ptrace(PTRACE_GETEVENTMSG, proc->pid, NULL, &event)) eprintf("ptrace PTRACE_GETEVENTMSG %ju NULL :", (uintmax_t)proc->pid); add_process((pid_t)event, trace_options); handle_syscall(proc); break; default: goto print_signal; } } else { print_signal: tprintf(proc, "\nProcess stopped by signal %i (%s)\n", WSTOPSIG(status), strsignal(WSTOPSIG(status))); /* TODO print signal name */ } } else if (WIFCONTINUED(status)) { tprintf(proc, "\nProcess continued\n", (uintmax_t)pid); } } fclose(outfp); return 0; }