Skip to content

Commit

Permalink
Merge tag 'for_linus-3.4-rc2' of git://git.kernel.org/pub/scm/linux/k…
Browse files Browse the repository at this point in the history
…ernel/git/jwessel/kgdb

Pull KGDB/KDB regression fixes from Jason Wessel:
 - Fix a Smatch warning that appeared in the 3.4 merge window
 - Fix kgdb test suite with SMP for all archs without HW single stepping
 - Fix kgdb sw breakpoints with CONFIG_DEBUG_RODATA=y limitations on x86
 - Fix oops on kgdb test suite with CONFIG_DEBUG_RODATA
 - Fix kgdb test suite with SMP for all archs with HW single stepping

* tag 'for_linus-3.4-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/jwessel/kgdb:
  x86,kgdb: Fix DEBUG_RODATA limitation using text_poke()
  kgdb,debug_core: pass the breakpoint struct instead of address and memory
  kgdbts: (2 of 2) fix single step awareness to work correctly with SMP
  kgdbts: (1 of 2) fix single step awareness to work correctly with SMP
  kgdbts: Fix kernel oops with CONFIG_DEBUG_RODATA
  kdb: Fix smatch warning on dbg_io_ops->is_console
  • Loading branch information
Linus Torvalds committed Apr 5, 2012
2 parents 58bca4a + 3751d3e commit 6c216ec
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 78 deletions.
60 changes: 60 additions & 0 deletions arch/x86/kernel/kgdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
#include <linux/smp.h>
#include <linux/nmi.h>
#include <linux/hw_breakpoint.h>
#include <linux/uaccess.h>
#include <linux/memory.h>

#include <asm/debugreg.h>
#include <asm/apicdef.h>
Expand Down Expand Up @@ -741,6 +743,64 @@ void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long ip)
regs->ip = ip;
}

int kgdb_arch_set_breakpoint(struct kgdb_bkpt *bpt)
{
int err;
char opc[BREAK_INSTR_SIZE];

bpt->type = BP_BREAKPOINT;
err = probe_kernel_read(bpt->saved_instr, (char *)bpt->bpt_addr,
BREAK_INSTR_SIZE);
if (err)
return err;
err = probe_kernel_write((char *)bpt->bpt_addr,
arch_kgdb_ops.gdb_bpt_instr, BREAK_INSTR_SIZE);
#ifdef CONFIG_DEBUG_RODATA
if (!err)
return err;
/*
* It is safe to call text_poke() because normal kernel execution
* is stopped on all cores, so long as the text_mutex is not locked.
*/
if (mutex_is_locked(&text_mutex))
return -EBUSY;
text_poke((void *)bpt->bpt_addr, arch_kgdb_ops.gdb_bpt_instr,
BREAK_INSTR_SIZE);
err = probe_kernel_read(opc, (char *)bpt->bpt_addr, BREAK_INSTR_SIZE);
if (err)
return err;
if (memcmp(opc, arch_kgdb_ops.gdb_bpt_instr, BREAK_INSTR_SIZE))
return -EINVAL;
bpt->type = BP_POKE_BREAKPOINT;
#endif /* CONFIG_DEBUG_RODATA */
return err;
}

int kgdb_arch_remove_breakpoint(struct kgdb_bkpt *bpt)
{
#ifdef CONFIG_DEBUG_RODATA
int err;
char opc[BREAK_INSTR_SIZE];

if (bpt->type != BP_POKE_BREAKPOINT)
goto knl_write;
/*
* It is safe to call text_poke() because normal kernel execution
* is stopped on all cores, so long as the text_mutex is not locked.
*/
if (mutex_is_locked(&text_mutex))
goto knl_write;
text_poke((void *)bpt->bpt_addr, bpt->saved_instr, BREAK_INSTR_SIZE);
err = probe_kernel_read(opc, (char *)bpt->bpt_addr, BREAK_INSTR_SIZE);
if (err || memcmp(opc, bpt->saved_instr, BREAK_INSTR_SIZE))
goto knl_write;
return err;
knl_write:
#endif /* CONFIG_DEBUG_RODATA */
return probe_kernel_write((char *)bpt->bpt_addr,
(char *)bpt->saved_instr, BREAK_INSTR_SIZE);
}

