+/*
+ * This file is part of ltrace.
+ * Copyright (C) 2007,2011,2012 Petr Machata, Red Hat Inc.
+ * Copyright (C) 1998,2001,2004,2007,2008,2009 Juan Cespedes
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
#include "config.h"
#define _GNU_SOURCE 1
-#include <stdlib.h>
+#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <assert.h>
#include <errno.h>
#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
-#include <sys/ptrace.h>
+#include <unistd.h>
-#include "common.h"
+#include "backend.h"
+#include "breakpoint.h"
+#include "debug.h"
+#include "events.h"
+#include "proc.h"
+#include "linux-gnu/trace-defs.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 callback_status
+first(struct process *proc, void *data)
+{
+ return CBS_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) {
+ fprintf(stderr, "event will be missed: %s\n", strerror(errno));
+ 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);
+}
+
+int linux_in_waitpid = 0;
+
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);
}
+
+ linux_in_waitpid = 1;
pid = waitpid(-1, &status, __WALL);
+ linux_in_waitpid = 0;
+
if (pid == -1) {
if (errno == ECHILD) {
debug(DEBUG_EVENT, "event: No more traced programs: exiting");
}
event.proc = pid2proc(pid);
if (!event.proc || event.proc->state == STATE_BEING_CREATED) {
+ /* Work around (presumably) a bug on some kernels,
+ * where we are seeing a waitpid event even though the
+ * process is still reported to be running. Wait for
+ * the tracing stop to propagate. But don't get stuck
+ * here forever.
+ *
+ * We need the process in T, because there's a lot of
+ * ptracing going on all over the place, and these
+ * calls fail when the process is not in T.
+ *
+ * N.B. This was observed on RHEL 5 Itanium, but I'm
+ * turning this on globally, to save some poor soul
+ * down the road (which could well be me a year from
+ * now) the pain of figuring this out all over again.
+ * Petr Machata 2011-11-22. */
+ int i = 0;
+ for (; i < 100 && process_status(pid) != PS_TRACING_STOP; ++i) {
+ debug(2, "waiting for %d to stop", pid);
+ usleep(10000);
+ }
event.type = EVENT_NEW;
event.e_un.newpid = pid;
debug(DEBUG_EVENT, "event: NEW: pid=%d", pid);
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) {
- enable_all_breakpoints(event.proc);
- event.type = EVENT_NONE;
- trace_set_options(event.proc, event.proc->pid);
- continue_process(event.proc->pid);
- debug(DEBUG_EVENT, "event: NONE: pid=%d (enabling breakpoints)", pid);
+ struct process *leader = event.proc->leader;
+
+ /* 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 (opt_i) {
- event.proc->instruction_pointer =
- get_instruction_pointer(event.proc);
+ 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;
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;
- ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data);
- event.type = EVENT_CLONE;
- event.e_un.newpid = data;
- debug(DEBUG_EVENT, "event: CLONE: pid=%d, newpid=%d", pid, (int)data);
- return &event;
+ if (WIFSTOPPED(status)) {
+ int what = status >> 16;
+ if (what == PTRACE_EVENT_VFORK
+ || what == PTRACE_EVENT_FORK
+ || what == PTRACE_EVENT_CLONE) {
+ unsigned long data;
+ event.type = what == PTRACE_EVENT_VFORK
+ ? EVENT_VFORK : EVENT_CLONE;
+ ptrace(PTRACE_GETEVENTMSG, pid, NULL, &data);
+ event.e_un.newpid = data;
+ debug(DEBUG_EVENT, "event: CLONE: pid=%d, newpid=%d",
+ pid, (int)data);
+ return &event;
+ }
}
if (WIFSTOPPED(status) && (status>>16 == PTRACE_EVENT_EXEC)) {
event.type = EVENT_EXEC;
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;
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. */
+ arch_addr_t 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) {
/* 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;
}
+
+static enum ecb_status
+event_for_proc(struct Event *event, void *data)
+{
+ if (event->proc == data)
+ return ECB_DEQUE;
+ else
+ return ECB_CONT;
+}
+
+void
+delete_events_for(struct process *proc)
+{
+ struct Event *event;
+ while ((event = each_qd_event(&event_for_proc, proc)) != NULL)
+ free(event);
+}