mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 14:41:39 +00:00
834b2964b7
DBG_MAX_REG_NUM incorrectly had the number of indices in the GDB regs array rather than the number of registers, leading to an oops when the "rd" command is used in KDB. Cc: stable@kernel.org Signed-off-by: Rabin Vincent <rabin@rab.in> Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
256 lines
6.2 KiB
C
256 lines
6.2 KiB
C
/*
|
|
* arch/arm/kernel/kgdb.c
|
|
*
|
|
* ARM KGDB support
|
|
*
|
|
* Copyright (c) 2002-2004 MontaVista Software, Inc
|
|
* Copyright (c) 2008 Wind River Systems, Inc.
|
|
*
|
|
* Authors: George Davis <davis_g@mvista.com>
|
|
* Deepak Saxena <dsaxena@plexity.net>
|
|
*/
|
|
#include <linux/irq.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/kgdb.h>
|
|
#include <asm/traps.h>
|
|
|
|
struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] =
|
|
{
|
|
{ "r0", 4, offsetof(struct pt_regs, ARM_r0)},
|
|
{ "r1", 4, offsetof(struct pt_regs, ARM_r1)},
|
|
{ "r2", 4, offsetof(struct pt_regs, ARM_r2)},
|
|
{ "r3", 4, offsetof(struct pt_regs, ARM_r3)},
|
|
{ "r4", 4, offsetof(struct pt_regs, ARM_r4)},
|
|
{ "r5", 4, offsetof(struct pt_regs, ARM_r5)},
|
|
{ "r6", 4, offsetof(struct pt_regs, ARM_r6)},
|
|
{ "r7", 4, offsetof(struct pt_regs, ARM_r7)},
|
|
{ "r8", 4, offsetof(struct pt_regs, ARM_r8)},
|
|
{ "r9", 4, offsetof(struct pt_regs, ARM_r9)},
|
|
{ "r10", 4, offsetof(struct pt_regs, ARM_r10)},
|
|
{ "fp", 4, offsetof(struct pt_regs, ARM_fp)},
|
|
{ "ip", 4, offsetof(struct pt_regs, ARM_ip)},
|
|
{ "sp", 4, offsetof(struct pt_regs, ARM_sp)},
|
|
{ "lr", 4, offsetof(struct pt_regs, ARM_lr)},
|
|
{ "pc", 4, offsetof(struct pt_regs, ARM_pc)},
|
|
{ "f0", 12, -1 },
|
|
{ "f1", 12, -1 },
|
|
{ "f2", 12, -1 },
|
|
{ "f3", 12, -1 },
|
|
{ "f4", 12, -1 },
|
|
{ "f5", 12, -1 },
|
|
{ "f6", 12, -1 },
|
|
{ "f7", 12, -1 },
|
|
{ "fps", 4, -1 },
|
|
{ "cpsr", 4, offsetof(struct pt_regs, ARM_cpsr)},
|
|
};
|
|
|
|
char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs)
|
|
{
|
|
if (regno >= DBG_MAX_REG_NUM || regno < 0)
|
|
return NULL;
|
|
|
|
if (dbg_reg_def[regno].offset != -1)
|
|
memcpy(mem, (void *)regs + dbg_reg_def[regno].offset,
|
|
dbg_reg_def[regno].size);
|
|
else
|
|
memset(mem, 0, dbg_reg_def[regno].size);
|
|
return dbg_reg_def[regno].name;
|
|
}
|
|
|
|
int dbg_set_reg(int regno, void *mem, struct pt_regs *regs)
|
|
{
|
|
if (regno >= DBG_MAX_REG_NUM || regno < 0)
|
|
return -EINVAL;
|
|
|
|
if (dbg_reg_def[regno].offset != -1)
|
|
memcpy((void *)regs + dbg_reg_def[regno].offset, mem,
|
|
dbg_reg_def[regno].size);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *task)
|
|
{
|
|
struct pt_regs *thread_regs;
|
|
int regno;
|
|
|
|
/* Just making sure... */
|
|
if (task == NULL)
|
|
return;
|
|
|
|
/* Initialize to zero */
|
|
for (regno = 0; regno < GDB_MAX_REGS; regno++)
|
|
gdb_regs[regno] = 0;
|
|
|
|
/* Otherwise, we have only some registers from switch_to() */
|
|
thread_regs = task_pt_regs(task);
|
|
gdb_regs[_R0] = thread_regs->ARM_r0;
|
|
gdb_regs[_R1] = thread_regs->ARM_r1;
|
|
gdb_regs[_R2] = thread_regs->ARM_r2;
|
|
gdb_regs[_R3] = thread_regs->ARM_r3;
|
|
gdb_regs[_R4] = thread_regs->ARM_r4;
|
|
gdb_regs[_R5] = thread_regs->ARM_r5;
|
|
gdb_regs[_R6] = thread_regs->ARM_r6;
|
|
gdb_regs[_R7] = thread_regs->ARM_r7;
|
|
gdb_regs[_R8] = thread_regs->ARM_r8;
|
|
gdb_regs[_R9] = thread_regs->ARM_r9;
|
|
gdb_regs[_R10] = thread_regs->ARM_r10;
|
|
gdb_regs[_FP] = thread_regs->ARM_fp;
|
|
gdb_regs[_IP] = thread_regs->ARM_ip;
|
|
gdb_regs[_SPT] = thread_regs->ARM_sp;
|
|
gdb_regs[_LR] = thread_regs->ARM_lr;
|
|
gdb_regs[_PC] = thread_regs->ARM_pc;
|
|
gdb_regs[_CPSR] = thread_regs->ARM_cpsr;
|
|
}
|
|
|
|
void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long pc)
|
|
{
|
|
regs->ARM_pc = pc;
|
|
}
|
|
|
|
static int compiled_break;
|
|
|
|
int kgdb_arch_handle_exception(int exception_vector, int signo,
|
|
int err_code, char *remcom_in_buffer,
|
|
char *remcom_out_buffer,
|
|
struct pt_regs *linux_regs)
|
|
{
|
|
unsigned long addr;
|
|
char *ptr;
|
|
|
|
switch (remcom_in_buffer[0]) {
|
|
case 'D':
|
|
case 'k':
|
|
case 'c':
|
|
/*
|
|
* Try to read optional parameter, pc unchanged if no parm.
|
|
* If this was a compiled breakpoint, we need to move
|
|
* to the next instruction or we will just breakpoint
|
|
* over and over again.
|
|
*/
|
|
ptr = &remcom_in_buffer[1];
|
|
if (kgdb_hex2long(&ptr, &addr))
|
|
linux_regs->ARM_pc = addr;
|
|
else if (compiled_break == 1)
|
|
linux_regs->ARM_pc += 4;
|
|
|
|
compiled_break = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int kgdb_brk_fn(struct pt_regs *regs, unsigned int instr)
|
|
{
|
|
kgdb_handle_exception(1, SIGTRAP, 0, regs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int kgdb_compiled_brk_fn(struct pt_regs *regs, unsigned int instr)
|
|
{
|
|
compiled_break = 1;
|
|
kgdb_handle_exception(1, SIGTRAP, 0, regs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct undef_hook kgdb_brkpt_hook = {
|
|
.instr_mask = 0xffffffff,
|
|
.instr_val = KGDB_BREAKINST,
|
|
.fn = kgdb_brk_fn
|
|
};
|
|
|
|
static struct undef_hook kgdb_compiled_brkpt_hook = {
|
|
.instr_mask = 0xffffffff,
|
|
.instr_val = KGDB_COMPILED_BREAK,
|
|
.fn = kgdb_compiled_brk_fn
|
|
};
|
|
|
|
static void kgdb_call_nmi_hook(void *ignored)
|
|
{
|
|
kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs());
|
|
}
|
|
|
|
void kgdb_roundup_cpus(unsigned long flags)
|
|
{
|
|
local_irq_enable();
|
|
smp_call_function(kgdb_call_nmi_hook, NULL, 0);
|
|
local_irq_disable();
|
|
}
|
|
|
|
static int __kgdb_notify(struct die_args *args, unsigned long cmd)
|
|
{
|
|
struct pt_regs *regs = args->regs;
|
|
|
|
if (kgdb_handle_exception(1, args->signr, cmd, regs))
|
|
return NOTIFY_DONE;
|
|
return NOTIFY_STOP;
|
|
}
|
|
static int
|
|
kgdb_notify(struct notifier_block *self, unsigned long cmd, void *ptr)
|
|
{
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
local_irq_save(flags);
|
|
ret = __kgdb_notify(ptr, cmd);
|
|
local_irq_restore(flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct notifier_block kgdb_notifier = {
|
|
.notifier_call = kgdb_notify,
|
|
.priority = -INT_MAX,
|
|
};
|
|
|
|
|
|
/**
|
|
* kgdb_arch_init - Perform any architecture specific initalization.
|
|
*
|
|
* This function will handle the initalization of any architecture
|
|
* specific callbacks.
|
|
*/
|
|
int kgdb_arch_init(void)
|
|
{
|
|
int ret = register_die_notifier(&kgdb_notifier);
|
|
|
|
if (ret != 0)
|
|
return ret;
|
|
|
|
register_undef_hook(&kgdb_brkpt_hook);
|
|
register_undef_hook(&kgdb_compiled_brkpt_hook);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* kgdb_arch_exit - Perform any architecture specific uninitalization.
|
|
*
|
|
* This function will handle the uninitalization of any architecture
|
|
* specific callbacks, for dynamic registration and unregistration.
|
|
*/
|
|
void kgdb_arch_exit(void)
|
|
{
|
|
unregister_undef_hook(&kgdb_brkpt_hook);
|
|
unregister_undef_hook(&kgdb_compiled_brkpt_hook);
|
|
unregister_die_notifier(&kgdb_notifier);
|
|
}
|
|
|
|
/*
|
|
* Register our undef instruction hooks with ARM undef core.
|
|
* We regsiter a hook specifically looking for the KGB break inst
|
|
* and we handle the normal undef case within the do_undefinstr
|
|
* handler.
|
|
*/
|
|
struct kgdb_arch arch_kgdb_ops = {
|
|
#ifndef __ARMEB__
|
|
.gdb_bpt_instr = {0xfe, 0xde, 0xff, 0xe7}
|
|
#else /* ! __ARMEB__ */
|
|
.gdb_bpt_instr = {0xe7, 0xff, 0xde, 0xfe}
|
|
#endif
|
|
};
|