struct kgdb_arch arch_kgdb_ops = {
/* Breakpoint instruction: */
.gdb_bpt_instr = { 0xcc },
Expand Down
160 changes: 115 additions & 45 deletions drivers/misc/kgdbts.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,17 @@ static int force_hwbrks;
static int hwbreaks_ok;
static int hw_break_val;
static int hw_break_val2;
static int cont_instead_of_sstep;
static unsigned long cont_thread_id;
static unsigned long sstep_thread_id;
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || defined(CONFIG_SPARC)
static int arch_needs_sstep_emulation = 1;
#else
static int arch_needs_sstep_emulation;
#endif
static unsigned long cont_addr;
static unsigned long sstep_addr;
static int restart_from_top_after_write;
static int sstep_state;

/* Storage for the registers, in GDB format. */
Expand Down Expand Up @@ -187,7 +192,8 @@ static int kgdbts_unreg_thread(void *ptr)
*/
while (!final_ack)
msleep_interruptible(1500);

/* Pause for any other threads to exit after final ack. */
msleep_interruptible(1000);
if (configured)
kgdb_unregister_io_module(&kgdbts_io_ops);
configured = 0;
Expand All @@ -211,7 +217,7 @@ static unsigned long lookup_addr(char *arg)
if (!strcmp(arg, "kgdbts_break_test"))
addr = (unsigned long)kgdbts_break_test;
else if (!strcmp(arg, "sys_open"))
addr = (unsigned long)sys_open;
addr = (unsigned long)do_sys_open;
else if (!strcmp(arg, "do_fork"))
addr = (unsigned long)do_fork;
else if (!strcmp(arg, "hw_break_val"))
Expand Down Expand Up @@ -283,6 +289,16 @@ static void hw_break_val_write(void)
hw_break_val++;
}

static int get_thread_id_continue(char *put_str, char *arg)
{
char *ptr = &put_str[11];

if (put_str[1] != 'T' || put_str[2] != '0')
return 1;
kgdb_hex2long(&ptr, &cont_thread_id);
return 0;
}

