| /* |
| * 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> |
| |
| #ifdef __powerpc__ |
| #include <sys/ptrace.h> |
| #endif |
| |
| #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; |
| } |
| |
| /*****************************************************************************/ |
| |
| 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); |
| |
| 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 |
| os_breakpoint_destroy(struct breakpoint *sbp) |
| { |
| } |
| |
| int |
| os_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp) |
| { |
| return 0; |
| } |
| #endif |
| |
| #ifndef ARCH_HAVE_BREAKPOINT_DATA |
| int |
| arch_breakpoint_init(struct process *proc, struct breakpoint *sbp) |
| { |
| return 0; |
| } |
| |
| void |
| arch_breakpoint_destroy(struct breakpoint *sbp) |
| { |
| } |
| |
| int |
| arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp) |
| { |
| return 0; |
| } |
| #endif |
| |
| 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; |
| } |
| |
| /* 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; |
| } |
| |
| void |
| breakpoint_set_callbacks(struct breakpoint *bp, struct bp_callbacks *cbs) |
| { |
| if (bp->cbs != NULL) |
| assert(bp->cbs == NULL); |
| bp->cbs = cbs; |
| } |
| |
| void |
| breakpoint_destroy(struct breakpoint *bp) |
| { |
| if (bp == NULL) |
| return; |
| arch_breakpoint_destroy(bp); |
| os_breakpoint_destroy(bp); |
| } |
| |
| 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; |
| } |
| |
| 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 |
| delete_breakpoint_at(struct process *proc, arch_addr_t addr) |
| { |
| debug(DEBUG_FUNCTION, "delete_breakpoint_at(pid=%d, addr=%p)", |
| proc->pid, addr); |
| |
| 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); |
| } |
| } |
| |
| 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(struct process *proc) |
| { |
| debug(DEBUG_FUNCTION, "disable_all_breakpoints(pid=%d)", proc->pid); |
| assert(proc->leader == proc); |
| DICT_EACH(proc->breakpoints, arch_addr_t, struct breakpoint *, |
| NULL, disable_bp_cb, proc); |
| } |
| |
| static void |
| 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); |
| } |
| |
| 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); |
| |
| /* 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); |
| } |
| library_destroy(lib); |
| free(entry_bp); |
| case 0: |
| return result; |
| } |
| |
| 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; |
| } |