Fix bug in `expr_clone`
[platform/upstream/ltrace.git] / handle_event.c
index 42a7a05..6fa7e98 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of ltrace.
- * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc.
+ * 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
@@ -32,7 +32,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/time.h>
+#include <stdbool.h>
 
 #include "backend.h"
 #include "breakpoint.h"
@@ -40,6 +40,8 @@
 #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);
@@ -54,20 +56,19 @@ static void handle_exec(Event *event);
 static void handle_breakpoint(Event *event);
 static void handle_new(Event *event);
 
-static void callstack_push_syscall(Process *proc, int sysnum);
-static void callstack_push_symfunc(Process *proc,
-                                  struct library_symbol *sym);
+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);
+void callstack_pop(struct process *proc);
 
-static char * shortsignal(Process *proc, int signum);
-static char * sysname(Process *proc, int sysnum);
-static char * arch_sysname(Process *proc, int sysnum);
+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(Process * proc, Event * event)
+call_handler(struct process *proc, Event *event)
 {
        assert(proc != NULL);
 
@@ -114,70 +115,91 @@ handle_event(Event *event)
        case EVENT_NONE:
                debug(1, "event: none");
                return;
+
        case EVENT_SIGNAL:
+               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:
+               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:
+               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:
+               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:
+               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:
+               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:
+               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:
        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:
+               assert(event->proc != NULL);
                debug(1, "[%d] event: exec()",
                      event->proc->pid);
                handle_exec(event);
                return;
+
        case 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, "[%d] event: new process",
                      event->e_un.newpid);
@@ -229,26 +251,18 @@ 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;
-       pred = NULL;
-       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
@@ -256,53 +270,56 @@ handle_clone(Event *event)
 {
        debug(DEBUG_FUNCTION, "handle_clone(pid=%d)", event->proc->pid);
 
-       struct Process *proc = malloc(sizeof(*proc));
-       if (proc == NULL) {
-       fail:
+       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,
-                       "Error during init of tracing process %d\n"
-                       "This process won't be traced.\n",
-                       event->proc->pid);
-               return;
+                       "Couldn't initialize tracing of process %d.\n",
+                       newpid);
+
+       } else {
+               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 (process_clone(proc, event->proc, event->e_un.newpid) < 0)
-               goto fail;
-       proc->parent = event->proc;
+       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;
+               }
 
-       /* We save register values to the arch pointer, and these need
-          to be per-thread.  */
-       proc->arch_ptr = NULL;
+               continue_process(newpid);
 
-       if (pending_new(proc->pid)) {
-               pending_new_remove(proc->pid);
-               /* XXX this used to be destroy_event_handler call, but
-                * I don't think we want to call that on a shared
-                * state.  */
-               proc->event_handler = NULL;
-               if (event->proc->state == STATE_ATTACHED && options.follow)
-                       proc->state = STATE_ATTACHED;
-               else
-                       proc->state = STATE_IGNORED;
-               continue_process(proc->pid);
-       } else {
+       } else if (proc != NULL) {
                proc->state = STATE_BEING_CREATED;
        }
 
-       if (event->type == EVENT_VFORK)
+       if (event->type != EVENT_VFORK)
+               continue_process(event->proc->pid);
+       else if (proc != NULL)
                continue_after_vfork(proc);
        else
-               continue_process(event->proc->pid);
+               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 {
@@ -317,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"
        };
@@ -331,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 {
@@ -341,53 +358,56 @@ 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);
@@ -399,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);
@@ -406,6 +504,8 @@ handle_exit(Event *event) {
                output_line(event->proc, "+++ exited (status %d) +++",
                                event->e_un.ret_val);
        }
+
+       account_current_callstack(event->proc);
        remove_process(event->proc);
 }
 
@@ -416,35 +516,49 @@ handle_exit_signal(Event *event) {
                output_line(event->proc, "+++ killed by %s +++",
                                shortsignal(event->proc, event->e_un.signum));
        }
+
+       account_current_callstack(event->proc);
        remove_process(event->proc);
 }
 
 static void
-output_syscall(struct Process *proc, const char *name, enum tof tof,
-              void (*output)(enum tof, struct Process *,
-                             struct library_symbol *))
+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 (library_symbol_init(&syscall, 0, name, 0, LS_TOPLT_NONE) >= 0) {
-               (*output)(tof, proc, &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);
+               }
+
                library_symbol_destroy(&syscall);
        }
 }
 
 static void
-output_syscall_left(struct Process *proc, const char *name)
+output_syscall_left(struct process *proc, const char *name)
 {
-       output_syscall(proc, name, LT_TOF_SYSCALL, &output_left);
+       output_syscall(proc, name, LT_TOF_SYSCALL, true, NULL);
 }
 
 static void
-output_syscall_right(struct Process *proc, const char *name)
+output_syscall_right(struct process *proc, const char *name,
+                    struct timedelta *spent)
 {
-       output_syscall(proc, name, LT_TOF_SYSCALLR, &output_right);
+       output_syscall(proc, name, LT_TOF_SYSCALLR, false, spent);
 }
 
 static void
