Handle multi-threaded attach/detach gracefully
diff --git a/ChangeLog b/ChangeLog
index 9e93ebd..3d68baa 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
2011-07-08 Petr Machata <pmachata@redhat.com>
+ * common.h (ltrace_exiting): New interface called when ^C is hit.
+ * ltrace.c, handle_event.c: Remove current exit handling.
+ * sysdeps/linux-gnu/events.c: Implement exit handling using custom
+ event handler.
+ * proc.c (open_one_pid): trace_set_options so that attach to
+ multi-threaded process works.
+
+2011-07-08 Petr Machata <pmachata@redhat.com>
+
* common.h (struct Process.breakpoint_being_enabled): Drop.
* handle_event.c: Adapt to above.
* sysdeps/linux-gnu/events.c: Implement breakpoint re-enablement
diff --git a/common.h b/common.h
index c8f012b..a83e497 100644
--- a/common.h
+++ b/common.h
@@ -330,6 +330,7 @@
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 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);
diff --git a/handle_event.c b/handle_event.c
index 0886846..0a7407c 100644
--- a/handle_event.c
+++ b/handle_event.c
@@ -37,6 +37,11 @@
void
handle_event(Event *event) {
+ 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,
@@ -330,13 +335,6 @@
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_process(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),
diff --git a/libltrace.c b/libltrace.c
index caccb48..e731fe1 100644
--- a/libltrace.c
+++ b/libltrace.c
@@ -54,15 +54,7 @@
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
diff --git a/proc.c b/proc.c
index 87b5a89..f896bac 100644
--- a/proc.c
+++ b/proc.c
@@ -63,6 +63,7 @@
}
proc = open_program(filename, pid, 1);
+ trace_set_options(proc, pid);
continue_process(pid);
proc->breakpoints_enabled = 1;
}
diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c
index 86741de..e678536 100644
--- a/sysdeps/linux-gnu/trace.c
+++ b/sysdeps/linux-gnu/trace.c
@@ -536,6 +536,171 @@
}
}
+/**
+ * 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 enum pcb_status
+remove_task(Process * task, void * data)
+{
+ /* Don't untrace leader just yet. */
+ if (task != data)
+ remove_process(task);
+ return pcb_cont;
+}
+
+static enum pcb_status
+untrace_task(Process * task, void * data)
+{
+ untrace_pid(task->pid);
+ return pcb_cont;
+}
+
+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 (await_sigstop_delivery(&self->pids, task_info, event)) {
+ debug(DEBUG_PROCESS, "all SIGSTOPs delivered %d", leader->pid);
+ 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);
+ return NULL;
+ }
+
+ /* Sink all non-exit events. We are about to exit, so we
+ * don't bother with queuing them. */
+ if (event_exit_or_none_p(event))
+ return event;
+ else
+ return NULL;
+}
+
+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;
+
+ struct ltrace_exiting_handler * handler
+ = calloc(sizeof(*handler), 1);
+ if (handler == NULL) {
+ perror("malloc exiting handler");
+ fatal:
+ /* XXXXXXXXXXXXXXXXXXX fixme */
+ return -1;
+ }
+
+ /* If we are in the middle of breakpoint, extract the
+ * pid-state information from that handler so that we can take
+ * over the SIGSTOP handling. */
+ if (proc->event_handler != NULL) {
+ debug(DEBUG_PROCESS, "taking over breakpoint handling");
+ assert(proc->event_handler->on_event
+ == &process_stopping_on_event);
+ struct process_stopping_handler * other
+ = (void *)proc->event_handler;
+ size_t i;
+ for (i = 0; i < other->pids.count; ++i) {
+ struct pid_task * oti = &other->pids.tasks[i];
+ struct pid_task * task_info
+ = add_task_info(&handler->pids, oti->pid);
+ if (task_info == NULL) {
+ perror("ltrace_exiting_install_handler"
+ ":add_task_info");
+ goto fatal;
+ }
+ /* Copy over the state. */
+ *task_info = *oti;
+ }
+
+ /* And destroy the original handler. */
+ destroy_event_handler(proc);
+ }
+
+ handler->super.on_event = ltrace_exiting_on_event;
+ handler->super.destroy = ltrace_exiting_destroy;
+ install_event_handler(proc->leader, &handler->super);
+
+ 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) {