2798 lines
78 KiB
Diff
2798 lines
78 KiB
Diff
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 <config.h>
|
|
#if defined(HAVE_LIBUNWIND)
|
|
#include <libunwind.h>
|
|
#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 <stdio.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
+#include <assert.h>
|
|
+#include <error.h>
|
|
|
|
#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 <signal.h>
|
|
#include <string.h>
|
|
#include <sys/ptrace.h>
|
|
+#include <assert.h>
|
|
|
|
#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 <sys/types.h>
|
|
+#include <sys/stat.h>
|
|
+#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <link.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
+#include <dirent.h>
|
|
+#include <ctype.h>
|
|
+#include <errno.h>
|
|
+#include <sys/syscall.h>
|
|
+#include <error.h>
|
|
+
|
|
|
|
/* /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 <sys/wait.h>
|
|
#include "ptrace.h"
|
|
#include <asm/unistd.h>
|
|
+#include <assert.h>
|
|
|
|
#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 <sys/reg.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
+#include <errno.h>
|
|
|
|
#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 <pthread.h>
|
|
+
|
|
+extern void print (char *);
|
|
+
|
|
+#define PRINT_LOOP 10
|
|
+
|
|
+void *
|
|
+th_main (void *arg)
|
|
+{
|
|
+ int i;
|
|
+ for (i=0; i<PRINT_LOOP; i++)
|
|
+ print (arg);
|
|
+}
|
|
+
|
|
+int
|
|
+main ()
|
|
+{
|
|
+ pthread_t thread1;
|
|
+ pthread_t thread2;
|
|
+ pthread_t thread3;
|
|
+ pthread_create (&thread1, NULL, th_main, "aaa");
|
|
+ pthread_create (&thread2, NULL, th_main, "bbb");
|
|
+ pthread_create (&thread3, NULL, th_main, "ccc");
|
|
+ pthread_join (thread1, NULL);
|
|
+ pthread_join (thread2, NULL);
|
|
+ pthread_join (thread3, NULL);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
diff --git a/testsuite/ltrace.main/main-threaded.exp b/testsuite/ltrace.main/main-threaded.exp
|
|
new file mode 100644
|
|
index 0000000..0157797
|
|
--- /dev/null
|
|
+++ b/testsuite/ltrace.main/main-threaded.exp
|
|
@@ -0,0 +1,39 @@
|
|
+# This file was written by Yao Qi <qiyao@cn.ibm.com>.
|
|
+
|
|
+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
|