diff options
Diffstat (limited to 'tools/perf/arch/x86/tests')
| -rw-r--r-- | tools/perf/arch/x86/tests/Build | 1 | ||||
| -rw-r--r-- | tools/perf/arch/x86/tests/arch-tests.c | 6 | ||||
| -rw-r--r-- | tools/perf/arch/x86/tests/bp-modify.c | 213 | 
3 files changed, 220 insertions, 0 deletions
| diff --git a/tools/perf/arch/x86/tests/Build b/tools/perf/arch/x86/tests/Build index 8e2c5a38c3b9..586849ff83a0 100644 --- a/tools/perf/arch/x86/tests/Build +++ b/tools/perf/arch/x86/tests/Build @@ -5,3 +5,4 @@ libperf-y += arch-tests.o  libperf-y += rdpmc.o  libperf-y += perf-time-to-tsc.o  libperf-$(CONFIG_AUXTRACE) += insn-x86.o +libperf-$(CONFIG_X86_64) += bp-modify.o diff --git a/tools/perf/arch/x86/tests/arch-tests.c b/tools/perf/arch/x86/tests/arch-tests.c index cc1802ff5410..d47d3f8e3c8e 100644 --- a/tools/perf/arch/x86/tests/arch-tests.c +++ b/tools/perf/arch/x86/tests/arch-tests.c @@ -24,6 +24,12 @@ struct test arch_tests[] = {  		.func = test__insn_x86,  	},  #endif +#if defined(__x86_64__) +	{ +		.desc = "x86 bp modify", +		.func = test__bp_modify, +	}, +#endif  	{  		.func = NULL,  	}, diff --git a/tools/perf/arch/x86/tests/bp-modify.c b/tools/perf/arch/x86/tests/bp-modify.c new file mode 100644 index 000000000000..f53e4406709f --- /dev/null +++ b/tools/perf/arch/x86/tests/bp-modify.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/compiler.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/user.h> +#include <syscall.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ptrace.h> +#include <asm/ptrace.h> +#include <errno.h> +#include "debug.h" +#include "tests/tests.h" +#include "arch-tests.h" + +static noinline int bp_1(void) +{ +	pr_debug("in %s\n", __func__); +	return 0; +} + +static noinline int bp_2(void) +{ +	pr_debug("in %s\n", __func__); +	return 0; +} + +static int spawn_child(void) +{ +	int child = fork(); + +	if (child == 0) { +		/* +		 * The child sets itself for as tracee and +		 * waits in signal for parent to trace it, +		 * then it calls bp_1 and quits. +		 */ +		int err = ptrace(PTRACE_TRACEME, 0, NULL, NULL); + +		if (err) { +			pr_debug("failed to PTRACE_TRACEME\n"); +			exit(1); +		} + +		raise(SIGCONT); +		bp_1(); +		exit(0); +	} + +	return child; +} + +/* + * This tests creates HW breakpoint, tries to + * change it and checks it was properly changed. + */ +static int bp_modify1(void) +{ +	pid_t child; +	int status; +	unsigned long rip = 0, dr7 = 1; + +	child = spawn_child(); + +	waitpid(child, &status, 0); +	if (WIFEXITED(status)) { +		pr_debug("tracee exited prematurely 1\n"); +		return TEST_FAIL; +	} + +	/* +	 * The parent does following steps: +	 *  - creates a new breakpoint (id 0) for bp_2 function +	 *  - changes that breakponit to bp_1 function +	 *  - waits for the breakpoint to hit and checks +	 *    it has proper rip of bp_1 function +	 *  - detaches the child +	 */ +	if (ptrace(PTRACE_POKEUSER, child, +		   offsetof(struct user, u_debugreg[0]), bp_2)) { +		pr_debug("failed to set breakpoint, 1st time: %s\n", +			 strerror(errno)); +		goto out; +	} + +	if (ptrace(PTRACE_POKEUSER, child, +		   offsetof(struct user, u_debugreg[0]), bp_1)) { +		pr_debug("failed to set breakpoint, 2nd time: %s\n", +			 strerror(errno)); +		goto out; +	} + +	if (ptrace(PTRACE_POKEUSER, child, +		   offsetof(struct user, u_debugreg[7]), dr7)) { +		pr_debug("failed to set dr7: %s\n", strerror(errno)); +		goto out; +	} + +	if (ptrace(PTRACE_CONT, child, NULL, NULL)) { +		pr_debug("failed to PTRACE_CONT: %s\n", strerror(errno)); +		goto out; +	} + +	waitpid(child, &status, 0); +	if (WIFEXITED(status)) { +		pr_debug("tracee exited prematurely 2\n"); +		return TEST_FAIL; +	} + +	rip = ptrace(PTRACE_PEEKUSER, child, +		     offsetof(struct user_regs_struct, rip), NULL); +	if (rip == (unsigned long) -1) { +		pr_debug("failed to PTRACE_PEEKUSER: %s\n", +			 strerror(errno)); +		goto out; +	} + +	pr_debug("rip %lx, bp_1 %p\n", rip, bp_1); + +out: +	if (ptrace(PTRACE_DETACH, child, NULL, NULL)) { +		pr_debug("failed to PTRACE_DETACH: %s", strerror(errno)); +		return TEST_FAIL; +	} + +	return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL; +} + +/* + * This tests creates HW breakpoint, tries to + * change it to bogus value and checks the original + * breakpoint is hit. + */ +static int bp_modify2(void) +{ +	pid_t child; +	int status; +	unsigned long rip = 0, dr7 = 1; + +	child = spawn_child(); + +	waitpid(child, &status, 0); +	if (WIFEXITED(status)) { +		pr_debug("tracee exited prematurely 1\n"); +		return TEST_FAIL; +	} + +	/* +	 * The parent does following steps: +	 *  - creates a new breakpoint (id 0) for bp_1 function +	 *  - tries to change that breakpoint to (-1) address +	 *  - waits for the breakpoint to hit and checks +	 *    it has proper rip of bp_1 function +	 *  - detaches the child +	 */ +	if (ptrace(PTRACE_POKEUSER, child, +		   offsetof(struct user, u_debugreg[0]), bp_1)) { +		pr_debug("failed to set breakpoint: %s\n", +			 strerror(errno)); +		goto out; +	} + +	if (ptrace(PTRACE_POKEUSER, child, +		   offsetof(struct user, u_debugreg[7]), dr7)) { +		pr_debug("failed to set dr7: %s\n", strerror(errno)); +		goto out; +	} + +	if (!ptrace(PTRACE_POKEUSER, child, +		   offsetof(struct user, u_debugreg[0]), (unsigned long) (-1))) { +		pr_debug("failed, breakpoint set to bogus address\n"); +		goto out; +	} + +	if (ptrace(PTRACE_CONT, child, NULL, NULL)) { +		pr_debug("failed to PTRACE_CONT: %s\n", strerror(errno)); +		goto out; +	} + +	waitpid(child, &status, 0); +	if (WIFEXITED(status)) { +		pr_debug("tracee exited prematurely 2\n"); +		return TEST_FAIL; +	} + +	rip = ptrace(PTRACE_PEEKUSER, child, +		     offsetof(struct user_regs_struct, rip), NULL); +	if (rip == (unsigned long) -1) { +		pr_debug("failed to PTRACE_PEEKUSER: %s\n", +			 strerror(errno)); +		goto out; +	} + +	pr_debug("rip %lx, bp_1 %p\n", rip, bp_1); + +out: +	if (ptrace(PTRACE_DETACH, child, NULL, NULL)) { +		pr_debug("failed to PTRACE_DETACH: %s", strerror(errno)); +		return TEST_FAIL; +	} + +	return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL; +} + +int test__bp_modify(struct test *test __maybe_unused, +		    int subtest __maybe_unused) +{ +	TEST_ASSERT_VAL("modify test 1 failed\n", !bp_modify1()); +	TEST_ASSERT_VAL("modify test 2 failed\n", !bp_modify2()); + +	return 0; +} |