static int check_and_rewind_pc(char *put_str, char *arg)
{
unsigned long addr = lookup_addr(arg);
Expand All @@ -299,13 +315,21 @@ static int check_and_rewind_pc(char *put_str, char *arg)
if (addr + BREAK_INSTR_SIZE == ip)
offset = -BREAK_INSTR_SIZE;
#endif
if (strcmp(arg, "silent") && ip + offset != addr) {

if (arch_needs_sstep_emulation && sstep_addr &&
ip + offset == sstep_addr &&
((!strcmp(arg, "sys_open") || !strcmp(arg, "do_fork")))) {
/* This is special case for emulated single step */
v2printk("Emul: rewind hit single step bp\n");
restart_from_top_after_write = 1;
} else if (strcmp(arg, "silent") && ip + offset != addr) {
eprintk("kgdbts: BP mismatch %lx expected %lx\n",
ip + offset, addr);
return 1;
}
/* Readjust the instruction pointer if needed */
ip += offset;
cont_addr = ip;
#ifdef GDB_ADJUSTS_BREAK_OFFSET
instruction_pointer_set(&kgdbts_regs, ip);
#endif
Expand All @@ -315,6 +339,8 @@ static int check_and_rewind_pc(char *put_str, char *arg)
static int check_single_step(char *put_str, char *arg)
{
unsigned long addr = lookup_addr(arg);
static int matched_id;

/*
* From an arch indepent point of view the instruction pointer
* should be on a different instruction
Expand All @@ -324,6 +350,29 @@ static int check_single_step(char *put_str, char *arg)
gdb_regs_to_pt_regs(kgdbts_gdb_regs, &kgdbts_regs);
v2printk("Singlestep stopped at IP: %lx\n",
instruction_pointer(&kgdbts_regs));

if (sstep_thread_id != cont_thread_id) {
/*
* Ensure we stopped in the same thread id as before, else the
* debugger should continue until the original thread that was
* single stepped is scheduled again, emulating gdb's behavior.
*/
v2printk("ThrID does not match: %lx\n", cont_thread_id);
if (arch_needs_sstep_emulation) {
if (matched_id &&
instruction_pointer(&kgdbts_regs) != addr)
goto continue_test;
matched_id++;
ts.idx -= 2;
sstep_state = 0;
return 0;
}
cont_instead_of_sstep = 1;
ts.idx -= 4;
return 0;
}
continue_test:
matched_id = 0;
if (instruction_pointer(&kgdbts_regs) == addr) {
eprintk("kgdbts: SingleStep failed at %lx\n",
instruction_pointer(&kgdbts_regs));
Expand Down Expand Up @@ -365,10 +414,40 @@ static int got_break(char *put_str, char *arg)
return 1;
}

static void get_cont_catch(char *arg)
{
/* Always send detach because the test is completed at this point */
fill_get_buf("D");
}

static int put_cont_catch(char *put_str, char *arg)
{
/* This is at the end of the test and we catch any and all input */
v2printk("kgdbts: cleanup task: %lx\n", sstep_thread_id);
ts.idx--;
return 0;
}

static int emul_reset(char *put_str, char *arg)
{
if (strncmp(put_str, "$OK", 3))
return 1;
if (restart_from_top_after_write) {
restart_from_top_after_write = 0;
ts.idx = -1;
}
return 0;
}

static void emul_sstep_get(char *arg)
{
if (!arch_needs_sstep_emulation) {
fill_get_buf(arg);
if (cont_instead_of_sstep) {
cont_instead_of_sstep = 0;
fill_get_buf("c");
} else {
fill_get_buf(arg);
}
return;
}
switch (sstep_state) {
Expand Down Expand Up @@ -398,9 +477,11 @@ static void emul_sstep_get(char *arg)
static int emul_sstep_put(char *put_str, char *arg)
{
if (!arch_needs_sstep_emulation) {
if (!strncmp(put_str+1, arg, 2))
return 0;
return 1;
char *ptr = &put_str[11];
if (put_str[1] != 'T' || put_str[2] != '0')
return 1;
kgdb_hex2long(&ptr, &sstep_thread_id);
return 0;
}
switch (sstep_state) {
case 1:
Expand All @@ -411,8 +492,7 @@ static int emul_sstep_put(char *put_str, char *arg)
v2printk("Stopped at IP: %lx\n",
instruction_pointer(&kgdbts_regs));
/* Want to stop at IP + break instruction size by default */
sstep_addr = instruction_pointer(&kgdbts_regs) +
BREAK_INSTR_SIZE;
sstep_addr = cont_addr + BREAK_INSTR_SIZE;
break;
case 2:
if (strncmp(put_str, "$OK", 3)) {
Expand All @@ -424,6 +504,9 @@ static int emul_sstep_put(char *put_str, char *arg)
if (strncmp(put_str, "$T0", 3)) {
eprintk("kgdbts: failed continue sstep\n");
return 1;
} else {
char *ptr = &put_str[11];
kgdb_hex2long(&ptr, &sstep_thread_id);
}
break;
case 4:
Expand Down Expand Up @@ -502,10 +585,10 @@ static struct test_struct bad_read_test[] = {
static struct test_struct singlestep_break_test[] = {
{ "?", "S0*" }, /* Clear break points */
{ "kgdbts_break_test", "OK", sw_break, }, /* set sw breakpoint */
{ "c", "T0*", }, /* Continue */
{ "c", "T0*", NULL, get_thread_id_continue }, /* Continue */
{ "kgdbts_break_test", "OK", sw_rem_break }, /*remove breakpoint */
{ "g", "kgdbts_break_test", NULL, check_and_rewind_pc },
{ "write", "OK", write_regs }, /* Write registers */
{ "kgdbts_break_test", "OK", sw_rem_break }, /*remove breakpoint */
{ "s", "T0*", emul_sstep_get, emul_sstep_put }, /* Single step */
{ "g", "kgdbts_break_test", NULL, check_single_step },
{ "kgdbts_break_test", "OK", sw_break, }, /* set sw breakpoint */
Expand All @@ -523,16 +606,16 @@ static struct test_struct singlestep_break_test[] = {
static struct test_struct do_fork_test[] = {
{ "?", "S0*" }, /* Clear break points */
{ "do_fork", "OK", sw_break, }, /* set sw breakpoint */
{ "c", "T0*", }, /* Continue */
{ "g", "do_fork", NULL, check_and_rewind_pc }, /* check location */
{ "write", "OK", write_regs }, /* Write registers */
{ "c", "T0*", NULL, get_thread_id_continue }, /* Continue */
{ "do_fork", "OK", sw_rem_break }, /*remove breakpoint */
{ "g", "do_fork", NULL, check_and_rewind_pc }, /* check location */
{ "write", "OK", write_regs, emul_reset }, /* Write registers */
{ "s", "T0*", emul_sstep_get, emul_sstep_put }, /* Single step */
{ "g", "do_fork", NULL, check_single_step },
{ "do_fork", "OK", sw_break, }, /* set sw breakpoint */
{ "7", "T0*", skip_back_repeat_test }, /* Loop based on repeat_test */
{ "D", "OK", NULL, final_ack_set }, /* detach and unregister I/O */
{ "", "" },
{ "", "", get_cont_catch, put_cont_catch },
};

/* Test for hitting a breakpoint at sys_open for what ever the number
Expand All @@ -541,16 +624,16 @@ static struct test_struct do_fork_test[] = {
static struct test_struct sys_open_test[] = {
{ "?", "S0*" }, /* Clear break points */
{ "sys_open", "OK", sw_break, }, /* set sw breakpoint */
{ "c", "T0*", }, /* Continue */
{ "g", "sys_open", NULL, check_and_rewind_pc }, /* check location */
{ "write", "OK", write_regs }, /* Write registers */
{ "c", "T0*", NULL, get_thread_id_continue }, /* Continue */
{ "sys_open", "OK", sw_rem_break }, /*remove breakpoint */
{ "g", "sys_open", NULL, check_and_rewind_pc }, /* check location */
{ "write", "OK", write_regs, emul_reset }, /* Write registers */
{ "s", "T0*", emul_sstep_get, emul_sstep_put }, /* Single step */
{ "g", "sys_open", NULL, check_single_step },
{ "sys_open", "OK", sw_break, }, /* set sw breakpoint */
{ "7", "T0*", skip_back_repeat_test }, /* Loop based on repeat_test */
{ "D", "OK", NULL, final_ack_set }, /* detach and unregister I/O */
{ "", "" },
{ "", "", get_cont_catch, put_cont_catch },
};

/*
Expand Down Expand Up @@ -693,8 +776,8 @@ static int run_simple_test(int is_get_char, int chr)
/* This callback is a put char which is when kgdb sends data to
* this I/O module.
*/
if (ts.tst[ts.idx].get[0] == '\0' &&
ts.tst[ts.idx].put[0] == '\0') {
if (ts.tst[ts.idx].get[0] == '\0' && ts.tst[ts.idx].put[0] == '\0' &&
!ts.tst[ts.idx].get_handler) {
eprintk("kgdbts: ERROR: beyond end of test on"
" '%s' line %i\n", ts.name, ts.idx);
return 0;
Expand Down Expand Up @@ -907,6 +990,17 @@ static void kgdbts_run_tests(void)
if (ptr)
sstep_test = simple_strtol(ptr+1, NULL, 10);

/* All HW break point tests */
if (arch_kgdb_ops.flags & KGDB_HW_BREAKPOINT) {
hwbreaks_ok = 1;
v1printk("kgdbts:RUN hw breakpoint test\n");
run_breakpoint_test(1);
v1printk("kgdbts:RUN hw write breakpoint test\n");
run_hw_break_test(1);
v1printk("kgdbts:RUN access write breakpoint test\n");
run_hw_break_test(0);
}

/* required internal KGDB tests */
v1printk("kgdbts:RUN plant and detach test\n");
run_plant_and_detach_test(0);
Expand All @@ -924,35 +1018,11 @@ static void kgdbts_run_tests(void)

/* ===Optional tests=== */

/* All HW break point tests */
if (arch_kgdb_ops.flags & KGDB_HW_BREAKPOINT) {
hwbreaks_ok = 1;
v1printk("kgdbts:RUN hw breakpoint test\n");
run_breakpoint_test(1);
v1printk("kgdbts:RUN hw write breakpoint test\n");
run_hw_break_test(1);
v1printk("kgdbts:RUN access write breakpoint test\n");
run_hw_break_test(0);
}

if (nmi_sleep) {
v1printk("kgdbts:RUN NMI sleep %i seconds test\n", nmi_sleep);
run_nmi_sleep_test(nmi_sleep);
}

#ifdef CONFIG_DEBUG_RODATA
/* Until there is an api to write to read-only text segments, use
* HW breakpoints for the remainder of any tests, else print a
* failure message if hw breakpoints do not work.
*/
if (!(arch_kgdb_ops.flags & KGDB_HW_BREAKPOINT && hwbreaks_ok)) {
eprintk("kgdbts: HW breakpoints do not work,"
"skipping remaining tests\n");
return;
}
force_hwbrks = 1;
#endif /* CONFIG_DEBUG_RODATA */

/* If the do_fork test is run it will be the last test that is
* executed because a kernel thread will be spawned at the very
* end to unregister the debug hooks.
Expand Down
Loading

0 comments on commit 6c216ec

Please sign in to comment.