diff options
Diffstat (limited to 'tools/testing/selftests/breakpoints/breakpoint_test.c')
| -rw-r--r-- | tools/testing/selftests/breakpoints/breakpoint_test.c | 394 | 
1 files changed, 394 insertions, 0 deletions
diff --git a/tools/testing/selftests/breakpoints/breakpoint_test.c b/tools/testing/selftests/breakpoints/breakpoint_test.c new file mode 100644 index 00000000000..a0743f3b2b5 --- /dev/null +++ b/tools/testing/selftests/breakpoints/breakpoint_test.c @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2011 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com> + * + * Licensed under the terms of the GNU GPL License version 2 + * + * Selftests for breakpoints (and more generally the do_debug() path) in x86. + */ + + +#include <sys/ptrace.h> +#include <unistd.h> +#include <stddef.h> +#include <sys/user.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> + + +/* Breakpoint access modes */ +enum { +	BP_X = 1, +	BP_RW = 2, +	BP_W = 4, +}; + +static pid_t child_pid; + +/* + * Ensures the child and parent are always "talking" about + * the same test sequence. (ie: that we haven't forgotten + * to call check_trapped() somewhere). + */ +static int nr_tests; + +static void set_breakpoint_addr(void *addr, int n) +{ +	int ret; + +	ret = ptrace(PTRACE_POKEUSER, child_pid, +		     offsetof(struct user, u_debugreg[n]), addr); +	if (ret) { +		perror("Can't set breakpoint addr\n"); +		exit(-1); +	} +} + +static void toggle_breakpoint(int n, int type, int len, +			      int local, int global, int set) +{ +	int ret; + +	int xtype, xlen; +	unsigned long vdr7, dr7; + +	switch (type) { +	case BP_X: +		xtype = 0; +		break; +	case BP_W: +		xtype = 1; +		break; +	case BP_RW: +		xtype = 3; +		break; +	} + +	switch (len) { +	case 1: +		xlen = 0; +		break; +	case 2: +		xlen = 4; +		break; +	case 4: +		xlen = 0xc; +		break; +	case 8: +		xlen = 8; +		break; +	} + +	dr7 = ptrace(PTRACE_PEEKUSER, child_pid, +		     offsetof(struct user, u_debugreg[7]), 0); + +	vdr7 = (xlen | xtype) << 16; +	vdr7 <<= 4 * n; + +	if (local) { +		vdr7 |= 1 << (2 * n); +		vdr7 |= 1 << 8; +	} +	if (global) { +		vdr7 |= 2 << (2 * n); +		vdr7 |= 1 << 9; +	} + +	if (set) +		dr7 |= vdr7; +	else +		dr7 &= ~vdr7; + +	ret = ptrace(PTRACE_POKEUSER, child_pid, +		     offsetof(struct user, u_debugreg[7]), dr7); +	if (ret) { +		perror("Can't set dr7"); +		exit(-1); +	} +} + +/* Dummy variables to test read/write accesses */ +static unsigned long long dummy_var[4]; + +/* Dummy functions to test execution accesses */ +static void dummy_func(void) { } +static void dummy_func1(void) { } +static void dummy_func2(void) { } +static void dummy_func3(void) { } + +static void (*dummy_funcs[])(void) = { +	dummy_func, +	dummy_func1, +	dummy_func2, +	dummy_func3, +}; + +static int trapped; + +static void check_trapped(void) +{ +	/* +	 * If we haven't trapped, wake up the parent +	 * so that it notices the failure. +	 */ +	if (!trapped) +		kill(getpid(), SIGUSR1); +	trapped = 0; + +	nr_tests++; +} + +static void write_var(int len) +{ +	char *pcval; short *psval; int *pival; long long *plval; +	int i; + +	for (i = 0; i < 4; i++) { +		switch (len) { +		case 1: +			pcval = (char *)&dummy_var[i]; +			*pcval = 0xff; +			break; +		case 2: +			psval = (short *)&dummy_var[i]; +			*psval = 0xffff; +			break; +		case 4: +			pival = (int *)&dummy_var[i]; +			*pival = 0xffffffff; +			break; +		case 8: +			plval = (long long *)&dummy_var[i]; +			*plval = 0xffffffffffffffffLL; +			break; +		} +		check_trapped(); +	} +} + +static void read_var(int len) +{ +	char cval; short sval; int ival; long long lval; +	int i; + +	for (i = 0; i < 4; i++) { +		switch (len) { +		case 1: +			cval = *(char *)&dummy_var[i]; +			break; +		case 2: +			sval = *(short *)&dummy_var[i]; +			break; +		case 4: +			ival = *(int *)&dummy_var[i]; +			break; +		case 8: +			lval = *(long long *)&dummy_var[i]; +			break; +		} +		check_trapped(); +	} +} + +/* + * Do the r/w/x accesses to trigger the breakpoints. And run + * the usual traps. + */ +static void trigger_tests(void) +{ +	int len, local, global, i; +	char val; +	int ret; + +	ret = ptrace(PTRACE_TRACEME, 0, NULL, 0); +	if (ret) { +		perror("Can't be traced?\n"); +		return; +	} + +	/* Wake up father so that it sets up the first test */ +	kill(getpid(), SIGUSR1); + +	/* Test instruction breakpoints */ +	for (local = 0; local < 2; local++) { +		for (global = 0; global < 2; global++) { +			if (!local && !global) +				continue; + +			for (i = 0; i < 4; i++) { +				dummy_funcs[i](); +				check_trapped(); +			} +		} +	} + +	/* Test write watchpoints */ +	for (len = 1; len <= sizeof(long); len <<= 1) { +		for (local = 0; local < 2; local++) { +			for (global = 0; global < 2; global++) { +				if (!local && !global) +					continue; +				write_var(len); +			} +		} +	} + +	/* Test read/write watchpoints (on read accesses) */ +	for (len = 1; len <= sizeof(long); len <<= 1) { +		for (local = 0; local < 2; local++) { +			for (global = 0; global < 2; global++) { +				if (!local && !global) +					continue; +				read_var(len); +			} +		} +	} + +	/* Icebp trap */ +	asm(".byte 0xf1\n"); +	check_trapped(); + +	/* Int 3 trap */ +	asm("int $3\n"); +	check_trapped(); + +	kill(getpid(), SIGUSR1); +} + +static void check_success(const char *msg) +{ +	const char *msg2; +	int child_nr_tests; +	int status; + +	/* Wait for the child to SIGTRAP */ +	wait(&status); + +	msg2 = "Failed"; + +	if (WSTOPSIG(status) == SIGTRAP) { +		child_nr_tests = ptrace(PTRACE_PEEKDATA, child_pid, +					&nr_tests, 0); +		if (child_nr_tests == nr_tests) +			msg2 = "Ok"; +		if (ptrace(PTRACE_POKEDATA, child_pid, &trapped, 1)) { +			perror("Can't poke\n"); +			exit(-1); +		} +	} + +	nr_tests++; + +	printf("%s [%s]\n", msg, msg2); +} + +static void launch_instruction_breakpoints(char *buf, int local, int global) +{ +	int i; + +	for (i = 0; i < 4; i++) { +		set_breakpoint_addr(dummy_funcs[i], i); +		toggle_breakpoint(i, BP_X, 1, local, global, 1); +		ptrace(PTRACE_CONT, child_pid, NULL, 0); +		sprintf(buf, "Test breakpoint %d with local: %d global: %d", +			i, local, global); +		check_success(buf); +		toggle_breakpoint(i, BP_X, 1, local, global, 0); +	} +} + +static void launch_watchpoints(char *buf, int mode, int len, +			       int local, int global) +{ +	const char *mode_str; +	int i; + +	if (mode == BP_W) +		mode_str = "write"; +	else +		mode_str = "read"; + +	for (i = 0; i < 4; i++) { +		set_breakpoint_addr(&dummy_var[i], i); +		toggle_breakpoint(i, mode, len, local, global, 1); +		ptrace(PTRACE_CONT, child_pid, NULL, 0); +		sprintf(buf, "Test %s watchpoint %d with len: %d local: " +			"%d global: %d", mode_str, i, len, local, global); +		check_success(buf); +		toggle_breakpoint(i, mode, len, local, global, 0); +	} +} + +/* Set the breakpoints and check the child successfully trigger them */ +static void launch_tests(void) +{ +	char buf[1024]; +	int len, local, global, i; + +	/* Instruction breakpoints */ +	for (local = 0; local < 2; local++) { +		for (global = 0; global < 2; global++) { +			if (!local && !global) +				continue; +			launch_instruction_breakpoints(buf, local, global); +		} +	} + +	/* Write watchpoint */ +	for (len = 1; len <= sizeof(long); len <<= 1) { +		for (local = 0; local < 2; local++) { +			for (global = 0; global < 2; global++) { +				if (!local && !global) +					continue; +				launch_watchpoints(buf, BP_W, len, +						   local, global); +			} +		} +	} + +	/* Read-Write watchpoint */ +	for (len = 1; len <= sizeof(long); len <<= 1) { +		for (local = 0; local < 2; local++) { +			for (global = 0; global < 2; global++) { +				if (!local && !global) +					continue; +				launch_watchpoints(buf, BP_RW, len, +						   local, global); +			} +		} +	} + +	/* Icebp traps */ +	ptrace(PTRACE_CONT, child_pid, NULL, 0); +	check_success("Test icebp"); + +	/* Int 3 traps */ +	ptrace(PTRACE_CONT, child_pid, NULL, 0); +	check_success("Test int 3 trap"); + +	ptrace(PTRACE_CONT, child_pid, NULL, 0); +} + +int main(int argc, char **argv) +{ +	pid_t pid; +	int ret; + +	pid = fork(); +	if (!pid) { +		trigger_tests(); +		return 0; +	} + +	child_pid = pid; + +	wait(NULL); + +	launch_tests(); + +	wait(NULL); + +	return 0; +}  | 
