Replace readdir_r with readdir
[platform/upstream/ltrace.git] / sysdeps / linux-gnu / events.c
index a1e2a14..51c2cae 100644 (file)
+/*
+ * 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");
@@ -40,26 +172,85 @@ next_event(void) {
        }
        event.proc = pid2proc(pid);
        if (!event.proc || event.proc->state == STATE_BEING_CREATED) {
+               /* Work around (presumably) a bug on some kernels,
+                * where we are seeing a waitpid event even though the
+                * process is still reported to be running.  Wait for
+                * the tracing stop to propagate.  But don't get stuck
+                * here forever.
+                *
+                * We need the process in T, because there's a lot of
+                * ptracing going on all over the place, and these
+                * calls fail when the process is not in T.
+                *
+                * N.B. This was observed on RHEL 5 Itanium, but I'm
+                * turning this on globally, to save some poor soul
+                * down the road (which could well be me a year from
+                * now) the pain of figuring this out all over again.
+                * Petr Machata 2011-11-22.  */
+               int i = 0;
+               for (; i < 100 && process_status(pid) != PS_TRACING_STOP; ++i) {
+                       debug(2, "waiting for %d to stop", pid);
+                       usleep(10000);
+               }
                event.type = EVENT_NEW;
                event.e_un.newpid = pid;
                debug(DEBUG_EVENT, "event: NEW: pid=%d", pid);
                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;
@@ -82,36 +273,30 @@ 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;
-               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;
@@ -122,22 +307,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.  */
+       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) {
@@ -150,12 +332,25 @@ 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;
 }
+
+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);
+}