-handle_syscall(Event *event) {
+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) {
                callstack_push_syscall(event->proc, event->e_un.sysnum);
@@ -457,8 +571,9 @@ handle_syscall(Event *event) {
 }
 
 static void
-handle_exec(Event * event) {
-       Process * proc = event->proc;
+handle_exec(Event *event)
+{
+       struct process *proc = event->proc;
 
        /* Save the PID so that we can use it after unsuccessful
         * process_exec.  */
@@ -473,24 +588,15 @@ handle_exec(Event * event) {
        }
        output_line(proc, "--- Called exec() ---");
 
+       account_current_callstack(proc);
+
        if (process_exec(proc) < 0) {
                fprintf(stderr,
                        "couldn't reinitialize process %d after exec\n", pid);
                goto untrace;
        }
 
-       continue_process(proc->pid);
-
-       /* After the exec, we expect to hit the first executable
-        * instruction.
-        *
-        * XXX TODO It would be nice to have this removed, but then we
-        * need to do that also for initial call to wait_for_proc in
-        * execute_program.  In that case we could generate a
-        * EVENT_FIRST event or something, or maybe this could somehow
-        * be rolled into EVENT_NEW.  */
-       wait_for_proc(proc->pid);
-       continue_process(proc->pid);
+       continue_after_exec(proc);
 }
 
 static void
@@ -507,77 +613,62 @@ handle_arch_syscall(Event *event) {
        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;
-
-       debug(DEBUG_FUNCTION, "calc_time_spent(pid=%d)", proc->pid);
-       elem = &proc->callstack[proc->callstack_depth - 1];
+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);
 
-       gettimeofday(&tv, &tz);
+       unsigned d = event->proc->callstack_depth;
+       assert(d > 0);
+       struct callstack_element *elem = &event->proc->callstack[d - 1];
+       assert(elem->is_syscall);
 
-       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;
-       }
-       current_time_spent = diff;
-}
-
-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);
-               }
+               struct timedelta spent = calc_time_spent(elem->enter_time);
                if (options.syscalls)
                        output_syscall_right(event->proc,
-                                            sysname(event->proc,
-                                                    event->e_un.sysnum));
+                                            name_cb(event->proc,
+                                                    event->e_un.sysnum),
+                                            &spent);
 
-               assert(event->proc->callstack_depth > 0);
-               unsigned d = event->proc->callstack_depth - 1;
-               assert(event->proc->callstack[d].is_syscall);
                callstack_pop(event->proc);
        }
        continue_after_syscall(event->proc, event->e_un.sysnum, 1);
 }
 
 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);
-               }
-               if (options.syscalls)
-                       output_syscall_right(event->proc,
-                                            arch_sysname(event->proc,
-                                                         event->e_un.sysnum));
-               callstack_pop(event->proc);
-       }
-       continue_process(event->proc->pid);
+handle_sysret(Event *event)
+{
+       handle_x_sysret(event, &sysname);
+}
+
+static void
+handle_arch_sysret(Event *event)
+{
+       handle_x_sysret(event, &arch_sysname);
 }
 
 static void
-output_right_tos(struct Process *proc)
+output_right_tos(struct process *proc)
 {
        size_t d = proc->callstack_depth;
+       assert(d > 0);
        struct callstack_element *elem = &proc->callstack[d - 1];
-       if (proc->state != STATE_IGNORED)
-               output_right(LT_TOF_FUNCTIONR, proc, elem->c_un.libfunc);
+       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);
+       }
 }
 
 #ifndef ARCH_HAVE_SYMBOL_RET
-void arch_symbol_ret(struct Process *proc, struct library_symbol *libsym)
+void arch_symbol_ret(struct process *proc, struct library_symbol *libsym)
 {
 }
 #endif
@@ -587,7 +678,7 @@ handle_breakpoint(Event *event)
 {
        int i, j;
        struct breakpoint *sbp;
-       Process *leader = event->proc->leader;
+       struct process *leader = event->proc->leader;
        void *brk_addr = event->e_un.brk_addr;
 
        /* The leader has terminated.  */
@@ -602,15 +693,8 @@ handle_breakpoint(Event *event)
 
        for (i = event->proc->callstack_depth - 1; i >= 0; i--) {
                if (brk_addr == event->proc->callstack[i].return_addr) {
-                       for (j = event->proc->callstack_depth - 1; j > i; j--) {
+                       for (j = event->proc->callstack_depth - 1; j > i; j--)
                                callstack_pop(event->proc);
-                       }
-                       if (event->proc->state != STATE_IGNORED) {
-                               if (opt_T || options.summary) {
-                                       calc_time_spent(event->proc);
-                               }
-                       }
-                       event->proc->return_addr = brk_addr;
 
                        struct library_symbol *libsym =
                            event->proc->callstack[i].c_un.libfunc;
@@ -663,13 +747,14 @@ handle_breakpoint(Event *event)
        /* 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);
-                       callstack_push_symfunc(event->proc, sbp->libsym);
-                       output_left(LT_TOF_FUNCTION, event->proc, sbp->libsym);
+                       callstack_push_symfunc(event->proc, sbp);
+                       if (! options.summary)
+                               output_left(LT_TOF_FUNCTION, event->proc,
+                                           sbp->libsym);
                }
 
                breakpoint_on_continue(sbp, event->proc);
@@ -682,7 +767,8 @@ handle_breakpoint(Event *event)
 }
 
 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);
@@ -702,15 +788,17 @@ 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, "%s: Error: call nesting too deep!\n", __func__);
@@ -721,28 +809,43 @@ callstack_push_symfunc(Process *proc, struct library_symbol *sym) {
        elem = &proc->callstack[proc->callstack_depth++];
        *elem = (struct callstack_element){};
        elem->is_syscall = 0;
-       elem->c_un.libfunc = sym;
+       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;
+               }
+       }
 
-       elem->return_addr = proc->return_addr;
-       if (elem->return_addr)
-               insert_breakpoint(proc, elem->return_addr, NULL);
+       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);
        }
 }
 
 void
-callstack_pop(struct Process *proc)
+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);
+       if (!elem->is_syscall && 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);