Skip to content

Commit

Permalink
MN10300: Emulate single stepping in KGDB on MN10300
Browse files Browse the repository at this point in the history
Emulate single stepping in KGDB on MN10300 by way of temporary breakpoint
insertion.  These breakpoints are never actually seen by KGDB, and will overlay
KGDB's own breakpoints.

The breakpoints are removed by switch_to() and reinstalled on switching back so
that if preemption occurs, the preempting task doesn't hit them (though it will
still hit KGDB's regular breakpoints).  If KGDB is reentered for any reason,
then the single step breakpoint is completely erased and must be set again by
the debugger.

We take advantage of the fact that KGDB will effectively halt all other CPUs
whilst this CPU is single-stepping to avoid SMP problems.

If the single-stepping task is preempted and killed without KGDB being
reinvoked, then the breakpoint(s) will be cleared and KGDB will be jumped back
into.

Signed-off-by: David Howells <[email protected]>
  • Loading branch information
dhowells committed Mar 18, 2011
1 parent 044264b commit 5141c46
Show file tree
Hide file tree
Showing 3 changed files with 425 additions and 12 deletions.
4 changes: 4 additions & 0 deletions arch/mn10300/include/asm/thread_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ static inline unsigned long current_stack_pointer(void)
#define alloc_thread_info(tsk) kmalloc(THREAD_SIZE, GFP_KERNEL)
#endif

#ifndef CONFIG_KGDB
#define free_thread_info(ti) kfree((ti))
#else
extern void free_thread_info(struct thread_info *);
#endif
#define get_thread_info(ti) get_task_struct((ti)->task)
#define put_thread_info(ti) put_task_struct((ti)->task)

Expand Down
322 changes: 311 additions & 11 deletions arch/mn10300/kernel/kgdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* 2 of the Licence, or (at your option) any later version.
*/

#include <linux/slab.h>
#include <linux/ptrace.h>
#include <linux/kgdb.h>
#include <linux/uaccess.h>
Expand All @@ -18,6 +19,13 @@
#include <asm/serial-regs.h>
#include "internal.h"

/*
* Software single-stepping breakpoint save (used by __switch_to())
*/
static struct thread_info *kgdb_sstep_thread;
u8 *kgdb_sstep_bp_addr[2];
u8 kgdb_sstep_bp[2];

/*
* Copy kernel exception frame registers to the GDB register file
*/
Expand Down Expand Up @@ -118,8 +126,293 @@ struct kgdb_arch arch_kgdb_ops = {
.flags = KGDB_HW_BREAKPOINT,
};

static const unsigned char mn10300_kgdb_insn_sizes[256] =
{
/* 1 2 3 4 5 6 7 8 9 a b c d e f */
1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, /* 0 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1 */
2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, /* 2 */
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, /* 3 */
1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, /* 4 */
1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, /* 5 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 8 */
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 9 */
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* a */
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* b */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 2, /* c */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */
0, 2, 2, 2, 2, 2, 2, 4, 0, 3, 0, 4, 0, 6, 7, 1 /* f */
};

