Add some stuff to libc.so.conf
[platform/upstream/ltrace.git] / breakpoints.c
index ba3b060..c3fa275 100644 (file)
+/*
+ * This file is part of ltrace.
+ * Copyright (C) 2006,2007,2011,2012,2013,2014 Petr Machata, Red Hat Inc.
+ * Copyright (C) 2009 Juan Cespedes
+ * Copyright (C) 1998,2001,2002,2003,2007,2008,2009 Juan Cespedes
+ * Copyright (C) 2006 Ian Wienand
+ *
+ * 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"
 
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <assert.h>
 
 #ifdef __powerpc__
 #include <sys/ptrace.h>
 #endif
 
-#include "common.h"
+#include "backend.h"
+#include "breakpoint.h"
+#include "debug.h"
+#include "library.h"
+#include "ltrace-elf.h"
+#include "proc.h"
+
+#ifndef ARCH_HAVE_TRANSLATE_ADDRESS
+int
+arch_translate_address_dyn(struct process *proc,
+                      arch_addr_t addr, arch_addr_t *ret)
+{
+       *ret = addr;
+       return 0;
+}
+
+struct ltelf;
+int
+arch_translate_address(struct ltelf *lte,
+                      arch_addr_t addr, arch_addr_t *ret)
+{
+       *ret = addr;
+       return 0;
+}
+#endif
+
+void
+breakpoint_on_hit(struct breakpoint *bp, struct process *proc)
+{
+       assert(bp != NULL);
+       if (bp->cbs != NULL && bp->cbs->on_hit != NULL)
+               (bp->cbs->on_hit)(bp, proc);
+}
+
+void
+breakpoint_on_continue(struct breakpoint *bp, struct process *proc)
+{
+       assert(bp != NULL);
+       if (bp->cbs != NULL && bp->cbs->on_continue != NULL)
+               (bp->cbs->on_continue)(bp, proc);
+       else
+               continue_after_breakpoint(proc, bp);
+}
+
+void
+breakpoint_on_retract(struct breakpoint *bp, struct process *proc)
+{
+       assert(bp != NULL);
+       if (bp->cbs != NULL && bp->cbs->on_retract != NULL)
+               (bp->cbs->on_retract)(bp, proc);
+}
+
+void
+breakpoint_on_install(struct breakpoint *bp, struct process *proc)
+{
+       assert(bp != NULL);
+       if (bp->cbs != NULL && bp->cbs->on_install != NULL)
+               (bp->cbs->on_install)(bp, proc);
+}
+
+int
+breakpoint_get_return_bp(struct breakpoint **ret,
+                        struct breakpoint *bp, struct process *proc)
+{
+       assert(bp != NULL);
+       if (bp->cbs != NULL && bp->cbs->get_return_bp != NULL)
+               return (bp->cbs->get_return_bp)(ret, bp, proc);
+
+       if ((*ret = create_default_return_bp(proc)) == NULL)
+               return -1;
+
+       return 0;
+}
 
 /*****************************************************************************/
 
-Breakpoint *
-address2bpstruct(Process *proc, void *addr) {
+struct breakpoint *
+address2bpstruct(struct process *proc, arch_addr_t addr)
+{
+       assert(proc != NULL);
+       assert(proc->breakpoints != NULL);
+       assert(proc->leader == proc);
        debug(DEBUG_FUNCTION, "address2bpstruct(pid=%d, addr=%p)", proc->pid, addr);
-       return dict_find_entry(proc->breakpoints, addr);
+
+       struct breakpoint *found;
+       if (DICT_FIND_VAL(proc->breakpoints, &addr, &found) < 0)
+               return NULL;
+       return found;
+}
+
+#ifndef OS_HAVE_BREAKPOINT_DATA
+int
+os_breakpoint_init(struct process *proc, struct breakpoint *sbp)
+{
+       return 0;
 }
 
 void
-insert_breakpoint(Process *proc, void *addr,
-                 struct library_symbol *libsym) {
-       Breakpoint *sbp;
+os_breakpoint_destroy(struct breakpoint *sbp)
+{
+}
 
-       debug(DEBUG_FUNCTION, "insert_breakpoint(pid=%d, addr=%p, symbol=%s)", proc->pid, addr, libsym ? libsym->name : "NULL");
-       debug(1, "symbol=%s, addr=%p", libsym?libsym->name:"(nil)", addr);
+int
+os_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp)
+{
+       return 0;
+}
+#endif
 
-       if (!addr)
-               return;
+#ifndef ARCH_HAVE_BREAKPOINT_DATA
+int
+arch_breakpoint_init(struct process *proc, struct breakpoint *sbp)
+{
+       return 0;
+}
 
-       if (libsym)
-               libsym->needs_init = 0;
+void
+arch_breakpoint_destroy(struct breakpoint *sbp)
+{
+}
 
-       sbp = dict_find_entry(proc->breakpoints, addr);
-       if (!sbp) {
-               sbp = calloc(1, sizeof(Breakpoint));
-               if (!sbp) {
-                       return; /* TODO FIXME XXX: error_mem */
-               }
-               dict_enter(proc->breakpoints, addr, sbp);
-               sbp->addr = addr;
-               sbp->libsym = libsym;
-       }
-#ifdef __arm__
-       sbp->thumb_mode = proc->thumb_mode;
-       proc->thumb_mode = 0;
+int
+arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp)
+{
+       return 0;
+}
 #endif
