| // 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; |
| } |