215 lines
4.7 KiB
C
215 lines
4.7 KiB
C
// 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 <string.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 breakpoint 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_suite *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;
|
|
}
|