diff options
Diffstat (limited to 'usr.bin/pmdb/break.c')
-rw-r--r-- | usr.bin/pmdb/break.c | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/usr.bin/pmdb/break.c b/usr.bin/pmdb/break.c new file mode 100644 index 00000000000..e7eaa61077a --- /dev/null +++ b/usr.bin/pmdb/break.c @@ -0,0 +1,301 @@ +/* $PMDB: break.c,v 1.7 2002/03/12 11:28:28 art Exp $ */ +/* + * Copyright (c) 2002 Artur Grabowski <art@openbsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ptrace.h> +#include <sys/wait.h> + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <signal.h> +#include <err.h> +#include <errno.h> +#include <string.h> + +#include "pmdb.h" +#include "symbol.h" +#include "pmdb_machdep.h" +#include "break.h" + +struct callback { + TAILQ_ENTRY(callback) cb_list; + int (*cb_fun)(struct pstate *, void *); + void *cb_arg; +}; + +struct breakpoint { + TAILQ_ENTRY(breakpoint) bkpt_list; + TAILQ_HEAD(,callback) bkpt_cbs; /* list of all callbacks */ + char bkpt_old[BREAKPOINT_LEN]; /* old contents at bkpt */ + reg bkpt_pc; +}; + +static char bkpt_insn[BREAKPOINT_LEN] = BREAKPOINT; + +/* + * Find a breakpoint at this address. + */ +struct breakpoint * +bkpt_find_at_pc(struct pstate *ps, reg pc) +{ + struct breakpoint *bkpt; + + TAILQ_FOREACH(bkpt, &ps->ps_bkpts, bkpt_list) + if (bkpt->bkpt_pc == pc) + break; + + return (bkpt); +} + +/* + * Enable this breakpoint. + */ +static int +bkpt_enable(struct pstate *ps, struct breakpoint *bkpt) +{ + reg pc = bkpt->bkpt_pc; + + if (read_from_pid(ps->ps_pid, pc, &bkpt->bkpt_old, BREAKPOINT_LEN)) { + warn("Can't read process contents at 0x%lx", pc); + return (-1); + } + if (write_to_pid(ps->ps_pid, pc, &bkpt_insn, BREAKPOINT_LEN)) { + warn("Can't write breakpoint at 0x%lx, attempting backout.", pc); + if (write_to_pid(ps->ps_pid, pc, &bkpt->bkpt_old, + BREAKPOINT_LEN)) + warn("Backout failed, process unstable"); + return (-1); + } + return (0); +} + +/* + * Create a new breakpoint and enable it. + */ +int +bkpt_add_cb(struct pstate *ps, reg pc, int (*fun)(struct pstate *, void *), + void *arg) +{ + struct breakpoint *bkpt; + struct callback *cb; + + if ((bkpt = bkpt_find_at_pc(ps, pc)) == NULL) { + bkpt = emalloc(sizeof(*bkpt)); + TAILQ_INIT(&bkpt->bkpt_cbs); + TAILQ_INSERT_TAIL(&ps->ps_bkpts, bkpt, bkpt_list); + bkpt->bkpt_pc = pc; + if (bkpt_enable(ps, bkpt)) { + free(bkpt); + return (-1); + } + } + + cb = emalloc(sizeof(*cb)); + cb->cb_fun = fun; + cb->cb_arg = arg; + TAILQ_INSERT_TAIL(&bkpt->bkpt_cbs, cb, cb_list); + + return (0); +} + +/* + * Disable and delete a breakpoint. + */ +void +bkpt_delete(struct pstate *ps, struct breakpoint *bkpt) +{ + TAILQ_REMOVE(&ps->ps_bkpts, bkpt, bkpt_list); + + if (write_to_pid(ps->ps_pid, bkpt->bkpt_pc, &bkpt->bkpt_old, + BREAKPOINT_LEN)) + warn("Breakpoint removal failed, process unstable"); + + free(bkpt); +} + +/* + * Normal standard breakpoint. Keep it. + */ +static int +bkpt_normal(struct pstate *ps, void *arg) +{ + return (BKPT_KEEP_STOP); +} + +/* + * Single-step callback for "stepping over" a breakpoint (we restore the + * breakpoint instruction to what it was, single-step over it and then + * call this function). + */ +static int +sstep_bkpt_readd(struct pstate *ps, void *arg) +{ + reg pc = (reg)arg; + + bkpt_add_cb(ps, pc, bkpt_normal, NULL); + + return (0); /* let the process continue */ +} + +/* + * Return 0 for stop, 1 for silent continue. + */ +int +bkpt_check(struct pstate *ps) +{ + struct breakpoint *bkpt; + struct callback *cb; + TAILQ_HEAD(,callback) sstep_cbs; + reg *rg, pc; + int ret; + int didsome = 0; + int stop = 0; + + /* Requeue all single-step callbacks because bkpts can add ssteps. */ + TAILQ_INIT(&sstep_cbs); + while ((cb = TAILQ_FIRST(&ps->ps_sstep_cbs)) != NULL) { + TAILQ_REMOVE(&ps->ps_sstep_cbs, cb, cb_list); + TAILQ_INSERT_TAIL(&sstep_cbs, cb, cb_list); + } + + /* + * The default is to stop. Unless we do some processing and none + * of the callbacks require a stop. + */ + rg = alloca(sizeof(*rg) * md_def.nregs); + if (md_getregs(ps, rg)) + err(1, "bkpt_check: Can't get registers."); + + pc = rg[md_def.pcoff]; + pc -= BREAKPOINT_DECR_PC; + + bkpt = bkpt_find_at_pc(ps, pc); + if (bkpt == NULL) + goto sstep; + + ps->ps_npc = pc; + + while ((cb = TAILQ_FIRST(&bkpt->bkpt_cbs)) != NULL) { + didsome = 1; + TAILQ_REMOVE(&bkpt->bkpt_cbs, cb, cb_list); + ret = (*cb->cb_fun)(ps, cb->cb_arg); + free(cb); + switch (ret) { + case BKPT_DEL_STOP: + stop = 1; + case BKPT_DEL_CONT: + break; + case BKPT_KEEP_STOP: + stop = 1; + case BKPT_KEEP_CONT: + sstep_set(ps, sstep_bkpt_readd, (void *)bkpt->bkpt_pc); + break; + default: + errx(1, "unkonwn bkpt_fun return, internal error"); + } + } + + bkpt_delete(ps, bkpt); + +sstep: + + while ((cb = TAILQ_FIRST(&sstep_cbs)) != NULL) { + didsome = 1; + TAILQ_REMOVE(&sstep_cbs, cb, cb_list); + stop |= (*cb->cb_fun)(ps, cb->cb_arg); + free(cb); + } + ps->ps_flags &= ~PSF_STEP; + + return (didsome && !stop); +} + +int +cmd_bkpt_add(int argc, char **argv, void *arg) +{ + struct pstate *ps = arg; + char *ep, *bkpt_name; + reg pc; + + if (ps->ps_state != STOPPED && ps->ps_state != LOADED) { + fprintf(stderr, "Process not loaded and stopped %d\n", + ps->ps_state); + return (0); + } + + bkpt_name = argv[1]; + pc = strtol(bkpt_name, &ep, 0); + if (bkpt_name[0] == '\0' || *ep != '\0' || pc < 1) { + if (sym_lookup(ps, bkpt_name, &pc)) { + warnx("%s is not a valid pc", bkpt_name); + return (0); + } + } + + if (bkpt_add_cb(ps, pc, bkpt_normal, 0)) + warn("Can't set break point"); + + return (0); +} + +static int +sstep_normal(struct pstate *ps, void *arg) +{ + return (1); /* stop the command line. */ +} + +int +cmd_sstep(int argc, char **argv, void *arg) +{ + struct pstate *ps = arg; + + if (ps->ps_state != STOPPED) { + fprintf(stderr, "Process not loaded and stopped %d\n", + ps->ps_state); + return 0; + } + + if (sstep_set(ps, sstep_normal, NULL)) + warn("Can't set single step"); + + return (cmd_process_cont(argc, argv, arg)); +} + +int +sstep_set(struct pstate *ps, int (*fun)(struct pstate *, void *), void *arg) +{ + struct callback *cb; + + cb = emalloc(sizeof(*cb)); + cb->cb_fun = fun; + cb->cb_arg = arg; + TAILQ_INSERT_TAIL(&ps->ps_sstep_cbs, cb, cb_list); + + ps->ps_flags |= PSF_STEP; + + return (0); +} |