5d5314d679
This patch contains only the kdb core. Because the change set was large, it was split. The next patch in the series includes the instrumentation into the core kernel which are mainly helper functions for kdb. This work is directly derived from kdb v4.4 found at: ftp://oss.sgi.com/projects/kdb/download/v4.4/ The kdb internals have been re-organized to make them mostly platform independent and to connect everything to the debug core which is used by gdbstub (which has long been known as kgdb). The original version of kdb was 58,000 lines worth of changes to support x86. From that implementation only the kdb shell, and basic commands for memory access, runcontrol, lsmod, and dmesg where carried forward. This is a generic implementation which aims to cover all the current architectures using the kgdb core: ppc, arm, x86, mips, sparc, sh and blackfin. More archictectures can be added by implementing the architecture specific kgdb functions. [mort@sgi.com: Compile fix with hugepages enabled] [mort@sgi.com: Clean breakpoint code renaming kdba_ -> kdb_] [mort@sgi.com: fix new line after printing registers] [mort@sgi.com: Remove the concept of global vs. local breakpoints] [mort@sgi.com: Rework kdb_si_swapinfo to use more generic name] [mort@sgi.com: fix the information dump macros, remove 'arch' from the names] [sfr@canb.auug.org.au: include fixup to include linux/slab.h] CC: linux-arch@vger.kernel.org Signed-off-by: Jason Wessel <jason.wessel@windriver.com> Signed-off-by: Martin Hicks <mort@sgi.com>
565 lines
11 KiB
C
565 lines
11 KiB
C
/*
|
|
* 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 (strnicmp(argv[nextarg], "datar", sizeof("datar")) == 0)
|
|
bp->bp_type = BP_ACCESS_WATCHPOINT;
|
|
else if (strnicmp(argv[nextarg], "dataw", sizeof("dataw")) == 0)
|
|
bp->bp_type = BP_WRITE_WATCHPOINT;
|
|
else if (strnicmp(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);
|
|
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;
|
|
int free;
|
|
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
|
|
*/
|
|
free = KDB_MAXBPT;
|
|
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) and 'ssb' (Single Step to Branch)
|
|
* commands.
|
|
*
|
|
* ss
|
|
* ssb
|
|
*
|
|
* Parameters:
|
|
* argc Argument count
|
|
* argv Argument vector
|
|
* Outputs:
|
|
* None.
|
|
* Returns:
|
|
* KDB_CMD_SS[B] for success, a kdb error if failure.
|
|
* Locking:
|
|
* None.
|
|
* Remarks:
|
|
*
|
|
* Set the arch specific option to trigger a debug trap after the next
|
|
* instruction.
|
|
*
|
|
* For 'ssb', set the trace flag in the debug trap handler
|
|
* after printing the current insn and return directly without
|
|
* invoking the kdb command processor, until a branch instruction
|
|
* is encountered.
|
|
*/
|
|
|
|
static int kdb_ss(int argc, const char **argv)
|
|
{
|
|
int ssb = 0;
|
|
|
|
ssb = (strcmp(argv[0], "ssb") == 0);
|
|
if (argc != 0)
|
|
return KDB_ARGCOUNT;
|
|
/*
|
|
* Set trace flag and go.
|
|
*/
|
|
KDB_STATE_SET(DOING_SS);
|
|
if (ssb) {
|
|
KDB_STATE_SET(DOING_SSB);
|
|
return KDB_CMD_SSB;
|
|
}
|
|
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_repeat("bp", kdb_bp, "[<vaddr>]",
|
|
"Set/Display breakpoints", 0, KDB_REPEAT_NO_ARGS);
|
|
kdb_register_repeat("bl", kdb_bp, "[<vaddr>]",
|
|
"Display breakpoints", 0, KDB_REPEAT_NO_ARGS);
|
|
if (arch_kgdb_ops.flags & KGDB_HW_BREAKPOINT)
|
|
kdb_register_repeat("bph", kdb_bp, "[<vaddr>]",
|
|
"[datar [length]|dataw [length]] Set hw brk", 0, KDB_REPEAT_NO_ARGS);
|
|
kdb_register_repeat("bc", kdb_bc, "<bpnum>",
|
|
"Clear Breakpoint", 0, KDB_REPEAT_NONE);
|
|
kdb_register_repeat("be", kdb_bc, "<bpnum>",
|
|
"Enable Breakpoint", 0, KDB_REPEAT_NONE);
|
|
kdb_register_repeat("bd", kdb_bc, "<bpnum>",
|
|
"Disable Breakpoint", 0, KDB_REPEAT_NONE);
|
|
|
|
kdb_register_repeat("ss", kdb_ss, "",
|
|
"Single Step", 1, KDB_REPEAT_NO_ARGS);
|
|
kdb_register_repeat("ssb", kdb_ss, "",
|
|
"Single step to branch/call", 0, KDB_REPEAT_NO_ARGS);
|
|
/*
|
|
* Architecture dependent initialization.
|
|
*/
|
|
}
|