diff options
-rw-r--r-- | common.h | 18 | ||||
-rw-r--r-- | print.c | 2 | ||||
-rw-r--r-- | process.c | 20 | ||||
-rw-r--r-- | sctrace.c | 314 | ||||
-rw-r--r-- | tests/Makefile | 2 | ||||
-rw-r--r-- | tests/abort-nodump.c | 9 | ||||
-rw-r--r-- | tests/abort.c | 2 | ||||
-rw-r--r-- | tests/signal-interrupt.c | 26 | ||||
-rw-r--r-- | util.c | 2 |
9 files changed, 258 insertions, 137 deletions
@@ -1,6 +1,8 @@ /* See LICENSE file for copyright and license details. */ #include <asm/unistd.h> +#include <sys/prctl.h> #include <sys/uio.h> +#include <asm/unistd.h> #include <sys/wait.h> #include <ctype.h> #include <errno.h> @@ -65,15 +67,9 @@ enum type { }; enum state { - Normal, - Syscall, - CloneChild, - ForkChild, - VforkChild, - CloneParent, - ForkParent, - VforkParent, - Exec + UserSpace, + KernelSpace, + Zombie }; struct output { @@ -89,7 +85,7 @@ struct process { struct process *next; struct process *prev; enum state state; - int silent_until_execed; /* 2 until exec, 1 until "= 0", 0 afterwards */ + int ignore_until_execed; /* 2 until exec, 1 until "= 0", 0 afterwards */ /* Syscall data */ unsigned long long int scall; @@ -131,7 +127,7 @@ void print_systemcall_exit(struct process *proc); /* process.c */ void init_process_list(void); struct process *find_process(pid_t pid); -struct process *add_process(pid_t pid, unsigned long int trace_options); +struct process *add_process(pid_t pid, pid_t leader, unsigned long int trace_options); void remove_process(struct process *proc); /* util.c */ @@ -864,7 +864,7 @@ print_nonconst_sockaddr(struct process *proc, size_t arg_index) socklen_t saved_len; void *mem; const char *err; - if (proc->state == Syscall) { + if (proc->state == KernelSpace) { /* on return */ saved_len = (socklen_t)proc->save[arg_index + 1]; len = len < saved_len ? len : saved_len; @@ -26,7 +26,7 @@ find_process(pid_t pid) struct process * -add_process(pid_t pid, unsigned long int trace_options) +add_process(pid_t pid, pid_t leader, unsigned long int trace_options) { struct process *proc; int status, sig; @@ -35,12 +35,16 @@ add_process(pid_t pid, unsigned long int trace_options) if (!proc) eprintf("calloc: %s\n"); proc->pid = pid; + proc->thread_leader = leader; proc->next = &tail; proc->prev = tail.prev; proc->prev->next = proc; tail.prev = proc; - while (waitpid(pid, &status, WSTOPPED) != pid) { + if (!leader) + leader = pid; + + while (waitpid(pid, &status, WUNTRACED) != pid) { if (errno == EINTR) continue; eprintf_and_kill(pid, "waitpid %ju <buffer> WSTOPPED:", (uintmax_t)pid); @@ -48,15 +52,15 @@ add_process(pid_t pid, unsigned long int trace_options) sig = WIFSTOPPED(status) ? WSTOPSIG(status) : 0; if (sig == SIGSTOP) { - if (ptrace(PTRACE_SEIZE, pid, 0, trace_options)) - eprintf_and_kill(pid, "ptrace PTRACE_SEIZE %ju 0 ...:", (uintmax_t)pid); + if (ptrace(PTRACE_SEIZE, pid, NULL, trace_options)) + eprintf_and_kill(pid, "ptrace PTRACE_SEIZE %ju NULL ...:", (uintmax_t)pid); if (ptrace(PTRACE_INTERRUPT, pid, NULL, 0)) eprintf_and_kill(pid, "ptrace PTRACE_INTERRUPT %ju NULL 0:", (uintmax_t)pid); - if (kill(pid, SIGCONT) < 0) - eprintf_and_kill(pid, "kill &ju SIGCONT:", (uintmax_t)pid); + if (tgkill(leader, pid, SIGCONT) < 0) + eprintf_and_kill(pid, "tgkill %ju %ju SIGCONT:", (uintmax_t)leader, (uintmax_t)pid); } else if (sig == SIGTRAP && status & PTRACE_EVENT_STOP << 16) { - if (ptrace(PTRACE_SYSCALL, pid, NULL, 0)) - eprintf_and_kill(pid, "ptrace PTRACE_SYSCALL %ju NULL 0:", (uintmax_t)pid); + if (ptrace(PTRACE_SETOPTIONS, pid, NULL, trace_options)) + eprintf_and_kill(pid, "ptrace PTRACE_SETOPTIONS %ju NULL ...:", (uintmax_t)pid); } else { eprintf_and_kill(pid, "unexpected return of waitpid %ju <buffer> WSTOPPED: %#x\n", (uintmax_t)pid, status); } @@ -3,7 +3,7 @@ char *argv0; -static unsigned long int trace_options = PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC; +static unsigned long int trace_options = PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC | PTRACE_O_TRACEEXIT; _Noreturn static void @@ -16,129 +16,208 @@ usage(void) static void -handle_syscall(struct process *proc) +fetch_systemcall(struct process *proc, struct user_regs_struct *regs) { - struct user_regs_struct regs; - - switch ((int)proc->state) { - default: - /* Get system call arguments */ - if (ptrace(PTRACE_GETREGS, proc->pid, REGARGS(NULL, ®s))) - eprintf("ptrace PTRACE_GETREGS %ju NULL <buffer>:", (uintmax_t)proc->pid); - proc->scall = regs.SYSCALL_NUM_REG; + if (ptrace(PTRACE_GETREGS, proc->pid, REGARGS(NULL, regs))) + eprintf("ptrace PTRACE_GETREGS %ju NULL <buffer>:", (uintmax_t)proc->pid); + proc->scall = regs->SYSCALL_NUM_REG; #ifdef CHECK_ARCHITECTURE - CHECK_ARCHITECTURE(proc, ®s); - proc->scall ^= proc->scall_xor; + CHECK_ARCHITECTURE(proc, regs); + proc->scall ^= proc->scall_xor; #endif - GET_SYSCALL_ARGUMENTS(proc, ®s); - memset(proc->save, 0, sizeof(proc->save)); + GET_SYSCALL_ARGUMENTS(proc, regs); + memset(proc->save, 0, sizeof(proc->save)); +} - /* 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); +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 <buffer>:", (uintmax_t)proc->pid); + proc->ret = regs->SYSCALL_RET_REG; +} - proc->state = Syscall; - break; - case Syscall: - case CloneParent: - case ForkParent: - /* Get system call result */ - if (ptrace(PTRACE_GETREGS, proc->pid, REGARGS(NULL, ®s))) - eprintf("ptrace PTRACE_GETREGS %ju NULL <buffer>:", (uintmax_t)proc->pid); +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); +} - /* Get or set return */ - if (proc->state == Syscall) { - proc->ret = regs.SYSCALL_RET_REG; - } else { - regs.SYSCALL_RET_REG = proc->ret; - if (ptrace(PTRACE_SETREGS, proc->pid, REGARGS(NULL, ®s))) - eprintf("ptrace PTRACE_SETREGS %ju NULL <buffer>:", (uintmax_t)proc->pid); - if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, 0)) - eprintf("ptrace PTRACE_SYSCALL %ju NULL 0:", (uintmax_t)proc->pid); - } - /* Print system call result */ - print_systemcall_exit(proc); +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); +} - proc->silent_until_execed -= (proc->silent_until_execed == 1); - /* 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); +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); +} - proc->state = Normal; - break; - case Exec: - proc->silent_until_execed -= (proc->silent_until_execed == 2); - FALL_THROUGH - /* fall through */ - case VforkParent: - if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, 0)) - eprintf("ptrace PTRACE_SYSCALL %ju NULL 0:", (uintmax_t)proc->pid); - proc->state = Syscall; +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_LISTN" : + 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 CloneChild: - case ForkChild: - case VforkChild: - tprintf(proc, "= 0\n"); - proc->state = Normal; + 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 <buffer>:", (uintmax_t)proc->pid); + return event; +} + + static void handle_event(struct process *proc, int status) { - int trace_event, sig; - unsigned long int event; struct process *proc2; - - sig = WSTOPSIG(status); - trace_event = status >> 16; + int sig = WSTOPSIG(status); + int trace_event = status >> 16; switch (trace_event) { case PTRACE_EVENT_VFORK: - tprintf(proc, "\nProcess stopped by vfork until child exits or exec(2)s\n"); - FALL_THROUGH - /* fall through */ case PTRACE_EVENT_FORK: case PTRACE_EVENT_CLONE: - if (ptrace(PTRACE_GETEVENTMSG, proc->pid, NULL, &event)) - eprintf("ptrace PTRACE_GETEVENTMSG %ju NULL <buffer>:", (uintmax_t)proc->pid); - proc2 = add_process((pid_t)event, trace_options); - if (trace_event == PTRACE_EVENT_CLONE) - proc2->thread_leader = proc->pid; - proc->ret = event; + 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; - proc->state = VforkParent; - } else { - proc->state = trace_event == PTRACE_EVENT_CLONE ? CloneParent : ForkParent; - handle_syscall(proc); } - tprintf(proc2, "\nTracing new process with parent %ju\n", (uintmax_t)proc->pid); - proc2->state = trace_event == PTRACE_EVENT_FORK ? ForkChild : - trace_event == PTRACE_EVENT_VFORK ? VforkChild : CloneChild; - handle_syscall(proc2); + process_created(proc2, proc); + restart_systemcall(proc2, 0); + restart_systemcall(proc, 0); break; case PTRACE_EVENT_EXEC: - proc->state = Exec; - handle_syscall(proc); + 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; - tprintf(proc2, "\nProcess continues due to exec(2) of vfork child\n"); - handle_syscall(proc2); + vfork_done(proc2, "exec(2)"); } break; @@ -148,27 +227,34 @@ handle_event(struct process *proc, int status) case SIGTSTP: case SIGTTIN: case SIGTTOU: - stop_signal: - tprintf(proc, "\nProcess stopped by signal %i (%s: %s)\n", sig, get_signum_name(sig), strsignal(sig)); - if (ptrace(PTRACE_LISTEN, proc->pid, NULL, 0)) - eprintf("ptrace PTRACE_LISTEN %ju NULL 0:", (uintmax_t)proc->pid); + process_signalled(proc, sig, 1); + restart_process(proc, PTRACE_LISTEN, 0); break; default: - if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, 0)) - eprintf("ptrace PTRACE_SYSCALL %ju NULL 0:", (uintmax_t)proc->pid); + 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: - if (ptrace(PTRACE_GETSIGINFO, proc->pid, 0, &(siginfo_t){0})) - goto stop_signal; - tprintf(proc, "\nProcess received signal %i (%s: %s)\n", sig, get_signum_name(sig), strsignal(sig)); - if (ptrace(PTRACE_SYSCALL, proc->pid, NULL, sig)) - eprintf("ptrace PTRACE_SYSCALL %ju NULL %i:", (uintmax_t)proc->pid, sig); + process_signalled(proc, sig, 0); + restart_systemcall(proc, sig); break; } } @@ -182,7 +268,7 @@ main(int argc, char **argv) FILE *outfp = stderr; int status, exit_code = 0, with_argv0 = 0, multiprocess = 0, i; char *arg; - struct process *proc, *proc2; + struct process *proc; struct sigaction sa; sigset_t sm; @@ -212,6 +298,7 @@ main(int argc, char **argv) case 'f': trace_options |= PTRACE_O_TRACEFORK; trace_options |= PTRACE_O_TRACEVFORK; + trace_options |= PTRACE_O_TRACEVFORKDONE; FALL_THROUGH /* fall through */ case 't': @@ -224,6 +311,9 @@ main(int argc, char **argv) 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: @@ -247,10 +337,12 @@ main(int argc, char **argv) outfp = outfile ? xfopen(outfile, "wb") : stderr; setup_trace_output(outfp, multiprocess); init_process_list(); - add_process(orig_pid, trace_options)->silent_until_execed = 2; + proc = add_process(orig_pid, 0, trace_options); + proc->ignore_until_execed = 2; + restart_systemcall(proc, 0); for (;;) { - pid = waitpid(-1, &status, __WALL | WCONTINUED); + pid = waitpid(-1, &status, __WALL | WCONTINUED); /* TODO WCONTINUED should require waitid */ if (pid < 0) { if (errno == ECHILD) break; @@ -264,32 +356,21 @@ main(int argc, char **argv) continue; if (WIFSTOPPED(status)) { - if (WSTOPSIG(status) == (SIGTRAP | 0x80)) + 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)) { - tprintf(proc, "\nProcess continued, presumably by signal %i (SIGCONT: %s)\n", SIGCONT, strsignal(SIGCONT)); + process_continued(proc, status); } else { if (pid == orig_pid) exit_code = status; - if (WIFEXITED(status)) { - tprintf(proc, "\nProcess exited with value %i%s\n", WEXITSTATUS(status), - WCOREDUMP(status) ? ", core dumped" : ""); - } else { - tprintf(proc, "\nProcess terminated by signal %i (%s: %s)%s\n", WTERMSIG(status), - get_signum_name(WTERMSIG(status)), strsignal(WTERMSIG(status)), - WCOREDUMP(status) ? ", core dumped" : ""); - } - proc2 = proc->continue_on_exit; + process_exited(proc, status); + if (proc->continue_on_exit) + vfork_done(proc->continue_on_exit, WIFEXITED(status) ? "exit" : "abnormal termination"); remove_process(proc); - if (proc2) { - if (WIFEXITED(status)) - tprintf(proc2, "\nProcess continues due to exit of vfork child\n"); - else - tprintf(proc2, "\nProcess continues due to abnormal termination of vfork child\n"); - handle_syscall(proc2); - } } fflush(outfp); @@ -301,6 +382,7 @@ main(int argc, char **argv) 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; diff --git a/tests/Makefile b/tests/Makefile index 7468bfa..527c690 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -2,6 +2,7 @@ BIN_64 =\ abort.64\ + abort-nodump.64\ cont.64\ exec.64\ exit.64\ @@ -13,6 +14,7 @@ BIN_64 =\ raise.64\ siginfo.64\ signal.64\ + signal-interrupt.64\ stop.64\ threads.64\ tstp.64\ diff --git a/tests/abort-nodump.c b/tests/abort-nodump.c new file mode 100644 index 0000000..9e13595 --- /dev/null +++ b/tests/abort-nodump.c @@ -0,0 +1,9 @@ +#include <sys/prctl.h> +#include <stdlib.h> + +int +main(void) +{ + prctl(PR_SET_DUMPABLE, 0); + abort(); +} diff --git a/tests/abort.c b/tests/abort.c index 3afda7b..aa9dac9 100644 --- a/tests/abort.c +++ b/tests/abort.c @@ -1,7 +1,9 @@ +#include <sys/prctl.h> #include <stdlib.h> int main(void) { + prctl(PR_SET_DUMPABLE, 1); abort(); } diff --git a/tests/signal-interrupt.c b/tests/signal-interrupt.c new file mode 100644 index 0000000..7b6988f --- /dev/null +++ b/tests/signal-interrupt.c @@ -0,0 +1,26 @@ +#include <signal.h> +#include <time.h> +#include <unistd.h> + +static void +interrupt() +{ + write(-2, "xyzzy\n", 6); +} + +int +main(void) +{ + struct timespec ts = {0, 100000000L}; + pid_t pid = getpid(); + signal(SIGINT, interrupt); + if (fork() == 0) { + ts.tv_nsec /= 2; + nanosleep(&ts, NULL); + kill(pid, SIGINT); + } else { + nanosleep(&ts, NULL); + write(-1, "qwerty\n", 7); + } + return 0; +} @@ -25,7 +25,7 @@ void tprintf(struct process *proc, const char *fmt, ...) { va_list ap; - if (proc->silent_until_execed) + if (proc->ignore_until_execed) return; if (fmt[0] == '\n' && fmt[1]) { last_pid = 0; |