From d2c71fc092486d674244df46e38f42416c6c58b9 Mon Sep 17 00:00:00 2001 From: Petr Machata Date: Nov 25 2011 14:16:39 +0000 Subject: Support vfork and fix a bunch of errors --- diff --git a/ltrace-0.6.0-ppc-args.patch b/ltrace-0.6.0-ppc-args.patch new file mode 100644 index 0000000..43bfa2e --- /dev/null +++ b/ltrace-0.6.0-ppc-args.patch @@ -0,0 +1,30 @@ +diff --git a/sysdeps/linux-gnu/ppc/trace.c b/sysdeps/linux-gnu/ppc/trace.c +index 20b3f5d..321e6ec 100644 +--- a/sysdeps/linux-gnu/ppc/trace.c ++++ b/sysdeps/linux-gnu/ppc/trace.c +@@ -87,10 +87,22 @@ gimme_arg_regset(enum tof type, Process *proc, int arg_num, arg_type_info *info, + } + else if (greg <= 10) + return (*regs)[greg++]; +- else ++ else { ++#ifdef __powerpc64__ ++ if (proc->mask_32bit) ++ return ptrace (PTRACE_PEEKDATA, proc->pid, ++ proc->stack_pointer + 8 + ++ sizeof (int) * (arg_num - 8), 0) >> 32; ++ else ++ return ptrace (PTRACE_PEEKDATA, proc->pid, ++ proc->stack_pointer + 112 + ++ sizeof (long) * (arg_num - 8), 0); ++#else + return ptrace (PTRACE_PEEKDATA, proc->pid, +- proc->stack_pointer + sizeof (long) * +- (arg_num - 8), 0); ++ proc->stack_pointer + 8 + ++ sizeof (long) * (arg_num - 8), 0); ++#endif ++ } + + return 0; + } diff --git a/ltrace-0.6.0-ppc-shift.patch b/ltrace-0.6.0-ppc-shift.patch new file mode 100644 index 0000000..27d8b95 --- /dev/null +++ b/ltrace-0.6.0-ppc-shift.patch @@ -0,0 +1,21 @@ +diff --git a/sysdeps/linux-gnu/ppc/plt.c b/sysdeps/linux-gnu/ppc/plt.c +index 980d028..668f63d 100644 +--- a/sysdeps/linux-gnu/ppc/plt.c ++++ b/sysdeps/linux-gnu/ppc/plt.c +@@ -44,12 +44,16 @@ sym2addr(Process *proc, struct library_symbol *sym) { + + pt_ret = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0); + ++#if SIZEOF_LONG == 8 + if (proc->mask_32bit) { + // Assume big-endian. + addr = (void *)((pt_ret >> 32) & 0xffffffff); + } else { + addr = (void *)pt_ret; + } ++#else ++ addr = (void *)pt_ret; ++#endif + + return addr; + } diff --git a/ltrace-0.6.0-thread-races.patch b/ltrace-0.6.0-thread-races.patch new file mode 100644 index 0000000..8250aae --- /dev/null +++ b/ltrace-0.6.0-thread-races.patch @@ -0,0 +1,406 @@ +diff --git a/common.h b/common.h +index 2fff8dd..715898d 100644 +--- a/common.h ++++ b/common.h +@@ -343,6 +343,7 @@ 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_syscall(Process *proc, int sysnum, int ret_p); + extern void continue_after_breakpoint(Process * proc, Breakpoint * sbp); + extern void continue_after_vfork(Process * proc); + extern void ltrace_exiting(void); +diff --git a/handle_event.c b/handle_event.c +index f56c537..203459c 100644 +--- a/handle_event.c ++++ b/handle_event.c +@@ -70,7 +70,9 @@ handle_event(Event *event) { + + /* Note: the previous handler has a chance to alter + * the event. */ +- if (event->proc->leader != NULL) { ++ if (event->proc != NULL ++ && event->proc->leader != NULL ++ && event->proc != event->proc->leader) { + event = call_handler(event->proc->leader, event); + if (event == NULL) + return; +@@ -454,7 +456,7 @@ handle_syscall(Event *event) { + enable_all_breakpoints(event->proc); + } + } +- continue_process(event->proc->pid); ++ continue_after_syscall(event->proc, event->e_un.sysnum, 0); + } + + static void +@@ -533,9 +535,12 @@ handle_sysret(Event *event) { + output_right(LT_TOF_SYSCALLR, event->proc, + sysname(event->proc, event->e_un.sysnum)); + } ++ assert(event->proc->callstack_depth > 0); ++ unsigned d = event->proc->callstack_depth - 1; ++ assert(event->proc->callstack[d].is_syscall); + callstack_pop(event->proc); + } +- continue_process(event->proc->pid); ++ continue_after_syscall(event->proc, event->e_un.sysnum, 1); + } + + static void +@@ -639,7 +644,7 @@ handle_breakpoint(Event *event) { + struct library_symbol *sym= event->proc->callstack[i].c_un.libfunc; + struct library_symbol *new_sym; + assert(sym); +- addr = sym2addr(leader, sym); ++ addr = sym2addr(event->proc, sym); + sbp = dict_find_entry(leader->breakpoints, addr); + if (sbp) { + if (addr != sbp->addr) { +diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c +index 0685342..021192f 100644 +--- a/sysdeps/linux-gnu/events.c ++++ b/sysdeps/linux-gnu/events.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + + #include "common.h" + +@@ -138,6 +139,26 @@ next_event(void) + } + event.proc = pid2proc(pid); + if (!event.proc || event.proc->state == STATE_BEING_CREATED) { ++ /* Work around (presumably) a bug on some kernels, ++ * where we are seeing a waitpid event even though the ++ * process is still reported to be running. Wait for ++ * the tracing stop to propagate. But don't get stuck ++ * here forever. ++ * ++ * We need the process in T, because there's a lot of ++ * ptracing going on all over the place, and these ++ * calls fail when the process is not in T. ++ * ++ * N.B. This was observed on RHEL 5 Itanium, but I'm ++ * turning this on globally, to save some poor soul ++ * down the road (which could well be me a year from ++ * now) the pain of figuring this out all over again. ++ * Petr Machata 2011-11-22. */ ++ int i = 0; ++ for (; i < 100 && process_status(pid) != ps_tracing_stop; ++i) { ++ debug(2, "waiting for %d to stop", pid); ++ usleep(10000); ++ } + event.type = EVENT_NEW; + event.e_un.newpid = pid; + debug(DEBUG_EVENT, "event: NEW: pid=%d", pid); +diff --git a/sysdeps/linux-gnu/ia64/regs.c b/sysdeps/linux-gnu/ia64/regs.c +index 00df572..3f5d951 100644 +--- a/sysdeps/linux-gnu/ia64/regs.c ++++ b/sysdeps/linux-gnu/ia64/regs.c +@@ -2,6 +2,7 @@ + + #include + #include ++#include + + #include + #include +@@ -36,12 +37,18 @@ set_instruction_pointer(Process *proc, void *addr) { + + void * + get_stack_pointer(Process *proc) { +- return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, PT_R12, 0); ++ long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_R12, 0); ++ if (l == -1 && errno) ++ return NULL; ++ return (void *)l; + } + + void * + get_return_addr(Process *proc, void *stack_pointer) { +- return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, PT_B0, 0); ++ long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_B0, 0); ++ if (l == -1 && errno) ++ return NULL; ++ return (void *)l; + } + + void +diff --git a/sysdeps/linux-gnu/ia64/trace.c b/sysdeps/linux-gnu/ia64/trace.c +index 799e0ff..079ed55 100644 +--- a/sysdeps/linux-gnu/ia64/trace.c ++++ b/sysdeps/linux-gnu/ia64/trace.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + + #include "common.h" + +@@ -48,9 +49,10 @@ int + syscall_p(Process *proc, int status, int *sysnum) { + if (WIFSTOPPED(status) + && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { +- unsigned long slot = +- (ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IPSR, 0) >> 41) & +- 0x3; ++ long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IPSR, 0); ++ if (l == -1 && errno) ++ return -1; ++ unsigned long slot = ((unsigned long)l >> 41) & 0x3; + unsigned long ip = + ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IIP, 0); + +diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c +index ba3806d..db18df0 100644 +--- a/sysdeps/linux-gnu/trace.c ++++ b/sysdeps/linux-gnu/trace.c +@@ -146,8 +146,8 @@ 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. */ ++ the queue for this process. Otherwise just wait for the ++ other events to arrive. */ + if (!have_events_for(pid)) + /* We always trace syscalls to control fork(), + * clone(), execve()... */ +@@ -168,6 +168,7 @@ struct pid_task { + int got_event : 1; + int delivered : 1; + int vforked : 1; ++ int sysret : 1; + } * pids; + + struct pid_set { +@@ -259,10 +260,14 @@ task_stopped(Process * task, void * data) + case ps_invalid: + case ps_tracing_stop: + case ps_zombie: ++ case ps_sleeping: + return pcb_cont; +- default: ++ case ps_stop: ++ case ps_other: + return pcb_stop; + } ++ ++ abort (); + } + + /* Task is blocked if it's stopped, or if it's a vfork parent. */ +@@ -376,7 +381,8 @@ process_stopping_done(struct process_stopping_handler * self, Process * leader) + if (!self->exiting) { + for (i = 0; i < self->pids.count; ++i) + if (self->pids.tasks[i].pid != 0 +- && self->pids.tasks[i].delivered) ++ && (self->pids.tasks[i].delivered ++ || self->pids.tasks[i].sysret)) + continue_process(self->pids.tasks[i].pid); + continue_process(self->task_enabling_breakpoint->pid); + destroy_event_handler(leader); +@@ -469,7 +475,10 @@ handle_stopping_event(struct pid_task * task_info, Event ** eventp) + /* 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. */ ++ * caught by ltrace. We don't mind that the process is after ++ * breakpoint (and therefore potentially doesn't have aligned IP), ++ * because the signal will be delivered without the process actually ++ * starting. */ + static void + continue_for_sigstop_delivery(struct pid_set * pids) + { +@@ -549,6 +558,14 @@ all_stops_accountable(struct pid_set * pids) + return 1; + } + ++static void ++singlestep(Process * proc) ++{ ++ debug(1, "PTRACE_SINGLESTEP"); ++ if (ptrace(PTRACE_SINGLESTEP, proc->pid, 0, 0)) ++ perror("PTRACE_SINGLESTEP"); ++} ++ + /* 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 +@@ -580,6 +597,17 @@ process_stopping_on_event(Event_Handler * super, Event * event) + if (event_exit_p(event) && task_info != NULL) + task_info->pid = 0; + ++ /* Always handle sysrets. Whether sysret occurred and what ++ * sys it rets from may need to be determined based on process ++ * stack, so we need to keep that in sync with reality. Note ++ * that we don't continue the process after the sysret is ++ * handled. See continue_after_syscall. */ ++ if (event != NULL && event->type == EVENT_SYSRET) { ++ debug(1, "%d LT_EV_SYSRET", event->proc->pid); ++ event_to_queue = 0; ++ task_info->sysret = 1; ++ } ++ + switch (state) { + case psh_stopping: + /* If everyone is stopped, singlestep. */ +@@ -588,16 +616,22 @@ process_stopping_on_event(Event_Handler * super, Event * event) + teb->pid); + if (sbp->enabled) + disable_breakpoint(teb, sbp); +- if (ptrace(PTRACE_SINGLESTEP, teb->pid, 0, 0)) +- perror("PTRACE_SINGLESTEP"); ++ singlestep(teb); + self->state = state = psh_singlestep; + } + break; + +- case psh_singlestep: { ++ case psh_singlestep: + /* In singlestep state, breakpoint signifies that we + * have now stepped, and can re-enable the breakpoint. */ + if (event != NULL && task == teb) { ++ ++ /* This is not the singlestep that we are waiting for. */ ++ if (event->type == EVENT_SIGNAL) { ++ singlestep(task); ++ break; ++ } ++ + /* Essentially we don't care what event caused + * the thread to stop. We can do the + * re-enablement now. */ +@@ -613,7 +647,6 @@ process_stopping_on_event(Event_Handler * super, Event * event) + event = NULL; // handled + } else + break; +- } + + /* fall-through */ + +@@ -806,9 +839,6 @@ ltrace_exiting_install_handler(Process * proc) + * with its parent, and handle it as a multi-threaded case, with the + * exception that we know that the parent is blocked, and don't + * attempt to stop it. When the child execs, we undo the setup. +- * +- * XXX The parent process could be un-suspended before ltrace gets +- * child exec/exit event. Make sure this is taken care of. + */ + + struct process_vfork_handler +@@ -840,9 +870,9 @@ process_vfork_on_event(Event_Handler * super, Event * event) + sbp = dict_find_entry(event->proc->leader->breakpoints, + self->bp_addr); + if (sbp != NULL) +- insert_breakpoint(event->proc->leader, +- self->bp_addr, sbp->libsym, +- 1); ++ insert_breakpoint(event->proc->parent, ++ self->bp_addr, ++ sbp->libsym, 1); + } + + continue_process(event->proc->parent->pid); +@@ -852,11 +882,6 @@ process_vfork_on_event(Event_Handler * super, Event * event) + change_process_leader(event->proc, event->proc); + destroy_event_handler(event->proc); + +- /* XXXXX this could happen in the middle of handling +- * multi-threaded breakpoint. We must be careful to +- * undo the effects that we introduced above (vforked +- * = 1 et.al.). */ +- + default: + ; + } +@@ -893,6 +918,27 @@ continue_after_vfork(Process * proc) + change_process_leader(proc, proc->parent->leader); + } + ++static int ++is_mid_stopping(Process *proc) ++{ ++ return proc != NULL ++ && proc->event_handler != NULL ++ && proc->event_handler->on_event == &process_stopping_on_event; ++} ++ ++void ++continue_after_syscall(Process * proc, int sysnum, int ret_p) ++{ ++ /* Don't continue if we are mid-stopping. */ ++ if (ret_p && (is_mid_stopping(proc) || is_mid_stopping(proc->leader))) { ++ debug(DEBUG_PROCESS, ++ "continue_after_syscall: don't continue %d", ++ proc->pid); ++ return; ++ } ++ continue_process(proc->pid); ++} ++ + /* 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 +diff --git a/testsuite/ltrace.main/hello-vfork.c b/testsuite/ltrace.main/hello-vfork.c +new file mode 100644 +index 0000000..228c052 +--- /dev/null ++++ b/testsuite/ltrace.main/hello-vfork.c +@@ -0,0 +1,11 @@ ++/* Copyright (C) 2008, Red Hat, Inc. ++ * Written by Denys Vlasenko */ ++#include ++#include ++ ++int main() { ++ int r = vfork(); ++ fprintf(stderr, "vfork():%d\n", r); ++ _exit(0); ++} ++ +diff --git a/testsuite/ltrace.main/hello-vfork.exp b/testsuite/ltrace.main/hello-vfork.exp +new file mode 100644 +index 0000000..12c9ca3 +--- /dev/null ++++ b/testsuite/ltrace.main/hello-vfork.exp +@@ -0,0 +1,35 @@ ++# This file was written by Yao Qi . ++ ++set testfile "hello-vfork" ++set srcfile ${testfile}.c ++set binfile ${testfile} ++ ++ ++if [get_compiler_info $binfile] { ++ return -1 ++} ++ ++verbose "compiling source file now....." ++if { [ltrace_compile $srcdir/$subdir/$srcfile $objdir/$subdir/$binfile executable debug ] != ""} { ++ send_user "Testcase compile failed, so all tests in this file will automatically fail.\n" ++} ++ ++# set options for ltrace. ++ltrace_options "-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-vfork.ltrace. ++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "_exit" 2 ++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "vfork resumed" 2 diff --git a/ltrace-0.6.0-vfork.patch b/ltrace-0.6.0-vfork.patch new file mode 100644 index 0000000..8f56414 --- /dev/null +++ b/ltrace-0.6.0-vfork.patch @@ -0,0 +1,1130 @@ +diff --git a/breakpoints.c b/breakpoints.c +index 1eff8b0..387b2a5 100644 +--- a/breakpoints.c ++++ b/breakpoints.c +@@ -203,14 +203,7 @@ breakpoints_init(Process *proc, int enable) + 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; +- } +- } ++ destroy_library_symbol_chain(proc->list_of_symbols); + proc->list_of_symbols = NULL; + + if (options.libcalls && proc->filename) { +diff --git a/common.h b/common.h +index 49861cf..c0b24e5 100644 +--- a/common.h ++++ b/common.h +@@ -252,6 +252,7 @@ enum process_status { + ps_invalid, /* Failure. */ + ps_stop, /* Job-control stop. */ + ps_tracing_stop, ++ ps_sleeping, + ps_zombie, + ps_other, /* Necessary other states can be added as needed. */ + }; +@@ -265,6 +266,7 @@ enum pcb_status { + extern Process * pid2proc(pid_t pid); + extern void add_process(Process * proc); + extern void remove_process(Process * proc); ++extern void change_process_leader(Process * proc, Process * leader); + extern Process *each_process(Process * start, + enum pcb_status (* cb)(Process * proc, void * data), + void * data); +@@ -313,6 +315,10 @@ extern void add_library_symbol(GElf_Addr addr, const char *name, + struct library_symbol **library_symbolspp, + enum toplt type_of_plt, int is_weak); + ++extern struct library_symbol * clone_library_symbol(struct library_symbol * s); ++extern void destroy_library_symbol(struct library_symbol * s); ++extern void destroy_library_symbol_chain(struct library_symbol * chain); ++ + /* Arch-dependent stuff: */ + extern char * pid2name(pid_t pid); + extern pid_t process_leader(pid_t pid); +@@ -335,6 +341,7 @@ 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_after_vfork(Process * proc); + 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); +diff --git a/dict.c b/dict.c +index 486a461..ba318cd 100644 +--- a/dict.c ++++ b/dict.c +@@ -180,7 +180,9 @@ dict_key_cmp_int(void *key1, void *key2) { + } + + Dict * +-dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*)) { ++dict_clone2(Dict * old, void * (*key_clone)(void *, void *), ++ void * (*value_clone)(void *, void *), void * data) ++{ + Dict *d; + int i; + +@@ -199,17 +201,64 @@ dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*)) + de_old = old->buckets[i]; + de_new = &d->buckets[i]; + while (de_old) { ++ void * nkey, * nval; + *de_new = malloc(sizeof(struct dict_entry)); + if (!*de_new) { + perror("malloc()"); + exit(1); + } + memcpy(*de_new, de_old, sizeof(struct dict_entry)); +- (*de_new)->key = key_clone(de_old->key); +- (*de_new)->value = value_clone(de_old->value); ++ ++ /* The error detection is rather weak :-/ */ ++ nkey = key_clone(de_old->key, data); ++ if (nkey == NULL && de_old->key != NULL) { ++ perror("key_clone"); ++ err: ++ /* XXX Will this actually work? We ++ * simply memcpy the old dictionary ++ * over up there. */ ++ dict_clear(d); ++ free(de_new); ++ return NULL; ++ } ++ ++ nval = value_clone(de_old->value, data); ++ if (nval == NULL && de_old->value != NULL) { ++ perror("value_clone"); ++ goto err; ++ } ++ ++ (*de_new)->key = nkey; ++ (*de_new)->value = nval; + de_new = &(*de_new)->next; + de_old = de_old->next; + } + } + return d; + } ++ ++struct wrap_clone_cb ++{ ++ void * (*key_clone)(void *); ++ void * (*value_clone)(void *); ++}; ++ ++static void * ++value_clone_1(void * arg, void * data) ++{ ++ return ((struct wrap_clone_cb *)data)->value_clone(arg); ++} ++ ++static void * ++key_clone_1(void * arg, void * data) ++{ ++ return ((struct wrap_clone_cb *)data)->key_clone(arg); ++} ++ ++Dict * ++dict_clone(Dict * old, void * (*key_clone)(void *), ++ void * (*value_clone)(void *)) ++{ ++ struct wrap_clone_cb cb = { key_clone, value_clone }; ++ return dict_clone2(old, &key_clone_1, &value_clone_1, &cb); ++} +diff --git a/dict.h b/dict.h +index a70c3d5..27dc7bf 100644 +--- a/dict.h ++++ b/dict.h +@@ -18,3 +18,7 @@ extern int dict_key_cmp_string(void *key1, void *key2); + extern unsigned int dict_key2hash_int(void *key); + extern int dict_key_cmp_int(void *key1, void *key2); + extern Dict * dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*)); ++extern Dict * dict_clone2(Dict * old, ++ void * (* key_clone)(void * key, void * data), ++ void * (* value_clone)(void * value, void * data), ++ void * data); +diff --git a/handle_event.c b/handle_event.c +index 0aa40f7..f56c537 100644 +--- a/handle_event.c ++++ b/handle_event.c +@@ -7,6 +7,7 @@ + #include + #include + #include ++#include + + #include "common.h" + +@@ -35,6 +36,18 @@ static char * shortsignal(Process *proc, int signum); + static char * sysname(Process *proc, int sysnum); + static char * arch_sysname(Process *proc, int sysnum); + ++static Event * ++call_handler(Process * proc, Event * event) ++{ ++ assert(proc != NULL); ++ ++ Event_Handler * handler = proc->event_handler; ++ if (handler == NULL) ++ return event; ++ ++ return (*handler->on_event) (handler, event); ++} ++ + void + handle_event(Event *event) { + if (exiting == 1) { +@@ -44,15 +57,22 @@ handle_event(Event *event) { + } + 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 the thread group or an individual task define an ++ overriding event handler, give them a chance to kick in. ++ We will end up calling both handlers, if the first one ++ doesn't sink the event. */ ++ if (event->proc != NULL) { ++ event = call_handler(event->proc, event); ++ if (event == NULL) ++ /* It was handled. */ ++ return; ++ ++ /* Note: the previous handler has a chance to alter ++ * the event. */ ++ if (event->proc->leader != NULL) { ++ event = call_handler(event->proc->leader, event); + if (event == NULL) +- /* It was handled. */ + return; + } + } +@@ -102,6 +122,7 @@ handle_event(Event *event) { + handle_arch_sysret(event); + return; + case EVENT_CLONE: ++ case EVENT_VFORK: + debug(1, "event: clone (%u)", event->e_un.newpid); + handle_clone(event); + return; +@@ -125,14 +146,17 @@ handle_event(Event *event) { + + /* TODO */ + static void * +-address_clone(void * addr) { ++address_clone(void * addr, void * data) ++{ + debug(DEBUG_FUNCTION, "address_clone(%p)", addr); + return addr; + } + + static void * +-breakpoint_clone(void * bp) { ++breakpoint_clone(void * bp, void * data) ++{ + Breakpoint * b; ++ Dict * map = data; + debug(DEBUG_FUNCTION, "breakpoint_clone(%p)", bp); + b = malloc(sizeof(Breakpoint)); + if (!b) { +@@ -140,6 +164,15 @@ breakpoint_clone(void * bp) { + exit(1); + } + memcpy(b, bp, sizeof(Breakpoint)); ++ if (b->libsym != NULL) { ++ struct library_symbol * sym = dict_find_entry(map, b->libsym); ++ if (b->libsym == NULL) { ++ fprintf(stderr, "Can't find cloned symbol %s.\n", ++ b->libsym->name); ++ return NULL; ++ } ++ b->libsym = sym; ++ } + return b; + } + +@@ -204,6 +237,40 @@ pending_new_remove(pid_t pid) { + } + } + ++static int ++clone_breakpoints(Process * proc, Process * orig_proc) ++{ ++ /* When copying breakpoints, we also have to copy the ++ * referenced symbols, and link them properly. */ ++ Dict * map = dict_init(&dict_key2hash_int, &dict_key_cmp_int); ++ struct library_symbol * it = proc->list_of_symbols; ++ proc->list_of_symbols = NULL; ++ for (; it != NULL; it = it->next) { ++ struct library_symbol * libsym = clone_library_symbol(it); ++ if (libsym == NULL) { ++ int save_errno; ++ err: ++ save_errno = errno; ++ destroy_library_symbol_chain(proc->list_of_symbols); ++ dict_clear(map); ++ errno = save_errno; ++ return -1; ++ } ++ libsym->next = proc->list_of_symbols; ++ proc->list_of_symbols = libsym; ++ if (dict_enter(map, it, libsym) != 0) ++ goto err; ++ } ++ ++ proc->breakpoints = dict_clone2(orig_proc->breakpoints, ++ address_clone, breakpoint_clone, map); ++ if (proc->breakpoints == NULL) ++ goto err; ++ ++ dict_clear(map); ++ return 0; ++} ++ + static void + handle_clone(Event * event) { + Process *p; +@@ -216,7 +283,6 @@ handle_clone(Event * event) { + exit(1); + } + memcpy(p, event->proc, sizeof(Process)); +- p->breakpoints = dict_clone(event->proc->breakpoints, address_clone, breakpoint_clone); + p->pid = event->e_un.newpid; + p->parent = event->proc; + +@@ -239,7 +305,17 @@ handle_clone(Event * event) { + p->state = STATE_BEING_CREATED; + add_process(p); + } +- continue_process(event->proc->pid); ++ ++ if (p->leader == p) ++ clone_breakpoints(p, event->proc->leader); ++ else ++ /* Thread groups share breakpoints. */ ++ p->breakpoints = NULL; ++ ++ if (event->type == EVENT_VFORK) ++ continue_after_vfork(p); ++ else ++ continue_process(event->proc->pid); + } + + static void +@@ -253,8 +329,6 @@ handle_new(Event * event) { + pending_new_insert(event->e_un.newpid); + } else { + assert(proc->state == STATE_BEING_CREATED); +- if (proc->event_handler != NULL) +- destroy_event_handler(proc); + if (options.follow) { + proc->state = STATE_ATTACHED; + } else { +diff --git a/libltrace.c b/libltrace.c +index e731fe1..19bfafd 100644 +--- a/libltrace.c ++++ b/libltrace.c +@@ -107,6 +107,11 @@ ltrace_init(int argc, char **argv) { + } + } + if (command) { ++ /* Check that the binary ABI is supported before ++ * calling execute_program. */ ++ struct ltelf lte = {}; ++ open_elf(<e, command); ++ + open_program(command, execute_program(command, argv), 0); + } + opt_p_tmp = opt_p; +diff --git a/ltrace-elf.c b/ltrace-elf.c +index d88d5a6..9aea4a9 100644 +--- a/ltrace-elf.c ++++ b/ltrace-elf.c +@@ -136,18 +136,14 @@ static GElf_Addr get_glink_vma(struct ltelf *lte, GElf_Addr ppcgot, + } + + int +-do_init_elf(struct ltelf *lte, const char *filename) { +- int i; +- GElf_Addr relplt_addr = 0; +- size_t relplt_size = 0; +- +- debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename); +- debug(1, "Reading ELF from %s...", filename); +- ++open_elf(struct ltelf *lte, const char *filename) ++{ + lte->fd = open(filename, O_RDONLY); + if (lte->fd == -1) + return 1; + ++ elf_version(EV_CURRENT); ++ + #ifdef HAVE_ELF_C_READ_MMAP + lte->elf = elf_begin(lte->fd, ELF_C_READ_MMAP, NULL); + #else +@@ -180,6 +176,21 @@ do_init_elf(struct ltelf *lte, const char *filename) { + error(EXIT_FAILURE, 0, + "\"%s\" is ELF from incompatible architecture", filename); + ++ return 0; ++} ++ ++int ++do_init_elf(struct ltelf *lte, const char *filename) { ++ int i; ++ GElf_Addr relplt_addr = 0; ++ size_t relplt_size = 0; ++ ++ debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename); ++ debug(1, "Reading ELF from %s...", filename); ++ ++ if (open_elf(lte, filename) < 0) ++ return -1; ++ + Elf_Data *plt_data = NULL; + GElf_Addr ppcgot = 0; + +@@ -465,30 +476,76 @@ do_close_elf(struct ltelf *lte) { + close(lte->fd); + } + ++static struct library_symbol * ++create_library_symbol(const char * name, GElf_Addr addr) ++{ ++ size_t namel = strlen(name) + 1; ++ struct library_symbol * sym = calloc(sizeof(*sym) + namel, 1); ++ if (sym == NULL) { ++ perror("create_library_symbol"); ++ return NULL; ++ } ++ sym->name = (char *)(sym + 1); ++ memcpy(sym->name, name, namel); ++ sym->enter_addr = (void *)(uintptr_t) addr; ++ return sym; ++} ++ + void + add_library_symbol(GElf_Addr addr, const char *name, + struct library_symbol **library_symbolspp, +- enum toplt type_of_plt, int is_weak) { ++ enum toplt type_of_plt, int is_weak) ++{ + struct library_symbol *s; + + debug(DEBUG_FUNCTION, "add_library_symbol()"); + +- s = malloc(sizeof(struct library_symbol) + strlen(name) + 1); ++ s = create_library_symbol(name, addr); + if (s == NULL) + error(EXIT_FAILURE, errno, "add_library_symbol failed"); + + s->needs_init = 1; + s->is_weak = is_weak; + s->plt_type = type_of_plt; ++ + s->next = *library_symbolspp; +- s->enter_addr = (void *)(uintptr_t) addr; +- s->name = (char *)(s + 1); +- strcpy(s->name, name); + *library_symbolspp = s; + + debug(2, "addr: %p, symbol: \"%s\"", (void *)(uintptr_t) addr, name); + } + ++struct library_symbol * ++clone_library_symbol(struct library_symbol * sym) ++{ ++ struct library_symbol * copy ++ = create_library_symbol(sym->name, ++ (GElf_Addr)(uintptr_t)sym->enter_addr); ++ if (copy == NULL) ++ return NULL; ++ ++ copy->needs_init = sym->needs_init; ++ copy->is_weak = sym->is_weak; ++ copy->plt_type = sym->plt_type; ++ ++ return copy; ++} ++ ++void ++destroy_library_symbol(struct library_symbol * sym) ++{ ++ free(sym); ++} ++ ++void ++destroy_library_symbol_chain(struct library_symbol * sym) ++{ ++ while (sym != NULL) { ++ struct library_symbol * next = sym->next; ++ destroy_library_symbol(sym); ++ sym = next; ++ } ++} ++ + /* stolen from elfutils-0.123 */ + static unsigned long + private_elf_gnu_hash(const char *name) { +@@ -620,8 +677,6 @@ read_elf(Process *proc) { + library_num = 0; + proc->libdl_hooked = 0; + +- elf_version(EV_CURRENT); +- + if (do_init_elf(lte, proc->filename)) + return NULL; + +@@ -637,7 +692,7 @@ read_elf(Process *proc) { + for (i = 0; i < library_num; ++i) { + if (do_init_elf(<e[i + 1], library[i])) + error(EXIT_FAILURE, errno, "Can't open \"%s\"", +- proc->filename); ++ library[i]); + } + + if (!options.no_plt) { +diff --git a/ltrace-elf.h b/ltrace-elf.h +index a29fe2c..3b675c5 100644 +--- a/ltrace-elf.h ++++ b/ltrace-elf.h +@@ -44,6 +44,7 @@ struct ltelf { + extern size_t library_num; + extern char *library[MAX_LIBRARIES]; + ++extern int open_elf(struct ltelf *lte, const char *filename); + extern struct library_symbol *read_elf(Process *); + + extern GElf_Addr arch_plt_sym_val(struct ltelf *, size_t, GElf_Rela *); +diff --git a/ltrace.h b/ltrace.h +index 0ff4572..194704d 100644 +--- a/ltrace.h ++++ b/ltrace.h +@@ -9,6 +9,7 @@ enum Event_type { + EVENT_ARCH_SYSCALL, + EVENT_ARCH_SYSRET, + EVENT_CLONE, ++ EVENT_VFORK, + EVENT_EXEC, + EVENT_BREAKPOINT, + EVENT_LIBCALL, +diff --git a/proc.c b/proc.c +index 0425e09..f4d3396 100644 +--- a/proc.c ++++ b/proc.c +@@ -154,9 +154,30 @@ pid2proc(pid_t pid) { + return each_process(NULL, &find_proc, (void *)(uintptr_t)pid); + } + +- + static Process * list_of_processes = NULL; + ++static void ++unlist_process(Process * proc) ++{ ++ Process *tmp; ++ ++ if (list_of_processes == proc) { ++ list_of_processes = list_of_processes->next; ++ return; ++ } ++ ++ for (tmp = list_of_processes; ; tmp = tmp->next) { ++ /* If the following assert fails, the process wasn't ++ * in the list. */ ++ assert(tmp->next != NULL); ++ ++ if (tmp->next == proc) { ++ tmp->next = tmp->next->next; ++ return; ++ } ++ } ++} ++ + Process * + each_process(Process * proc, + enum pcb_status (* cb)(Process * proc, void * data), +@@ -213,6 +234,23 @@ add_process(Process * proc) + *leaderp = proc; + } + ++void ++change_process_leader(Process * proc, Process * leader) ++{ ++ Process ** leaderp = &list_of_processes; ++ if (proc->leader == leader) ++ return; ++ ++ assert(leader != NULL); ++ unlist_process(proc); ++ if (proc != leader) ++ leaderp = &leader->next; ++ ++ proc->leader = leader; ++ proc->next = *leaderp; ++ *leaderp = proc; ++} ++ + static enum pcb_status + clear_leader(Process * proc, void * data) + { +@@ -242,31 +280,14 @@ delete_events_for(Process * proc) + 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->next) { +- if (tmp->next == proc) { +- tmp2 = tmp->next; +- tmp->next = tmp->next->next; +- delete_events_for(tmp2); +- free(tmp2); +- return; +- } +- tmp = tmp->next; +- } ++ unlist_process(proc); ++ delete_events_for(proc); ++ free(proc); + } + + void +@@ -283,7 +304,8 @@ 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); ++ if (handler->destroy != NULL) ++ handler->destroy(handler); + free(handler); + proc->event_handler = NULL; + } +diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c +index 8a79583..0685342 100644 +--- a/sysdeps/linux-gnu/events.c ++++ b/sysdeps/linux-gnu/events.c +@@ -240,13 +240,20 @@ next_event(void) + 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; +- ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data); +- event.type = EVENT_CLONE; +- event.e_un.newpid = data; +- debug(DEBUG_EVENT, "event: CLONE: pid=%d, newpid=%d", pid, (int)data); +- return &event; ++ if (WIFSTOPPED(status)) { ++ int what = status >> 16; ++ if (what == PTRACE_EVENT_VFORK ++ || what == PTRACE_EVENT_FORK ++ || what == PTRACE_EVENT_CLONE) { ++ unsigned long data; ++ event.type = what == PTRACE_EVENT_VFORK ++ ? EVENT_VFORK : EVENT_CLONE; ++ ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data); ++ event.e_un.newpid = data; ++ debug(DEBUG_EVENT, "event: CLONE: pid=%d, newpid=%d", ++ pid, (int)data); ++ return &event; ++ } + } + if (WIFSTOPPED(status) && (status>>16 == PTRACE_EVENT_EXEC)) { + event.type = EVENT_EXEC; +diff --git a/sysdeps/linux-gnu/proc.c b/sysdeps/linux-gnu/proc.c +index e3b71e5..a99593c 100644 +--- a/sysdeps/linux-gnu/proc.c ++++ b/sysdeps/linux-gnu/proc.c +@@ -148,7 +148,7 @@ process_status_cb(const char * line, const char * prefix, void * data) + switch (c) { + case 'Z': RETURN(ps_zombie); + case 't': RETURN(ps_tracing_stop); +- case 'T': { ++ case 'T': + /* This can be either "T (stopped)" or, for older + * kernels, "T (tracing stop)". */ + if (!strcmp(status, "T (stopped)\n")) +@@ -161,7 +161,8 @@ process_status_cb(const char * line, const char * prefix, void * data) + RETURN(ps_stop); /* Some sort of stop + * anyway. */ + } +- } ++ case 'D': ++ case 'S': RETURN(ps_sleeping); + } + + RETURN(ps_other); +diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c +index f8a1779..ba3806d 100644 +--- a/sysdeps/linux-gnu/trace.c ++++ b/sysdeps/linux-gnu/trace.c +@@ -164,9 +164,10 @@ continue_process(pid_t pid) + struct pid_task { + pid_t pid; /* This may be 0 for tasks that exited + * mid-handling. */ +- int sigstopped; +- int got_event; +- int delivered; ++ int sigstopped : 1; ++ int got_event : 1; ++ int delivered : 1; ++ int vforked : 1; + } * pids; + + struct pid_set { +@@ -213,23 +214,6 @@ struct process_stopping_handler + 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) + { +@@ -261,6 +245,57 @@ add_task_info(struct pid_set * pids, pid_t pid) + } + + static enum pcb_status ++task_stopped(Process * task, void * data) ++{ ++ enum process_status st = process_status(task->pid); ++ if (data != NULL) ++ *(enum process_status *)data = st; ++ ++ /* 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 (st) { ++ case ps_invalid: ++ case ps_tracing_stop: ++ case ps_zombie: ++ return pcb_cont; ++ default: ++ return pcb_stop; ++ } ++} ++ ++/* Task is blocked if it's stopped, or if it's a vfork parent. */ ++static enum pcb_status ++task_blocked(Process * task, void * data) ++{ ++ struct pid_set * pids = data; ++ struct pid_task * task_info = get_task_info(pids, task->pid); ++ if (task_info != NULL ++ && task_info->vforked) ++ return pcb_cont; ++ ++ return task_stopped(task, NULL); ++} ++ ++static Event * process_vfork_on_event(Event_Handler * super, Event * event); ++ ++static enum pcb_status ++task_vforked(Process * task, void * data) ++{ ++ if (task->event_handler != NULL ++ && task->event_handler->on_event == &process_vfork_on_event) ++ return pcb_stop; ++ return pcb_cont; ++} ++ ++static int ++is_vfork_parent(Process * task) ++{ ++ return each_task(task->leader, &task_vforked, NULL) != NULL; ++} ++ ++static enum pcb_status + send_sigstop(Process * task, void * data) + { + Process * leader = task->leader; +@@ -283,9 +318,11 @@ send_sigstop(Process * task, void * data) + 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) ++ * if we sent the SIGSTOP already, which happens when we are ++ * handling "onexit" and inherited the handler from breakpoint ++ * re-enablement. */ ++ enum process_status st; ++ if (task_stopped(task, &st) == pcb_cont) + return pcb_cont; + if (task_info->sigstopped) { + if (!task_info->delivered) +@@ -293,6 +330,16 @@ send_sigstop(Process * task, void * data) + task_info->delivered = 0; + } + ++ /* Also don't attempt to stop the process if it's a parent of ++ * vforked process. We set up event handler specially to hint ++ * us. In that case parent is in D state, which we use to ++ * weed out unnecessary looping. */ ++ if (st == ps_sleeping ++ && is_vfork_parent (task)) { ++ task_info->vforked = 1; ++ return pcb_cont; ++ } ++ + if (task_kill(task->pid, SIGSTOP) >= 0) { + debug(DEBUG_PROCESS, "send SIGSTOP to %d", task->pid); + task_info->sigstopped = 1; +@@ -536,7 +583,7 @@ process_stopping_on_event(Event_Handler * super, Event * event) + switch (state) { + case psh_stopping: + /* If everyone is stopped, singlestep. */ +- if (each_task(leader, &task_stopped, NULL) == NULL) { ++ if (each_task(leader, &task_blocked, &self->pids) == NULL) { + debug(DEBUG_PROCESS, "all stopped, now SINGLESTEP %d", + teb->pid); + if (sbp->enabled) +@@ -742,6 +789,110 @@ ltrace_exiting_install_handler(Process * proc) + return 0; + } + ++/* ++ * When the traced process vforks, it's suspended until the child ++ * process calls _exit or exec*. In the meantime, the two share the ++ * address space. ++ * ++ * The child process should only ever call _exit or exec*, but we ++ * can't count on that (it's not the role of ltrace to policy, but to ++ * observe). In any case, we will _at least_ have to deal with ++ * removal of vfork return breakpoint (which we have to smuggle back ++ * in, so that the parent can see it, too), and introduction of exec* ++ * return breakpoint. Since we already have both breakpoint actions ++ * to deal with, we might as well support it all. ++ * ++ * The gist is that we pretend that the child is in a thread group ++ * with its parent, and handle it as a multi-threaded case, with the ++ * exception that we know that the parent is blocked, and don't ++ * attempt to stop it. When the child execs, we undo the setup. ++ * ++ * XXX The parent process could be un-suspended before ltrace gets ++ * child exec/exit event. Make sure this is taken care of. ++ */ ++ ++struct process_vfork_handler ++{ ++ Event_Handler super; ++ void * bp_addr; ++}; ++ ++static Event * ++process_vfork_on_event(Event_Handler * super, Event * event) ++{ ++ struct process_vfork_handler * self = (void *)super; ++ Breakpoint * sbp; ++ assert(self != NULL); ++ ++ switch (event->type) { ++ case EVENT_BREAKPOINT: ++ /* Remember the vfork return breakpoint. */ ++ if (self->bp_addr == NULL) ++ self->bp_addr = event->e_un.brk_addr; ++ break; ++ ++ case EVENT_EXIT: ++ case EVENT_EXIT_SIGNAL: ++ case EVENT_EXEC: ++ /* Smuggle back in the vfork return breakpoint, so ++ * that our parent can trip over it once again. */ ++ if (self->bp_addr != NULL) { ++ sbp = dict_find_entry(event->proc->leader->breakpoints, ++ self->bp_addr); ++ if (sbp != NULL) ++ insert_breakpoint(event->proc->leader, ++ self->bp_addr, sbp->libsym, ++ 1); ++ } ++ ++ continue_process(event->proc->parent->pid); ++ ++ /* Remove the leader that we artificially set up ++ * earlier. */ ++ change_process_leader(event->proc, event->proc); ++ destroy_event_handler(event->proc); ++ ++ /* XXXXX this could happen in the middle of handling ++ * multi-threaded breakpoint. We must be careful to ++ * undo the effects that we introduced above (vforked ++ * = 1 et.al.). */ ++ ++ default: ++ ; ++ } ++ ++ return event; ++} ++ ++void ++continue_after_vfork(Process * proc) ++{ ++ debug(DEBUG_PROCESS, "continue_after_vfork: pid=%d", proc->pid); ++ struct process_vfork_handler * handler = calloc(sizeof(*handler), 1); ++ if (handler == NULL) { ++ perror("malloc vfork handler"); ++ /* Carry on not bothering to treat the process as ++ * necessary. */ ++ continue_process(proc->parent->pid); ++ return; ++ } ++ ++ /* We must set up custom event handler, so that we see ++ * exec/exit events for the task itself. */ ++ handler->super.on_event = process_vfork_on_event; ++ install_event_handler(proc, &handler->super); ++ ++ /* Make sure that the child is sole thread. */ ++ assert(proc->leader == proc); ++ assert(proc->next == NULL || proc->next->leader != proc); ++ ++ /* Make sure that the child's parent is properly set up. */ ++ assert(proc->parent != NULL); ++ assert(proc->parent->leader != NULL); ++ ++ change_process_leader(proc, proc->parent->leader); ++} ++ + /* 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 +diff --git a/testsuite/ltrace.main/main-threaded.exp b/testsuite/ltrace.main/main-threaded.exp +index 0157797..5539805 100644 +--- a/testsuite/ltrace.main/main-threaded.exp ++++ b/testsuite/ltrace.main/main-threaded.exp +@@ -19,7 +19,7 @@ if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != "" + } + + # set options for ltrace. +-ltrace_options "-l" "$objdir/$subdir/libmain.so" "-f" ++ltrace_options "-l" "$lib_sl" "-f" + + # Run PUT for ltarce. + set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] +@@ -35,5 +35,5 @@ if [regexp {ELF from incompatible architecture} $exec_output] { + } + + # Verify the output by checking numbers of print in main-threaded.ltrace. +-set pattern "print(" ++set pattern "print" + ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 30 +diff --git a/testsuite/ltrace.main/main-vfork.c b/testsuite/ltrace.main/main-vfork.c +new file mode 100644 +index 0000000..a5f6c40 +--- /dev/null ++++ b/testsuite/ltrace.main/main-vfork.c +@@ -0,0 +1,28 @@ ++#include ++ ++extern void print (char *); ++ ++#define PRINT_LOOP 10 ++ ++void ++th_main (char * arg) ++{ ++ int i; ++ for (i=0; i. ++ ++set testfile "main-vfork" ++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] ] != ""} { ++ send_user "Testcase compile failed, so all tests in this file will automatically fail.\n" ++} ++ ++# set options for ltrace. ++ltrace_options "-l" "$lib_sl" "-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-vfork.ltrace. ++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "print" 20 ++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "vfork resumed" 2 +diff --git a/testsuite/ltrace.torture/vfork-thread.c b/testsuite/ltrace.torture/vfork-thread.c +new file mode 100644 +index 0000000..f909bd3 +--- /dev/null ++++ b/testsuite/ltrace.torture/vfork-thread.c +@@ -0,0 +1,50 @@ ++#include ++#include ++#include ++#include ++ ++ ++void * ++routine (void *data) ++{ ++ int i; ++ for (i = 0; i < 6; ++i) ++ { ++ puts ("bleble"); ++ sleep (1); ++ } ++} ++ ++ ++void * ++routine2 (void *data) ++{ ++ pid_t child = vfork (); ++ if (child == 0) ++ { ++ int i, j; ++ puts ("vforked"); ++ for (i = 0; i < 100000; ++i) ++ for (j = 0; j < 10000; ++j) ++ ; ++ puts ("vforked child exiting"); ++ _exit (0); ++ } ++ puts ("parent continuing"); ++ return NULL; ++} ++ ++int ++main(int argc, char *argv[]) ++{ ++ pthread_t thread; ++ pthread_create (&thread, NULL, &routine, NULL); ++ ++ sleep (1); ++ ++ pthread_t thread2; ++ pthread_create (&thread2, NULL, &routine2, NULL); ++ pthread_join (thread2, NULL); ++ pthread_join (thread, NULL); ++ return 0; ++} +diff --git a/testsuite/ltrace.torture/vfork-thread.exp b/testsuite/ltrace.torture/vfork-thread.exp +new file mode 100644 +index 0000000..bd01319 +--- /dev/null ++++ b/testsuite/ltrace.torture/vfork-thread.exp +@@ -0,0 +1,32 @@ ++# This file was written by Yao Qi . ++ ++set testfile "vfork-thread" ++set srcfile ${testfile}.c ++set binfile ${testfile} ++ ++ ++verbose "compiling source file now....." ++# Build the shared libraries this test case needs. ++if { [ ltrace_compile "${srcdir}/${subdir}/${testfile}.c" "${objdir}/${subdir}/${binfile}" executable [list debug ldflags=-pthread] ] != "" } { ++ send_user "Testcase compile failed, so all tests in this file will automatically fail\n." ++} ++ ++ltrace_options "-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 ++} ++ ++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "puts" 9 ++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "sleep" 7 ++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "vfork resumed" 2 ++ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace "_exit" 1 diff --git a/ltrace.spec b/ltrace.spec index cf57e98..bf21297 100644 --- a/ltrace.spec +++ b/ltrace.spec @@ -23,6 +23,10 @@ Patch5: ltrace-0.6.0-return-string-n.patch Patch6: ltrace-0.6.0-threads.patch Patch7: ltrace-0.6.0-endian.patch Patch8: ltrace-0.6.0-clone-test.patch +Patch9: ltrace-0.6.0-ppc-args.patch +Patch10: ltrace-0.6.0-ppc-shift.patch +Patch11: ltrace-0.6.0-vfork.patch +Patch12: ltrace-0.6.0-thread-races.patch %description Ltrace is a debugging program which runs a specified command until the @@ -44,6 +48,10 @@ execution of processes. %patch6 -p1 %patch7 -p1 %patch8 -p1 +%patch9 -p1 +%patch10 -p1 +%patch11 -p1 +%patch12 -p1 sed -i -e 's/-o root -g root//' Makefile.in %build @@ -76,6 +84,14 @@ rm -rf $RPM_BUILD_ROOT %config(noreplace) %{_sysconfdir}/ltrace.conf %changelog +* Fri Nov 25 2011 Petr Machata - 0.6.0-4 +- Add several upstream patches that fix various races in tracing + multi-threaded processes +- Add upstream patches for support of tracing across vfork +- Add upstream patches for ppc: excessive shift, and fetching + function arguments +- Bump up revision to preserve upgrade path + * Fri Sep 2 2011 Petr Machata - 0.6.0-2 - Add upstream patches for tracing multi-threaded processes, endian fixes, and a test suite fixlet