diff -urp ltrace-0.5/ltrace.h ltrace-0.5-pm/ltrace.h --- ltrace-0.5/ltrace.h 2011-07-28 23:06:44.963572683 +0200 +++ ltrace-0.5-pm/ltrace.h 2011-07-29 12:29:33.905014880 +0200 @@ -149,7 +149,6 @@ struct process { void *instruction_pointer; void *stack_pointer; /* To get return addr, args... */ void *return_addr; - struct breakpoint *breakpoint_being_enabled; void *arch_ptr; short e_machine; short need_to_reinitialize_breakpoints; @@ -266,8 +265,6 @@ extern void disable_breakpoint(struct pr extern int syscall_p(struct 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(struct process *proc, - struct breakpoint *sbp); extern void continue_enabling_breakpoint(struct process *proc, struct breakpoint *sbp); extern long gimme_arg(enum tof type, struct process *proc, int arg_num); nТолько в ltrace-0.5-pm/: ltrace.h~ Только в ltrace-0.5-pm/: .#process_event.c Только в ltrace-0.5-pm/: #process_event.c# diff -up ltrace-0.5-pm/process_event.c\~ ltrace-0.5-pm/process_event.c --- ltrace-0.5-pm/process_event.c~ 2011-07-29 12:28:28.138147880 +0200 +++ ltrace-0.5-pm/process_event.c 2011-07-29 12:50:14.580042380 +0200 @@ -114,10 +114,6 @@ static void process_clone(struct event * if (pending_new(p->pid)) { pending_new_remove(p->pid); - if (p->breakpoint_being_enabled) { - enable_breakpoint(p, p->breakpoint_being_enabled); - p->breakpoint_being_enabled = NULL; - } if (p->event_handler != NULL) destroy_event_handler(p); if (event->proc->state == STATE_ATTACHED && opt_f) { @@ -140,10 +136,6 @@ static void process_new(struct event * e pending_new_insert(event->e_un.newpid); } else { assert(proc->state == STATE_BEING_CREATED); - if (proc->breakpoint_being_enabled) { - enable_breakpoint(proc, proc->breakpoint_being_enabled); - proc->breakpoint_being_enabled = NULL; - } if (proc->event_handler != NULL) destroy_event_handler(proc); if (opt_f) { @@ -396,26 +388,6 @@ static void process_breakpoint(struct ev } debug(2, "event: breakpoint (%p)", event->e_un.brk_addr); - if ((sbp = event->proc->breakpoint_being_enabled) != 0) { -#ifdef __powerpc__ - char nop_inst[] = PPC_NOP; - if (memcmp(sbp->orig_value, nop_inst, PPC_NOP_LENGTH) == 0) { - nxtbp = address2bpstruct(leader, - event->e_un.brk_addr + - PPC_NOP_LENGTH); - if (nxtbp != 0) { - enable_breakpoint(event->proc->pid, sbp); - continue_after_breakpoint(event->proc, nxtbp); - return; - } - } -#endif - /* Reinsert breakpoint */ - sbp = event->proc->breakpoint_being_enabled; - continue_enabling_breakpoint(event->proc, sbp); - event->proc->breakpoint_being_enabled = NULL; - return; - } for (i = event->proc->callstack_depth - 1; i >= 0; i--) { if (event->e_un.brk_addr == diff -urp ltrace-0.5/sysdeps/linux-gnu/trace.c ltrace-0.5-pm/sysdeps/linux-gnu/trace.c --- ltrace-0.5/sysdeps/linux-gnu/trace.c 2011-07-28 23:06:44.952572308 +0200 +++ ltrace-0.5-pm/sysdeps/linux-gnu/trace.c 2011-07-29 12:41:37.969866381 +0200 @@ -8,6 +8,9 @@ #include <asm/unistd.h> #include <linux/ptrace.h> #include <assert.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <stdint.h> #include "ltrace.h" #include "options.h" @@ -68,15 +68,346 @@ void continue_after_signal(pid_t pid, in ptrace(PTRACE_SYSCALL, pid, 0, signum); } -void continue_process(pid_t pid) +static enum ecb_status +event_for_pid(struct event * event, void * data) { - continue_after_signal(pid, 0); + if (event->proc != NULL && event->proc->pid == (pid_t)(uintptr_t)data) + return ecb_yield; + return ecb_cont; } -void continue_enabling_breakpoint(struct process *proc, struct breakpoint *sbp) +static int +have_events_for(pid_t pid) { - enable_breakpoint(proc, sbp); - continue_process(proc->pid); + return each_qd_event(event_for_pid, (void *)(uintptr_t)pid) != NULL; +} + +void +continue_process(pid_t pid) +{ + //printf("continue_process %d\n", 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); +} + +/** + * This is used for bookkeeping related to PIDs that the event + * handlers work with. */ +struct pid_task { + pid_t pid; + 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. */ + struct process * task_enabling_breakpoint; + + /* The pointer being re-enabled. */ + struct 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, + } state; + + struct pid_set pids; +}; + +static enum pcb_status +task_stopped(struct process * task, void * data) +{ + int status; + + /* 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 (status = process_status(task->pid)) + case -1: + case 't': + case 'Z': + return pcb_cont; + + return pcb_stop; +} + +static struct pid_task * +get_task_info(struct pid_set * pids, pid_t pid) +{ + 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(struct process * task, void * data) +{ + struct 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) { + task_info->sigstopped = 1; + } else + fprintf(stderr, + "Warning: couldn't send SIGSTOP to %d\n", task->pid); + + return pcb_cont; +} + +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].delivered) + continue_process(self->pids.tasks[i].pid); + continue_process(self->task_enabling_breakpoint->pid); + destroy_event_handler(leader); +} + +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) + task_info->got_event = 1; + + struct event * event = *eventp; + + /* In every state, sink SIGSTOP events for tasks that it was + * sent to. */ + if (task_info != NULL + && event->thing == LT_EV_SIGNAL + && event->e_un.signum == SIGSTOP) { + 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].sigstopped + && !pids->tasks[i].delivered + && pids->tasks[i].got_event) { + ptrace(PTRACE_SYSCALL, pids->tasks[i].pid, 0, 0); + } + } +} + +static int +event_exit_or_none_p(struct event * event) +{ + return event == NULL + || event->thing == LT_EV_EXIT + || event->thing == LT_EV_EXIT_SIGNAL + || event->thing == LT_EV_NONE; +} + +static int +await_sigstop_delivery(struct pid_set * pids, struct pid_task * task_info, + struct 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) { + /* 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].sigstopped + && !pids->tasks[i].delivered) { + all_clear = 0; + break; + } + return all_clear; + } + + return 0; +} + +/* 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 struct event * +process_stopping_on_event(Event_Handler * super, struct event * event) +{ + struct process_stopping_handler * self = (void *)super; + struct process * task = event->proc; + struct process * leader = task->leader; + + 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); + + switch (state) { + case psh_stopping: + /* If everyone is stopped, singlestep. */ + if (each_task(leader, &task_stopped, NULL) == NULL) { + ptrace(PTRACE_SINGLESTEP, + self->task_enabling_breakpoint->pid, 0, 0); + 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 == self->task_enabling_breakpoint) { + /* 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); + + continue_for_sigstop_delivery(&self->pids); + + self->breakpoint_being_enabled = NULL; + self->state = state = psh_sinking; + + if (event->thing == LT_EV_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); + } + + 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; + if (self->breakpoint_being_enabled != NULL) + enable_breakpoint(self->task_enabling_breakpoint, + self->breakpoint_being_enabled); + free(self->pids.tasks); } void continue_after_breakpoint(struct process *proc, struct breakpoint *sbp) @@ -87,12 +432,36 @@ void continue_after_breakpoint(struct pr if (sbp->enabled == 0) { continue_process(proc->pid); } else { - proc->breakpoint_being_enabled = sbp; #if defined __sparc__ || defined __ia64___ /* 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. */ + struct event ev; + ev.thing = LT_EV_NONE; + ev.proc = proc; + process_stopping_on_event(&handler->super, &ev); #endif } } Только в ltrace-0.5-pm/sysdeps/linux-gnu: trace.c~