| /* |
| * 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); |
| #ifdef CONFIG_DEBUG_RODATA |
| if (!bp->bp_type) { |
| kdb_printf("Software breakpoints are unavailable.\n" |
| " Change the kernel CONFIG_DEBUG_RODATA=n\n" |
| " OR use hw breaks: help bph\n"); |
| } |
| #endif |
| 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("\taddr 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; |
| |
| /* |
| * 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) { |
| bp = &kdb_breakpoints[addr]; |
| 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; |
| } |
| |
| /* 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_flags("bp", kdb_bp, "[<vaddr>]", |
| "Set/Display breakpoints", 0, KDB_REPEAT_NO_ARGS); |
| kdb_register_flags("bl", kdb_bp, "[<vaddr>]", |
| "Display breakpoints", 0, KDB_REPEAT_NO_ARGS); |
| if (arch_kgdb_ops.flags & KGDB_HW_BREAKPOINT) |
| kdb_register_flags("bph", kdb_bp, "[<vaddr>]", |
| "[datar [length]|dataw [length]] Set hw brk", 0, KDB_REPEAT_NO_ARGS); |
| kdb_register_flags("bc", kdb_bc, "<bpnum>", |
| "Clear Breakpoint", 0, KDB_REPEAT_NONE); |
| kdb_register_flags("be", kdb_bc, "<bpnum>", |
| "Enable Breakpoint", 0, KDB_REPEAT_NONE); |
| kdb_register_flags("bd", kdb_bc, "<bpnum>", |
| "Disable Breakpoint", 0, KDB_REPEAT_NONE); |
| |
| kdb_register_flags("ss", kdb_ss, "", |
| "Single Step", 1, KDB_REPEAT_NO_ARGS); |
| /* |
| * Architecture dependent initialization. |
| */ |
| } |