mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-05 18:39:59 +00:00
selftests/powerpc: Add ptrace hw breakpoint test
[ Upstream commit9c2ddfe55c
] This test the ptrace hw breakpoints via PTRACE_SET_DEBUGREG and PPC_PTRACE_SETHWDEBUG. This test was use to find the bugs fixed by these recent commits:4f7c06e26e
powerpc/ptrace: Fix setting 512B aligned breakpoints with PTRACE_SET_DEBUGREGcd6ef7eebf
powerpc/ptrace: Fix enforcement of DAWR constraints Signed-off-by: Michael Neuling <mikey@neuling.org> [mpe: Add SPDX tag, clang format it] Signed-off-by: Michael Ellerman <mpe@ellerman.id.au> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
parent
672fdbd596
commit
07e2121f4f
3 changed files with 344 additions and 1 deletions
|
@ -8,3 +8,4 @@ ptrace-vsx
|
|||
ptrace-tm-vsx
|
||||
ptrace-tm-spd-vsx
|
||||
ptrace-tm-spr
|
||||
ptrace-hwbreak
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
TEST_PROGS := ptrace-gpr ptrace-tm-gpr ptrace-tm-spd-gpr \
|
||||
ptrace-tar ptrace-tm-tar ptrace-tm-spd-tar ptrace-vsx ptrace-tm-vsx \
|
||||
ptrace-tm-spd-vsx ptrace-tm-spr
|
||||
ptrace-tm-spd-vsx ptrace-tm-spr ptrace-hwbreak
|
||||
|
||||
include ../../lib.mk
|
||||
|
||||
|
|
342
tools/testing/selftests/powerpc/ptrace/ptrace-hwbreak.c
Normal file
342
tools/testing/selftests/powerpc/ptrace/ptrace-hwbreak.c
Normal file
|
@ -0,0 +1,342 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
/*
|
||||
* Ptrace test for hw breakpoints
|
||||
*
|
||||
* Based on tools/testing/selftests/breakpoints/breakpoint_test.c
|
||||
*
|
||||
* This test forks and the parent then traces the child doing various
|
||||
* types of ptrace enabled breakpoints
|
||||
*
|
||||
* Copyright (C) 2018 Michael Neuling, IBM Corporation.
|
||||
*/
|
||||
|
||||
#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>
|
||||
#include "ptrace.h"
|
||||
|
||||
/* Breakpoint access modes */
|
||||
enum {
|
||||
BP_X = 1,
|
||||
BP_RW = 2,
|
||||
BP_W = 4,
|
||||
};
|
||||
|
||||
static pid_t child_pid;
|
||||
static struct ppc_debug_info dbginfo;
|
||||
|
||||
static void get_dbginfo(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ptrace(PPC_PTRACE_GETHWDBGINFO, child_pid, NULL, &dbginfo);
|
||||
if (ret) {
|
||||
perror("Can't get breakpoint info\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool hwbreak_present(void)
|
||||
{
|
||||
return (dbginfo.num_data_bps != 0);
|
||||
}
|
||||
|
||||
static bool dawr_present(void)
|
||||
{
|
||||
return !!(dbginfo.features & PPC_DEBUG_FEATURE_DATA_BP_DAWR);
|
||||
}
|
||||
|
||||
static void set_breakpoint_addr(void *addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ptrace(PTRACE_SET_DEBUGREG, child_pid, 0, addr);
|
||||
if (ret) {
|
||||
perror("Can't set breakpoint addr\n");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
static int set_hwbreakpoint_addr(void *addr, int range)
|
||||
{
|
||||
int ret;
|
||||
|
||||
struct ppc_hw_breakpoint info;
|
||||
|
||||
info.version = 1;
|
||||
info.trigger_type = PPC_BREAKPOINT_TRIGGER_RW;
|
||||
info.addr_mode = PPC_BREAKPOINT_MODE_EXACT;
|
||||
if (range > 0)
|
||||
info.addr_mode = PPC_BREAKPOINT_MODE_RANGE_INCLUSIVE;
|
||||
info.condition_mode = PPC_BREAKPOINT_CONDITION_NONE;
|
||||
info.addr = (__u64)addr;
|
||||
info.addr2 = (__u64)addr + range;
|
||||
info.condition_value = 0;
|
||||
|
||||
ret = ptrace(PPC_PTRACE_SETHWDEBUG, child_pid, 0, &info);
|
||||
if (ret < 0) {
|
||||
perror("Can't set breakpoint\n");
|
||||
exit(-1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int del_hwbreakpoint_addr(int watchpoint_handle)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, watchpoint_handle);
|
||||
if (ret < 0) {
|
||||
perror("Can't delete hw breakpoint\n");
|
||||
exit(-1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define DAWR_LENGTH_MAX 512
|
||||
|
||||
/* Dummy variables to test read/write accesses */
|
||||
static unsigned long long
|
||||
dummy_array[DAWR_LENGTH_MAX / sizeof(unsigned long long)]
|
||||
__attribute__((aligned(512)));
|
||||
static unsigned long long *dummy_var = dummy_array;
|
||||
|
||||
static void write_var(int len)
|
||||
{
|
||||
long long *plval;
|
||||
char *pcval;
|
||||
short *psval;
|
||||
int *pival;
|
||||
|
||||
switch (len) {
|
||||
case 1:
|
||||
pcval = (char *)dummy_var;
|
||||
*pcval = 0xff;
|
||||
break;
|
||||
case 2:
|
||||
psval = (short *)dummy_var;
|
||||
*psval = 0xffff;
|
||||
break;
|
||||
case 4:
|
||||
pival = (int *)dummy_var;
|
||||
*pival = 0xffffffff;
|
||||
break;
|
||||
case 8:
|
||||
plval = (long long *)dummy_var;
|
||||
*plval = 0xffffffffffffffffLL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_var(int len)
|
||||
{
|
||||
char cval __attribute__((unused));
|
||||
short sval __attribute__((unused));
|
||||
int ival __attribute__((unused));
|
||||
long long lval __attribute__((unused));
|
||||
|
||||
switch (len) {
|
||||
case 1:
|
||||
cval = *(char *)dummy_var;
|
||||
break;
|
||||
case 2:
|
||||
sval = *(short *)dummy_var;
|
||||
break;
|
||||
case 4:
|
||||
ival = *(int *)dummy_var;
|
||||
break;
|
||||
case 8:
|
||||
lval = *(long long *)dummy_var;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Do the r/w accesses to trigger the breakpoints. And run
|
||||
* the usual traps.
|
||||
*/
|
||||
static void trigger_tests(void)
|
||||
{
|
||||
int len, 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 write watchpoints */
|
||||
for (len = 1; len <= sizeof(long); len <<= 1)
|
||||
write_var(len);
|
||||
|
||||
/* Test read/write watchpoints (on read accesses) */
|
||||
for (len = 1; len <= sizeof(long); len <<= 1)
|
||||
read_var(len);
|
||||
|
||||
/* Test when breakpoint is unset */
|
||||
|
||||
/* Test write watchpoints */
|
||||
for (len = 1; len <= sizeof(long); len <<= 1)
|
||||
write_var(len);
|
||||
|
||||
/* Test read/write watchpoints (on read accesses) */
|
||||
for (len = 1; len <= sizeof(long); len <<= 1)
|
||||
read_var(len);
|
||||
}
|
||||
|
||||
static void check_success(const char *msg)
|
||||
{
|
||||
const char *msg2;
|
||||
int status;
|
||||
|
||||
/* Wait for the child to SIGTRAP */
|
||||
wait(&status);
|
||||
|
||||
msg2 = "Failed";
|
||||
|
||||
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
|
||||
msg2 = "Child process hit the breakpoint";
|
||||
}
|
||||
|
||||
printf("%s Result: [%s]\n", msg, msg2);
|
||||
}
|
||||
|
||||
static void launch_watchpoints(char *buf, int mode, int len,
|
||||
struct ppc_debug_info *dbginfo, bool dawr)
|
||||
{
|
||||
const char *mode_str;
|
||||
unsigned long data = (unsigned long)(dummy_var);
|
||||
int wh, range;
|
||||
|
||||
data &= ~0x7UL;
|
||||
|
||||
if (mode == BP_W) {
|
||||
data |= (1UL << 1);
|
||||
mode_str = "write";
|
||||
} else {
|
||||
data |= (1UL << 0);
|
||||
data |= (1UL << 1);
|
||||
mode_str = "read";
|
||||
}
|
||||
|
||||
/* Set DABR_TRANSLATION bit */
|
||||
data |= (1UL << 2);
|
||||
|
||||
/* use PTRACE_SET_DEBUGREG breakpoints */
|
||||
set_breakpoint_addr((void *)data);
|
||||
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
||||
sprintf(buf, "Test %s watchpoint with len: %d ", mode_str, len);
|
||||
check_success(buf);
|
||||
/* Unregister hw brkpoint */
|
||||
set_breakpoint_addr(NULL);
|
||||
|
||||
data = (data & ~7); /* remove dabr control bits */
|
||||
|
||||
/* use PPC_PTRACE_SETHWDEBUG breakpoint */
|
||||
if (!(dbginfo->features & PPC_DEBUG_FEATURE_DATA_BP_RANGE))
|
||||
return; /* not supported */
|
||||
wh = set_hwbreakpoint_addr((void *)data, 0);
|
||||
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
||||
sprintf(buf, "Test %s watchpoint with len: %d ", mode_str, len);
|
||||
check_success(buf);
|
||||
/* Unregister hw brkpoint */
|
||||
del_hwbreakpoint_addr(wh);
|
||||
|
||||
/* try a wider range */
|
||||
range = 8;
|
||||
if (dawr)
|
||||
range = 512 - ((int)data & (DAWR_LENGTH_MAX - 1));
|
||||
wh = set_hwbreakpoint_addr((void *)data, range);
|
||||
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
||||
sprintf(buf, "Test %s watchpoint with len: %d ", mode_str, len);
|
||||
check_success(buf);
|
||||
/* Unregister hw brkpoint */
|
||||
del_hwbreakpoint_addr(wh);
|
||||
}
|
||||
|
||||
/* Set the breakpoints and check the child successfully trigger them */
|
||||
static int launch_tests(bool dawr)
|
||||
{
|
||||
char buf[1024];
|
||||
int len, i, status;
|
||||
|
||||
struct ppc_debug_info dbginfo;
|
||||
|
||||
i = ptrace(PPC_PTRACE_GETHWDBGINFO, child_pid, NULL, &dbginfo);
|
||||
if (i) {
|
||||
perror("Can't set breakpoint info\n");
|
||||
exit(-1);
|
||||
}
|
||||
if (!(dbginfo.features & PPC_DEBUG_FEATURE_DATA_BP_RANGE))
|
||||
printf("WARNING: Kernel doesn't support PPC_PTRACE_SETHWDEBUG\n");
|
||||
|
||||
/* Write watchpoint */
|
||||
for (len = 1; len <= sizeof(long); len <<= 1)
|
||||
launch_watchpoints(buf, BP_W, len, &dbginfo, dawr);
|
||||
|
||||
/* Read-Write watchpoint */
|
||||
for (len = 1; len <= sizeof(long); len <<= 1)
|
||||
launch_watchpoints(buf, BP_RW, len, &dbginfo, dawr);
|
||||
|
||||
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
||||
|
||||
/*
|
||||
* Now we have unregistered the breakpoint, access by child
|
||||
* should not cause SIGTRAP.
|
||||
*/
|
||||
|
||||
wait(&status);
|
||||
|
||||
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
|
||||
printf("FAIL: Child process hit the breakpoint, which is not expected\n");
|
||||
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
||||
return TEST_FAIL;
|
||||
}
|
||||
|
||||
if (WIFEXITED(status))
|
||||
printf("Child exited normally\n");
|
||||
|
||||
return TEST_PASS;
|
||||
}
|
||||
|
||||
static int ptrace_hwbreak(void)
|
||||
{
|
||||
pid_t pid;
|
||||
int ret;
|
||||
bool dawr;
|
||||
|
||||
pid = fork();
|
||||
if (!pid) {
|
||||
trigger_tests();
|
||||
return 0;
|
||||
}
|
||||
|
||||
wait(NULL);
|
||||
|
||||
child_pid = pid;
|
||||
|
||||
get_dbginfo();
|
||||
SKIP_IF(!hwbreak_present());
|
||||
dawr = dawr_present();
|
||||
|
||||
ret = launch_tests(dawr);
|
||||
|
||||
wait(NULL);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv, char **envp)
|
||||
{
|
||||
return test_harness(ptrace_hwbreak, "ptrace-hwbreak");
|
||||
}
|
Loading…
Reference in a new issue