X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=breakpoints.c;h=c3fa275ff41a7661ab2e1b5a0a7040829f153e35;hb=63099a2916d2f281aadc6304583bda09909f2a53;hp=ba3b060cfbefdbdda0de95d112dc08030695f998;hpb=59749d048d9e452f049f9151735b5256756919c3;p=platform%2Fupstream%2Fltrace.git diff --git a/breakpoints.c b/breakpoints.c index ba3b060..c3fa275 100644 --- a/breakpoints.c +++ b/breakpoints.c @@ -1,230 +1,486 @@ +/* + * 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 +#include +#include #include #include -#include #ifdef __powerpc__ #include #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; }