mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
591 lines
11 KiB
591 lines
11 KiB
/* |
|
* Kernel Debugger Architecture Independent Breakpoint Handler |
|
* |
|
* This file is subject to the terms and conditions of the GNU General Public |
|
* License. See the file "COPYING" in the main directory of this archive |
|
* for more details. |
|
* |
|
* Copyright (c) 1999-2004 Silicon Graphics, Inc. All Rights Reserved. |
|
* Copyright (c) 2009 Wind River Systems, Inc. All Rights Reserved. |
|
*/ |
|
|
|
#include <linux/string.h> |
|
#include <linux/kernel.h> |
|
#include <linux/init.h> |
|
#include <linux/kdb.h> |
|
#include <linux/kgdb.h> |
|
#include <linux/smp.h> |
|
#include <linux/sched.h> |
|
#include <linux/interrupt.h> |
|
#include "kdb_private.h" |
|
|
|
/* |
|
* Table of kdb_breakpoints |
|
*/ |
|
kdb_bp_t kdb_breakpoints[KDB_MAXBPT]; |
|
|
|
static void kdb_setsinglestep(struct pt_regs *regs) |
|
{ |
|
KDB_STATE_SET(DOING_SS); |
|
} |
|
|
|
static char *kdb_rwtypes[] = { |
|
"Instruction(i)", |
|
"Instruction(Register)", |
|
"Data Write", |
|
"I/O", |
|
"Data Access" |
|
}; |
|
|
|
static char *kdb_bptype(kdb_bp_t *bp) |
|
{ |
|
if (bp->bp_type < 0 || bp->bp_type > 4) |
|
return ""; |
|
|
|
return kdb_rwtypes[bp->bp_type]; |
|
} |
|
|
|
static int kdb_parsebp(int argc, const char **argv, int *nextargp, kdb_bp_t *bp) |
|
{ |
|
int nextarg = *nextargp; |
|
int diag; |
|
|
|
bp->bph_length = 1; |
|
if ((argc + 1) != nextarg) { |
|
if (strncasecmp(argv[nextarg], "datar", sizeof("datar")) == 0) |
|
bp->bp_type = BP_ACCESS_WATCHPOINT; |
|
else if (strncasecmp(argv[nextarg], "dataw", sizeof("dataw")) == 0) |
|
bp->bp_type = BP_WRITE_WATCHPOINT; |
|
else if (strncasecmp(argv[nextarg], "inst", sizeof("inst")) == 0) |
|
bp->bp_type = BP_HARDWARE_BREAKPOINT; |
|
else |
|
return KDB_ARGCOUNT; |
|
|
|
bp->bph_length = 1; |
|
|
|
nextarg++; |
|
|
|
if ((argc + 1) != nextarg) { |
|
unsigned long len; |
|
|
|
diag = kdbgetularg((char *)argv[nextarg], |
|
&len); |
|
if (diag) |
|
return diag; |
|
|
|
|
|
if (len > 8) |
|
return KDB_BADLENGTH; |
|
|
|
bp->bph_length = len; |
|
nextarg++; |
|
} |
|
|
|
if ((argc + 1) != nextarg) |
|
return KDB_ARGCOUNT; |
|
} |
|
|
|
*nextargp = nextarg; |
|
return 0; |
|
} |
|
|
|
static int _kdb_bp_remove(kdb_bp_t *bp) |
|
{ |
|
int ret = 1; |
|
if (!bp->bp_installed) |
|
return ret; |
|
if (!bp->bp_type) |
|
ret = dbg_remove_sw_break(bp->bp_addr); |
|
else |
|
ret = arch_kgdb_ops.remove_hw_breakpoint(bp->bp_addr, |
|
bp->bph_length, |
|
bp->bp_type); |
|
if (ret == 0) |
|
bp->bp_installed = 0; |
|
return ret; |
|
} |
|
|
|
static void kdb_handle_bp(struct pt_regs *regs, kdb_bp_t *bp) |
|
{ |
|
if (KDB_DEBUG(BP)) |
|
kdb_printf("regs->ip = 0x%lx\n", instruction_pointer(regs)); |
|
|
|
/* |
|
* Setup single step |
|
*/ |
|
kdb_setsinglestep(regs); |
|
|
|
/* |
|
* Reset delay attribute |
|
*/ |
|
bp->bp_delay = 0; |
|
bp->bp_delayed = 1; |
|
} |
|
|
|
static int _kdb_bp_install(struct pt_regs *regs, kdb_bp_t *bp) |
|
{ |
|
int ret; |
|
/* |
|
* Install the breakpoint, if it is not already installed. |
|
*/ |
|
|
|
if (KDB_DEBUG(BP)) |
|
kdb_printf("%s: bp_installed %d\n", |
|
__func__, bp->bp_installed); |
|
if (!KDB_STATE(SSBPT)) |
|
bp->bp_delay = 0; |
|
if (bp->bp_installed) |
|
return 1; |
|
if (bp->bp_delay || (bp->bp_delayed && KDB_STATE(DOING_SS))) { |
|
if (KDB_DEBUG(BP)) |
|
kdb_printf("%s: delayed bp\n", __func__); |
|
kdb_handle_bp(regs, bp); |
|
return 0; |
|
} |
|
if (!bp->bp_type) |
|
ret = dbg_set_sw_break(bp->bp_addr); |
|
else |
|
ret = arch_kgdb_ops.set_hw_breakpoint(bp->bp_addr, |
|
bp->bph_length, |
|
bp->bp_type); |
|
if (ret == 0) { |
|
bp->bp_installed = 1; |
|
} else { |
|
kdb_printf("%s: failed to set breakpoint at 0x%lx\n", |
|
__func__, bp->bp_addr); |
|
if (!bp->bp_type) { |
|
kdb_printf("Software breakpoints are unavailable.\n" |
|
" Boot the kernel with rodata=off\n" |
|
" OR use hw breaks: help bph\n"); |
|
} |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
|
|
/* |
|
* kdb_bp_install |
|
* |
|
* Install kdb_breakpoints prior to returning from the |
|
* kernel debugger. This allows the kdb_breakpoints to be set |
|
* upon functions that are used internally by kdb, such as |
|
* printk(). This function is only called once per kdb session. |
|
*/ |
|
void kdb_bp_install(struct pt_regs *regs) |
|
{ |
|
int i; |
|
|
|
for (i = 0; i < KDB_MAXBPT; i++) { |
|
kdb_bp_t *bp = &kdb_breakpoints[i]; |
|
|
|
if (KDB_DEBUG(BP)) { |
|
kdb_printf("%s: bp %d bp_enabled %d\n", |
|
__func__, i, bp->bp_enabled); |
|
} |
|
if (bp->bp_enabled) |
|
_kdb_bp_install(regs, bp); |
|
} |
|
} |
|
|
|
/* |
|
* kdb_bp_remove |
|
* |
|
* Remove kdb_breakpoints upon entry to the kernel debugger. |
|
* |
|
* Parameters: |
|
* None. |
|
* Outputs: |
|
* None. |
|
* Returns: |
|
* None. |
|
* Locking: |
|
* None. |
|
* Remarks: |
|
*/ |
|
void kdb_bp_remove(void) |
|
{ |
|
int i; |
|
|
|
for (i = KDB_MAXBPT - 1; i >= 0; i--) { |
|
kdb_bp_t *bp = &kdb_breakpoints[i]; |
|
|
|
if (KDB_DEBUG(BP)) { |
|
kdb_printf("%s: bp %d bp_enabled %d\n", |
|
__func__, i, bp->bp_enabled); |
|
} |
|
if (bp->bp_enabled) |
|
_kdb_bp_remove(bp); |
|
} |
|
} |
|
|
|
|
|
/* |
|
* kdb_printbp |
|
* |
|
* Internal function to format and print a breakpoint entry. |
|
* |
|
* Parameters: |
|
* None. |
|
* Outputs: |
|
* None. |
|
* Returns: |
|
* None. |
|
* Locking: |
|
* None. |
|
* Remarks: |
|
*/ |
|
|
|
static void kdb_printbp(kdb_bp_t *bp, int i) |
|
{ |
|
kdb_printf("%s ", kdb_bptype(bp)); |
|
kdb_printf("BP #%d at ", i); |
|
kdb_symbol_print(bp->bp_addr, NULL, KDB_SP_DEFAULT); |
|
|
|
if (bp->bp_enabled) |
|
kdb_printf("\n is enabled "); |
|
else |
|
kdb_printf("\n is disabled"); |
|
|
|
kdb_printf(" addr at %016lx, hardtype=%d installed=%d\n", |
|
bp->bp_addr, bp->bp_type, bp->bp_installed); |
|
|
|
kdb_printf("\n"); |
|
} |
|
|
|
/* |
|
* kdb_bp |
|
* |
|
* Handle the bp commands. |
|
* |
|
* [bp|bph] <addr-expression> [DATAR|DATAW] |
|
* |
|
* Parameters: |
|
* argc Count of arguments in argv |
|
* argv Space delimited command line arguments |
|
* Outputs: |
|
* None. |
|
* Returns: |
|
* Zero for success, a kdb diagnostic if failure. |
|
* Locking: |
|
* None. |
|
* Remarks: |
|
* |
|
* bp Set breakpoint on all cpus. Only use hardware assist if need. |
|
* bph Set breakpoint on all cpus. Force hardware register |
|
*/ |
|
|
|
static int kdb_bp(int argc, const char **argv) |
|
{ |
|
int i, bpno; |
|
kdb_bp_t *bp, *bp_check; |
|
int diag; |
|
char *symname = NULL; |
|
long offset = 0ul; |
|
int nextarg; |
|
kdb_bp_t template = {0}; |
|
|
|
if (argc == 0) { |
|
/* |
|
* Display breakpoint table |
|
*/ |
|
for (bpno = 0, bp = kdb_breakpoints; bpno < KDB_MAXBPT; |
|
bpno++, bp++) { |
|
if (bp->bp_free) |
|
continue; |
|
kdb_printbp(bp, bpno); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
nextarg = 1; |
|
diag = kdbgetaddrarg(argc, argv, &nextarg, &template.bp_addr, |
|
&offset, &symname); |
|
if (diag) |
|
return diag; |
|
if (!template.bp_addr) |
|
return KDB_BADINT; |
|
|
|
/* |
|
* This check is redundant (since the breakpoint machinery should |
|
* be doing the same check during kdb_bp_install) but gives the |
|
* user immediate feedback. |
|
*/ |
|
diag = kgdb_validate_break_address(template.bp_addr); |
|
if (diag) |
|
return diag; |
|
|
|
/* |
|
* Find an empty bp structure to allocate |
|
*/ |
|
for (bpno = 0, bp = kdb_breakpoints; bpno < KDB_MAXBPT; bpno++, bp++) { |
|
if (bp->bp_free) |
|
break; |
|
} |
|
|
|
if (bpno == KDB_MAXBPT) |
|
return KDB_TOOMANYBPT; |
|
|
|
if (strcmp(argv[0], "bph") == 0) { |
|
template.bp_type = BP_HARDWARE_BREAKPOINT; |
|
diag = kdb_parsebp(argc, argv, &nextarg, &template); |
|
if (diag) |
|
return diag; |
|
} else { |
|
template.bp_type = BP_BREAKPOINT; |
|
} |
|
|
|
/* |
|
* Check for clashing breakpoints. |
|
* |
|
* Note, in this design we can't have hardware breakpoints |
|
* enabled for both read and write on the same address. |
|
*/ |
|
for (i = 0, bp_check = kdb_breakpoints; i < KDB_MAXBPT; |
|
i++, bp_check++) { |
|
if (!bp_check->bp_free && |
|
bp_check->bp_addr == template.bp_addr) { |
|
kdb_printf("You already have a breakpoint at " |
|
kdb_bfd_vma_fmt0 "\n", template.bp_addr); |
|
return KDB_DUPBPT; |
|
} |
|
} |
|
|
|
template.bp_enabled = 1; |
|
|
|
/* |
|
* Actually allocate the breakpoint found earlier |
|
*/ |
|
*bp = template; |
|
bp->bp_free = 0; |
|
|
|
kdb_printbp(bp, bpno); |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* kdb_bc |
|
* |
|
* Handles the 'bc', 'be', and 'bd' commands |
|
* |
|
* [bd|bc|be] <breakpoint-number> |
|
* [bd|bc|be] * |
|
* |
|
* Parameters: |
|
* argc Count of arguments in argv |
|
* argv Space delimited command line arguments |
|
* Outputs: |
|
* None. |
|
* Returns: |
|
* Zero for success, a kdb diagnostic for failure |
|
* Locking: |
|
* None. |
|
* Remarks: |
|
*/ |
|
static int kdb_bc(int argc, const char **argv) |
|
{ |
|
unsigned long addr; |
|
kdb_bp_t *bp = NULL; |
|
int lowbp = KDB_MAXBPT; |
|
int highbp = 0; |
|
int done = 0; |
|
int i; |
|
int diag = 0; |
|
|
|
int cmd; /* KDBCMD_B? */ |
|
#define KDBCMD_BC 0 |
|
#define KDBCMD_BE 1 |
|
#define KDBCMD_BD 2 |
|
|
|
if (strcmp(argv[0], "be") == 0) |
|
cmd = KDBCMD_BE; |
|
else if (strcmp(argv[0], "bd") == 0) |
|
cmd = KDBCMD_BD; |
|
else |
|
cmd = KDBCMD_BC; |
|
|
|
if (argc != 1) |
|
return KDB_ARGCOUNT; |
|
|
|
if (strcmp(argv[1], "*") == 0) { |
|
lowbp = 0; |
|
highbp = KDB_MAXBPT; |
|
} else { |
|
diag = kdbgetularg(argv[1], &addr); |
|
if (diag) |
|
return diag; |
|
|
|
/* |
|
* For addresses less than the maximum breakpoint number, |
|
* assume that the breakpoint number is desired. |
|
*/ |
|
if (addr < KDB_MAXBPT) { |
|
lowbp = highbp = addr; |
|
highbp++; |
|
} else { |
|
for (i = 0, bp = kdb_breakpoints; i < KDB_MAXBPT; |
|
i++, bp++) { |
|
if (bp->bp_addr == addr) { |
|
lowbp = highbp = i; |
|
highbp++; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* Now operate on the set of breakpoints matching the input |
|
* criteria (either '*' for all, or an individual breakpoint). |
|
*/ |
|
for (bp = &kdb_breakpoints[lowbp], i = lowbp; |
|
i < highbp; |
|
i++, bp++) { |
|
if (bp->bp_free) |
|
continue; |
|
|
|
done++; |
|
|
|
switch (cmd) { |
|
case KDBCMD_BC: |
|
bp->bp_enabled = 0; |
|
|
|
kdb_printf("Breakpoint %d at " |
|
kdb_bfd_vma_fmt " cleared\n", |
|
i, bp->bp_addr); |
|
|
|
bp->bp_addr = 0; |
|
bp->bp_free = 1; |
|
|
|
break; |
|
case KDBCMD_BE: |
|
bp->bp_enabled = 1; |
|
|
|
kdb_printf("Breakpoint %d at " |
|
kdb_bfd_vma_fmt " enabled", |
|
i, bp->bp_addr); |
|
|
|
kdb_printf("\n"); |
|
break; |
|
case KDBCMD_BD: |
|
if (!bp->bp_enabled) |
|
break; |
|
|
|
bp->bp_enabled = 0; |
|
|
|
kdb_printf("Breakpoint %d at " |
|
kdb_bfd_vma_fmt " disabled\n", |
|
i, bp->bp_addr); |
|
|
|
break; |
|
} |
|
if (bp->bp_delay && (cmd == KDBCMD_BC || cmd == KDBCMD_BD)) { |
|
bp->bp_delay = 0; |
|
KDB_STATE_CLEAR(SSBPT); |
|
} |
|
} |
|
|
|
return (!done) ? KDB_BPTNOTFOUND : 0; |
|
} |
|
|
|
/* |
|
* kdb_ss |
|
* |
|
* Process the 'ss' (Single Step) command. |
|
* |
|
* ss |
|
* |
|
* Parameters: |
|
* argc Argument count |
|
* argv Argument vector |
|
* Outputs: |
|
* None. |
|
* Returns: |
|
* KDB_CMD_SS for success, a kdb error if failure. |
|
* Locking: |
|
* None. |
|
* Remarks: |
|
* |
|
* Set the arch specific option to trigger a debug trap after the next |
|
* instruction. |
|
*/ |
|
|
|
static int kdb_ss(int argc, const char **argv) |
|
{ |
|
if (argc != 0) |
|
return KDB_ARGCOUNT; |
|
/* |
|
* Set trace flag and go. |
|
*/ |
|
KDB_STATE_SET(DOING_SS); |
|
return KDB_CMD_SS; |
|
} |
|
|
|
static kdbtab_t bptab[] = { |
|
{ .name = "bp", |
|
.func = kdb_bp, |
|
.usage = "[<vaddr>]", |
|
.help = "Set/Display breakpoints", |
|
.flags = KDB_ENABLE_FLOW_CTRL | KDB_REPEAT_NO_ARGS, |
|
}, |
|
{ .name = "bl", |
|
.func = kdb_bp, |
|
.usage = "[<vaddr>]", |
|
.help = "Display breakpoints", |
|
.flags = KDB_ENABLE_FLOW_CTRL | KDB_REPEAT_NO_ARGS, |
|
}, |
|
{ .name = "bc", |
|
.func = kdb_bc, |
|
.usage = "<bpnum>", |
|
.help = "Clear Breakpoint", |
|
.flags = KDB_ENABLE_FLOW_CTRL, |
|
}, |
|
{ .name = "be", |
|
.func = kdb_bc, |
|
.usage = "<bpnum>", |
|
.help = "Enable Breakpoint", |
|
.flags = KDB_ENABLE_FLOW_CTRL, |
|
}, |
|
{ .name = "bd", |
|
.func = kdb_bc, |
|
.usage = "<bpnum>", |
|
.help = "Disable Breakpoint", |
|
.flags = KDB_ENABLE_FLOW_CTRL, |
|
}, |
|
{ .name = "ss", |
|
.func = kdb_ss, |
|
.usage = "", |
|
.help = "Single Step", |
|
.minlen = 1, |
|
.flags = KDB_ENABLE_FLOW_CTRL | KDB_REPEAT_NO_ARGS, |
|
}, |
|
}; |
|
|
|
static kdbtab_t bphcmd = { |
|
.name = "bph", |
|
.func = kdb_bp, |
|
.usage = "[<vaddr>]", |
|
.help = "[datar [length]|dataw [length]] Set hw brk", |
|
.flags = KDB_ENABLE_FLOW_CTRL | KDB_REPEAT_NO_ARGS, |
|
}; |
|
|
|
/* Initialize the breakpoint table and register breakpoint commands. */ |
|
|
|
void __init kdb_initbptab(void) |
|
{ |
|
int i; |
|
kdb_bp_t *bp; |
|
|
|
/* |
|
* First time initialization. |
|
*/ |
|
memset(&kdb_breakpoints, '\0', sizeof(kdb_breakpoints)); |
|
|
|
for (i = 0, bp = kdb_breakpoints; i < KDB_MAXBPT; i++, bp++) |
|
bp->bp_free = 1; |
|
|
|
kdb_register_table(bptab, ARRAY_SIZE(bptab)); |
|
if (arch_kgdb_ops.flags & KGDB_HW_BREAKPOINT) |
|
kdb_register_table(&bphcmd, 1); |
|
}
|
|
|