diff --git a/breakpoints.c b/breakpoints.c index 1ea406a..1eff8b0 100644 --- a/breakpoints.c +++ b/breakpoints.c @@ -14,15 +14,25 @@ Breakpoint * address2bpstruct(Process *proc, void *addr) { + assert(proc != NULL); + assert(proc->breakpoints != NULL); + assert(proc->leader == proc); debug(DEBUG_FUNCTION, "address2bpstruct(pid=%d, addr=%p)", proc->pid, addr); return dict_find_entry(proc->breakpoints, addr); } void insert_breakpoint(Process *proc, void *addr, - struct library_symbol *libsym) { + struct library_symbol *libsym, int enable) { Breakpoint *sbp; + Process * leader = proc->leader; + + /* Only the group leader should be getting the breakpoints and + * thus have ->breakpoint initialized. */ + assert(leader != NULL); + assert(leader->breakpoints != NULL); + #ifdef __arm__ int thumb_mode = (int)addr & 1; if (thumb_mode) @@ -38,13 +48,13 @@ insert_breakpoint(Process *proc, void *addr, if (libsym) libsym->needs_init = 0; - sbp = dict_find_entry(proc->breakpoints, addr); + sbp = dict_find_entry(leader->breakpoints, addr); if (!sbp) { sbp = calloc(1, sizeof(Breakpoint)); if (!sbp) { return; /* TODO FIXME XXX: error_mem */ } - dict_enter(proc->breakpoints, addr, sbp); + dict_enter(leader->breakpoints, addr, sbp); sbp->addr = addr; sbp->libsym = libsym; } @@ -53,8 +63,10 @@ insert_breakpoint(Process *proc, void *addr, proc->thumb_mode = 0; #endif sbp->enabled++; - if (sbp->enabled == 1 && proc->pid) - enable_breakpoint(proc->pid, sbp); + if (sbp->enabled == 1 && enable) { + assert(proc->pid != 0); + enable_breakpoint(proc, sbp); + } } void @@ -63,7 +75,10 @@ delete_breakpoint(Process *proc, void *addr) { debug(DEBUG_FUNCTION, "delete_breakpoint(pid=%d, addr=%p)", proc->pid, addr); - sbp = dict_find_entry(proc->breakpoints, addr); + Process * leader = proc->leader; + assert(leader != NULL); + + sbp = dict_find_entry(leader->breakpoints, addr); assert(sbp); /* FIXME: remove after debugging has been done. */ /* This should only happen on out-of-memory conditions. */ if (sbp == NULL) @@ -71,7 +86,7 @@ delete_breakpoint(Process *proc, void *addr) { sbp->enabled--; if (sbp->enabled == 0) - disable_breakpoint(proc->pid, sbp); + disable_breakpoint(proc, sbp); assert(sbp->enabled >= 0); } @@ -79,7 +94,7 @@ static void enable_bp_cb(void *addr, void *sbp, void *proc) { debug(DEBUG_FUNCTION, "enable_bp_cb(pid=%d)", ((Process *)proc)->pid); if (((Breakpoint *)sbp)->enabled) { - enable_breakpoint(((Process *)proc)->pid, sbp); + enable_breakpoint(proc, sbp); } } @@ -146,13 +161,14 @@ static void disable_bp_cb(void *addr, void *sbp, void *proc) { debug(DEBUG_FUNCTION, "disable_bp_cb(pid=%d)", ((Process *)proc)->pid); if (((Breakpoint *)sbp)->enabled) { - disable_breakpoint(((Process *)proc)->pid, sbp); + disable_breakpoint(proc, sbp); } } void disable_all_breakpoints(Process *proc) { debug(DEBUG_FUNCTION, "disable_all_breakpoints(pid=%d)", proc->pid); + assert(proc->leader == proc); if (proc->breakpoints_enabled) { debug(1, "Disabling breakpoints for pid %u...", proc->pid); dict_apply_to_all(proc->breakpoints, disable_bp_cb, proc); @@ -167,8 +183,9 @@ free_bp_cb(void *addr, void *sbp, void *data) { free(sbp); } -void -breakpoints_init(Process *proc) { +int +breakpoints_init(Process *proc, int enable) +{ struct library_symbol *sym; debug(DEBUG_FUNCTION, "breakpoints_init(pid=%d)", proc->pid); @@ -177,19 +194,41 @@ breakpoints_init(Process *proc) { dict_clear(proc->breakpoints); proc->breakpoints = NULL; } - proc->breakpoints = dict_init(dict_key2hash_int, dict_key_cmp_int); + + /* Only the thread group leader should hold the breakpoints. + * (N.B. PID may be set to 0 temporarily when called by + * handle_exec). */ + assert(proc->leader == proc); + + proc->breakpoints = dict_init(dict_key2hash_int, + dict_key_cmp_int); + + if (proc->list_of_symbols != NULL) { + struct library_symbol * sym = proc->list_of_symbols; + while (sym != NULL) { + struct library_symbol * next = sym->next; + free(sym); + sym = next; + } + } + proc->list_of_symbols = NULL; if (options.libcalls && proc->filename) { - /* FIXME: memory leak when called by exec(): */ proc->list_of_symbols = read_elf(proc); + if (proc->list_of_symbols == NULL) { + /* XXX leak breakpoints */ + return -1; + } + if (opt_e) { - struct library_symbol **tmp1 = &(proc->list_of_symbols); + struct library_symbol **tmp1 = &proc->list_of_symbols; while (*tmp1) { struct opt_e_t *tmp2 = opt_e; int keep = !opt_e_enable; while (tmp2) { - if (!strcmp((*tmp1)->name, tmp2->name)) { + if (!strcmp((*tmp1)->name, + tmp2->name)) { keep = opt_e_enable; } tmp2 = tmp2->next; @@ -201,15 +240,14 @@ breakpoints_init(Process *proc) { } } } - } else { - proc->list_of_symbols = NULL; - } - for (sym = proc->list_of_symbols; sym; sym = sym->next) { - /* proc->pid==0 delays enabling. */ - insert_breakpoint(proc, sym2addr(proc, sym), sym); } + + for (sym = proc->list_of_symbols; sym; sym = sym->next) + insert_breakpoint(proc, sym2addr(proc, sym), sym, enable); + proc->callstack_depth = 0; proc->breakpoints_enabled = -1; + return 0; } void @@ -222,8 +260,7 @@ reinitialize_breakpoints(Process *proc) { while (sym) { if (sym->needs_init) { - insert_breakpoint(proc, sym2addr(proc, sym), - sym); + insert_breakpoint(proc, sym2addr(proc, sym), sym, 1); if (sym->needs_init && !sym->is_weak) { fprintf(stderr, "could not re-initialize breakpoint for \"%s\" in file \"%s\"\n", diff --git a/common.h b/common.h index 70e4a5a..49861cf 100644 --- a/common.h +++ b/common.h @@ -1,3 +1,4 @@ +#include #if defined(HAVE_LIBUNWIND) #include #endif /* defined(HAVE_LIBUNWIND) */ @@ -161,12 +162,32 @@ enum Process_State { STATE_IGNORED /* ignore this process (it's a fork and no -f was used) */ }; +typedef struct Event_Handler Event_Handler; +struct Event_Handler { + /* Event handler that overrides the default one. Should + * return NULL if the event was handled, otherwise the + * returned event is passed to the default handler. */ + Event * (* on_event)(Event_Handler * self, Event * event); + + /* Called when the event handler removal is requested. */ + void (* destroy)(Event_Handler * self); +}; + +/* XXX We would rather have this all organized a little differently, + * have Process for the whole group and Task for what's there for + * per-thread stuff. But for now this is the less invasive way of + * structuring it. */ struct Process { Process_State state; Process * parent; /* needed by STATE_BEING_CREATED */ char * filename; pid_t pid; + + /* Dictionary of breakpoints (which is a mapping + * address->Breakpoint). This is NULL for non-leader + * processes. */ Dict * breakpoints; + int breakpoints_enabled; /* -1:not enabled yet, 0:disabled, 1:enabled */ int mask_32bit; /* 1 if 64-bit ltrace is tracing 32-bit process */ unsigned int personality; @@ -183,7 +204,6 @@ struct Process { void * instruction_pointer; void * stack_pointer; /* To get return addr, args... */ void * return_addr; - Breakpoint * breakpoint_being_enabled; void * arch_ptr; short e_machine; short need_to_reinitialize_breakpoints; @@ -191,16 +211,28 @@ struct Process { int thumb_mode; /* ARM execution mode: 0: ARM, 1: Thumb */ #endif - /* output: */ - enum tof type_being_displayed; - #if defined(HAVE_LIBUNWIND) /* libunwind address space */ unw_addr_space_t unwind_as; void *unwind_priv; #endif /* defined(HAVE_LIBUNWIND) */ + /* Set in leader. */ + Event_Handler * event_handler; + + + /** + * Process chaining. + **/ Process * next; + + /* LEADER points to the leader thread of the POSIX.1 process. + If X->LEADER == X, then X is the leader thread and the + Process structures chained by NEXT represent other threads, + up until, but not including, the next leader thread. + LEADER may be NULL after the leader has already exited. In + that case this process is waiting to be collected. */ + Process * leader; }; struct opt_c_struct { @@ -216,27 +248,64 @@ struct opt_c_struct { extern Dict * dict_opt_c; -extern Process * list_of_processes; +enum process_status { + ps_invalid, /* Failure. */ + ps_stop, /* Job-control stop. */ + ps_tracing_stop, + ps_zombie, + ps_other, /* Necessary other states can be added as needed. */ +}; -extern Event * next_event(void); +enum pcb_status { + pcb_stop, /* The iteration should stop. */ + pcb_cont, /* The iteration should continue. */ +}; + +/* Process list */ extern Process * pid2proc(pid_t pid); +extern void add_process(Process * proc); +extern void remove_process(Process * proc); +extern Process *each_process(Process * start, + enum pcb_status (* cb)(Process * proc, void * data), + void * data); +extern Process *each_task(Process * start, + enum pcb_status (* cb)(Process * proc, void * data), + void * data); + +/* Events */ +enum ecb_status { + ecb_cont, /* The iteration should continue. */ + ecb_yield, /* The iteration should stop, yielding this + * event. */ + ecb_deque, /* Like ecb_stop, but the event should be removed + * from the queue. */ +}; +extern Event * next_event(void); +extern Event * each_qd_event(enum ecb_status (* cb)(Event * event, void * data), + void * data); +extern void enque_event(Event * event); extern void handle_event(Event * event); -extern void execute_program(Process *, char **); + +extern void install_event_handler(Process * proc, Event_Handler * handler); +extern void destroy_event_handler(Process * proc); + +extern pid_t execute_program(const char * command, char ** argv); extern int display_arg(enum tof type, Process * proc, int arg_num, arg_type_info * info); extern Breakpoint * address2bpstruct(Process * proc, void * addr); -extern void breakpoints_init(Process * proc); -extern void insert_breakpoint(Process * proc, void * addr, struct library_symbol * libsym); +extern int breakpoints_init(Process * proc, int enable); +extern void insert_breakpoint(Process * proc, void * addr, + struct library_symbol * libsym, int enable); extern void delete_breakpoint(Process * proc, void * addr); extern void enable_all_breakpoints(Process * proc); extern void disable_all_breakpoints(Process * proc); extern void reinitialize_breakpoints(Process *); -extern Process * open_program(char * filename, pid_t pid); +extern Process * open_program(char * filename, pid_t pid, int init_breakpoints); extern void open_pid(pid_t pid); extern void show_summary(void); extern arg_type_info * lookup_prototype(enum arg_type at); -extern void do_init_elf(struct ltelf *lte, const char *filename); +extern int do_init_elf(struct ltelf *lte, const char *filename); extern void do_close_elf(struct ltelf *lte); extern int in_load_libraries(const char *name, struct ltelf *lte, size_t count, GElf_Sym *sym); extern struct library_symbol *library_symbols; @@ -246,6 +315,10 @@ extern void add_library_symbol(GElf_Addr addr, const char *name, /* Arch-dependent stuff: */ extern char * pid2name(pid_t pid); +extern pid_t process_leader(pid_t pid); +extern int process_tasks(pid_t pid, pid_t **ret_tasks, size_t *ret_n); +extern int process_stopped(pid_t pid); +extern enum process_status process_status(pid_t pid); extern void trace_set_options(Process * proc, pid_t pid); extern void trace_me(void); extern int trace_pid(pid_t pid); @@ -256,13 +329,13 @@ extern void set_instruction_pointer(Process * proc, void * addr); extern void * get_stack_pointer(Process * proc); extern void * get_return_addr(Process * proc, void * stack_pointer); extern void set_return_addr(Process * proc, void * addr); -extern void enable_breakpoint(pid_t pid, Breakpoint * sbp); -extern void disable_breakpoint(pid_t pid, const Breakpoint * sbp); +extern void enable_breakpoint(Process * proc, Breakpoint * sbp); +extern void disable_breakpoint(Process * proc, Breakpoint * sbp); extern int syscall_p(Process * proc, int status, int * sysnum); extern void continue_process(pid_t pid); extern void continue_after_signal(pid_t pid, int signum); extern void continue_after_breakpoint(Process * proc, Breakpoint * sbp); -extern void continue_enabling_breakpoint(pid_t pid, Breakpoint * sbp); +extern void ltrace_exiting(void); extern long gimme_arg(enum tof type, Process * proc, int arg_num, arg_type_info * info); extern void save_register_args(enum tof type, Process * proc); extern int umovestr(Process * proc, void * addr, int len, void * laddr); @@ -272,5 +345,7 @@ extern int ffcheck(void * maddr); extern void * sym2addr(Process *, struct library_symbol *); extern int linkmap_init(Process *, struct ltelf *); extern void arch_check_dbg(Process *proc); +extern int task_kill (pid_t pid, int sig); + extern struct ltelf main_lte; diff --git a/execute_program.c b/execute_program.c index 3651b66..47f514d 100644 --- a/execute_program.c +++ b/execute_program.c @@ -17,7 +17,8 @@ #include "common.h" static void -change_uid(Process *proc) { +change_uid(const char * command) +{ uid_t run_uid, run_euid; gid_t run_gid, run_egid; @@ -49,7 +50,7 @@ change_uid(Process *proc) { run_euid = run_uid; run_egid = run_gid; - if (!stat(proc->filename, &statbuf)) { + if (!stat(command, &statbuf)) { if (statbuf.st_mode & S_ISUID) { run_euid = statbuf.st_uid; } @@ -68,32 +69,27 @@ change_uid(Process *proc) { } } -void -execute_program(Process *sp, char **argv) { +pid_t +execute_program(const char * command, char **argv) +{ pid_t pid; - debug(1, "Executing `%s'...", sp->filename); + debug(1, "Executing `%s'...", command); pid = fork(); if (pid < 0) { perror("ltrace: fork"); exit(1); } else if (!pid) { /* child */ - change_uid(sp); + change_uid(command); trace_me(); - execvp(sp->filename, argv); - fprintf(stderr, "Can't execute `%s': %s\n", sp->filename, + execvp(command, argv); + fprintf(stderr, "Can't execute `%s': %s\n", command, strerror(errno)); _exit(1); } debug(1, "PID=%d", pid); - sp->pid = pid; - -#if defined(HAVE_LIBUNWIND) - sp->unwind_priv = _UPT_create(pid); -#endif /* defined(HAVE_LIBUNWIND) */ - - return; + return pid; } diff --git a/handle_event.c b/handle_event.c index 01309ff..0aa40f7 100644 --- a/handle_event.c +++ b/handle_event.c @@ -25,7 +25,6 @@ static void handle_clone(Event *event); static void handle_exec(Event *event); static void handle_breakpoint(Event *event); static void handle_new(Event *event); -static void remove_proc(Process *proc); static void callstack_push_syscall(Process *proc, int sysnum); static void callstack_push_symfunc(Process *proc, @@ -38,7 +37,26 @@ static char * arch_sysname(Process *proc, int sysnum); void handle_event(Event *event) { - debug(DEBUG_FUNCTION, "handle_event(pid=%d, type=%d)", event->proc ? event->proc->pid : -1, event->type); + if (exiting == 1) { + exiting = 2; + debug(1, "ltrace about to exit"); + ltrace_exiting(); + } + debug(DEBUG_FUNCTION, "handle_event(pid=%d, type=%d)", + event->proc ? event->proc->pid : -1, event->type); + /* If the thread group defines an overriding event handler, + give it a chance to kick in. */ + if (event->proc != NULL + && event->proc->leader != NULL) { + Event_Handler * handler = event->proc->leader->event_handler; + if (handler != NULL) { + event = (*handler->on_event) (handler, event); + if (event == NULL) + /* It was handled. */ + return; + } + } + switch (event->type) { case EVENT_NONE: debug(1, "event: none"); @@ -202,24 +220,24 @@ handle_clone(Event * event) { p->pid = event->e_un.newpid; p->parent = event->proc; + /* We save register values to the arch pointer, and these need + to be per-thread. */ + p->arch_ptr = NULL; + if (pending_new(p->pid)) { pending_new_remove(p->pid); - if (p->breakpoint_being_enabled) { - enable_breakpoint(p->pid, p->breakpoint_being_enabled); - p->breakpoint_being_enabled = NULL; - } + if (p->event_handler != NULL) + destroy_event_handler(p); if (event->proc->state == STATE_ATTACHED && options.follow) { p->state = STATE_ATTACHED; } else { p->state = STATE_IGNORED; } continue_process(p->pid); - p->next = list_of_processes; - list_of_processes = p; + add_process(p); } else { p->state = STATE_BEING_CREATED; - p->next = list_of_processes; - list_of_processes = p; + add_process(p); } continue_process(event->proc->pid); } @@ -235,10 +253,8 @@ handle_new(Event * event) { pending_new_insert(event->e_un.newpid); } else { assert(proc->state == STATE_BEING_CREATED); - if (proc->breakpoint_being_enabled) { - enable_breakpoint(proc->pid, proc->breakpoint_being_enabled); - proc->breakpoint_being_enabled = NULL; - } + if (proc->event_handler != NULL) + destroy_event_handler(proc); if (options.follow) { proc->state = STATE_ATTACHED; } else { @@ -323,13 +339,6 @@ arch_sysname(Process *proc, int sysnum) { static void handle_signal(Event *event) { debug(DEBUG_FUNCTION, "handle_signal(pid=%d, signum=%d)", event->proc->pid, event->e_un.signum); - if (exiting && event->e_un.signum == SIGSTOP) { - pid_t pid = event->proc->pid; - disable_all_breakpoints(event->proc); - untrace_pid(pid); - remove_proc(event->proc); - return; - } if (event->proc->state != STATE_IGNORED && !options.no_signals) { output_line(event->proc, "--- %s (%s) ---", shortsignal(event->proc, event->e_un.signum), @@ -345,7 +354,7 @@ handle_exit(Event *event) { output_line(event->proc, "+++ exited (status %d) +++", event->e_un.ret_val); } - remove_proc(event->proc); + remove_process(event->proc); } static void @@ -355,31 +364,7 @@ handle_exit_signal(Event *event) { output_line(event->proc, "+++ killed by %s +++", shortsignal(event->proc, event->e_un.signum)); } - remove_proc(event->proc); -} - -static void -remove_proc(Process *proc) { - Process *tmp, *tmp2; - - debug(DEBUG_FUNCTION, "remove_proc(pid=%d)", proc->pid); - - if (list_of_processes == proc) { - tmp = list_of_processes; - list_of_processes = list_of_processes->next; - free(tmp); - return; - } - tmp = list_of_processes; - while (tmp->next) { - if (tmp->next == proc) { - tmp2 = tmp->next; - tmp->next = tmp->next->next; - free(tmp2); - continue; - } - tmp = tmp->next; - } + remove_process(event->proc); } static void @@ -389,7 +374,7 @@ handle_syscall(Event *event) { callstack_push_syscall(event->proc, event->e_un.sysnum); if (options.syscalls) { output_left(LT_TOF_SYSCALL, event->proc, - sysname(event->proc, event->e_un.sysnum)); + sysname(event->proc, event->e_un.sysnum)); } if (event->proc->breakpoints_enabled == 0) { enable_all_breakpoints(event->proc); @@ -406,7 +391,7 @@ handle_exec(Event * event) { debug(DEBUG_FUNCTION, "handle_exec(pid=%d)", proc->pid); if (proc->state == STATE_IGNORED) { untrace_pid(proc->pid); - remove_proc(proc); + remove_process(proc); return; } output_line(proc, "--- Called exec() ---"); @@ -417,7 +402,7 @@ handle_exec(Event * event) { proc->filename = pid2name(proc->pid); saved_pid = proc->pid; proc->pid = 0; - breakpoints_init(proc); + breakpoints_init(proc, 0); proc->pid = saved_pid; proc->callstack_depth = 0; continue_process(proc->pid); @@ -503,6 +488,13 @@ static void handle_breakpoint(Event *event) { int i, j; Breakpoint *sbp; + Process *leader = event->proc->leader; + + /* The leader has terminated. */ + if (leader == NULL) { + continue_process(event->proc->pid); + return; + } debug(DEBUG_FUNCTION, "handle_breakpoint(pid=%d, addr=%p)", event->proc->pid, event->e_un.brk_addr); debug(2, "event: breakpoint (%p)", event->e_un.brk_addr); @@ -513,7 +505,7 @@ handle_breakpoint(Event *event) { Breakpoint *stub_bp = NULL; char nop_instruction[] = PPC_NOP; - stub_bp = address2bpstruct (event->proc, event->e_un.brk_addr); + stub_bp = address2bpstruct(leader, event->e_un.brk_addr); if (stub_bp) { unsigned char *bp_instruction = stub_bp->orig_value; @@ -528,14 +520,6 @@ handle_breakpoint(Event *event) { } } #endif - if ((sbp = event->proc->breakpoint_being_enabled) != 0) { - /* Reinsert breakpoint */ - continue_enabling_breakpoint(event->proc->pid, - event->proc-> - breakpoint_being_enabled); - event->proc->breakpoint_being_enabled = NULL; - return; - } for (i = event->proc->callstack_depth - 1; i >= 0; i--) { if (event->e_un.brk_addr == @@ -554,7 +538,7 @@ handle_breakpoint(Event *event) { if (libsym->plt_type != LS_TOPLT_POINT) { unsigned char break_insn[] = BREAKPOINT_VALUE; - sbp = address2bpstruct(event->proc, addr); + sbp = address2bpstruct(leader, addr); assert(sbp); a = ptrace(PTRACE_PEEKTEXT, event->proc->pid, addr); @@ -562,10 +546,10 @@ handle_breakpoint(Event *event) { if (memcmp(&a, break_insn, BREAKPOINT_LENGTH)) { sbp->enabled--; insert_breakpoint(event->proc, addr, - libsym); + libsym, 1); } } else { - sbp = dict_find_entry(event->proc->breakpoints, addr); + sbp = dict_find_entry(leader->breakpoints, addr); /* On powerpc, the breakpoint address may end up being actual entry point of the library symbol, not the PLT @@ -573,7 +557,7 @@ handle_breakpoint(Event *event) { sbp is NULL. */ if (sbp == NULL || addr != sbp->addr) { insert_breakpoint(event->proc, addr, - libsym); + libsym, 1); } } #elif defined(__mips__) @@ -581,18 +565,18 @@ handle_breakpoint(Event *event) { struct library_symbol *sym= event->proc->callstack[i].c_un.libfunc; struct library_symbol *new_sym; assert(sym); - addr=sym2addr(event->proc,sym); - sbp = dict_find_entry(event->proc->breakpoints, addr); + addr = sym2addr(leader, sym); + sbp = dict_find_entry(leader->breakpoints, addr); if (sbp) { if (addr != sbp->addr) { - insert_breakpoint(event->proc, addr, sym); + insert_breakpoint(event->proc, addr, sym, 1); } } else { new_sym=malloc(sizeof(*new_sym) + strlen(sym->name) + 1); memcpy(new_sym,sym,sizeof(*new_sym) + strlen(sym->name) + 1); - new_sym->next=event->proc->list_of_symbols; - event->proc->list_of_symbols=new_sym; - insert_breakpoint(event->proc, addr, new_sym); + new_sym->next = leader->list_of_symbols; + leader->list_of_symbols = new_sym; + insert_breakpoint(event->proc, addr, new_sym, 1); } #endif for (j = event->proc->callstack_depth - 1; j > i; j--) { @@ -609,18 +593,23 @@ handle_breakpoint(Event *event) { event->proc->callstack[i].c_un.libfunc->name); } callstack_pop(event->proc); - continue_after_breakpoint(event->proc, - address2bpstruct(event->proc, - event->e_un.brk_addr)); + sbp = address2bpstruct(leader, event->e_un.brk_addr); + continue_after_breakpoint(event->proc, sbp); return; } } - if ((sbp = address2bpstruct(event->proc, event->e_un.brk_addr))) { + if ((sbp = address2bpstruct(leader, event->e_un.brk_addr))) { + if (sbp->libsym == NULL) { + continue_after_breakpoint(event->proc, sbp); + return; + } + if (strcmp(sbp->libsym->name, "") == 0) { - debug(2, "Hit _dl_debug_state breakpoint!\n"); - arch_check_dbg(event->proc); + debug(DEBUG_PROCESS, "Hit _dl_debug_state breakpoint!\n"); + arch_check_dbg(leader); } + if (event->proc->state != STATE_IGNORED) { event->proc->stack_pointer = get_stack_pointer(event->proc); event->proc->return_addr = @@ -632,7 +621,7 @@ handle_breakpoint(Event *event) { if (event->proc->need_to_reinitialize_breakpoints && (strcmp(sbp->libsym->name, PLTs_initialized_by_here) == 0)) - reinitialize_breakpoints(event->proc); + reinitialize_breakpoints(leader); #endif continue_after_breakpoint(event->proc, sbp); @@ -689,7 +678,7 @@ callstack_push_symfunc(Process *proc, struct library_symbol *sym) { elem->return_addr = proc->return_addr; if (elem->return_addr) { - insert_breakpoint(proc, elem->return_addr, 0); + insert_breakpoint(proc, elem->return_addr, NULL, 1); } /* handle functions like atexit() on mips which have no return */ @@ -709,6 +698,7 @@ callstack_pop(Process *proc) { debug(DEBUG_FUNCTION, "callstack_pop(pid=%d)", proc->pid); elem = &proc->callstack[proc->callstack_depth - 1]; if (!elem->is_syscall && elem->return_addr) { + assert(proc->leader != NULL); delete_breakpoint(proc, elem->return_addr); } if (elem->arch_ptr != NULL) { diff --git a/libltrace.c b/libltrace.c index 0f48d11..e731fe1 100644 --- a/libltrace.c +++ b/libltrace.c @@ -12,32 +12,39 @@ #include "common.h" char *command = NULL; -Process *list_of_processes = NULL; int exiting = 0; /* =1 if a SIGINT or SIGTERM has been received */ -static void -signal_alarm(int sig) { - Process *tmp = list_of_processes; +static enum pcb_status +stop_non_p_processes (Process * proc, void * data) +{ + int stop = 1; - signal(SIGALRM, SIG_DFL); - while (tmp) { - struct opt_p_t *tmp2 = opt_p; - while (tmp2) { - if (tmp->pid == tmp2->pid) { - tmp = tmp->next; - if (!tmp) { - return; - } - tmp2 = opt_p; - continue; - } - tmp2 = tmp2->next; + struct opt_p_t *it; + for (it = opt_p; it != NULL; it = it->next) { + Process * p_proc = pid2proc(it->pid); + if (p_proc == NULL) { + printf("stop_non_p_processes: %d terminated?\n", it->pid); + continue; + } + if (p_proc == proc || p_proc->leader == proc->leader) { + stop = 0; + break; } - debug(2, "Sending SIGSTOP to process %u\n", tmp->pid); - kill(tmp->pid, SIGSTOP); - tmp = tmp->next; } + + if (stop) { + debug(2, "Sending SIGSTOP to process %u", proc->pid); + kill(proc->pid, SIGSTOP); + } + + return pcb_cont; +} + +static void +signal_alarm(int sig) { + signal(SIGALRM, SIG_DFL); + each_process(NULL, &stop_non_p_processes, NULL); } static void @@ -47,15 +54,7 @@ signal_exit(int sig) { signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGALRM, signal_alarm); - if (opt_p) { - struct opt_p_t *tmp = opt_p; - while (tmp) { - debug(2, "Sending SIGSTOP to process %u\n", tmp->pid); - kill(tmp->pid, SIGSTOP); - tmp = tmp->next; - } - } - alarm(1); + //alarm(1); } static void @@ -108,7 +107,7 @@ ltrace_init(int argc, char **argv) { } } if (command) { - execute_program(open_program(command, 0), argv); + open_program(command, execute_program(command, argv), 0); } opt_p_tmp = opt_p; while (opt_p_tmp) { diff --git a/ltrace-elf.c b/ltrace-elf.c index 1a33ec3..d88d5a6 100644 --- a/ltrace-elf.c +++ b/ltrace-elf.c @@ -14,7 +14,6 @@ #include "common.h" -void do_init_elf(struct ltelf *lte, const char *filename); void do_close_elf(struct ltelf *lte); void add_library_symbol(GElf_Addr addr, const char *name, struct library_symbol **library_symbolspp, @@ -136,7 +135,7 @@ static GElf_Addr get_glink_vma(struct ltelf *lte, GElf_Addr ppcgot, return 0; } -void +int do_init_elf(struct ltelf *lte, const char *filename) { int i; GElf_Addr relplt_addr = 0; @@ -147,7 +146,7 @@ do_init_elf(struct ltelf *lte, const char *filename) { lte->fd = open(filename, O_RDONLY); if (lte->fd == -1) - error(EXIT_FAILURE, errno, "Can't open \"%s\"", filename); + return 1; #ifdef HAVE_ELF_C_READ_MMAP lte->elf = elf_begin(lte->fd, ELF_C_READ_MMAP, NULL); @@ -454,6 +453,7 @@ do_init_elf(struct ltelf *lte, const char *filename) { debug(1, "%s %zd PLT relocations", filename, lte->relplt_count); } + return 0; } void @@ -622,7 +622,8 @@ read_elf(Process *proc) { elf_version(EV_CURRENT); - do_init_elf(lte, proc->filename); + if (do_init_elf(lte, proc->filename)) + return NULL; memcpy(&main_lte, lte, sizeof(struct ltelf)); @@ -634,7 +635,9 @@ read_elf(Process *proc) { proc->e_machine = lte->ehdr.e_machine; for (i = 0; i < library_num; ++i) { - do_init_elf(<e[i + 1], library[i]); + if (do_init_elf(<e[i + 1], library[i])) + error(EXIT_FAILURE, errno, "Can't open \"%s\"", + proc->filename); } if (!options.no_plt) { diff --git a/ltrace.h b/ltrace.h index 5e43ba5..0ff4572 100644 --- a/ltrace.h +++ b/ltrace.h @@ -20,6 +20,7 @@ enum Event_type { typedef struct Process Process; typedef struct Event Event; struct Event { + struct Event * next; Process * proc; Event_type type; union { diff --git a/output.c b/output.c index de2a836..945dd52 100644 --- a/output.c +++ b/output.c @@ -96,7 +96,7 @@ begin_of_line(enum tof type, Process *proc) { } static Function * -name2func(char *name) { +name2func(char const *name) { Function *tmp; const char *str1, *str2; @@ -153,7 +153,7 @@ tabto(int col) { } void -output_left(enum tof type, Process *proc, char *function_name) { +output_left(enum tof type, Process *proc, char const *function_name) { Function *func; static arg_type_info *arg_unknown = NULL; if (arg_unknown == NULL) @@ -168,7 +168,6 @@ output_left(enum tof type, Process *proc, char *function_name) { } current_proc = proc; current_depth = proc->callstack_depth; - proc->type_being_displayed = type; begin_of_line(type, proc); #ifdef USE_DEMANGLE current_column += diff --git a/output.h b/output.h index c58577a..fa840c7 100644 --- a/output.h +++ b/output.h @@ -1,3 +1,3 @@ void output_line(Process *proc, char *fmt, ...); -void output_left(enum tof type, Process *proc, char *function_name); +void output_left(enum tof type, Process *proc, char const *function_name); void output_right(enum tof type, Process *proc, char *function_name); diff --git a/proc.c b/proc.c index 1c57532..0425e09 100644 --- a/proc.c +++ b/proc.c @@ -10,73 +10,280 @@ #include #include #include +#include +#include #include "common.h" Process * -open_program(char *filename, pid_t pid) { +open_program(char *filename, pid_t pid, int enable) { Process *proc; + assert(pid != 0); proc = calloc(sizeof(Process), 1); if (!proc) { perror("malloc"); exit(1); } + proc->filename = strdup(filename); proc->breakpoints_enabled = -1; - if (pid) { - proc->pid = pid; + proc->pid = pid; #if defined(HAVE_LIBUNWIND) - proc->unwind_priv = _UPT_create(pid); - } else { - proc->unwind_priv = NULL; + proc->unwind_priv = _UPT_create(pid); + proc->unwind_as = unw_create_addr_space(&_UPT_accessors, 0); #endif /* defined(HAVE_LIBUNWIND) */ - } - breakpoints_init(proc); + add_process(proc); + if (proc->leader == NULL) { + free(proc); + return NULL; + } - proc->next = list_of_processes; - list_of_processes = proc; + if (proc->leader == proc) + if (breakpoints_init(proc, enable)) { + fprintf(stderr, "failed to init breakpoints %d\n", + proc->pid); + remove_process(proc); + return NULL; + } -#if defined(HAVE_LIBUNWIND) - proc->unwind_as = unw_create_addr_space(&_UPT_accessors, 0); -#endif /* defined(HAVE_LIBUNWIND) */ return proc; } -void -open_pid(pid_t pid) { +static int +open_one_pid(pid_t pid) +{ Process *proc; char *filename; + debug(DEBUG_PROCESS, "open_one_pid(pid=%d)", pid); - if (trace_pid(pid) < 0) { - fprintf(stderr, "Cannot attach to pid %u: %s\n", pid, - strerror(errno)); - return; + /* Get the filename first. Should the trace_pid fail, we can + * easily free it, untracing is more work. */ + if ((filename = pid2name(pid)) == NULL + || trace_pid(pid) < 0) { + free(filename); + return -1; } - filename = pid2name(pid); + proc = open_program(filename, pid, 0); + if (proc == NULL) + return -1; + trace_set_options(proc, pid); + + return 0; +} + +enum pcb_status +start_one_pid(Process * proc, void * data) +{ + continue_process(proc->pid); + proc->breakpoints_enabled = 1; + return pcb_cont; +} + +void +open_pid(pid_t pid) +{ + debug(DEBUG_PROCESS, "open_pid(pid=%d)", pid); + /* If we are already tracing this guy, we should be seeing all + * his children via normal tracing route. */ + if (pid2proc(pid) != NULL) + return; - if (!filename) { - fprintf(stderr, "Cannot trace pid %u: %s\n", pid, - strerror(errno)); + /* First, see if we can attach the requested PID itself. */ + if (open_one_pid(pid)) { + fprintf(stderr, "Cannot attach to pid %u: %s\n", + pid, strerror(errno)); return; } - proc = open_program(filename, pid); - continue_process(pid); - proc->breakpoints_enabled = 1; + /* Now attach to all tasks that belong to that PID. There's a + * race between process_tasks and open_one_pid. So when we + * fail in open_one_pid below, we just do another round. + * Chances are that by then that PID will have gone away, and + * that's why we have seen the failure. The processes that we + * manage to open_one_pid are stopped, so we should eventually + * reach a point where process_tasks doesn't give any new + * processes (because there's nobody left to produce + * them). */ + size_t old_ntasks = 0; + int have_all; + while (1) { + pid_t *tasks; + size_t ntasks; + size_t i; + + if (process_tasks(pid, &tasks, &ntasks) < 0) { + fprintf(stderr, "Cannot obtain tasks of pid %u: %s\n", + pid, strerror(errno)); + goto start; + } + + have_all = 1; + for (i = 0; i < ntasks; ++i) + if (pid2proc(tasks[i]) == NULL + && open_one_pid(tasks[i])) + have_all = 0; + + free(tasks); + + if (have_all && old_ntasks == ntasks) + break; + old_ntasks = ntasks; + } + + /* Done. Now initialize breakpoints and then continue + * everyone. */ + Process * leader; +start: + leader = pid2proc(pid)->leader; + enable_all_breakpoints(leader); + + each_task(pid2proc(pid)->leader, start_one_pid, NULL); +} + +static enum pcb_status +find_proc(Process * proc, void * data) +{ + pid_t pid = (pid_t)(uintptr_t)data; + return proc->pid == pid ? pcb_stop : pcb_cont; } Process * pid2proc(pid_t pid) { - Process *tmp; + return each_process(NULL, &find_proc, (void *)(uintptr_t)pid); +} + +static Process * list_of_processes = NULL; + +Process * +each_process(Process * proc, + enum pcb_status (* cb)(Process * proc, void * data), + void * data) +{ + Process * it = proc ?: list_of_processes; + for (; it != NULL; ) { + /* Callback might call remove_process. */ + Process * next = it->next; + if ((*cb) (it, data) == pcb_stop) + return it; + it = next; + } + return NULL; +} + +Process * +each_task(Process * it, enum pcb_status (* cb)(Process * proc, void * data), + void * data) +{ + if (it != NULL) { + Process * leader = it->leader; + for (; it != NULL && it->leader == leader; ) { + /* Callback might call remove_process. */ + Process * next = it->next; + if ((*cb) (it, data) == pcb_stop) + return it; + it = next; + } + } + return NULL; +} + +void +add_process(Process * proc) +{ + Process ** leaderp = &list_of_processes; + if (proc->pid) { + pid_t tgid = process_leader(proc->pid); + if (tgid == 0) + /* Must have been terminated before we managed + * to fully attach. */ + return; + if (tgid == proc->pid) + proc->leader = proc; + else { + Process * leader = pid2proc(tgid); + proc->leader = leader; + if (leader != NULL) + leaderp = &leader->next; + } + } + proc->next = *leaderp; + *leaderp = proc; +} + +static enum pcb_status +clear_leader(Process * proc, void * data) +{ + debug(DEBUG_FUNCTION, "detach_task %d from leader %d", + proc->pid, proc->leader->pid); + proc->leader = NULL; + return pcb_cont; +} + +static enum ecb_status +event_for_proc(Event * event, void * data) +{ + if (event->proc == data) + return ecb_deque; + else + return ecb_cont; +} + +static void +delete_events_for(Process * proc) +{ + Event * event; + while ((event = each_qd_event(&event_for_proc, proc)) != NULL) + free(event); +} + +void +remove_process(Process *proc) +{ + Process *tmp, *tmp2; + + debug(DEBUG_FUNCTION, "remove_proc(pid=%d)", proc->pid); + + if (proc->leader == proc) + each_task(proc, &clear_leader, NULL); + + if (list_of_processes == proc) { + tmp = list_of_processes; + list_of_processes = list_of_processes->next; + delete_events_for(tmp); + free(tmp); + return; + } tmp = list_of_processes; - while (tmp) { - if (pid == tmp->pid) { - return tmp; + while (tmp->next) { + if (tmp->next == proc) { + tmp2 = tmp->next; + tmp->next = tmp->next->next; + delete_events_for(tmp2); + free(tmp2); + return; } tmp = tmp->next; } - return NULL; +} + +void +install_event_handler(Process * proc, Event_Handler * handler) +{ + debug(DEBUG_FUNCTION, "install_event_handler(pid=%d, %p)", proc->pid, handler); + assert(proc->event_handler == NULL); + proc->event_handler = handler; +} + +void +destroy_event_handler(Process * proc) +{ + Event_Handler * handler = proc->event_handler; + debug(DEBUG_FUNCTION, "destroy_event_handler(pid=%d, %p)", proc->pid, handler); + assert(handler != NULL); + handler->destroy(handler); + free(handler); + proc->event_handler = NULL; } diff --git a/sysdeps/linux-gnu/breakpoint.c b/sysdeps/linux-gnu/breakpoint.c index 9104189..5a49e9d 100644 --- a/sysdeps/linux-gnu/breakpoint.c +++ b/sysdeps/linux-gnu/breakpoint.c @@ -8,21 +8,11 @@ #ifdef ARCH_HAVE_ENABLE_BREAKPOINT extern void arch_enable_breakpoint(pid_t, Breakpoint *); +#else /* ARCH_HAVE_ENABLE_BREAKPOINT */ void -enable_breakpoint(pid_t pid, Breakpoint *sbp) { - if (sbp->libsym) { - debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p, symbol=%s", pid, sbp->addr, sbp->libsym->name); - } else { - debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p", pid, sbp->addr); - } - arch_enable_breakpoint(pid, sbp); -} -#else - -static unsigned char break_insn[] = BREAKPOINT_VALUE; - -void -enable_breakpoint(pid_t pid, Breakpoint *sbp) { +arch_enable_breakpoint(pid_t pid, Breakpoint *sbp) +{ + static unsigned char break_insn[] = BREAKPOINT_VALUE; unsigned int i, j; if (sbp->libsym) { @@ -32,9 +22,8 @@ enable_breakpoint(pid_t pid, Breakpoint *sbp) { } for (i = 0; i < 1 + ((BREAKPOINT_LENGTH - 1) / sizeof(long)); i++) { - long a = - ptrace(PTRACE_PEEKTEXT, pid, sbp->addr + i * sizeof(long), - 0); + long a = ptrace(PTRACE_PEEKTEXT, pid, + sbp->addr + i * sizeof(long), 0); for (j = 0; j < sizeof(long) && i * sizeof(long) + j < BREAKPOINT_LENGTH; j++) { @@ -48,20 +37,22 @@ enable_breakpoint(pid_t pid, Breakpoint *sbp) { } #endif /* ARCH_HAVE_ENABLE_BREAKPOINT */ -#ifdef ARCH_HAVE_DISABLE_BREAKPOINT -extern void arch_disable_breakpoint(pid_t, const Breakpoint *sbp); void -disable_breakpoint(pid_t pid, const Breakpoint *sbp) { +enable_breakpoint(Process * proc, Breakpoint *sbp) { if (sbp->libsym) { - debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p, symbol=%s", pid, sbp->addr, sbp->libsym->name); + debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p, symbol=%s", proc->pid, sbp->addr, sbp->libsym->name); } else { - debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p", pid, sbp->addr); + debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p", proc->pid, sbp->addr); } - arch_disable_breakpoint(pid, sbp); + arch_enable_breakpoint(proc->pid, sbp); } -#else + +#ifdef ARCH_HAVE_DISABLE_BREAKPOINT +extern void arch_disable_breakpoint(pid_t, const Breakpoint *sbp); +#else /* ARCH_HAVE_DISABLE_BREAKPOINT */ void -disable_breakpoint(pid_t pid, const Breakpoint *sbp) { +arch_disable_breakpoint(pid_t pid, const Breakpoint *sbp) +{ unsigned int i, j; if (sbp->libsym) { @@ -85,3 +76,13 @@ disable_breakpoint(pid_t pid, const Breakpoint *sbp) { } } #endif /* ARCH_HAVE_DISABLE_BREAKPOINT */ + +void +disable_breakpoint(Process * proc, Breakpoint *sbp) { + if (sbp->libsym) { + debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p, symbol=%s", proc->pid, sbp->addr, sbp->libsym->name); + } else { + debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p", proc->pid, sbp->addr); + } + arch_disable_breakpoint(proc->pid, sbp); +} diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c index fd19e71..8a79583 100644 --- a/sysdeps/linux-gnu/events.c +++ b/sysdeps/linux-gnu/events.c @@ -8,20 +8,118 @@ #include #include #include +#include #include "common.h" static Event event; +/* A queue of events that we missed while enabling the + * breakpoint in one of tasks. */ +static Event * delayed_events = NULL; +static Event * end_delayed_events = NULL; + +static enum pcb_status +first (Process * proc, void * data) +{ + return pcb_stop; +} + +void +enque_event(Event * event) +{ + debug(DEBUG_FUNCTION, "%d: queuing event %d for later", + event->proc->pid, event->type); + Event * ne = malloc(sizeof(*ne)); + if (ne == NULL) { + perror("event will be missed: malloc"); + return; + } + + *ne = *event; + ne->next = NULL; + if (end_delayed_events == NULL) { + assert(delayed_events == NULL); + end_delayed_events = delayed_events = ne; + } + else { + assert(delayed_events != NULL); + end_delayed_events = end_delayed_events->next = ne; + } +} + +Event * +each_qd_event(enum ecb_status (*pred)(Event *, void *), void * data) +{ + Event * prev = delayed_events; + Event * event; + for (event = prev; event != NULL; ) { + switch ((*pred)(event, data)) { + case ecb_cont: + prev = event; + event = event->next; + continue; + + case ecb_deque: + debug(DEBUG_FUNCTION, "dequeuing event %d for %d", + event->type, + event->proc != NULL ? event->proc->pid : -1); + /* + printf("dequeuing event %d for %d\n", event->type, + event->proc != NULL ? event->proc->pid : -1) ; + */ + if (end_delayed_events == event) + end_delayed_events = prev; + if (delayed_events == event) + delayed_events = event->next; + else + prev->next = event->next; + if (delayed_events == NULL) + end_delayed_events = NULL; + /* fall-through */ + + case ecb_yield: + return event; + } + } + + return NULL; +} + +static enum ecb_status +event_process_not_reenabling(Event * event, void * data) +{ + if (event->proc == NULL + || event->proc->leader == NULL + || event->proc->leader->event_handler == NULL) + return ecb_deque; + else + return ecb_cont; +} + +static Event * +next_qd_event(void) +{ + return each_qd_event(&event_process_not_reenabling, NULL); +} + Event * -next_event(void) { +next_event(void) +{ pid_t pid; int status; int tmp; int stop_signal; debug(DEBUG_FUNCTION, "next_event()"); - if (!list_of_processes) { + Event * ev; + if ((ev = next_qd_event()) != NULL) { + event = *ev; + free(ev); + return &event; + } + + if (!each_process(NULL, &first, NULL)) { debug(DEBUG_EVENT, "event: No more traced programs: exiting"); exit(0); } @@ -46,26 +144,76 @@ next_event(void) { return &event; } get_arch_dep(event.proc); - event.proc->instruction_pointer = NULL; debug(3, "event from pid %u", pid); - if (event.proc->breakpoints_enabled == -1) { - event.type = EVENT_NONE; + if (event.proc->breakpoints_enabled == -1) trace_set_options(event.proc, event.proc->pid); - enable_all_breakpoints(event.proc); - continue_process(event.proc->pid); - debug(DEBUG_EVENT, "event: NONE: pid=%d (enabling breakpoints)", pid); - return &event; - } else if (!event.proc->libdl_hooked) { - /* debug struct may not have been written yet.. */ - if (linkmap_init(event.proc, &main_lte) == 0) { - event.proc->libdl_hooked = 1; + Process *leader = event.proc->leader; + if (leader == event.proc) { + if (event.proc->breakpoints_enabled == -1) { + event.type = EVENT_NONE; + enable_all_breakpoints(event.proc); + continue_process(event.proc->pid); + debug(DEBUG_EVENT, + "event: NONE: pid=%d (enabling breakpoints)", + pid); + return &event; + } else if (!event.proc->libdl_hooked) { + /* debug struct may not have been written yet.. */ + if (linkmap_init(event.proc, &main_lte) == 0) { + event.proc->libdl_hooked = 1; + } } } - if (opt_i) { - event.proc->instruction_pointer = - get_instruction_pointer(event.proc); + /* The process should be stopped after the waitpid call. But + * when the whole thread group is terminated, we see + * individual tasks spontaneously transitioning from 't' to + * 'R' and 'Z'. Calls to ptrace fail and /proc/pid/status may + * not even be available anymore, so we can't check in + * advance. So we just drop the error checking around ptrace + * calls. We check for termination ex post when it fails, + * suppress the event, and let the event loop collect the + * termination in the next iteration. */ +#define CHECK_PROCESS_TERMINATED \ + do { \ + int errno_save = errno; \ + switch (process_stopped(pid)) \ + case 0: \ + case -1: { \ + debug(DEBUG_EVENT, \ + "process not stopped, is it terminating?"); \ + event.type = EVENT_NONE; \ + continue_process(event.proc->pid); \ + return &event; \ + } \ + errno = errno_save; \ + } while (0) + + event.proc->instruction_pointer = (void *)(uintptr_t)-1; + + /* Check for task termination now, before we have a need to + * call CHECK_PROCESS_TERMINATED later. That would suppress + * the event that we are processing. */ + if (WIFSIGNALED(status)) { + event.type = EVENT_EXIT_SIGNAL; + event.e_un.signum = WTERMSIG(status); + debug(DEBUG_EVENT, "event: EXIT_SIGNAL: pid=%d, signum=%d", pid, event.e_un.signum); + return &event; + } + if (WIFEXITED(status)) { + event.type = EVENT_EXIT; + event.e_un.ret_val = WEXITSTATUS(status); + debug(DEBUG_EVENT, "event: EXIT: pid=%d, status=%d", pid, event.e_un.ret_val); + return &event; + } + + event.proc->instruction_pointer = get_instruction_pointer(event.proc); + if (event.proc->instruction_pointer == (void *)(uintptr_t)-1) { + CHECK_PROCESS_TERMINATED; + if (errno != 0) + perror("get_instruction_pointer"); } + switch (syscall_p(event.proc, status, &tmp)) { case 1: event.type = EVENT_SYSCALL; @@ -88,10 +236,9 @@ next_event(void) { debug(DEBUG_EVENT, "event: ARCH_SYSRET: pid=%d, sysnum=%d", pid, tmp); return &event; case -1: - event.type = EVENT_NONE; - continue_process(event.proc->pid); - debug(DEBUG_EVENT, "event: NONE: pid=%d (syscall_p returned -1)", pid); - return &event; + CHECK_PROCESS_TERMINATED; + if (errno != 0) + perror("syscall_p"); } if (WIFSTOPPED(status) && ((status>>16 == PTRACE_EVENT_FORK) || (status>>16 == PTRACE_EVENT_VFORK) || (status>>16 == PTRACE_EVENT_CLONE))) { unsigned long data; @@ -106,18 +253,6 @@ next_event(void) { debug(DEBUG_EVENT, "event: EXEC: pid=%d", pid); return &event; } - if (WIFEXITED(status)) { - event.type = EVENT_EXIT; - event.e_un.ret_val = WEXITSTATUS(status); - debug(DEBUG_EVENT, "event: EXIT: pid=%d, status=%d", pid, event.e_un.ret_val); - return &event; - } - if (WIFSIGNALED(status)) { - event.type = EVENT_EXIT_SIGNAL; - event.e_un.signum = WTERMSIG(status); - debug(DEBUG_EVENT, "event: EXIT_SIGNAL: pid=%d, signum=%d", pid, event.e_un.signum); - return &event; - } if (!WIFSTOPPED(status)) { /* should never happen */ event.type = EVENT_NONE; @@ -128,22 +263,19 @@ next_event(void) { stop_signal = WSTOPSIG(status); /* On some targets, breakpoints are signalled not using - SIGTRAP, but also with SIGILL, SIGSEGV or SIGEMT. Check - for these. (TODO: is this true?) */ - if (stop_signal == SIGSEGV - || stop_signal == SIGILL -#ifdef SIGEMT - || stop_signal == SIGEMT -#endif - ) { - if (!event.proc->instruction_pointer) { - event.proc->instruction_pointer = - get_instruction_pointer(event.proc); - } + SIGTRAP, but also with SIGILL, SIGSEGV or SIGEMT. SIGEMT + is not defined on Linux, but check for the others. - if (address2bpstruct(event.proc, event.proc->instruction_pointer)) + N.B. see comments in GDB's infrun.c for details. I've + actually seen this on an Itanium machine on RHEL 5, I don't + remember the exact kernel version anymore. ia64-sigill.s + in the test suite tests this. Petr Machata 2011-06-08. */ + void * break_address + = event.proc->instruction_pointer - DECR_PC_AFTER_BREAK; + if ((stop_signal == SIGSEGV || stop_signal == SIGILL) + && leader != NULL + && address2bpstruct(leader, break_address)) stop_signal = SIGTRAP; - } if (stop_signal != (SIGTRAP | event.proc->tracesysgood) && stop_signal != SIGTRAP) { @@ -156,12 +288,8 @@ next_event(void) { /* last case [by exhaustion] */ event.type = EVENT_BREAKPOINT; - if (!event.proc->instruction_pointer) { - event.proc->instruction_pointer = - get_instruction_pointer(event.proc); - } - event.e_un.brk_addr = - event.proc->instruction_pointer - DECR_PC_AFTER_BREAK; + event.e_un.brk_addr = break_address; debug(DEBUG_EVENT, "event: BREAKPOINT: pid=%d, addr=%p", pid, event.e_un.brk_addr); + return &event; } diff --git a/sysdeps/linux-gnu/proc.c b/sysdeps/linux-gnu/proc.c index e1cadf7..e3b71e5 100644 --- a/sysdeps/linux-gnu/proc.c +++ b/sysdeps/linux-gnu/proc.c @@ -1,13 +1,22 @@ +#define _GNU_SOURCE /* For getline. */ #include "config.h" #include "common.h" #include +#include +#include #include #include #include #include #include #include +#include +#include +#include +#include +#include + /* /proc/pid doesn't exist just after the fork, and sometimes `ltrace' * couldn't open it to find the executable. So it may be necessary to @@ -16,17 +25,19 @@ #define MAX_DELAY 100000 /* 100000 microseconds = 0.1 seconds */ +#define PROC_PID_FILE(VAR, FORMAT, PID) \ + char VAR[strlen(FORMAT) + 6]; \ + sprintf(VAR, FORMAT, PID) + /* * Returns a (malloc'd) file name corresponding to a running pid */ char * pid2name(pid_t pid) { - char proc_exe[1024]; - if (!kill(pid, 0)) { int delay = 0; - sprintf(proc_exe, "/proc/%d/exe", pid); + PROC_PID_FILE(proc_exe, "/proc/%d/exe", pid); while (delay < MAX_DELAY) { if (!access(proc_exe, F_OK)) { @@ -38,6 +49,197 @@ pid2name(pid_t pid) { return NULL; } +static FILE * +open_status_file(pid_t pid) +{ + PROC_PID_FILE(fn, "/proc/%d/status", pid); + /* Don't complain if we fail. This would typically happen + when the process is about to terminate, and these files are + not available anymore. This function is called from the + event loop, and we don't want to clutter the output just + because the process terminates. */ + return fopen(fn, "r"); +} + +static char * +find_line_starting(FILE * file, const char * prefix, size_t len) +{ + char * line = NULL; + size_t line_len = 0; + while (!feof(file)) { + if (getline(&line, &line_len, file) < 0) + return NULL; + if (strncmp(line, prefix, len) == 0) + return line; + } + return NULL; +} + +static void +each_line_starting(FILE * file, const char *prefix, + enum pcb_status (*cb)(const char * line, const char * prefix, + void * data), + void * data) +{ + size_t len = strlen(prefix); + char * line; + while ((line = find_line_starting(file, prefix, len)) != NULL) { + enum pcb_status st = (*cb)(line, prefix, data); + free (line); + if (st == pcb_stop) + return; + } +} + +static enum pcb_status +process_leader_cb(const char * line, const char * prefix, void * data) +{ + pid_t * pidp = data; + *pidp = atoi(line + strlen(prefix)); + return pcb_stop; +} + +pid_t +process_leader(pid_t pid) +{ + pid_t tgid = 0; + FILE * file = open_status_file(pid); + if (file != NULL) { + each_line_starting(file, "Tgid:\t", &process_leader_cb, &tgid); + fclose(file); + } + + return tgid; +} + +static enum pcb_status +process_stopped_cb(const char * line, const char * prefix, void * data) +{ + char c = line[strlen(prefix)]; + // t:tracing stop, T:job control stop + *(int *)data = (c == 't' || c == 'T'); + return pcb_stop; +} + +int +process_stopped(pid_t pid) +{ + int is_stopped = -1; + FILE * file = open_status_file(pid); + if (file != NULL) { + each_line_starting(file, "State:\t", &process_stopped_cb, + &is_stopped); + fclose(file); + } + return is_stopped; +} + +static enum pcb_status +process_status_cb(const char * line, const char * prefix, void * data) +{ + const char * status = line + strlen(prefix); + const char c = *status; + +#define RETURN(C) do { \ + *(enum process_status *)data = C; \ + return pcb_stop; \ + } while (0) + + switch (c) { + case 'Z': RETURN(ps_zombie); + case 't': RETURN(ps_tracing_stop); + case 'T': { + /* This can be either "T (stopped)" or, for older + * kernels, "T (tracing stop)". */ + if (!strcmp(status, "T (stopped)\n")) + RETURN(ps_stop); + else if (!strcmp(status, "T (tracing stop)\n")) + RETURN(ps_tracing_stop); + else { + fprintf(stderr, "Unknown process status: %s", + status); + RETURN(ps_stop); /* Some sort of stop + * anyway. */ + } + } + } + + RETURN(ps_other); +#undef RETURN +} + +enum process_status +process_status(pid_t pid) +{ + enum process_status ret = ps_invalid; + FILE * file = open_status_file(pid); + if (file != NULL) { + each_line_starting(file, "State:\t", &process_status_cb, &ret); + fclose(file); + if (ret == ps_invalid) + error(0, errno, "process_status %d", pid); + } else + /* If the file is not present, the process presumably + * exited already. */ + ret = ps_zombie; + + return ret; +} + +static int +all_digits(const char *str) +{ + while (isdigit(*str)) + str++; + return !*str; +} + +int +process_tasks(pid_t pid, pid_t **ret_tasks, size_t *ret_n) +{ + PROC_PID_FILE(fn, "/proc/%d/task", pid); + DIR * d = opendir(fn); + if (d == NULL) + return -1; + + pid_t *tasks = NULL; + size_t n = 0; + size_t alloc = 0; + + while (1) { + struct dirent entry; + struct dirent *result; + if (readdir_r(d, &entry, &result) != 0) { + free(tasks); + return -1; + } + if (result == NULL) + break; + if (result->d_type == DT_DIR && all_digits(result->d_name)) { + pid_t npid = atoi(result->d_name); + if (n >= alloc) { + alloc = alloc > 0 ? (2 * alloc) : 8; + pid_t *ntasks = realloc(tasks, + sizeof(*tasks) * alloc); + if (ntasks == NULL) { + free(tasks); + return -1; + } + tasks = ntasks; + } + if (n >= alloc) + abort(); + tasks[n++] = npid; + } + } + + closedir(d); + + *ret_tasks = tasks; + *ret_n = n; + return 0; +} + static int find_dynamic_entry_addr(Process *proc, void *pvAddr, int d_tag, void **addr) { int i = 0, done = 0; @@ -187,7 +389,10 @@ linkmap_add_cb(void *data) { //const char *lib_name, ElfW(Addr) addr) { addr = sym.st_value; add_library_symbol(addr, xptr->name, &library_symbols, LS_TOPLT_NONE, 0); xptr->found = 1; - insert_breakpoint(lm_add->proc, sym2addr(lm_add->proc, library_symbols), library_symbols); + insert_breakpoint(lm_add->proc, + sym2addr(lm_add->proc, + library_symbols), + library_symbols, 1); } } do_close_elf(<e); @@ -275,10 +480,22 @@ linkmap_init(Process *proc, struct ltelf *lte) { data.lte = lte; add_library_symbol(rdbg->r_brk, "", &library_symbols, LS_TOPLT_NONE, 0); - insert_breakpoint(proc, sym2addr(proc, library_symbols), library_symbols); + insert_breakpoint(proc, sym2addr(proc, library_symbols), + library_symbols, 1); crawl_linkmap(proc, rdbg, hook_libdl_cb, &data); free(rdbg); return 0; } + +int +task_kill (pid_t pid, int sig) +{ + // Taken from GDB + int ret; + + errno = 0; + ret = syscall (__NR_tkill, pid, sig); + return ret; +} diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c index e4be465..3800fad 100644 --- a/sysdeps/linux-gnu/trace.c +++ b/sysdeps/linux-gnu/trace.c @@ -7,6 +7,7 @@ #include #include "ptrace.h" #include +#include #include "common.h" @@ -69,7 +70,7 @@ umovelong (Process *proc, void *addr, long *result, arg_type_info *info) { void trace_me(void) { - debug(DEBUG_PROCESS, "trace_me: pid=%d\n", getpid()); + debug(DEBUG_PROCESS, "trace_me: pid=%d", getpid()); if (ptrace(PTRACE_TRACEME, 0, 1, 0) < 0) { perror("PTRACE_TRACEME"); exit(1); @@ -78,7 +79,7 @@ trace_me(void) { int trace_pid(pid_t pid) { - debug(DEBUG_PROCESS, "trace_pid: pid=%d\n", pid); + debug(DEBUG_PROCESS, "trace_pid: pid=%d", pid); if (ptrace(PTRACE_ATTACH, pid, 1, 0) < 0) { return -1; } @@ -87,9 +88,9 @@ trace_pid(pid_t pid) { in pid. The child is sent a SIGSTOP, but will not necessarily have stopped by the completion of this call; use wait() to wait for the child to stop. */ - if (waitpid (pid, NULL, 0) != pid) { + if (waitpid (pid, NULL, __WALL) != pid) { perror ("trace_pid: waitpid"); - exit (1); + return -1; } return 0; @@ -100,7 +101,7 @@ trace_set_options(Process *proc, pid_t pid) { if (proc->tracesysgood & 0x80) return; - debug(DEBUG_PROCESS, "trace_set_options: pid=%d\n", pid); + debug(DEBUG_PROCESS, "trace_set_options: pid=%d", pid); long options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE | @@ -115,7 +116,7 @@ trace_set_options(Process *proc, pid_t pid) { void untrace_pid(pid_t pid) { - debug(DEBUG_PROCESS, "untrace_pid: pid=%d\n", pid); + debug(DEBUG_PROCESS, "untrace_pid: pid=%d", pid); ptrace(PTRACE_DETACH, pid, 1, 0); } @@ -126,56 +127,652 @@ continue_after_signal(pid_t pid, int signum) { void continue_after_signal(pid_t pid, int signum) { - Process *proc; - debug(DEBUG_PROCESS, "continue_after_signal: pid=%d, signum=%d", pid, signum); - - proc = pid2proc(pid); - if (proc && proc->breakpoint_being_enabled) { -#if defined __sparc__ || defined __ia64___ || defined __mips__ - ptrace(PTRACE_SYSCALL, pid, 0, signum); -#else - ptrace(PTRACE_SINGLESTEP, pid, 0, signum); -#endif + ptrace(PTRACE_SYSCALL, pid, 0, signum); +} + +static enum ecb_status +event_for_pid(Event * event, void * data) +{ + if (event->proc != NULL && event->proc->pid == (pid_t)(uintptr_t)data) + return ecb_yield; + return ecb_cont; +} + +static int +have_events_for(pid_t pid) +{ + return each_qd_event(event_for_pid, (void *)(uintptr_t)pid) != NULL; +} + +void +continue_process(pid_t pid) +{ + debug(DEBUG_PROCESS, "continue_process: pid=%d", pid); + + /* Only really continue the process if there are no events in + the queue for this process. Otherwise just for the other + events to arrive. */ + if (!have_events_for(pid)) + /* We always trace syscalls to control fork(), + * clone(), execve()... */ + ptrace(PTRACE_SYSCALL, pid, 0, 0); + else + debug(DEBUG_PROCESS, + "putting off the continue, events in que."); +} + +/** + * This is used for bookkeeping related to PIDs that the event + * handlers work with. + */ +struct pid_task { + pid_t pid; /* This may be 0 for tasks that exited + * mid-handling. */ + int sigstopped; + int got_event; + int delivered; +} * pids; + +struct pid_set { + struct pid_task * tasks; + size_t count; + size_t alloc; +}; + +/** + * Breakpoint re-enablement. When we hit a breakpoint, we must + * disable it, single-step, and re-enable it. That single-step can be + * done only by one task in a task group, while others are stopped, + * otherwise the processes would race for who sees the breakpoint + * disabled and who doesn't. The following is to keep track of it + * all. + */ +struct process_stopping_handler +{ + Event_Handler super; + + /* The task that is doing the re-enablement. */ + Process * task_enabling_breakpoint; + + /* The pointer being re-enabled. */ + Breakpoint * breakpoint_being_enabled; + + enum { + /* We are waiting for everyone to land in t/T. */ + psh_stopping = 0, + + /* We are doing the PTRACE_SINGLESTEP. */ + psh_singlestep, + + /* We are waiting for all the SIGSTOPs to arrive so + * that we can sink them. */ + psh_sinking, + + /* This is for tracking the ugly workaround. */ + psh_ugly_workaround, + } state; + + int exiting; + + struct pid_set pids; +}; + +static enum pcb_status +task_stopped(Process * task, void * data) +{ + /* If the task is already stopped, don't worry about it. + * Likewise if it managed to become a zombie or terminate in + * the meantime. This can happen when the whole thread group + * is terminating. */ + switch (process_status(task->pid)) { + case ps_invalid: + case ps_tracing_stop: + case ps_zombie: + return pcb_cont; + default: + return pcb_stop; + } +} + +static struct pid_task * +get_task_info(struct pid_set * pids, pid_t pid) +{ + assert(pid != 0); + size_t i; + for (i = 0; i < pids->count; ++i) + if (pids->tasks[i].pid == pid) + return &pids->tasks[i]; + + return NULL; +} + +static struct pid_task * +add_task_info(struct pid_set * pids, pid_t pid) +{ + if (pids->count == pids->alloc) { + size_t ns = (2 * pids->alloc) ?: 4; + struct pid_task * n = realloc(pids->tasks, + sizeof(*pids->tasks) * ns); + if (n == NULL) + return NULL; + pids->tasks = n; + pids->alloc = ns; + } + struct pid_task * task_info = &pids->tasks[pids->count++]; + memset(task_info, 0, sizeof(*task_info)); + task_info->pid = pid; + return task_info; +} + +static enum pcb_status +send_sigstop(Process * task, void * data) +{ + Process * leader = task->leader; + struct pid_set * pids = data; + + /* Look for pre-existing task record, or add new. */ + struct pid_task * task_info = get_task_info(pids, task->pid); + if (task_info == NULL) + task_info = add_task_info(pids, task->pid); + if (task_info == NULL) { + perror("send_sigstop: add_task_info"); + destroy_event_handler(leader); + /* Signal failure upwards. */ + return pcb_stop; + } + + /* This task still has not been attached to. It should be + stopped by the kernel. */ + if (task->state == STATE_BEING_CREATED) + return pcb_cont; + + /* Don't bother sending SIGSTOP if we are already stopped, or + * if we sent the SIGSTOP already, which happens when we + * inherit the handler from breakpoint re-enablement. */ + if (task_stopped(task, NULL) == pcb_cont) + return pcb_cont; + if (task_info->sigstopped) { + if (!task_info->delivered) + return pcb_cont; + task_info->delivered = 0; + } + + if (task_kill(task->pid, SIGSTOP) >= 0) { + debug(DEBUG_PROCESS, "send SIGSTOP to %d", task->pid); + task_info->sigstopped = 1; + } else + fprintf(stderr, + "Warning: couldn't send SIGSTOP to %d\n", task->pid); + + return pcb_cont; +} + +/* On certain kernels, detaching right after a singlestep causes the + tracee to be killed with a SIGTRAP (that even though the singlestep + was properly caught by waitpid. The ugly workaround is to put a + breakpoint where IP points and let the process continue. After + this the breakpoint can be retracted and the process detached. */ +static void +ugly_workaround(Process * proc) +{ + void * ip = get_instruction_pointer(proc); + Breakpoint * sbp = dict_find_entry(proc->leader->breakpoints, ip); + if (sbp != NULL) + enable_breakpoint(proc, sbp); + else + insert_breakpoint(proc, ip, NULL, 1); + ptrace(PTRACE_CONT, proc->pid, 0, 0); +} + +static void +process_stopping_done(struct process_stopping_handler * self, Process * leader) +{ + debug(DEBUG_PROCESS, "process stopping done %d", + self->task_enabling_breakpoint->pid); + size_t i; + if (!self->exiting) { + for (i = 0; i < self->pids.count; ++i) + if (self->pids.tasks[i].pid != 0 + && self->pids.tasks[i].delivered) + continue_process(self->pids.tasks[i].pid); + continue_process(self->task_enabling_breakpoint->pid); + destroy_event_handler(leader); } else { - ptrace(PTRACE_SYSCALL, pid, 0, signum); + self->state = psh_ugly_workaround; + ugly_workaround(self->task_enabling_breakpoint); } } -void -continue_process(pid_t pid) { - /* We always trace syscalls to control fork(), clone(), execve()... */ +/* Before we detach, we need to make sure that task's IP is on the + * edge of an instruction. So for tasks that have a breakpoint event + * in the queue, we adjust the instruction pointer, just like + * continue_after_breakpoint does. */ +static enum ecb_status +undo_breakpoint(Event * event, void * data) +{ + if (event != NULL + && event->proc->leader == data + && event->type == EVENT_BREAKPOINT) + set_instruction_pointer(event->proc, event->e_un.brk_addr); + return ecb_cont; +} - debug(DEBUG_PROCESS, "continue_process: pid=%d", pid); +static enum pcb_status +untrace_task(Process * task, void * data) +{ + if (task != data) + untrace_pid(task->pid); + return pcb_cont; +} - ptrace(PTRACE_SYSCALL, pid, 0, 0); +static enum pcb_status +remove_task(Process * task, void * data) +{ + /* Don't untrace leader just yet. */ + if (task != data) + remove_process(task); + return pcb_cont; } -void -continue_enabling_breakpoint(pid_t pid, Breakpoint *sbp) { - enable_breakpoint(pid, sbp); - continue_process(pid); +static void +detach_process(Process * leader) +{ + each_qd_event(&undo_breakpoint, leader); + disable_all_breakpoints(leader); + + /* Now untrace the process, if it was attached to by -p. */ + struct opt_p_t * it; + for (it = opt_p; it != NULL; it = it->next) { + Process * proc = pid2proc(it->pid); + if (proc == NULL) + continue; + if (proc->leader == leader) { + each_task(leader, &untrace_task, NULL); + break; + } + } + each_task(leader, &remove_task, leader); + destroy_event_handler(leader); + remove_task(leader, NULL); +} + +static void +handle_stopping_event(struct pid_task * task_info, Event ** eventp) +{ + /* Mark all events, so that we know whom to SIGCONT later. */ + if (task_info != NULL) + task_info->got_event = 1; + + Event * event = *eventp; + + /* In every state, sink SIGSTOP events for tasks that it was + * sent to. */ + if (task_info != NULL + && event->type == EVENT_SIGNAL + && event->e_un.signum == SIGSTOP) { + debug(DEBUG_PROCESS, "SIGSTOP delivered to %d", task_info->pid); + if (task_info->sigstopped + && !task_info->delivered) { + task_info->delivered = 1; + *eventp = NULL; // sink the event + } else + fprintf(stderr, "suspicious: %d got SIGSTOP, but " + "sigstopped=%d and delivered=%d\n", + task_info->pid, task_info->sigstopped, + task_info->delivered); + } +} + +/* Some SIGSTOPs may have not been delivered to their respective tasks + * yet. They are still in the queue. If we have seen an event for + * that process, continue it, so that the SIGSTOP can be delivered and + * caught by ltrace. */ +static void +continue_for_sigstop_delivery(struct pid_set * pids) +{ + size_t i; + for (i = 0; i < pids->count; ++i) { + if (pids->tasks[i].pid != 0 + && pids->tasks[i].sigstopped + && !pids->tasks[i].delivered + && pids->tasks[i].got_event) { + debug(DEBUG_PROCESS, "continue %d for SIGSTOP delivery", + pids->tasks[i].pid); + ptrace(PTRACE_SYSCALL, pids->tasks[i].pid, 0, 0); + } + } +} + +static int +event_exit_p(Event * event) +{ + return event != NULL && (event->type == EVENT_EXIT + || event->type == EVENT_EXIT_SIGNAL); +} + +static int +event_exit_or_none_p(Event * event) +{ + return event == NULL || event_exit_p(event) + || event->type == EVENT_NONE; +} + +static int +await_sigstop_delivery(struct pid_set * pids, struct pid_task * task_info, + Event * event) +{ + /* If we still didn't get our SIGSTOP, continue the process + * and carry on. */ + if (event != NULL && !event_exit_or_none_p(event) + && task_info != NULL && task_info->sigstopped) { + debug(DEBUG_PROCESS, "continue %d for SIGSTOP delivery", + task_info->pid); + /* We should get the signal the first thing + * after this, so it should be OK to continue + * even if we are over a breakpoint. */ + ptrace(PTRACE_SYSCALL, task_info->pid, 0, 0); + + } else { + /* If all SIGSTOPs were delivered, uninstall the + * handler and continue everyone. */ + /* XXX I suspect that we should check tasks that are + * still around. Is things are now, there should be a + * race between waiting for everyone to stop and one + * of the tasks exiting. */ + int all_clear = 1; + size_t i; + for (i = 0; i < pids->count; ++i) + if (pids->tasks[i].pid != 0 + && pids->tasks[i].sigstopped + && !pids->tasks[i].delivered) { + all_clear = 0; + break; + } + return all_clear; + } + + return 0; +} + +static int +all_stops_accountable(struct pid_set * pids) +{ + size_t i; + for (i = 0; i < pids->count; ++i) + if (pids->tasks[i].pid != 0 + && !pids->tasks[i].got_event + && !have_events_for(pids->tasks[i].pid)) + return 0; + return 1; +} + +/* This event handler is installed when we are in the process of + * stopping the whole thread group to do the pointer re-enablement for + * one of the threads. We pump all events to the queue for later + * processing while we wait for all the threads to stop. When this + * happens, we let the re-enablement thread to PTRACE_SINGLESTEP, + * re-enable, and continue everyone. */ +static Event * +process_stopping_on_event(Event_Handler * super, Event * event) +{ + struct process_stopping_handler * self = (void *)super; + Process * task = event->proc; + Process * leader = task->leader; + Breakpoint * sbp = self->breakpoint_being_enabled; + Process * teb = self->task_enabling_breakpoint; + + debug(DEBUG_PROCESS, + "pid %d; event type %d; state %d", + task->pid, event->type, self->state); + + struct pid_task * task_info = get_task_info(&self->pids, task->pid); + if (task_info == NULL) + fprintf(stderr, "new task??? %d\n", task->pid); + handle_stopping_event(task_info, &event); + + int state = self->state; + int event_to_queue = !event_exit_or_none_p(event); + + /* Deactivate the entry if the task exits. */ + if (event_exit_p(event) && task_info != NULL) + task_info->pid = 0; + + switch (state) { + case psh_stopping: + /* If everyone is stopped, singlestep. */ + if (each_task(leader, &task_stopped, NULL) == NULL) { + debug(DEBUG_PROCESS, "all stopped, now SINGLESTEP %d", + teb->pid); + if (sbp->enabled) + disable_breakpoint(teb, sbp); + if (ptrace(PTRACE_SINGLESTEP, teb->pid, 0, 0)) + perror("PTRACE_SINGLESTEP"); + self->state = state = psh_singlestep; + } + break; + + case psh_singlestep: { + /* In singlestep state, breakpoint signifies that we + * have now stepped, and can re-enable the breakpoint. */ + if (event != NULL && task == teb) { + /* Essentially we don't care what event caused + * the thread to stop. We can do the + * re-enablement now. */ + if (sbp->enabled) + enable_breakpoint(teb, sbp); + + continue_for_sigstop_delivery(&self->pids); + + self->breakpoint_being_enabled = NULL; + self->state = state = psh_sinking; + + if (event->type == EVENT_BREAKPOINT) + event = NULL; // handled + } else + break; + } + + /* fall-through */ + + case psh_sinking: + if (await_sigstop_delivery(&self->pids, task_info, event)) + process_stopping_done(self, leader); + break; + + case psh_ugly_workaround: + if (event == NULL) + break; + if (event->type == EVENT_BREAKPOINT) { + undo_breakpoint(event, leader); + if (task == teb) + self->task_enabling_breakpoint = NULL; + } + if (self->task_enabling_breakpoint == NULL + && all_stops_accountable(&self->pids)) { + undo_breakpoint(event, leader); + detach_process(leader); + event = NULL; // handled + } + } + + if (event != NULL && event_to_queue) { + enque_event(event); + event = NULL; // sink the event + } + + return event; +} + +static void +process_stopping_destroy(Event_Handler * super) +{ + struct process_stopping_handler * self = (void *)super; + free(self->pids.tasks); } void -continue_after_breakpoint(Process *proc, Breakpoint *sbp) { - if (sbp->enabled) - disable_breakpoint(proc->pid, sbp); +continue_after_breakpoint(Process *proc, Breakpoint *sbp) +{ set_instruction_pointer(proc, sbp->addr); if (sbp->enabled == 0) { continue_process(proc->pid); } else { - debug(DEBUG_PROCESS, "continue_after_breakpoint: pid=%d, addr=%p", proc->pid, sbp->addr); - proc->breakpoint_being_enabled = sbp; + debug(DEBUG_PROCESS, + "continue_after_breakpoint: pid=%d, addr=%p", + proc->pid, sbp->addr); #if defined __sparc__ || defined __ia64___ || defined __mips__ /* we don't want to singlestep here */ continue_process(proc->pid); #else - ptrace(PTRACE_SINGLESTEP, proc->pid, 0, 0); + struct process_stopping_handler * handler + = calloc(sizeof(*handler), 1); + if (handler == NULL) { + perror("malloc breakpoint disable handler"); + fatal: + /* Carry on not bothering to re-enable. */ + continue_process(proc->pid); + return; + } + + handler->super.on_event = process_stopping_on_event; + handler->super.destroy = process_stopping_destroy; + handler->task_enabling_breakpoint = proc; + handler->breakpoint_being_enabled = sbp; + install_event_handler(proc->leader, &handler->super); + + if (each_task(proc->leader, &send_sigstop, + &handler->pids) != NULL) + goto fatal; + + /* And deliver the first fake event, in case all the + * conditions are already fulfilled. */ + Event ev; + ev.type = EVENT_NONE; + ev.proc = proc; + process_stopping_on_event(&handler->super, &ev); #endif } } +/** + * Ltrace exit. When we are about to exit, we have to go through all + * the processes, stop them all, remove all the breakpoints, and then + * detach the processes that we attached to using -p. If we left the + * other tasks running, they might hit stray return breakpoints and + * produce artifacts, so we better stop everyone, even if it's a bit + * of extra work. + */ +struct ltrace_exiting_handler +{ + Event_Handler super; + struct pid_set pids; +}; + +static Event * +ltrace_exiting_on_event(Event_Handler * super, Event * event) +{ + struct ltrace_exiting_handler * self = (void *)super; + Process * task = event->proc; + Process * leader = task->leader; + + debug(DEBUG_PROCESS, "pid %d; event type %d", task->pid, event->type); + + struct pid_task * task_info = get_task_info(&self->pids, task->pid); + handle_stopping_event(task_info, &event); + + if (event != NULL && event->type == EVENT_BREAKPOINT) + undo_breakpoint(event, leader); + + if (await_sigstop_delivery(&self->pids, task_info, event) + && all_stops_accountable(&self->pids)) + detach_process(leader); + + /* Sink all non-exit events. We are about to exit, so we + * don't bother with queuing them. */ + if (event_exit_or_none_p(event)) + return event; + + return NULL; +} + +static void +ltrace_exiting_destroy(Event_Handler * super) +{ + struct ltrace_exiting_handler * self = (void *)super; + free(self->pids.tasks); +} + +static int +ltrace_exiting_install_handler(Process * proc) +{ + /* Only install to leader. */ + if (proc->leader != proc) + return 0; + + /* Perhaps we are already installed, if the user passed + * several -p options that are tasks of one process. */ + if (proc->event_handler != NULL + && proc->event_handler->on_event == <race_exiting_on_event) + return 0; + + /* If stopping handler is already present, let it do the + * work. */ + if (proc->event_handler != NULL) { + assert(proc->event_handler->on_event + == &process_stopping_on_event); + struct process_stopping_handler * other + = (void *)proc->event_handler; + other->exiting = 1; + return 0; + } + + struct ltrace_exiting_handler * handler + = calloc(sizeof(*handler), 1); + if (handler == NULL) { + perror("malloc exiting handler"); + fatal: + /* XXXXXXXXXXXXXXXXXXX fixme */ + return -1; + } + + handler->super.on_event = ltrace_exiting_on_event; + handler->super.destroy = ltrace_exiting_destroy; + install_event_handler(proc->leader, &handler->super); + + if (each_task(proc->leader, &send_sigstop, + &handler->pids) != NULL) + goto fatal; + + return 0; +} + +/* If ltrace gets SIGINT, the processes directly or indirectly run by + * ltrace get it too. We just have to wait long enough for the signal + * to be delivered and the process terminated, which we notice and + * exit ltrace, too. So there's not much we need to do there. We + * want to keep tracing those processes as usual, in case they just + * SIG_IGN the SIGINT to do their shutdown etc. + * + * For processes ran on the background, we want to install an exit + * handler that stops all the threads, removes all breakpoints, and + * detaches. + */ +void +ltrace_exiting(void) +{ + struct opt_p_t * it; + for (it = opt_p; it != NULL; it = it->next) { + Process * proc = pid2proc(it->pid); + if (proc == NULL || proc->leader == NULL) + continue; + if (ltrace_exiting_install_handler(proc->leader) < 0) + fprintf(stderr, + "Couldn't install exiting handler for %d.\n", + proc->pid); + } +} + size_t umovebytes(Process *proc, void *addr, void *laddr, size_t len) { diff --git a/sysdeps/linux-gnu/x86_64/trace.c b/sysdeps/linux-gnu/x86_64/trace.c index e8581af..d0299d9 100644 --- a/sysdeps/linux-gnu/x86_64/trace.c +++ b/sysdeps/linux-gnu/x86_64/trace.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "common.h" #include "ptrace.h" @@ -44,8 +45,11 @@ int syscall_p(Process *proc, int status, int *sysnum) { if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { - *sysnum = ptrace(PTRACE_PEEKUSER, proc->pid, 8 * ORIG_RAX, 0); + long int ret = ptrace(PTRACE_PEEKUSER, proc->pid, 8 * ORIG_RAX, 0); + if (ret == -1 && errno) + return -1; + *sysnum = ret; if (proc->callstack_depth > 0 && proc->callstack[proc->callstack_depth - 1].is_syscall && proc->callstack[proc->callstack_depth - 1].c_un.syscall == *sysnum) { diff --git a/testsuite/ltrace.main/main-threaded.c b/testsuite/ltrace.main/main-threaded.c new file mode 100644 index 0000000..a183966 --- /dev/null +++ b/testsuite/ltrace.main/main-threaded.c @@ -0,0 +1,29 @@ +#include + +extern void print (char *); + +#define PRINT_LOOP 10 + +void * +th_main (void *arg) +{ + int i; + for (i=0; i. + +set testfile "main-threaded" +set srcfile ${testfile}.c +set binfile ${testfile} +set libfile "main-lib" +set libsrc $srcdir/$subdir/$libfile.c +set lib_sl $objdir/$subdir/lib$testfile.so + + +if [get_compiler_info $binfile] { + return -1 +} + +verbose "compiling source file now....." +if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != "" + || [ltrace_compile $srcdir/$subdir/$srcfile $objdir/$subdir/$binfile executable [list debug shlib=$lib_sl ldflags=-pthread] ] != ""} { + send_user "Testcase compile failed, so all tests in this file will automatically fail.\n" +} + +# set options for ltrace. +ltrace_options "-l" "$objdir/$subdir/libmain.so" "-f" + +# Run PUT for ltarce. +set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] + +# Check the output of this program. +verbose "ltrace runtest output: $exec_output\n" +if [regexp {ELF from incompatible architecture} $exec_output] { + fail "32-bit ltrace can not perform on 64-bit PUTs and rebuild ltrace in 64 bit mode!" + return +} elseif [ regexp {Couldn't get .hash data} $exec_output ] { + fail "Couldn't get .hash data!" + return +} + +# Verify the output by checking numbers of print in main-threaded.ltrace. +set pattern "print(" +ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 30