diff options
Diffstat (limited to 'drivers/misc/kgdbts.c')
| -rw-r--r-- | drivers/misc/kgdbts.c | 160 | 
1 files changed, 115 insertions, 45 deletions
diff --git a/drivers/misc/kgdbts.c b/drivers/misc/kgdbts.c index 3f7ad83ed74..3aa9a969b37 100644 --- a/drivers/misc/kgdbts.c +++ b/drivers/misc/kgdbts.c @@ -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. */ @@ -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; @@ -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")) @@ -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); @@ -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 @@ -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 @@ -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)); @@ -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) { @@ -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: @@ -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)) { @@ -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: @@ -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 */ @@ -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 @@ -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 },  };  /* @@ -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; @@ -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); @@ -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.  |