1192 lines · 30.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * kgdbts is a test suite for kgdb for the sole purpose of validating * that key pieces of the kgdb internals are working properly such as * HW/SW breakpoints, single stepping, and NMI. * * Created by: Jason Wessel <jason.wessel@windriver.com> * * Copyright (c) 2008 Wind River Systems, Inc. *//* Information about the kgdb test suite. * ------------------------------------- * * The kgdb test suite is designed as a KGDB I/O module which * simulates the communications that a debugger would have with kgdb. * The tests are broken up in to a line by line and referenced here as * a "get" which is kgdb requesting input and "put" which is kgdb * sending a response. * * The kgdb suite can be invoked from the kernel command line * arguments system or executed dynamically at run time.  The test * suite uses the variable "kgdbts" to obtain the information about * which tests to run and to configure the verbosity level.  The * following are the various characters you can use with the kgdbts= * line: * * When using the "kgdbts=" you only choose one of the following core * test types: * A = Run all the core tests silently * V1 = Run all the core tests with minimal output * V2 = Run all the core tests in debug mode * * You can also specify optional tests: * N## = Go to sleep with interrupts of for ## seconds *       to test the HW NMI watchdog * F## = Break at kernel_clone for ## iterations * S## = Break at sys_open for ## iterations * I## = Run the single step test ## iterations * * NOTE: that the kernel_clone and sys_open tests are mutually exclusive. * * To invoke the kgdb test suite from boot you use a kernel start * argument as follows: * 	kgdbts=V1 kgdbwait * Or if you wanted to perform the NMI test for 6 seconds and kernel_clone * test for 100 forks, you could use: * 	kgdbts=V1N6F100 kgdbwait * * The test suite can also be invoked at run time with: *	echo kgdbts=V1N6F100 > /sys/module/kgdbts/parameters/kgdbts * Or as another example: *	echo kgdbts=V2 > /sys/module/kgdbts/parameters/kgdbts * * When developing a new kgdb arch specific implementation or * using these tests for the purpose of regression testing, * several invocations are required. * * 1) Boot with the test suite enabled by using the kernel arguments *       "kgdbts=V1F100 kgdbwait" *    ## If kgdb arch specific implementation has NMI use *       "kgdbts=V1N6F100 * * 2) After the system boot run the basic test. * echo kgdbts=V1 > /sys/module/kgdbts/parameters/kgdbts * * 3) Run the concurrency tests.  It is best to use n+1 *    while loops where n is the number of cpus you have *    in your system.  The example below uses only two *    loops. * * ## This tests break points on sys_open * while [ 1 ] ; do find / > /dev/null 2>&1 ; done & * while [ 1 ] ; do find / > /dev/null 2>&1 ; done & * echo kgdbts=V1S10000 > /sys/module/kgdbts/parameters/kgdbts * fg # and hit control-c * fg # and hit control-c * ## This tests break points on kernel_clone * while [ 1 ] ; do date > /dev/null ; done & * while [ 1 ] ; do date > /dev/null ; done & * echo kgdbts=V1F1000 > /sys/module/kgdbts/parameters/kgdbts * fg # and hit control-c * */ #include <linux/kernel.h>#include <linux/kgdb.h>#include <linux/ctype.h>#include <linux/uaccess.h>#include <linux/syscalls.h>#include <linux/nmi.h>#include <linux/delay.h>#include <linux/hex.h>#include <linux/kthread.h>#include <linux/module.h>#include <linux/sched/task.h>#include <linux/kallsyms.h> #include <asm/sections.h>#include <asm/rwonce.h> #define v1printk(a...) do {		\	if (verbose)			\		printk(KERN_INFO a);	\} while (0)#define v2printk(a...) do {		\	if (verbose > 1) {		\		printk(KERN_INFO a);	\	}				\	touch_nmi_watchdog();		\} while (0)#define eprintk(a...) do {		\	printk(KERN_ERR a);		\	WARN_ON(1);			\} while (0)#define MAX_CONFIG_LEN		40 static struct kgdb_io kgdbts_io_ops;static char get_buf[BUFMAX];static int get_buf_cnt;static char put_buf[BUFMAX];static int put_buf_cnt;static char scratch_buf[BUFMAX];static int verbose;static int repeat_test;static int test_complete;static int send_ack;static int final_ack;static int force_hwbrks;static int hwbreaks_ok;static int hw_break_val;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;#elsestatic int arch_needs_sstep_emulation;#endifstatic 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. */static unsigned long kgdbts_gdb_regs[(NUMREGBYTES +					sizeof(unsigned long) - 1) /					sizeof(unsigned long)];static struct pt_regs kgdbts_regs; /* -1 = init not run yet, 0 = unconfigured, 1 = configured. */static int configured		= -1; #ifdef CONFIG_KGDB_TESTS_BOOT_STRINGstatic char config[MAX_CONFIG_LEN] = CONFIG_KGDB_TESTS_BOOT_STRING;#elsestatic char config[MAX_CONFIG_LEN];#endifstatic struct kparam_string kps = {	.string			= config,	.maxlen			= MAX_CONFIG_LEN,}; static void fill_get_buf(char *buf); struct test_struct {	char *get;	char *put;	void (*get_handler)(char *);	int (*put_handler)(char *, char *);}; struct test_state {	char *name;	struct test_struct *tst;	int idx;	int (*run_test) (int, int);	int (*validate_put) (char *);}; static struct test_state ts; static int kgdbts_unreg_thread(void *ptr){	/* Wait until the tests are complete and then ungresiter the I/O	 * driver.	 */	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; 	return 0;} /* This is noinline such that it can be used for a single location to * place a breakpoint */static noinline void kgdbts_break_test(void){	v2printk("kgdbts: breakpoint complete\n");} /* * This is a cached wrapper for kallsyms_lookup_name(). * * The cache is a big win for several tests. For example it more the doubles * the cycles per second during the sys_open test. This is not theoretic, * the performance improvement shows up at human scale, especially when * testing using emulators. * * Obviously neither re-entrant nor thread-safe but that is OK since it * can only be called from the debug trap (and therefore all other CPUs * are halted). */static unsigned long lookup_addr(char *arg){	static char cached_arg[KSYM_NAME_LEN];	static unsigned long cached_addr; 	if (strcmp(arg, cached_arg)) {		strscpy(cached_arg, arg, KSYM_NAME_LEN);		cached_addr = kallsyms_lookup_name(arg);	} 	return (unsigned long)dereference_function_descriptor(			(void *)cached_addr);} static void break_helper(char *bp_type, char *arg, unsigned long vaddr){	unsigned long addr; 	if (arg)		addr = lookup_addr(arg);	else		addr = vaddr; 	sprintf(scratch_buf, "%s,%lx,%i", bp_type, addr,		BREAK_INSTR_SIZE);	fill_get_buf(scratch_buf);} static void sw_break(char *arg){	break_helper(force_hwbrks ? "Z1" : "Z0", arg, 0);} static void sw_rem_break(char *arg){	break_helper(force_hwbrks ? "z1" : "z0", arg, 0);} static void hw_break(char *arg){	break_helper("Z1", arg, 0);} static void hw_rem_break(char *arg){	break_helper("z1", arg, 0);} static void hw_write_break(char *arg){	break_helper("Z2", arg, 0);} static void hw_rem_write_break(char *arg){	break_helper("z2", arg, 0);} static void hw_access_break(char *arg){	break_helper("Z4", arg, 0);} static void hw_rem_access_break(char *arg){	break_helper("z4", arg, 0);} static void hw_break_val_access(void){	READ_ONCE(hw_break_val);} 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);	unsigned long ip;	int offset = 0; 	kgdb_hex2mem(&put_str[1], (char *)kgdbts_gdb_regs,		 NUMREGBYTES);	gdb_regs_to_pt_regs(kgdbts_gdb_regs, &kgdbts_regs);	ip = instruction_pointer(&kgdbts_regs);	v2printk("Stopped at IP: %lx\n", ip);#ifdef GDB_ADJUSTS_BREAK_OFFSET	/* On some arches, a breakpoint stop requires it to be decremented */	if (addr + BREAK_INSTR_SIZE == ip)		offset = -BREAK_INSTR_SIZE;#endif 	if (arch_needs_sstep_emulation && sstep_addr &&	    ip + offset == sstep_addr &&	    ((!strcmp(arg, "do_sys_openat2") || !strcmp(arg, "kernel_clone")))) {		/* 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	return 0;} 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	 */	kgdb_hex2mem(&put_str[1], (char *)kgdbts_gdb_regs,		 NUMREGBYTES);	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));		return 1;	} 	return 0;} static void write_regs(char *arg){	memset(scratch_buf, 0, sizeof(scratch_buf));	scratch_buf[0] = 'G';	pt_regs_to_gdb_regs(kgdbts_gdb_regs, &kgdbts_regs);	kgdb_mem2hex((char *)kgdbts_gdb_regs, &scratch_buf[1], NUMREGBYTES);	fill_get_buf(scratch_buf);} static void skip_back_repeat_test(char *arg){	int go_back = simple_strtol(arg, NULL, 10); 	repeat_test--;	if (repeat_test <= 0) {		ts.idx++;	} else {		if (repeat_test % 100 == 0)			v1printk("kgdbts:RUN ... %d remaining\n", repeat_test); 		ts.idx -= go_back;	}	fill_get_buf(ts.tst[ts.idx].get);} static int got_break(char *put_str, char *arg){	test_complete = 1;	if (!strncmp(put_str+1, arg, 2)) {		if (!strncmp(arg, "T0", 2))			test_complete = 2;		return 0;	}	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) {		if (cont_instead_of_sstep) {			cont_instead_of_sstep = 0;			fill_get_buf("c");		} else {			fill_get_buf(arg);		}		return;	}	switch (sstep_state) {	case 0:		v2printk("Emulate single step\n");		/* Start by looking at the current PC */		fill_get_buf("g");		break;	case 1:		/* set breakpoint */		break_helper("Z0", NULL, sstep_addr);		break;	case 2:		/* Continue */		fill_get_buf("c");		break;	case 3:		/* Clear breakpoint */		break_helper("z0", NULL, sstep_addr);		break;	default:		eprintk("kgdbts: ERROR failed sstep get emulation\n");	}	sstep_state++;} static int emul_sstep_put(char *put_str, char *arg){	if (!arch_needs_sstep_emulation) {		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:		/* validate the "g" packet to get the IP */		kgdb_hex2mem(&put_str[1], (char *)kgdbts_gdb_regs,			 NUMREGBYTES);		gdb_regs_to_pt_regs(kgdbts_gdb_regs, &kgdbts_regs);		v2printk("Stopped at IP: %lx\n",

Lines 1–500 of 1192.