diff -urp ltrace-0.5-orig/sysdeps/linux-gnu/trace.c ltrace-0.5-pm/sysdeps/linux-gnu/trace.c --- ltrace-0.5-orig/sysdeps/linux-gnu/trace.c 2011-08-20 23:03:58.093608714 +0200 +++ ltrace-0.5-pm/sysdeps/linux-gnu/trace.c 2011-08-20 23:06:03.348270689 +0200 @@ -126,7 +126,7 @@ struct pid_set { }; /** - * Breakpoint re-enablement. When we hit a breakpoint, we must + * struct 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 @@ -153,8 +153,13 @@ struct process_stopping_handler /* 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; }; @@ -248,23 +253,94 @@ send_sigstop(struct process * task, void } static void +ugly_workaround(struct process * proc, int cont) +{ + void * ip = get_instruction_pointer(proc); + struct breakpoint * sbp = dict_find_entry(proc->leader->breakpoints, ip); + if (sbp != NULL) + enable_breakpoint(proc, sbp); + else + insert_breakpoint(proc, ip, NULL, 1); + if (cont) + ptrace(PTRACE_CONT, proc->pid, 0, 0); +} + +static void process_stopping_done(struct process_stopping_handler * self, struct process * leader) { size_t i; - 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); + 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 { + self->state = psh_ugly_workaround; + ugly_workaround(self->task_enabling_breakpoint, 1); + } +} + +/* 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(struct event * event, void * data) +{ + if (event != NULL + && event->proc->leader == data + && event->thing == LT_EV_BREAKPOINT) + set_instruction_pointer(event->proc, event->e_un.brk_addr); + return ecb_cont; +} + +static enum pcb_status +untrace_task(struct process * task, void * data) +{ + if (task != data) + untrace_pid(task->pid); + return pcb_cont; +} + +static enum pcb_status +remove_task(struct process * task, void * data) +{ + /* Don't untrace leader just yet. */ + if (task != data) + remove_process(task); + return pcb_cont; +} + +static void +detach_process(struct 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) { + struct 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, struct event ** eventp) { /* Mark all events, so that we know whom to SIGCONT later. */ - if (task_info != NULL && task_info->sigstopped) + if (task_info != NULL) task_info->got_event = 1; struct event * event = *eventp; @@ -354,6 +430,18 @@ await_sigstop_delivery(struct pid_set * 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 @@ -401,8 +489,8 @@ process_stopping_on_event(Event_Handler /* Essentially we don't care what event caused * the thread to stop. We can do the * re-enablement now. */ - enable_breakpoint(self->task_enabling_breakpoint, - self->breakpoint_being_enabled); + if (sbp->enabled) + enable_breakpoint(teb, sbp); continue_for_sigstop_delivery(&self->pids); @@ -420,6 +508,22 @@ process_stopping_on_event(Event_Handler 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->thing == LT_EV_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) { @@ -434,9 +538,6 @@ static void process_stopping_destroy(Event_Handler * super) { struct process_stopping_handler * self = (void *)super; - if (self->breakpoint_being_enabled != NULL) - enable_breakpoint(self->task_enabling_breakpoint, - self->breakpoint_being_enabled); free(self->pids.tasks); } @@ -444,8 +545,6 @@ void continue_after_breakpoint(struct pr { set_instruction_pointer(proc, sbp->addr); if (sbp->enabled == 0) { - if (sbp->enabled) - disable_breakpoint(proc, sbp); continue_process(proc->pid); } else { #if defined __sparc__ || defined __ia64___ @@ -532,22 +631,6 @@ struct ltrace_exiting_handler struct pid_set pids; }; -static enum pcb_status -remove_task(struct process * task, void * data) -{ - /* Don't untrace leader just yet. */ - if (task != data) - remove_process(task); - return pcb_cont; -} - -static enum pcb_status -untrace_task(struct process * task, void * data) -{ - untrace_pid(task->pid); - return pcb_cont; -} - static struct event * ltrace_exiting_on_event(Event_Handler * super, struct event * event) { @@ -558,33 +641,19 @@ ltrace_exiting_on_event(Event_Handler * struct pid_task * task_info = get_task_info(&self->pids, task->pid); handle_stopping_event(task_info, &event); - if (await_sigstop_delivery(&self->pids, task_info, event)) { - 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) { - struct process * proc = pid2proc(it->pid); - if (proc == NULL) - continue; - if (proc->leader == leader) { - each_task(leader, &untrace_task, NULL); - break; - } - } + if (event != NULL && event->thing == LT_EV_BREAKPOINT) + undo_breakpoint(event, leader); - each_task(leader, &remove_task, leader); - destroy_event_handler(leader); - remove_task(leader, NULL); - return NULL; - } + 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; - else - return NULL; + + return NULL; } static void @@ -607,6 +676,17 @@ ltrace_exiting_install_handler(struct pr && 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) { @@ -616,34 +696,6 @@ ltrace_exiting_install_handler(struct pr return -1; } - /* If we are in the middle of breakpoint, extract the - * pid-state information from that handler so that we can take - * over the SIGSTOP handling. */ - if (proc->event_handler != NULL) { - assert(proc->event_handler->on_event - == &process_stopping_on_event); - struct process_stopping_handler * other - = (void *)proc->event_handler; - size_t i; - for (i = 0; i < other->pids.count; ++i) { - struct pid_task * oti = &other->pids.tasks[i]; - if (oti->pid == 0) - continue; - struct pid_task * task_info - = add_task_info(&handler->pids, oti->pid); - if (task_info == NULL) { - perror("ltrace_exiting_install_handler" - ":add_task_info"); - goto fatal; - } - /* Copy over the state. */ - *task_info = *oti; - } - - /* And destroy the original handler. */ - destroy_event_handler(proc); - } - handler->super.on_event = ltrace_exiting_on_event; handler->super.destroy = ltrace_exiting_destroy; install_event_handler(proc->leader, &handler->super); Only in ltrace-0.5-pm/sysdeps/linux-gnu: trace.c~