/*
* Handle unknown packets and [Ccs] packets
* Attempt to emulate single stepping by means of breakpoint instructions.
* Although there is a single-step trace flag in EPSW, its use is not
* sufficiently documented and is only intended for use with the JTAG debugger.
*/
static int kgdb_arch_do_singlestep(struct pt_regs *regs)
{
unsigned long arg;
unsigned size;
u8 *pc = (u8 *)regs->pc, *sp = (u8 *)(regs + 1), cur;
u8 *x = NULL, *y = NULL;
int ret;

ret = probe_kernel_read(&cur, pc, 1);
if (ret < 0)
return ret;

size = mn10300_kgdb_insn_sizes[cur];
if (size > 0) {
x = pc + size;
goto set_x;
}

switch (cur) {
/* Bxx (d8,PC) */
case 0xc0 ... 0xca:
ret = probe_kernel_read(&arg, pc + 1, 1);
if (ret < 0)
return ret;
x = pc + 2;
if (arg >= 0 && arg <= 2)
goto set_x;
y = pc + (s8)arg;
goto set_x_and_y;

/* LXX (d8,PC) */
case 0xd0 ... 0xda:
x = pc + 1;
if (regs->pc == regs->lar)
goto set_x;
y = (u8 *)regs->lar;
goto set_x_and_y;

/* SETLB - loads the next four bytes into the LIR register
* (which mustn't include a breakpoint instruction) */
case 0xdb:
x = pc + 5;
goto set_x;

/* JMP (d16,PC) or CALL (d16,PC) */
case 0xcc:
case 0xcd:
ret = probe_kernel_read(&arg, pc + 1, 2);
if (ret < 0)
return ret;
x = pc + (s16)arg;
goto set_x;

/* JMP (d32,PC) or CALL (d32,PC) */
case 0xdc:
case 0xdd:
ret = probe_kernel_read(&arg, pc + 1, 4);
if (ret < 0)
return ret;
x = pc + (s32)arg;
goto set_x;

/* RETF */
case 0xde:
x = (u8 *)regs->mdr;
goto set_x;

/* RET */
case 0xdf:
ret = probe_kernel_read(&arg, pc + 2, 1);
if (ret < 0)
return ret;
ret = probe_kernel_read(&x, sp + (s8)arg, 4);
if (ret < 0)
return ret;
goto set_x;

case 0xf0:
ret = probe_kernel_read(&cur, pc + 1, 1);
if (ret < 0)
return ret;

if (cur >= 0xf0 && cur <= 0xf7) {
/* JMP (An) / CALLS (An) */
switch (cur & 3) {
case 0: x = (u8 *)regs->a0; break;
case 1: x = (u8 *)regs->a1; break;
case 2: x = (u8 *)regs->a2; break;
case 3: x = (u8 *)regs->a3; break;
}
goto set_x;
} else if (cur == 0xfc) {
/* RETS */
ret = probe_kernel_read(&x, sp, 4);
if (ret < 0)
return ret;
goto set_x;
} else if (cur == 0xfd) {
/* RTI */
ret = probe_kernel_read(&x, sp + 4, 4);
if (ret < 0)
return ret;
goto set_x;
} else {
x = pc + 2;
goto set_x;
}
break;

/* potential 3-byte conditional branches */
case 0xf8:
ret = probe_kernel_read(&cur, pc + 1, 1);
if (ret < 0)
return ret;
x = pc + 3;

if (cur >= 0xe8 && cur <= 0xeb) {
ret = probe_kernel_read(&arg, pc + 2, 1);
if (ret < 0)
return ret;
if (arg >= 0 && arg <= 3)
goto set_x;
y = pc + (s8)arg;
goto set_x_and_y;
}
goto set_x;

case 0xfa:
ret = probe_kernel_read(&cur, pc + 1, 1);
if (ret < 0)
return ret;

if (cur == 0xff) {
/* CALLS (d16,PC) */
ret = probe_kernel_read(&arg, pc + 2, 2);
if (ret < 0)
return ret;
x = pc + (s16)arg;
goto set_x;
}

x = pc + 4;
goto set_x;

case 0xfc:
ret = probe_kernel_read(&cur, pc + 1, 1);
if (ret < 0)
return ret;

if (cur == 0xff) {
/* CALLS (d32,PC) */
ret = probe_kernel_read(&arg, pc + 2, 4);
if (ret < 0)
return ret;
x = pc + (s32)arg;
goto set_x;
}

x = pc + 6;
goto set_x;
}

return 0;

set_x:
kgdb_sstep_bp_addr[0] = x;
kgdb_sstep_bp_addr[1] = NULL;
ret = probe_kernel_read(&kgdb_sstep_bp[0], x, 1);
if (ret < 0)
return ret;
ret = probe_kernel_write(x, &arch_kgdb_ops.gdb_bpt_instr, 1);
if (ret < 0)
return ret;
kgdb_sstep_thread = current_thread_info();
debugger_local_cache_flushinv_one(x);
return ret;

set_x_and_y:
kgdb_sstep_bp_addr[0] = x;
kgdb_sstep_bp_addr[1] = y;
ret = probe_kernel_read(&kgdb_sstep_bp[0], x, 1);
if (ret < 0)
return ret;
ret = probe_kernel_read(&kgdb_sstep_bp[1], y, 1);
if (ret < 0)
return ret;
ret = probe_kernel_write(x, &arch_kgdb_ops.gdb_bpt_instr, 1);
if (ret < 0)
return ret;
ret = probe_kernel_write(y, &arch_kgdb_ops.gdb_bpt_instr, 1);
if (ret < 0) {
probe_kernel_write(kgdb_sstep_bp_addr[0],
&kgdb_sstep_bp[0], 1);
} else {
kgdb_sstep_thread = current_thread_info();
}
debugger_local_cache_flushinv_one(x);
debugger_local_cache_flushinv_one(y);
return ret;
}

