unknown pointers now return void*, not void
[platform/upstream/ltrace.git] / handle_event.c
index 1dfb82b..6fa7e98 100644 (file)
@@ -1,18 +1,48 @@
+/*
+ * This file is part of ltrace.
+ * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2010 Arnaud Patard, Mandriva SA
+ * Copyright (C) 1998,2001,2002,2003,2004,2007,2008,2009 Juan Cespedes
+ * Copyright (C) 2008 Luis Machado, IBM Corporation
+ * Copyright (C) 2006 Ian Wienand
+ * Copyright (C) 2006 Paul Gilliam, IBM Corporation
+ *
+ * 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
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
 #include <stdio.h>
-#include <string.h>
 #include <stdlib.h>
-#include <signal.h>
-#include <assert.h>
-#include <sys/time.h>
+#include <string.h>
+#include <stdbool.h>
 
+#include "backend.h"
+#include "breakpoint.h"
 #include "common.h"
-
-#ifdef __powerpc__
-#include <sys/ptrace.h>
-#endif
+#include "fetch.h"
+#include "library.h"
+#include "proc.h"
+#include "prototype.h"
+#include "summary.h"
+#include "value_dict.h"
 
 static void handle_signal(Event *event);
 static void handle_exit(Event *event);
@@ -25,78 +55,154 @@ 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,
-                                  struct library_symbol *sym);
-static void callstack_pop(Process *proc);
+static void callstack_push_syscall(struct process *proc, int sysnum);
+static void callstack_push_symfunc(struct process *proc, struct breakpoint *bp);
+/* XXX Stack maintenance should be moved to a dedicated module, or to
+ * proc.c, and push/pop should be visible outside this module.  For
+ * now, because we need this in proc.c, this is non-static.  */
+void callstack_pop(struct process *proc);
+
+static char *shortsignal(struct process *proc, int signum);
+static char *sysname(struct process *proc, int sysnum);
+static char *arch_sysname(struct process *proc, int sysnum);
+
+static Event *
+call_handler(struct process *proc, Event *event)
+{
+       assert(proc != NULL);
+
+       struct event_handler *handler = proc->event_handler;
+       if (handler == NULL)
+               return event;
 
-static char * shortsignal(Process *proc, int signum);
-static char * sysname(Process *proc, int sysnum);
-static char * arch_sysname(Process *proc, int sysnum);
+       return (*handler->on_event) (handler, event);
+}
 
 void
-handle_event(Event *event) {
-       debug(DEBUG_FUNCTION, "handle_event(pid=%d, type=%d)", event->proc ? event->proc->pid : -1, event->type);
+handle_event(Event *event)
+{
+       if (exiting == 1) {
+               debug(1, "ltrace about to exit");
+               os_ltrace_exiting();
+               exiting = 2;
+       }
+       debug(DEBUG_FUNCTION, "handle_event(pid=%d, type=%d)",
+             event->proc ? event->proc->pid : -1, event->type);
+
+       /* If the thread group or an individual task define an
+          overriding event handler, give them a chance to kick in.
+          We will end up calling both handlers, if the first one
+          doesn't sink the event.  */
+       if (event->proc != NULL) {
+               event = call_handler(event->proc, event);
+               if (event == NULL)
+                       /* It was handled.  */
+                       return;
+
+               /* Note: the previous handler has a chance to alter
+                * the event.  */
+               if (event->proc != NULL
+                   && event->proc->leader != NULL
+                   && event->proc != event->proc->leader) {
+                       event = call_handler(event->proc->leader, event);
+                       if (event == NULL)
+                               return;
+               }
+       }
+
        switch (event->type) {
        case EVENT_NONE:
                debug(1, "event: none");
                return;
+
        case EVENT_SIGNAL:
-               debug(1, "event: signal (%s [%d])",
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: signal (%s [%d])",
+                     event->proc->pid,
                      shortsignal(event->proc, event->e_un.signum),
                      event->e_un.signum);
                handle_signal(event);
                return;
+
        case EVENT_EXIT:
-               debug(1, "event: exit (%d)", event->e_un.ret_val);
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: exit (%d)",
+                     event->proc->pid,
+                     event->e_un.ret_val);
                handle_exit(event);
                return;
+
        case EVENT_EXIT_SIGNAL:
-               debug(1, "event: exit signal (%s [%d])",
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: exit signal (%s [%d])",
+                     event->proc->pid,
                      shortsignal(event->proc, event->e_un.signum),
                      event->e_un.signum);
                handle_exit_signal(event);
                return;
+
        case EVENT_SYSCALL:
-               debug(1, "event: syscall (%s [%d])",
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: syscall (%s [%d])",
+                     event->proc->pid,
                      sysname(event->proc, event->e_un.sysnum),
                      event->e_un.sysnum);
                handle_syscall(event);
                return;
+
        case EVENT_SYSRET:
-               debug(1, "event: sysret (%s [%d])",
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: sysret (%s [%d])",
+                     event->proc->pid,
                      sysname(event->proc, event->e_un.sysnum),
                      event->e_un.sysnum);
                handle_sysret(event);
                return;
+
        case EVENT_ARCH_SYSCALL:
-               debug(1, "event: arch_syscall (%s [%d])",
-                               arch_sysname(event->proc, event->e_un.sysnum),
-                               event->e_un.sysnum);
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: arch_syscall (%s [%d])",
+                     event->proc->pid,
+                     arch_sysname(event->proc, event->e_un.sysnum),
+                     event->e_un.sysnum);
                handle_arch_syscall(event);
                return;
+
        case EVENT_ARCH_SYSRET:
-               debug(1, "event: arch_sysret (%s [%d])",
-                               arch_sysname(event->proc, event->e_un.sysnum),
-                               event->e_un.sysnum);
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: arch_sysret (%s [%d])",
+                     event->proc->pid,
+                     arch_sysname(event->proc, event->e_un.sysnum),
+                     event->e_un.sysnum);
                handle_arch_sysret(event);
                return;