-       sbp->enabled++;
-       if (sbp->enabled == 1 && proc->pid)
-               enable_breakpoint(proc->pid, sbp);
+
+static void
+breakpoint_init_base(struct breakpoint *bp,
+                    arch_addr_t addr, struct library_symbol *libsym)
+{
+       bp->cbs = NULL;
+       bp->addr = addr;
+       memset(bp->orig_value, 0, sizeof(bp->orig_value));
+       bp->enabled = 0;
+       bp->libsym = libsym;
 }
 
-void
-delete_breakpoint(Process *proc, void *addr) {
-       Breakpoint *sbp;
+/* On second thought, I don't think we need PROC.  All the translation
+ * (arch_translate_address in particular) should be doable using
+ * static lookups of various sections in the ELF file.  We shouldn't
+ * need process for anything.  */
+int
+breakpoint_init(struct breakpoint *bp, struct process *proc,
+               arch_addr_t addr, struct library_symbol *libsym)
+{
+       breakpoint_init_base(bp, addr, libsym);
+       if (os_breakpoint_init(proc, bp) < 0)
+               return -1;
+       if (arch_breakpoint_init(proc, bp) < 0) {
+               os_breakpoint_destroy(bp);
+               return -1;
+       }
+       return 0;
+}
 
-       debug(DEBUG_FUNCTION, "delete_breakpoint(pid=%d, addr=%p)", proc->pid, addr);
+void
+breakpoint_set_callbacks(struct breakpoint *bp, struct bp_callbacks *cbs)
+{
+       if (bp->cbs != NULL)
+               assert(bp->cbs == NULL);
+       bp->cbs = cbs;
+}
 
-       sbp = dict_find_entry(proc->breakpoints, addr);
-       assert(sbp);            /* FIXME: remove after debugging has been done. */
-       /* This should only happen on out-of-memory conditions. */
-       if (sbp == NULL)
+void
+breakpoint_destroy(struct breakpoint *bp)
+{
+       if (bp == NULL)
                return;
+       arch_breakpoint_destroy(bp);
+       os_breakpoint_destroy(bp);
+}
 
-       sbp->enabled--;
-       if (sbp->enabled == 0)
-               disable_breakpoint(proc->pid, sbp);
-       assert(sbp->enabled >= 0);
+int
+breakpoint_clone(struct breakpoint *retp, struct process *new_proc,
+                struct breakpoint *bp)
+{
+       struct library_symbol *libsym = NULL;
+       if (bp->libsym != NULL) {
+               int rc = proc_find_symbol(new_proc, bp->libsym, NULL, &libsym);
+               assert(rc == 0);
+       }
+
+       breakpoint_init_base(retp, bp->addr, libsym);
+       memcpy(retp->orig_value, bp->orig_value, sizeof(bp->orig_value));
+       retp->enabled = bp->enabled;
+       if (os_breakpoint_clone(retp, bp) < 0)
+               return -1;
+       if (arch_breakpoint_clone(retp, bp) < 0) {
+               os_breakpoint_destroy(retp);
+               return -1;
+       }
+       breakpoint_set_callbacks(retp, bp->cbs);
+       return 0;
 }
 
-static void
-enable_bp_cb(void *addr, void *sbp, void *proc) {
-       debug(DEBUG_FUNCTION, "enable_bp_cb(pid=%d)", ((Process *)proc)->pid);
-       if (((Breakpoint *)sbp)->enabled) {
-               enable_breakpoint(((Process *)proc)->pid, sbp);
+int
+breakpoint_turn_on(struct breakpoint *bp, struct process *proc)
+{
+       bp->enabled++;
+       if (bp->enabled == 1) {
+               assert(proc->pid != 0);
+               enable_breakpoint(proc, bp);
+               breakpoint_on_install(bp, proc);
+       }
+       return 0;
+}
+
+int
+breakpoint_turn_off(struct breakpoint *bp, struct process *proc)
+{
+       bp->enabled--;
+       if (bp->enabled == 0)
+               disable_breakpoint(proc, bp);
+       assert(bp->enabled >= 0);
+       return 0;
+}
+
+struct breakpoint *
+create_default_return_bp(struct process *proc)
+{
+       struct breakpoint *bp = malloc(sizeof *bp);
+       arch_addr_t return_addr = get_return_addr(proc, proc->stack_pointer);
+       if (return_addr == 0 || bp == NULL
+           || breakpoint_init(bp, proc, return_addr, NULL) < 0) {
+               free(bp);
+               return NULL;
        }
+       return bp;
+}
+
+struct breakpoint *
+insert_breakpoint_at(struct process *proc, arch_addr_t addr,
+                    struct library_symbol *libsym)
+{
+       debug(DEBUG_FUNCTION,
+             "insert_breakpoint_at(pid=%d, addr=%p, symbol=%s)",
+             proc->pid, addr, libsym ? libsym->name : "NULL");
+
+       assert(addr != 0);
+
+       struct breakpoint *bp = malloc(sizeof *bp);
+       if (bp == NULL || breakpoint_init(bp, proc, addr, libsym) < 0) {
+               free(bp);
+               return NULL;
+       }
+
+       /* N.B. (and XXX): BP->addr might differ from ADDR.  On ARM
+        * this is a real possibility.  The problem here is that to
+        * create a return breakpoint ltrace calls get_return_addr and
+        * then insert_breakpoint_at.  So get_return_addr needs to
+        * encode all the information necessary for breakpoint_init
+        * into the address itself, so ADDR is potentially
+        * mangled.  */
+
+       struct breakpoint *tmp = insert_breakpoint(proc, bp);
+       if (tmp != bp) {
+               breakpoint_destroy(bp);
+               free(bp);
+       }
+       return tmp;
+}
+
+struct breakpoint *
+insert_breakpoint(struct process *proc, struct breakpoint *bp)
+{
+       /* Only the group leader should be getting the breakpoints and
+        * thus have ->breakpoint initialized.  */
+       struct process *leader = proc->leader;
+       assert(leader != NULL);
+       assert(leader->breakpoints != NULL);
+
+       /* XXX what we need to do instead is have a list of
+        * breakpoints that are enabled at this address.  The
+        * following works if every breakpoint is the same and there's
+        * no extra data, but that doesn't hold anymore.  For now it
+        * will suffice, about the only realistic case where we need
+        * to have more than one breakpoint per address is return from
+        * a recursive library call.  */
+       struct breakpoint *ext_bp = bp;
+       if (DICT_FIND_VAL(leader->breakpoints, &bp->addr, &ext_bp) != 0) {
+               if (proc_add_breakpoint(leader, bp) < 0)
+                       return NULL;
+               ext_bp = bp;
+       }
+
+       if (breakpoint_turn_on(ext_bp, proc) < 0) {
+               if (ext_bp != bp)
+                       proc_remove_breakpoint(leader, bp);
+               return NULL;
+       }
+
+       return ext_bp;
 }
 
 void
-enable_all_breakpoints(Process *proc) {
-       debug(DEBUG_FUNCTION, "enable_all_breakpoints(pid=%d)", proc->pid);
-       if (proc->breakpoints_enabled <= 0) {
-#ifdef __powerpc__
-               unsigned long a;
-
-               /*
-                * PPC HACK! (XXX FIXME TODO)
-                * If the dynamic linker hasn't populated the PLT then
-                * dont enable the breakpoints
-                */
-               if (options.libcalls) {
-                       a = ptrace(PTRACE_PEEKTEXT, proc->pid,
-                                  sym2addr(proc, proc->list_of_symbols),
-                                  0);
-                       if (a == 0x0)
-                               return;
-               }
-#endif
+delete_breakpoint_at(struct process *proc, arch_addr_t addr)
+{
+       debug(DEBUG_FUNCTION, "delete_breakpoint_at(pid=%d, addr=%p)",
+             proc->pid, addr);
 
-               debug(1, "Enabling breakpoints for pid %u...", proc->pid);
-               if (proc->breakpoints) {
-                       dict_apply_to_all(proc->breakpoints, enable_bp_cb,
-                                         proc);
-               }
-#ifdef __mips__
-               {
-                       /*
-                        * I'm sure there is a nicer way to do this. We need to
-                        * insert breakpoints _after_ the child has been started.
-                        */
-                       struct library_symbol *sym;
-                       struct library_symbol *new_sym;
-                       sym=proc->list_of_symbols;
-                       while(sym){
-                               void *addr= sym2addr(proc,sym);
-                               if(!addr){
-                                       sym=sym->next;
-                                       continue;
-                               }
-                               if(dict_find_entry(proc->breakpoints,addr)){
-                                       sym=sym->next;
-                                       continue;
-                               }
-                               debug(2,"inserting bp %p %s",addr,sym->name);
-                               new_sym=malloc(sizeof(*new_sym));
-                               memcpy(new_sym,sym,sizeof(*new_sym));
-                               new_sym->next=proc->list_of_symbols;
-                               proc->list_of_symbols=new_sym;
-                               insert_breakpoint(proc, addr, new_sym);
-                               sym=sym->next;
-                       }
-               }
-#endif
+       struct process *leader = proc->leader;
+       assert(leader != NULL);
+
+       struct breakpoint *bp = NULL;
+       DICT_FIND_VAL(leader->breakpoints, &addr, &bp);
+       assert(bp != NULL);
+
+       if (delete_breakpoint(proc, bp) < 0) {
+               fprintf(stderr, "Couldn't turn off the breakpoint %s@%p\n",
+                       breakpoint_name(bp), bp->addr);
        }
-       proc->breakpoints_enabled = 1;
 }
 
-static void
-disable_bp_cb(void *addr, void *sbp, void *proc) {
-       debug(DEBUG_FUNCTION, "disable_bp_cb(pid=%d)", ((Process *)proc)->pid);
-       if (((Breakpoint *)sbp)->enabled) {
-               disable_breakpoint(((Process *)proc)->pid, sbp);
+int
+delete_breakpoint(struct process *proc, struct breakpoint *bp)
+{
+       struct process *leader = proc->leader;
+       assert(leader != NULL);
+
+       if (breakpoint_turn_off(bp, proc) < 0)
+               return -1;
+
+       if (bp->enabled == 0) {
+               proc_remove_breakpoint(leader, bp);
+               breakpoint_destroy(bp);
+               free(bp);
        }
+
+       return 0;
+}
+
+const char *
+breakpoint_name(const struct breakpoint *bp)
+{
+       assert(bp != NULL);
+       return bp->libsym != NULL ? bp->libsym->name : NULL;
+}
+
+struct library *
+breakpoint_library(const struct breakpoint *bp)
+{
+       assert(bp != NULL);
+       return bp->libsym != NULL ? bp->libsym->lib : NULL;
+}
+
+static enum callback_status
+disable_bp_cb(arch_addr_t *addr, struct breakpoint **bpp, void *data)
+{
+       struct process *proc = data;
+       debug(DEBUG_FUNCTION, "disable_bp_cb(pid=%d)", proc->pid);
+       if ((*bpp)->enabled)
+               disable_breakpoint(proc, *bpp);
+       return CBS_CONT;
 }
 
 void
-disable_all_breakpoints(Process *proc) {
+disable_all_breakpoints(struct process *proc)
+{
        debug(DEBUG_FUNCTION, "disable_all_breakpoints(pid=%d)", proc->pid);
-       if (proc->breakpoints_enabled) {
-               debug(1, "Disabling breakpoints for pid %u...", proc->pid);
-               dict_apply_to_all(proc->breakpoints, disable_bp_cb, proc);
-       }
-       proc->breakpoints_enabled = 0;
+       assert(proc->leader == proc);
+       DICT_EACH(proc->breakpoints, arch_addr_t, struct breakpoint *,
+                 NULL, disable_bp_cb, proc);
 }
 
 static void
-free_bp_cb(void *addr, void *sbp, void *data) {
-       debug(DEBUG_FUNCTION, "free_bp_cb(sbp=%p)", sbp);
-       assert(sbp);
-       free(sbp);
+entry_breakpoint_on_hit(struct breakpoint *bp, struct process *proc)
+{
+       if (proc == NULL || proc->leader == NULL)
+               return;
+       delete_breakpoint_at(proc, bp->addr);
+       process_hit_start(proc);
 }
 
-void
-breakpoints_init(Process *proc) {
-       struct library_symbol *sym;
+int
+entry_breakpoint_init(struct process *proc,
+                     struct breakpoint *bp, arch_addr_t addr,
+                     struct library *lib)
+{
+       assert(addr != 0);
+       int err = breakpoint_init(bp, proc, addr, NULL);
+       if (err < 0)
+               return err;
+
+       static struct bp_callbacks entry_callbacks = {
+               .on_hit = entry_breakpoint_on_hit,
+       };
+       bp->cbs = &entry_callbacks;
+       return 0;
+}
 
+int
+breakpoints_init(struct process *proc)
+{
        debug(DEBUG_FUNCTION, "breakpoints_init(pid=%d)", proc->pid);
-       if (proc->breakpoints) {        /* let's remove that struct */
-               dict_apply_to_all(proc->breakpoints, free_bp_cb, NULL);
-               dict_clear(proc->breakpoints);
-               proc->breakpoints = NULL;
-       }
-       proc->breakpoints = dict_init(dict_key2hash_int, dict_key_cmp_int);
-
-       if (options.libcalls && proc->filename) {
-               /* FIXME: memory leak when called by exec(): */
-               proc->list_of_symbols = read_elf(proc);
-               if (opt_e) {
-                       struct library_symbol **tmp1 = &(proc->list_of_symbols);
-                       while (*tmp1) {
-                               struct opt_e_t *tmp2 = opt_e;
-                               int keep = !opt_e_enable;
-
-                               while (tmp2) {
-                                       if (!strcmp((*tmp1)->name, tmp2->name)) {
-                                               keep = opt_e_enable;
-                                       }
-                                       tmp2 = tmp2->next;
-                               }
-                               if (!keep) {
-                                       *tmp1 = (*tmp1)->next;
-                               } else {
-                                       tmp1 = &((*tmp1)->next);
-                               }
-                       }
+
+       /* XXX breakpoint dictionary should be initialized
+        * outside.  Here we just put in breakpoints.  */
+       assert(proc->breakpoints != NULL);
+
+       /* Only the thread group leader should hold the breakpoints.  */
+       assert(proc->leader == proc);
+
+       /* N.B. the following used to be conditional on this, and
+        * maybe it still needs to be.  */
+       assert(proc->filename != NULL);
+
+       struct library *lib = ltelf_read_main_binary(proc, proc->filename);
+       struct breakpoint *entry_bp = NULL;
+       int bp_state = 0;
+       int result = -1;
+       switch ((int)(lib != NULL)) {
+       fail:
+               switch (bp_state) {
+               case 2:
+                       proc_remove_library(proc, lib);
+                       proc_remove_breakpoint(proc, entry_bp);
+               case 1:
+                       breakpoint_destroy(entry_bp);
                }
-       } else {
-               proc->list_of_symbols = NULL;
+               library_destroy(lib);
+               free(entry_bp);
+       case 0:
+               return result;
        }
-       for (sym = proc->list_of_symbols; sym; sym = sym->next) {
-               /* proc->pid==0 delays enabling. */
-               insert_breakpoint(proc, sym2addr(proc, sym), sym);
-       }
-       proc->callstack_depth = 0;
-       proc->breakpoints_enabled = -1;
-}
 
-void
-reinitialize_breakpoints(Process *proc) {
-       struct library_symbol *sym;
-
-       debug(DEBUG_FUNCTION, "reinitialize_breakpoints(pid=%d)", proc->pid);
-
-       sym = proc->list_of_symbols;
-
-       while (sym) {
-               if (sym->needs_init) {
-                       insert_breakpoint(proc, sym2addr(proc, sym),
-                                         sym);
-                       if (sym->needs_init && !sym->is_weak) {
-                               fprintf(stderr,
-                                       "could not re-initialize breakpoint for \"%s\" in file \"%s\"\n",
-                                       sym->name, proc->filename);
-                               exit(1);
-                       }
-               }
-               sym = sym->next;
+       entry_bp = malloc(sizeof(*entry_bp));
+       if (entry_bp == NULL
+           || (entry_breakpoint_init(proc, entry_bp,
+                                     lib->entry, lib)) < 0) {
+               fprintf(stderr,
+                       "Couldn't initialize entry breakpoint for PID %d.\n"
+                       "Some tracing events may be missed.\n", proc->pid);
+               free(entry_bp);
+
+       } else {
+               ++bp_state;
+
+               if ((result = proc_add_breakpoint(proc, entry_bp)) < 0)
+                       goto fail;
+               ++bp_state;
+
+               if ((result = breakpoint_turn_on(entry_bp, proc)) < 0)
+                       goto fail;
        }
+       proc_add_library(proc, lib);
+
+       proc->callstack_depth = 0;
+       return 0;
 }