diff --git a/ltrace-0.6.0-clone-test.patch b/ltrace-0.6.0-clone-test.patch new file mode 100644 index 0000000..c01221b --- /dev/null +++ b/ltrace-0.6.0-clone-test.patch @@ -0,0 +1,12 @@ +diff --git a/testsuite/ltrace.minor/trace-clone.c b/testsuite/ltrace.minor/trace-clone.c +index a1ccb22..6e1c809 100644 +--- a/testsuite/ltrace.minor/trace-clone.c ++++ b/testsuite/ltrace.minor/trace-clone.c +@@ -4,6 +4,7 @@ + + This file was written by Yao Qi . */ + ++#define _GNU_SOURCE + #include + #include + #include diff --git a/ltrace-0.6.0-endian.patch b/ltrace-0.6.0-endian.patch new file mode 100644 index 0000000..7c12043 --- /dev/null +++ b/ltrace-0.6.0-endian.patch @@ -0,0 +1,203 @@ +diff --git a/sysdeps/linux-gnu/alpha/arch.h b/sysdeps/linux-gnu/alpha/arch.h +index 1107b5f..2dfeec7 100644 +--- a/sysdeps/linux-gnu/alpha/arch.h ++++ b/sysdeps/linux-gnu/alpha/arch.h +@@ -1,6 +1,7 @@ + #define BREAKPOINT_VALUE { 0x80, 0x00, 0x00, 0x00 } + #define BREAKPOINT_LENGTH 4 + #define DECR_PC_AFTER_BREAK 4 ++#define ARCH_ENDIAN_LITTLE + + #define LT_ELFCLASS ELFCLASS64 + #define LT_ELF_MACHINE EM_ALPHA +diff --git a/sysdeps/linux-gnu/arm/arch.h b/sysdeps/linux-gnu/arm/arch.h +index 8f2dfb3..e2a62ab 100644 +--- a/sysdeps/linux-gnu/arm/arch.h ++++ b/sysdeps/linux-gnu/arm/arch.h +@@ -6,6 +6,7 @@ + #define THUMB_BREAKPOINT_VALUE { 0x01, 0xde } + #define THUMB_BREAKPOINT_LENGTH 2 + #define DECR_PC_AFTER_BREAK 0 ++#define ARCH_ENDIAN_LITTLE + + #define LT_ELFCLASS ELFCLASS32 + #define LT_ELF_MACHINE EM_ARM +diff --git a/sysdeps/linux-gnu/i386/arch.h b/sysdeps/linux-gnu/i386/arch.h +index dc7383f..15f0d78 100644 +--- a/sysdeps/linux-gnu/i386/arch.h ++++ b/sysdeps/linux-gnu/i386/arch.h +@@ -1,6 +1,7 @@ + #define BREAKPOINT_VALUE {0xcc} + #define BREAKPOINT_LENGTH 1 + #define DECR_PC_AFTER_BREAK 1 ++#define ARCH_ENDIAN_LITTLE + + #define LT_ELFCLASS ELFCLASS32 + #define LT_ELF_MACHINE EM_386 +diff --git a/sysdeps/linux-gnu/ia64/arch.h b/sysdeps/linux-gnu/ia64/arch.h +index 673047c..d7e0d23 100644 +--- a/sysdeps/linux-gnu/ia64/arch.h ++++ b/sysdeps/linux-gnu/ia64/arch.h +@@ -4,6 +4,7 @@ + #define BREAKPOINT_LENGTH 16 + #define BREAKPOINT_VALUE {0} + #define DECR_PC_AFTER_BREAK 0 ++#define ARCH_ENDIAN_LITTLE + + #define LT_ELFCLASS ELFCLASS64 + #define LT_ELF_MACHINE EM_IA_64 +diff --git a/sysdeps/linux-gnu/m68k/arch.h b/sysdeps/linux-gnu/m68k/arch.h +index 1790d09..44fad89 100644 +--- a/sysdeps/linux-gnu/m68k/arch.h ++++ b/sysdeps/linux-gnu/m68k/arch.h +@@ -1,6 +1,7 @@ + #define BREAKPOINT_VALUE { 0x4e, 0x4f } + #define BREAKPOINT_LENGTH 2 + #define DECR_PC_AFTER_BREAK 2 ++#define ARCH_ENDIAN_BIG + + #define LT_ELFCLASS ELFCLASS32 + #define LT_ELF_MACHINE EM_68K +diff --git a/sysdeps/linux-gnu/mipsel/arch.h b/sysdeps/linux-gnu/mipsel/arch.h +index dd0ca35..45c4598 100644 +--- a/sysdeps/linux-gnu/mipsel/arch.h ++++ b/sysdeps/linux-gnu/mipsel/arch.h +@@ -1,6 +1,7 @@ + #define BREAKPOINT_VALUE { 0x0d, 0x00, 0x00, 0x00 } + #define BREAKPOINT_LENGTH 4 + #define DECR_PC_AFTER_BREAK 0 ++#define ARCH_ENDIAN_LITTLE + + #define LT_ELFCLASS ELFCLASS32 + #define LT_ELF_MACHINE EM_MIPS +diff --git a/sysdeps/linux-gnu/ppc/arch.h b/sysdeps/linux-gnu/ppc/arch.h +index 711b4a3..8b955e8 100644 +--- a/sysdeps/linux-gnu/ppc/arch.h ++++ b/sysdeps/linux-gnu/ppc/arch.h +@@ -14,7 +14,7 @@ + #define PLT_REINITALISATION_BP "_start" + + /* Start of arch-specific functions. */ +-#define ARCH_HAVE_UMOVELONG ++#define ARCH_ENDIAN_BIG + + #define PPC_NOP { 0x60, 0x00, 0x00, 0x00 } + #define PPC_NOP_LENGTH 4 +diff -up ltrace-0.6.0/sysdeps/linux-gnu/ppc/trace.c\~ ltrace-0.6.0/sysdeps/linux-gnu/ppc/trace.c +--- ltrace-0.6.0/sysdeps/linux-gnu/ppc/trace.c~ 2011-02-14 16:48:25.000000000 +0100 ++++ ltrace-0.6.0/sysdeps/linux-gnu/ppc/trace.c 2011-09-03 00:18:14.518317665 +0200 +@@ -156,34 +156,3 @@ save_register_args(enum tof type, Proces + memcpy(&arch->regs_copy, &arch->regs, sizeof(arch->regs)); + memcpy(&arch->fpregs_copy, &arch->fpregs, sizeof(arch->fpregs)); + } +- +-/* Read a single long from the process's memory address 'addr'. */ +-int +-arch_umovelong (Process *proc, void *addr, long *result, arg_type_info *info) { +- long pointed_to; +- +- errno = 0; +- +- pointed_to = ptrace (PTRACE_PEEKTEXT, proc->pid, addr, 0); +- +- if (pointed_to == -1 && errno) +- return -errno; +- +- /* Since int's are 4-bytes (long is 8-bytes) in length for ppc64, we +- need to shift the long values returned by ptrace to end up with +- the correct value. */ +- +- if (info) { +- if (info->type == ARGTYPE_INT || (proc->mask_32bit && (info->type == ARGTYPE_POINTER +- || info->type == ARGTYPE_STRING))) { +- pointed_to = pointed_to >> 32; +- +- /* Make sure we have nothing in the upper word so we can +- do a explicit cast from long to int later in the code. */ +- pointed_to &= 0x00000000ffffffff; +- } +- } +- +- *result = pointed_to; +- return 0; +-} +diff --git a/sysdeps/linux-gnu/s390/arch.h b/sysdeps/linux-gnu/s390/arch.h +index 5cf168c..c6fed74 100644 +--- a/sysdeps/linux-gnu/s390/arch.h ++++ b/sysdeps/linux-gnu/s390/arch.h +@@ -6,6 +6,7 @@ + #define BREAKPOINT_VALUE { 0x00, 0x01 } + #define BREAKPOINT_LENGTH 2 + #define DECR_PC_AFTER_BREAK 2 ++#define ARCH_ENDIAN_BIG + + #ifdef __s390x__ + #define LT_ELFCLASS ELFCLASS64 +diff --git a/sysdeps/linux-gnu/sparc/arch.h b/sysdeps/linux-gnu/sparc/arch.h +index 75251b8..bec22ce 100644 +--- a/sysdeps/linux-gnu/sparc/arch.h ++++ b/sysdeps/linux-gnu/sparc/arch.h +@@ -1,6 +1,7 @@ + #define BREAKPOINT_VALUE {0x91, 0xd0, 0x20, 0x01} + #define BREAKPOINT_LENGTH 4 + #define DECR_PC_AFTER_BREAK 0 ++#define ARCH_ENDIAN_BIG + + #define LT_ELFCLASS ELFCLASS32 + #define LT_ELF_MACHINE EM_SPARC +diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c +index e4be465..9b0b980 100644 +--- a/sysdeps/linux-gnu/trace.c ++++ b/sysdeps/linux-gnu/trace.c +@@ -10,6 +10,7 @@ + #include + + #include "common.h" ++#include "config.h" + + /* If the system headers did not provide the constants, hard-code the normal + values. */ +@@ -54,15 +55,23 @@ umovelong (Process *proc, void *addr, long *result, arg_type_info *info) { + if (pointed_to == -1 && errno) + return -errno; + +- *result = pointed_to; +- if (info) { +- switch(info->type) { +- case ARGTYPE_INT: +- *result &= 0x00000000ffffffffUL; +- default: +- break; +- }; ++#if SIZEOF_LONG == 8 ++ if (info != NULL ++ && (info->type == ARGTYPE_INT ++ || (proc->mask_32bit ++ && (info->type == ARGTYPE_POINTER ++ || info->type == ARGTYPE_STRING)))) { ++#if defined (ARCH_ENDIAN_LITTLE) ++ pointed_to &= 0x00000000ffffffffUL; ++#elif defined (ARCH_ENDIAN_BIG) ++ pointed_to = (long)(((unsigned long)pointed_to) >> 32); ++#else ++# error arch.h has to define endianness ++#endif + } ++#endif ++ ++ *result = pointed_to; + return 0; + } + #endif +diff --git a/sysdeps/linux-gnu/x86_64/arch.h b/sysdeps/linux-gnu/x86_64/arch.h +index 255395c..34376c4 100644 +--- a/sysdeps/linux-gnu/x86_64/arch.h ++++ b/sysdeps/linux-gnu/x86_64/arch.h +@@ -1,6 +1,7 @@ + #define BREAKPOINT_VALUE {0xcc} + #define BREAKPOINT_LENGTH 1 + #define DECR_PC_AFTER_BREAK 1 ++#define ARCH_ENDIAN_LITTLE + + #define LT_ELFCLASS ELFCLASS64 + #define LT_ELF_MACHINE EM_X86_64 diff --git a/ltrace-0.6.0-threads.patch b/ltrace-0.6.0-threads.patch new file mode 100644 index 0000000..bf48fda --- /dev/null +++ b/ltrace-0.6.0-threads.patch @@ -0,0 +1,2797 @@ +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 diff --git a/ltrace.spec b/ltrace.spec index 5adcc4c..9a689f2 100644 --- a/ltrace.spec +++ b/ltrace.spec @@ -1,7 +1,7 @@ Summary: Tracks runtime library calls from dynamically linked executables Name: ltrace Version: 0.6.0 -Release: 1%{?dist} +Release: 2%{?dist} URL: http://ltrace.alioth.debian.org/ License: GPLv2+ Group: Development/Debuggers @@ -20,6 +20,9 @@ Patch2: ltrace-0.6.0-exec-stripped.patch Patch3: ltrace-0.5-demangle.patch Patch4: ltrace-0.5-etc-memmove.patch 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 %description Ltrace is a debugging program which runs a specified command until the @@ -38,6 +41,9 @@ execution of processes. #%patch3 -p1 %patch4 -p1 %patch5 -p1 +%patch6 -p1 +%patch7 -p1 +%patch8 -p1 sed -i -e 's/-o root -g root//' Makefile.in %build @@ -70,6 +76,10 @@ rm -rf $RPM_BUILD_ROOT %config(noreplace) %{_sysconfdir}/ltrace.conf %changelog +* 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 + * Tue Feb 15 2011 Petr Machata - 0.6.0-1 - Update to 0.6.0 - Drop most patches