/*
* Remove emplaced single-step breakpoints, returning true if we hit one of
* them.
*/
static bool kgdb_arch_undo_singlestep(struct pt_regs *regs)
{
bool hit = false;
u8 *x = kgdb_sstep_bp_addr[0], *y = kgdb_sstep_bp_addr[1];
u8 opcode;

if (kgdb_sstep_thread == current_thread_info()) {
if (x) {
if (x == (u8 *)regs->pc)
hit = true;
if (probe_kernel_read(&opcode, x,
1) < 0 ||
opcode != 0xff)
BUG();
probe_kernel_write(x, &kgdb_sstep_bp[0], 1);
debugger_local_cache_flushinv_one(x);
}
if (y) {
if (y == (u8 *)regs->pc)
hit = true;
if (probe_kernel_read(&opcode, y,
1) < 0 ||
opcode != 0xff)
BUG();
probe_kernel_write(y, &kgdb_sstep_bp[1], 1);
debugger_local_cache_flushinv_one(y);
}
}

kgdb_sstep_bp_addr[0] = NULL;
kgdb_sstep_bp_addr[1] = NULL;
kgdb_sstep_thread = NULL;
return hit;
}

/*
* Catch a single-step-pending thread being deleted and make sure the global
* single-step state is cleared. At this point the breakpoints should have
* been removed by __switch_to().
*/
void free_thread_info(struct thread_info *ti)
{
if (kgdb_sstep_thread == ti) {
kgdb_sstep_thread = NULL;

/* However, we may now be running in degraded mode, with most
* of the CPUs disabled until such a time as KGDB is reentered,
* so force immediate reentry */
kgdb_breakpoint();
}
kfree(ti);
}

/*
* Handle unknown packets and [CcsDk] packets
* - at this point breakpoints have been installed
*/
int kgdb_arch_handle_exception(int vector, int signo, int err_code,
char *remcom_in_buffer, char *remcom_out_buffer,
Expand All @@ -130,21 +423,22 @@ int kgdb_arch_handle_exception(int vector, int signo, int err_code,

switch (remcom_in_buffer[0]) {
case 'c':
if (kgdb_contthread && kgdb_contthread != current) {
strcpy(remcom_out_buffer, "E00");
break;
}

kgdb_contthread = NULL;

case 's':
/* try to read optional parameter, pc unchanged if no parm */
ptr = &remcom_in_buffer[1];
if (kgdb_hex2long(&ptr, &addr))
regs->pc = addr;
return 0;
case 'D':
case 'k':
atomic_set(&kgdb_cpu_doing_single_step, -1);

case 's':
break; /* we don't do hardware single stepping */
if (remcom_in_buffer[0] == 's') {
kgdb_arch_do_singlestep(regs);
kgdb_single_step = 1;
atomic_set(&kgdb_cpu_doing_single_step,
raw_smp_processor_id());
}
return 0;
}
return -1; /* this means that we do not want to exit from the handler */
}
Expand All @@ -158,6 +452,12 @@ int debugger_intercept(enum exception_code excep, int signo, int si_code,
{
int ret;

if (kgdb_arch_undo_singlestep(regs)) {
excep = EXCEP_TRAP;
signo = SIGTRAP;
si_code = TRAP_TRACE;
}

ret = kgdb_handle_exception(excep, signo, si_code, regs);

debugger_local_cache_flushinv();
Expand Down
Loading

0 comments on commit 5141c46

Please sign in to comment.