+
        case EVENT_CLONE:
-               debug(1, "event: clone (%u)", event->e_un.newpid);
+       case EVENT_VFORK:
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: clone (%u)",
+                     event->proc->pid, event->e_un.newpid);
                handle_clone(event);
                return;
+
        case EVENT_EXEC:
-               debug(1, "event: exec()");
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: exec()",
+                     event->proc->pid);
                handle_exec(event);
                return;
+
        case EVENT_BREAKPOINT:
-               debug(1, "event: breakpoint");
+               assert(event->proc != NULL);
+               debug(1, "[%d] event: breakpoint %p",
+                     event->proc->pid, event->e_un.brk_addr);
                handle_breakpoint(event);
                return;
+
        case EVENT_NEW:
-               debug(1, "event: new process");
+               debug(1, "[%d] event: new process",
+                     event->e_un.newpid);
                handle_new(event);
                return;
        default:
@@ -105,26 +211,6 @@ handle_event(Event *event) {
        }
 }
 
-/* TODO */
-static void *
-address_clone(void * addr) {
-       debug(DEBUG_FUNCTION, "address_clone(%p)", addr);
-       return addr;
-}
-
-static void *
-breakpoint_clone(void * bp) {
-       Breakpoint * b;
-       debug(DEBUG_FUNCTION, "breakpoint_clone(%p)", bp);
-       b = malloc(sizeof(Breakpoint));
-       if (!b) {
-               perror("malloc()");
-               exit(1);
-       }
-       memcpy(b, bp, sizeof(Breakpoint));
-       return b;
-}
-
 typedef struct Pending_New Pending_New;
 struct Pending_New {
        pid_t pid;
@@ -165,80 +251,79 @@ pending_new_insert(pid_t pid) {
 }
 
 static void
-pending_new_remove(pid_t pid) {
-       Pending_New *p, *pred;
-
+pending_new_remove(pid_t pid)
+{
        debug(DEBUG_FUNCTION, "pending_new_remove(%d)", pid);
 
-       p = pending_news;
-       if (p->pid == pid) {
-               pending_news = p->next;
-               free(p);
-       } else {
-               while (p) {
-                       if (p->pid == pid) {
-                               pred->next = p->next;
-                               free(p);
-                       }
-                       pred = p;
-                       p = p->next;
+       Pending_New **pp;
+       for (pp = &pending_news; *pp != NULL; pp = &(*pp)->next)
+               if ((*pp)->pid == pid) {
+                       Pending_New *p = *pp;
+                       *pp = p->next;
+                       free(p);
+                       return;
                }
-       }
 }
 
 static void
-handle_clone(Event * event) {
-       Process *p;
-
+handle_clone(Event *event)
+{
        debug(DEBUG_FUNCTION, "handle_clone(pid=%d)", event->proc->pid);
 
-       p = malloc(sizeof(Process));
-       if (!p) {
-               perror("malloc()");
-               exit(1);
-       }
-       memcpy(p, event->proc, sizeof(Process));
-       p->breakpoints = dict_clone(event->proc->breakpoints, address_clone, breakpoint_clone);
-       p->pid = event->e_un.newpid;
-       p->parent = event->proc;
+       struct process *proc = malloc(sizeof(*proc));
+       pid_t newpid = event->e_un.newpid;
+       if (proc == NULL
+           || process_clone(proc, event->proc, newpid) < 0) {
+               free(proc);
+               proc = NULL;
+               fprintf(stderr,
+                       "Couldn't initialize tracing of process %d.\n",
+                       newpid);
 
-       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 (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;
        } else {
-               p->state = STATE_BEING_CREATED;
-               p->next = list_of_processes;
-               list_of_processes = p;
+               proc->parent = event->proc;
+               /* We save register values to the arch pointer, and
+                * these need to be per-thread.  XXX arch_ptr should
+                * be retired in favor of fetch interface anyway.  */
+               proc->arch_ptr = NULL;
+       }
+
+       if (pending_new(newpid)) {
+               pending_new_remove(newpid);
+
+               if (proc != NULL) {
+                       proc->event_handler = NULL;
+                       if (event->proc->state == STATE_ATTACHED
+                           && options.follow)
+                               proc->state = STATE_ATTACHED;
+                       else
+                               proc->state = STATE_IGNORED;
+               }
+
+               continue_process(newpid);
+
+       } else if (proc != NULL) {
+               proc->state = STATE_BEING_CREATED;
        }
-       continue_process(event->proc->pid);
+
+       if (event->type != EVENT_VFORK)
+               continue_process(event->proc->pid);
+       else if (proc != NULL)
+               continue_after_vfork(proc);
+       else
+               continue_process(newpid);
 }
 
 static void
-handle_new(Event * event) {
-       Process * proc;
-
+handle_new(Event *event)
+{
        debug(DEBUG_FUNCTION, "handle_new(pid=%d)", event->e_un.newpid);
 
-       proc = pid2proc(event->e_un.newpid);
+       struct process *proc = pid2proc(event->e_un.newpid);
        if (!proc) {
                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 (options.follow) {
                        proc->state = STATE_ATTACHED;
                } else {
@@ -249,7 +334,8 @@ handle_new(Event * event) {
 }
 
 static char *
-shortsignal(Process *proc, int signum) {
+shortsignal(struct process *proc, int signum)
+{
        static char *signalent0[] = {
 #include "signalent.h"
        };
@@ -263,8 +349,7 @@ shortsignal(Process *proc, int signum) {
 
        debug(DEBUG_FUNCTION, "shortsignal(pid=%d, signum=%d)", proc->pid, signum);
 
-       if (proc->personality > sizeof signalents / sizeof signalents[0])
-               abort();
+       assert(proc->personality < sizeof signalents / sizeof signalents[0]);
        if (signum < 0 || signum >= nsignals[proc->personality]) {
                return "UNKNOWN_SIGNAL";
        } else {
@@ -273,64 +358,60 @@ shortsignal(Process *proc, int signum) {
 }
 
 static char *
-sysname(Process *proc, int sysnum) {
+sysname(struct process *proc, int sysnum)
+{
        static char result[128];
-       static char *syscalent0[] = {
+       static char *syscallent0[] = {
 #include "syscallent.h"
        };
-       static char *syscalent1[] = {
+       static char *syscallent1[] = {
 #include "syscallent1.h"
        };
-       static char **syscalents[] = { syscalent0, syscalent1 };
-       int nsyscals[] = { sizeof syscalent0 / sizeof syscalent0[0],
-               sizeof syscalent1 / sizeof syscalent1[0]
+       static char **syscallents[] = { syscallent0, syscallent1 };
+       int nsyscalls[] = {
+               sizeof syscallent0 / sizeof syscallent0[0],
+               sizeof syscallent1 / sizeof syscallent1[0],
        };
 
        debug(DEBUG_FUNCTION, "sysname(pid=%d, sysnum=%d)", proc->pid, sysnum);
 
-       if (proc->personality > sizeof syscalents / sizeof syscalents[0])
-               abort();
-       if (sysnum < 0 || sysnum >= nsyscals[proc->personality]) {
+       assert(proc->personality < sizeof syscallents / sizeof syscallents[0]);
+       if (sysnum < 0 || sysnum >= nsyscalls[proc->personality]) {
                sprintf(result, "SYS_%d", sysnum);
                return result;
        } else {
-               sprintf(result, "SYS_%s",
-                       syscalents[proc->personality][sysnum]);
-               return result;
+               return syscallents[proc->personality][sysnum];
        }
 }
 
 static char *
-arch_sysname(Process *proc, int sysnum) {
+arch_sysname(struct process *proc, int sysnum)
+{
        static char result[128];
-       static char *arch_syscalent[] = {
+       static char *arch_syscallent[] = {
 #include "arch_syscallent.h"
        };
-       int nsyscals = sizeof arch_syscalent / sizeof arch_syscalent[0];
+       int nsyscalls = sizeof arch_syscallent / sizeof arch_syscallent[0];
 
        debug(DEBUG_FUNCTION, "arch_sysname(pid=%d, sysnum=%d)", proc->pid, sysnum);
 
-       if (sysnum < 0 || sysnum >= nsyscals) {
+       if (sysnum < 0 || sysnum >= nsyscalls) {
                sprintf(result, "ARCH_%d", sysnum);
                return result;
        } else {
-               sprintf(result, "ARCH_%s",
-                               arch_syscalent[sysnum]);
+               sprintf(result, "ARCH_%s", arch_syscallent[sysnum]);
                return result;
        }
 }
 
+#ifndef HAVE_STRSIGNAL
+# define strsignal(SIGNUM) "???"
+#endif
+
 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) {
+       if (event->proc->state != STATE_IGNORED && !options.no_signals) {
                output_line(event->proc, "--- %s (%s) ---",
                                shortsignal(event->proc, event->e_un.signum),
                                strsignal(event->e_un.signum));
@@ -338,6 +419,84 @@ handle_signal(Event *event) {
        continue_after_signal(event->proc->pid, event->e_un.signum);
 }
 
+static int
+init_syscall_symbol(struct library_symbol *libsym, const char *name)
+{
+       static struct library syscall_lib;
+
+       if (syscall_lib.protolib == NULL) {
+               struct protolib *protolib
+                       = protolib_cache_load(&g_protocache, "syscalls", 0, 1);
+               if (protolib == NULL) {
+                       fprintf(stderr, "Couldn't load system call prototypes:"
+                               " %s.\n", strerror(errno));
+
+                       /* Instead, get a fake one just so we can
+                        * carry on, limping.  */
+                       protolib = malloc(sizeof *protolib);
+                       if (protolib == NULL) {
+                               fprintf(stderr, "Couldn't even allocate a fake "
+                                       "prototype library: %s.\n",
+                                       strerror(errno));
+                               abort();
+                       }
+                       protolib_init(protolib);
+               }
+
+               assert(protolib != NULL);
+               if (library_init(&syscall_lib, LT_LIBTYPE_SYSCALL) < 0) {
+                       fprintf(stderr, "Couldn't initialize system call "
+                               "library: %s.\n", strerror(errno));
+                       abort();
+               }
+
+               library_set_soname(&syscall_lib, "SYS", 0);
+               syscall_lib.protolib = protolib;
+       }
+
+       if (library_symbol_init(libsym, 0, name, 0, LS_TOPLT_NONE) < 0)
+               return -1;
+
+       libsym->lib = &syscall_lib;
+       return 0;
+}
+
+/* Account the unfinished functions on the call stack.  */
+static void
+account_current_callstack(struct process *proc)
+{
+       if (! options.summary)
+               return;
+
+       struct timedelta spent[proc->callstack_depth];
+
+       size_t i;
+       for (i = 0; i < proc->callstack_depth; ++i) {
+               struct callstack_element *elem = &proc->callstack[i];
+               spent[i] = calc_time_spent(elem->enter_time);
+       }
+
+       for (i = 0; i < proc->callstack_depth; ++i) {
+               struct callstack_element *elem = &proc->callstack[i];
+               struct library_symbol syscall, *libsym = NULL;
+               if (elem->is_syscall) {
+                       const char *name = sysname(proc, elem->c_un.syscall);
+                       if (init_syscall_symbol(&syscall, name) >= 0)
+                               libsym = &syscall;
+
+               } else {
+                       libsym = elem->c_un.libfunc;
+               }
+
+               if (libsym != NULL) {
+                       summary_account_call(libsym, spent[i]);
+
+                       if (elem->is_syscall)
+                               library_symbol_destroy(&syscall);
+               }
+       }
+}
+
 static void
 handle_exit(Event *event) {
        debug(DEBUG_FUNCTION, "handle_exit(pid=%d, status=%d)", event->proc->pid, event->e_un.ret_val);
@@ -345,7 +504,9 @@ handle_exit(Event *event) {
                output_line(event->proc, "+++ exited (status %d) +++",
                                event->e_un.ret_val);
        }
-       remove_proc(event->proc);
+
+       account_current_callstack(event->proc);
+       remove_process(event->proc);
 }
 
 static void
@@ -355,295 +516,271 @@ handle_exit_signal(Event *event) {
                output_line(event->proc, "+++ killed by %s +++",
                                shortsignal(event->proc, event->e_un.signum));
        }
-       remove_proc(event->proc);
+
+       account_current_callstack(event->proc);
+       remove_process(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;
+output_syscall(struct process *proc, const char *name, enum tof tof,
+              bool left, struct timedelta *spent)
+{
+       if (left)
+               assert(spent == NULL);
+
+       struct library_symbol syscall;
+       if (init_syscall_symbol(&syscall, name) >= 0) {
+               if (left) {
+                       if (! options.summary)
+                               output_left(tof, proc, &syscall);
+               } else if (options.summary) {
+                       summary_account_call(&syscall, *spent);
+               } else {
+                       output_right(tof, proc, &syscall, spent);
                }
-               tmp = tmp->next;
+
+               library_symbol_destroy(&syscall);
        }
 }
 
 static void
-handle_syscall(Event *event) {
+output_syscall_left(struct process *proc, const char *name)
+{
+       output_syscall(proc, name, LT_TOF_SYSCALL, true, NULL);
+}
+
+static void
+output_syscall_right(struct process *proc, const char *name,
+                    struct timedelta *spent)
+{
+       output_syscall(proc, name, LT_TOF_SYSCALLR, false, spent);
+}
+
+static void
+handle_syscall(Event *event)
+{
        debug(DEBUG_FUNCTION, "handle_syscall(pid=%d, sysnum=%d)", event->proc->pid, event->e_un.sysnum);
        if (event->proc->state != STATE_IGNORED) {
-               if (options.syscalls) {
-                       output_left(LT_TOF_SYSCALL, event->proc,
-                                       sysname(event->proc, event->e_un.sysnum));
-               }
-               if (event->proc->breakpoints_enabled == 0) {
-                       enable_all_breakpoints(event->proc);
-               }
                callstack_push_syscall(event->proc, event->e_un.sysnum);
+               if (options.syscalls)
+                       output_syscall_left(event->proc,
+                                           sysname(event->proc,
+                                                   event->e_un.sysnum));
        }
-       continue_process(event->proc->pid);
+       continue_after_syscall(event->proc, event->e_un.sysnum, 0);
 }
 
 static void
-handle_exec(Event * event) {
-       Process * proc = event->proc;
-       pid_t saved_pid;
+handle_exec(Event *event)
+{
+       struct process *proc = event->proc;
+
+       /* Save the PID so that we can use it after unsuccessful
+        * process_exec.  */
+       pid_t pid = proc->pid;
 
        debug(DEBUG_FUNCTION, "handle_exec(pid=%d)", proc->pid);
        if (proc->state == STATE_IGNORED) {
-               untrace_pid(proc->pid);
-               remove_proc(proc);
+       untrace:
+               untrace_pid(pid);
+               remove_process(proc);
                return;
        }
        output_line(proc, "--- Called exec() ---");
-       proc->mask_32bit = 0;
-       proc->personality = 0;
-       proc->arch_ptr = NULL;
-       free(proc->filename);
-       proc->filename = pid2name(proc->pid);
-       saved_pid = proc->pid;
-       proc->pid = 0;
-       breakpoints_init(proc);
-       proc->pid = saved_pid;
-       proc->callstack_depth = 0;
-       continue_process(proc->pid);
+
+       account_current_callstack(proc);
+
+       if (process_exec(proc) < 0) {
+               fprintf(stderr,
+                       "couldn't reinitialize process %d after exec\n", pid);
+               goto untrace;
+       }
+
+       continue_after_exec(proc);
 }
 
 static void
 handle_arch_syscall(Event *event) {
        debug(DEBUG_FUNCTION, "handle_arch_syscall(pid=%d, sysnum=%d)", event->proc->pid, event->e_un.sysnum);
        if (event->proc->state != STATE_IGNORED) {
+               callstack_push_syscall(event->proc, 0xf0000 + event->e_un.sysnum);
                if (options.syscalls) {
-                       output_left(LT_TOF_SYSCALL, event->proc,
-                                       arch_sysname(event->proc, event->e_un.sysnum));
-               }
-               if (event->proc->breakpoints_enabled == 0) {
-                       enable_all_breakpoints(event->proc);
+                       output_syscall_left(event->proc,
+                                           arch_sysname(event->proc,
+                                                        event->e_un.sysnum));
                }
-               callstack_push_syscall(event->proc, 0xf0000 + event->e_un.sysnum);
        }
        continue_process(event->proc->pid);
 }
 
-struct timeval current_time_spent;
-
 static void
-calc_time_spent(Process *proc) {
-       struct timeval tv;
-       struct timezone tz;
-       struct timeval diff;
-       struct callstack_element *elem;
+handle_x_sysret(Event *event, char *(*name_cb)(struct process *, int))
+{
+       debug(DEBUG_FUNCTION, "handle_x_sysret(pid=%d, sysnum=%d)",
+             event->proc->pid, event->e_un.sysnum);
 
-       debug(DEBUG_FUNCTION, "calc_time_spent(pid=%d)", proc->pid);
-       elem = &proc->callstack[proc->callstack_depth - 1];
+       unsigned d = event->proc->callstack_depth;
+       assert(d > 0);
+       struct callstack_element *elem = &event->proc->callstack[d - 1];
+       assert(elem->is_syscall);
 
-       gettimeofday(&tv, &tz);
+       if (event->proc->state != STATE_IGNORED) {
+               struct timedelta spent = calc_time_spent(elem->enter_time);
+               if (options.syscalls)
+                       output_syscall_right(event->proc,
+                                            name_cb(event->proc,
+                                                    event->e_un.sysnum),
+                                            &spent);
 
-       diff.tv_sec = tv.tv_sec - elem->time_spent.tv_sec;
-       if (tv.tv_usec >= elem->time_spent.tv_usec) {
-               diff.tv_usec = tv.tv_usec - elem->time_spent.tv_usec;
-       } else {
-               diff.tv_sec++;
-               diff.tv_usec = 1000000 + tv.tv_usec - elem->time_spent.tv_usec;
+               callstack_pop(event->proc);
        }
-       current_time_spent = diff;
+       continue_after_syscall(event->proc, event->e_un.sysnum, 1);
 }
 
 static void
-handle_sysret(Event *event) {
-       debug(DEBUG_FUNCTION, "handle_sysret(pid=%d, sysnum=%d)", event->proc->pid, event->e_un.sysnum);
-       if (event->proc->state != STATE_IGNORED) {
-               if (opt_T || options.summary) {
-                       calc_time_spent(event->proc);
-               }
-               callstack_pop(event->proc);
-               if (options.syscalls) {
-                       output_right(LT_TOF_SYSCALLR, event->proc,
-                                       sysname(event->proc, event->e_un.sysnum));
-               }
-       }
-       continue_process(event->proc->pid);
+handle_sysret(Event *event)
+{
+       handle_x_sysret(event, &sysname);
 }
 
 static void
-handle_arch_sysret(Event *event) {
-       debug(DEBUG_FUNCTION, "handle_arch_sysret(pid=%d, sysnum=%d)", event->proc->pid, event->e_un.sysnum);
-       if (event->proc->state != STATE_IGNORED) {
-               if (opt_T || options.summary) {
-                       calc_time_spent(event->proc);
-               }
-               callstack_pop(event->proc);
-               if (options.syscalls) {
-                       output_right(LT_TOF_SYSCALLR, event->proc,
-                                       arch_sysname(event->proc, event->e_un.sysnum));
-               }
-       }
-       continue_process(event->proc->pid);
+handle_arch_sysret(Event *event)
+{
+       handle_x_sysret(event, &arch_sysname);
 }
 
 static void
-handle_breakpoint(Event *event) {
-       int i, j;
-       Breakpoint *sbp;
-
-       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);
-
-#ifdef __powerpc__
-       /* Need to skip following NOP's to prevent a fake function from being stacked.  */
-       long stub_addr = (long) get_count_register(event->proc);
-       Breakpoint *stub_bp = NULL;
-       char nop_instruction[] = PPC_NOP;
+output_right_tos(struct process *proc)
+{
+       size_t d = proc->callstack_depth;
+       assert(d > 0);
+       struct callstack_element *elem = &proc->callstack[d - 1];
+       assert(! elem->is_syscall);
+
+       if (proc->state != STATE_IGNORED) {
+               struct timedelta spent = calc_time_spent(elem->enter_time);
+               if (options.summary)
+                       summary_account_call(elem->c_un.libfunc, spent);
+               else
+                       output_right(LT_TOF_FUNCTIONR, proc, elem->c_un.libfunc,
+                                    &spent);
+       }
+}
 
-       stub_bp = address2bpstruct (event->proc, event->e_un.brk_addr);
+#ifndef ARCH_HAVE_SYMBOL_RET
+void arch_symbol_ret(struct process *proc, struct library_symbol *libsym)
+{
+}
+#endif
 
-       if (stub_bp) {
-               unsigned char *bp_instruction = stub_bp->orig_value;
+static void
+handle_breakpoint(Event *event)
+{
+       int i, j;
+       struct breakpoint *sbp;
+       struct process *leader = event->proc->leader;
+       void *brk_addr = event->e_un.brk_addr;
 
-               if (memcmp(bp_instruction, nop_instruction,
-                           PPC_NOP_LENGTH) == 0) {
-                       if (stub_addr != (long) event->e_un.brk_addr) {
-                               set_instruction_pointer (event->proc, event->e_un.brk_addr + 4);
-                               continue_process(event->proc->pid);
-                               return;
-                       }
-               }
-       }
-#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;
+       /* 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, brk_addr);
+       debug(2, "event: breakpoint (%p)", brk_addr);
+
        for (i = event->proc->callstack_depth - 1; i >= 0; i--) {
-               if (event->e_un.brk_addr ==
-                   event->proc->callstack[i].return_addr) {
-#ifdef __powerpc__
-                       /*
-                        * PPC HACK! (XXX FIXME TODO)
-                        * The PLT gets modified during the first call,
-                        * so be sure to re-enable the breakpoint.
-                        */
-                       unsigned long a;
+               if (brk_addr == event->proc->callstack[i].return_addr) {
+                       for (j = event->proc->callstack_depth - 1; j > i; j--)
+                               callstack_pop(event->proc);
+
                        struct library_symbol *libsym =
                            event->proc->callstack[i].c_un.libfunc;
-                       void *addr = sym2addr(event->proc, libsym);
-
-                       if (libsym->plt_type != LS_TOPLT_POINT) {
-                               unsigned char break_insn[] = BREAKPOINT_VALUE;
 
-                               sbp = address2bpstruct(event->proc, addr);
-                               assert(sbp);
-                               a = ptrace(PTRACE_PEEKTEXT, event->proc->pid,
-                                          addr);
+                       arch_symbol_ret(event->proc, libsym);
+                       output_right_tos(event->proc);
+                       callstack_pop(event->proc);
 
-                               if (memcmp(&a, break_insn, BREAKPOINT_LENGTH)) {
-                                       sbp->enabled--;
-                                       insert_breakpoint(event->proc, addr,
-                                                         libsym);
-                               }
-                       } else {
-                               sbp = dict_find_entry(event->proc->breakpoints, sym2addr(event->proc, libsym));
-                               assert(sbp);
-                               if (addr != sbp->addr) {
-                                       insert_breakpoint(event->proc, addr,
-                                                         libsym);
-                               }
-                       }
-#elif defined(__mips__)
-                       void *addr;
-                       void *old_addr;
-                       struct library_symbol *sym= event->proc->callstack[i].c_un.libfunc;
-                       assert(sym);
-                       old_addr = dict_find_entry(event->proc->breakpoints, sym2addr(event->proc, sym))->addr;
-                       addr=sym2addr(event->proc,sym);
-                       assert(old_addr !=0 && addr !=0);
-                       if(addr != old_addr){
-                               struct library_symbol *new_sym;
-                               new_sym=malloc(sizeof(*new_sym));
-                               memcpy(new_sym,sym,sizeof(*new_sym));
-                               new_sym->next=event->proc->list_of_symbols;
-                               event->proc->list_of_symbols=new_sym;
-                               insert_breakpoint(event->proc, addr, new_sym);
-                       }
-#endif
-                       for (j = event->proc->callstack_depth - 1; j > i; j--) {
+                       /* Pop also any other entries that seem like
+                        * they are linked to the current one: they
+                        * have the same return address, but were made
+                        * for different symbols.  This should only
+                        * happen for entry point tracing, i.e. for -x
+                        * everywhere, or -x and -e on MIPS.  */
+                       while (event->proc->callstack_depth > 0) {
+                               struct callstack_element *prev;
+                               size_t d = event->proc->callstack_depth;
+                               prev = &event->proc->callstack[d - 1];
+
+                               if (prev->c_un.libfunc == libsym
+                                   || prev->return_addr != brk_addr)
+                                       break;
+
+                               arch_symbol_ret(event->proc,
+                                               prev->c_un.libfunc);
+                               output_right_tos(event->proc);
                                callstack_pop(event->proc);
                        }
-                       if (event->proc->state != STATE_IGNORED) {
-                               if (opt_T || options.summary) {
-                                       calc_time_spent(event->proc);
-                               }
-                       }
-                       callstack_pop(event->proc);
-                       event->proc->return_addr = event->e_un.brk_addr;
-                       if (event->proc->state != STATE_IGNORED) {
-                               output_right(LT_TOF_FUNCTIONR, event->proc,
-                                               event->proc->callstack[i].c_un.libfunc->name);
+
+                       /* Maybe the previous callstack_pop's got rid
+                        * of the breakpoint, but if we are in a
+                        * recursive call, it's still enabled.  In
+                        * that case we need to skip it properly.  */
+                       if ((sbp = address2bpstruct(leader, brk_addr)) != NULL) {
+                               continue_after_breakpoint(event->proc, sbp);
+                       } else {
+                               set_instruction_pointer(event->proc, brk_addr);
+                               continue_process(event->proc->pid);
                        }
-                       continue_after_breakpoint(event->proc,
-                                       address2bpstruct(event->proc,
-                                               event->e_un.brk_addr));
                        return;
                }
        }
 
-       if ((sbp = address2bpstruct(event->proc, event->e_un.brk_addr))) {
-               if (event->proc->state != STATE_IGNORED) {
+       if ((sbp = address2bpstruct(leader, brk_addr)) != NULL)
+               breakpoint_on_hit(sbp, event->proc);
+       else if (event->proc->state != STATE_IGNORED)
+               output_line(event->proc,
+                           "unexpected breakpoint at %p", brk_addr);
+
+       /* breakpoint_on_hit may delete its own breakpoint, so we have
+        * to look it up again.  */
+       if ((sbp = address2bpstruct(leader, brk_addr)) != NULL) {
+
+               if (event->proc->state != STATE_IGNORED
+                   && sbp->libsym != NULL) {
                        event->proc->stack_pointer = get_stack_pointer(event->proc);
-                       event->proc->return_addr =
-                               get_return_addr(event->proc, event->proc->stack_pointer);
-                       output_left(LT_TOF_FUNCTION, event->proc, sbp->libsym->name);
-                       callstack_push_symfunc(event->proc, sbp->libsym);
+                       callstack_push_symfunc(event->proc, sbp);
+                       if (! options.summary)
+                               output_left(LT_TOF_FUNCTION, event->proc,
+                                           sbp->libsym);
                }
-#ifdef PLT_REINITALISATION_BP
-               if (event->proc->need_to_reinitialize_breakpoints
-                   && (strcmp(sbp->libsym->name, PLTs_initialized_by_here) ==
-                       0))
-                       reinitialize_breakpoints(event->proc);
-#endif
 
-               continue_after_breakpoint(event->proc, sbp);
+               breakpoint_on_continue(sbp, event->proc);
                return;
+       } else {
+               set_instruction_pointer(event->proc, brk_addr);
        }
 
-       if (event->proc->state != STATE_IGNORED) {
-               output_line(event->proc, "unexpected breakpoint at %p",
-                               (void *)event->e_un.brk_addr);
-       }
        continue_process(event->proc->pid);
 }
 
 static void
-callstack_push_syscall(Process *proc, int sysnum) {
+callstack_push_syscall(struct process *proc, int sysnum)
+{
        struct callstack_element *elem;
 
        debug(DEBUG_FUNCTION, "callstack_push_syscall(pid=%d, sysnum=%d)", proc->pid, sysnum);
        /* FIXME: not good -- should use dynamic allocation. 19990703 mortene. */
        if (proc->callstack_depth == MAX_CALLDEPTH - 1) {
-               fprintf(stderr, "Error: call nesting too deep!\n");
+               fprintf(stderr, "%s: Error: call nesting too deep!\n", __func__);
+               abort();
                return;
        }
 
        elem = &proc->callstack[proc->callstack_depth];
+       *elem = (struct callstack_element){};
        elem->is_syscall = 1;
        elem->c_un.syscall = sysnum;
        elem->return_addr = NULL;
@@ -651,46 +788,72 @@ callstack_push_syscall(Process *proc, int sysnum) {
        proc->callstack_depth++;
        if (opt_T || options.summary) {
                struct timezone tz;
-               gettimeofday(&elem->time_spent, &tz);
+               gettimeofday(&elem->enter_time, &tz);
        }
 }
 
 static void
-callstack_push_symfunc(Process *proc, struct library_symbol *sym) {
+callstack_push_symfunc(struct process *proc, struct breakpoint *bp)
+{
        struct callstack_element *elem;
 
-       debug(DEBUG_FUNCTION, "callstack_push_symfunc(pid=%d, symbol=%s)", proc->pid, sym->name);
+       debug(DEBUG_FUNCTION, "callstack_push_symfunc(pid=%d, symbol=%s)",
+             proc->pid, bp->libsym->name);
        /* FIXME: not good -- should use dynamic allocation. 19990703 mortene. */
        if (proc->callstack_depth == MAX_CALLDEPTH - 1) {
-               fprintf(stderr, "Error: call nesting too deep!\n");
+               fprintf(stderr, "%s: Error: call nesting too deep!\n", __func__);
+               abort();
                return;
        }
 
-       elem = &proc->callstack[proc->callstack_depth];
+       elem = &proc->callstack[proc->callstack_depth++];
+       *elem = (struct callstack_element){};
        elem->is_syscall = 0;
-       elem->c_un.libfunc = sym;
-
-       elem->return_addr = proc->return_addr;
-       if (elem->return_addr) {
-               insert_breakpoint(proc, elem->return_addr, 0);
+       elem->c_un.libfunc = bp->libsym;
+
+       struct breakpoint *rbp = NULL;
+       if (breakpoint_get_return_bp(&rbp, bp, proc) == 0
+           && rbp != NULL) {
+               struct breakpoint *ext_rbp = insert_breakpoint(proc, rbp);
+               if (ext_rbp != rbp) {
+                       breakpoint_destroy(rbp);
+                       free(rbp);
+                       rbp = ext_rbp;
+               }
        }
 
-       proc->callstack_depth++;
+       elem->return_addr = rbp != NULL ? rbp->addr : 0;
+
        if (opt_T || options.summary) {
                struct timezone tz;
-               gettimeofday(&elem->time_spent, &tz);
+               gettimeofday(&elem->enter_time, &tz);
        }
 }
 
-static void
-callstack_pop(Process *proc) {
+void
+callstack_pop(struct process *proc)
+{
        struct callstack_element *elem;
        assert(proc->callstack_depth > 0);
 
        debug(DEBUG_FUNCTION, "callstack_pop(pid=%d)", proc->pid);
        elem = &proc->callstack[proc->callstack_depth - 1];
        if (!elem->is_syscall && elem->return_addr) {
-               delete_breakpoint(proc, elem->return_addr);
+               struct breakpoint *bp
+                       = address2bpstruct(proc->leader, elem->return_addr);
+               if (bp != NULL) {
+                       breakpoint_on_hit(bp, proc);
+                       delete_breakpoint(proc, bp);
+               }
+       }
+
+       if (elem->fetch_context != NULL)
+               fetch_arg_done(elem->fetch_context);
+
+       if (elem->arguments != NULL) {
+               val_dict_destroy(elem->arguments);
+               free(elem->arguments);
        }
+
        proc->callstack_depth--;
 }