You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
508 lines
13 KiB
508 lines
13 KiB
// 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"
|
|
|
|
#define SPRN_PVR 0x11F
|
|
#define PVR_8xx 0x00500000
|
|
|
|
bool is_8xx;
|
|
|
|
/*
|
|
* Use volatile on all global var so that compiler doesn't
|
|
* optimise their load/stores. Otherwise selftest can fail.
|
|
*/
|
|
static volatile __u64 glvar;
|
|
|
|
#define DAWR_MAX_LEN 512
|
|
static volatile __u8 big_var[DAWR_MAX_LEN] __attribute__((aligned(512)));
|
|
|
|
#define A_LEN 6
|
|
#define B_LEN 6
|
|
struct gstruct {
|
|
__u8 a[A_LEN]; /* double word aligned */
|
|
__u8 b[B_LEN]; /* double word unaligned */
|
|
};
|
|
static volatile struct gstruct gstruct __attribute__((aligned(512)));
|
|
|
|
|
|
static void get_dbginfo(pid_t child_pid, struct ppc_debug_info *dbginfo)
|
|
{
|
|
if (ptrace(PPC_PTRACE_GETHWDBGINFO, child_pid, NULL, dbginfo)) {
|
|
perror("Can't get breakpoint info");
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
static bool dawr_present(struct ppc_debug_info *dbginfo)
|
|
{
|
|
return !!(dbginfo->features & PPC_DEBUG_FEATURE_DATA_BP_DAWR);
|
|
}
|
|
|
|
static void write_var(int len)
|
|
{
|
|
__u8 *pcvar;
|
|
__u16 *psvar;
|
|
__u32 *pivar;
|
|
__u64 *plvar;
|
|
|
|
switch (len) {
|
|
case 1:
|
|
pcvar = (__u8 *)&glvar;
|
|
*pcvar = 0xff;
|
|
break;
|
|
case 2:
|
|
psvar = (__u16 *)&glvar;
|
|
*psvar = 0xffff;
|
|
break;
|
|
case 4:
|
|
pivar = (__u32 *)&glvar;
|
|
*pivar = 0xffffffff;
|
|
break;
|
|
case 8:
|
|
plvar = (__u64 *)&glvar;
|
|
*plvar = 0xffffffffffffffffLL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void read_var(int len)
|
|
{
|
|
__u8 cvar __attribute__((unused));
|
|
__u16 svar __attribute__((unused));
|
|
__u32 ivar __attribute__((unused));
|
|
__u64 lvar __attribute__((unused));
|
|
|
|
switch (len) {
|
|
case 1:
|
|
cvar = (__u8)glvar;
|
|
break;
|
|
case 2:
|
|
svar = (__u16)glvar;
|
|
break;
|
|
case 4:
|
|
ivar = (__u32)glvar;
|
|
break;
|
|
case 8:
|
|
lvar = (__u64)glvar;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void test_workload(void)
|
|
{
|
|
__u8 cvar __attribute__((unused));
|
|
__u32 ivar __attribute__((unused));
|
|
int len = 0;
|
|
|
|
if (ptrace(PTRACE_TRACEME, 0, NULL, 0)) {
|
|
perror("Child can't be traced?");
|
|
exit(-1);
|
|
}
|
|
|
|
/* Wake up father so that it sets up the first test */
|
|
kill(getpid(), SIGUSR1);
|
|
|
|
/* PTRACE_SET_DEBUGREG, WO test */
|
|
for (len = 1; len <= sizeof(glvar); len <<= 1)
|
|
write_var(len);
|
|
|
|
/* PTRACE_SET_DEBUGREG, RO test */
|
|
for (len = 1; len <= sizeof(glvar); len <<= 1)
|
|
read_var(len);
|
|
|
|
/* PTRACE_SET_DEBUGREG, RW test */
|
|
for (len = 1; len <= sizeof(glvar); len <<= 1) {
|
|
if (rand() % 2)
|
|
read_var(len);
|
|
else
|
|
write_var(len);
|
|
}
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, WO test */
|
|
write_var(1);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, RO test */
|
|
read_var(1);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, RW test */
|
|
if (rand() % 2)
|
|
write_var(1);
|
|
else
|
|
read_var(1);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, WO test */
|
|
gstruct.a[rand() % A_LEN] = 'a';
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, RO test */
|
|
cvar = gstruct.a[rand() % A_LEN];
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, RW test */
|
|
if (rand() % 2)
|
|
gstruct.a[rand() % A_LEN] = 'a';
|
|
else
|
|
cvar = gstruct.a[rand() % A_LEN];
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, WO test */
|
|
gstruct.b[rand() % B_LEN] = 'b';
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, RO test */
|
|
cvar = gstruct.b[rand() % B_LEN];
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, RW test */
|
|
if (rand() % 2)
|
|
gstruct.b[rand() % B_LEN] = 'b';
|
|
else
|
|
cvar = gstruct.b[rand() % B_LEN];
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, DAR OUTSIDE, RW test */
|
|
if (rand() % 2)
|
|
*((int *)(gstruct.a + 4)) = 10;
|
|
else
|
|
ivar = *((int *)(gstruct.a + 4));
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG. DAWR_MAX_LEN. RW test */
|
|
if (rand() % 2)
|
|
big_var[rand() % DAWR_MAX_LEN] = 'a';
|
|
else
|
|
cvar = big_var[rand() % DAWR_MAX_LEN];
|
|
}
|
|
|
|
static void check_success(pid_t child_pid, const char *name, const char *type,
|
|
unsigned long saddr, int len)
|
|
{
|
|
int status;
|
|
siginfo_t siginfo;
|
|
unsigned long eaddr = (saddr + len - 1) | 0x7;
|
|
|
|
saddr &= ~0x7;
|
|
|
|
/* Wait for the child to SIGTRAP */
|
|
wait(&status);
|
|
|
|
ptrace(PTRACE_GETSIGINFO, child_pid, NULL, &siginfo);
|
|
|
|
if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGTRAP ||
|
|
(unsigned long)siginfo.si_addr < saddr ||
|
|
(unsigned long)siginfo.si_addr > eaddr) {
|
|
printf("%s, %s, len: %d: Fail\n", name, type, len);
|
|
exit(-1);
|
|
}
|
|
|
|
printf("%s, %s, len: %d: Ok\n", name, type, len);
|
|
|
|
if (!is_8xx) {
|
|
/*
|
|
* For ptrace registered watchpoint, signal is generated
|
|
* before executing load/store. Singlestep the instruction
|
|
* and then continue the test.
|
|
*/
|
|
ptrace(PTRACE_SINGLESTEP, child_pid, NULL, 0);
|
|
wait(NULL);
|
|
}
|
|
}
|
|
|
|
static void ptrace_set_debugreg(pid_t child_pid, unsigned long wp_addr)
|
|
{
|
|
if (ptrace(PTRACE_SET_DEBUGREG, child_pid, 0, wp_addr)) {
|
|
perror("PTRACE_SET_DEBUGREG failed");
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
static int ptrace_sethwdebug(pid_t child_pid, struct ppc_hw_breakpoint *info)
|
|
{
|
|
int wh = ptrace(PPC_PTRACE_SETHWDEBUG, child_pid, 0, info);
|
|
|
|
if (wh <= 0) {
|
|
perror("PPC_PTRACE_SETHWDEBUG failed");
|
|
exit(-1);
|
|
}
|
|
return wh;
|
|
}
|
|
|
|
static void ptrace_delhwdebug(pid_t child_pid, int wh)
|
|
{
|
|
if (ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, wh) < 0) {
|
|
perror("PPC_PTRACE_DELHWDEBUG failed");
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
#define DABR_READ_SHIFT 0
|
|
#define DABR_WRITE_SHIFT 1
|
|
#define DABR_TRANSLATION_SHIFT 2
|
|
|
|
static int test_set_debugreg(pid_t child_pid)
|
|
{
|
|
unsigned long wp_addr = (unsigned long)&glvar;
|
|
char *name = "PTRACE_SET_DEBUGREG";
|
|
int len;
|
|
|
|
/* PTRACE_SET_DEBUGREG, WO test*/
|
|
wp_addr &= ~0x7UL;
|
|
wp_addr |= (1UL << DABR_WRITE_SHIFT);
|
|
wp_addr |= (1UL << DABR_TRANSLATION_SHIFT);
|
|
for (len = 1; len <= sizeof(glvar); len <<= 1) {
|
|
ptrace_set_debugreg(child_pid, wp_addr);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "WO", wp_addr, len);
|
|
}
|
|
|
|
/* PTRACE_SET_DEBUGREG, RO test */
|
|
wp_addr &= ~0x7UL;
|
|
wp_addr |= (1UL << DABR_READ_SHIFT);
|
|
wp_addr |= (1UL << DABR_TRANSLATION_SHIFT);
|
|
for (len = 1; len <= sizeof(glvar); len <<= 1) {
|
|
ptrace_set_debugreg(child_pid, wp_addr);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RO", wp_addr, len);
|
|
}
|
|
|
|
/* PTRACE_SET_DEBUGREG, RW test */
|
|
wp_addr &= ~0x7UL;
|
|
wp_addr |= (1Ul << DABR_READ_SHIFT);
|
|
wp_addr |= (1UL << DABR_WRITE_SHIFT);
|
|
wp_addr |= (1UL << DABR_TRANSLATION_SHIFT);
|
|
for (len = 1; len <= sizeof(glvar); len <<= 1) {
|
|
ptrace_set_debugreg(child_pid, wp_addr);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RW", wp_addr, len);
|
|
}
|
|
|
|
ptrace_set_debugreg(child_pid, 0);
|
|
return 0;
|
|
}
|
|
|
|
static void get_ppc_hw_breakpoint(struct ppc_hw_breakpoint *info, int type,
|
|
unsigned long addr, int len)
|
|
{
|
|
info->version = 1;
|
|
info->trigger_type = type;
|
|
info->condition_mode = PPC_BREAKPOINT_CONDITION_NONE;
|
|
info->addr = (__u64)addr;
|
|
info->addr2 = (__u64)addr + len;
|
|
info->condition_value = 0;
|
|
if (!len)
|
|
info->addr_mode = PPC_BREAKPOINT_MODE_EXACT;
|
|
else
|
|
info->addr_mode = PPC_BREAKPOINT_MODE_RANGE_INCLUSIVE;
|
|
}
|
|
|
|
static void test_sethwdebug_exact(pid_t child_pid)
|
|
{
|
|
struct ppc_hw_breakpoint info;
|
|
unsigned long wp_addr = (unsigned long)&glvar;
|
|
char *name = "PPC_PTRACE_SETHWDEBUG, MODE_EXACT";
|
|
int len = 1; /* hardcoded in kernel */
|
|
int wh;
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, WO test */
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_WRITE, wp_addr, 0);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "WO", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, RO test */
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_READ, wp_addr, 0);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RO", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_EXACT, RW test */
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_RW, wp_addr, 0);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RW", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
}
|
|
|
|
static void test_sethwdebug_range_aligned(pid_t child_pid)
|
|
{
|
|
struct ppc_hw_breakpoint info;
|
|
unsigned long wp_addr;
|
|
char *name = "PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED";
|
|
int len;
|
|
int wh;
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, WO test */
|
|
wp_addr = (unsigned long)&gstruct.a;
|
|
len = A_LEN;
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_WRITE, wp_addr, len);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "WO", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, RO test */
|
|
wp_addr = (unsigned long)&gstruct.a;
|
|
len = A_LEN;
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_READ, wp_addr, len);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RO", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW ALIGNED, RW test */
|
|
wp_addr = (unsigned long)&gstruct.a;
|
|
len = A_LEN;
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_RW, wp_addr, len);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RW", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
}
|
|
|
|
static void test_sethwdebug_range_unaligned(pid_t child_pid)
|
|
{
|
|
struct ppc_hw_breakpoint info;
|
|
unsigned long wp_addr;
|
|
char *name = "PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED";
|
|
int len;
|
|
int wh;
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, WO test */
|
|
wp_addr = (unsigned long)&gstruct.b;
|
|
len = B_LEN;
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_WRITE, wp_addr, len);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "WO", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, RO test */
|
|
wp_addr = (unsigned long)&gstruct.b;
|
|
len = B_LEN;
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_READ, wp_addr, len);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RO", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, RW test */
|
|
wp_addr = (unsigned long)&gstruct.b;
|
|
len = B_LEN;
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_RW, wp_addr, len);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RW", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
|
|
}
|
|
|
|
static void test_sethwdebug_range_unaligned_dar(pid_t child_pid)
|
|
{
|
|
struct ppc_hw_breakpoint info;
|
|
unsigned long wp_addr;
|
|
char *name = "PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, DAR OUTSIDE";
|
|
int len;
|
|
int wh;
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, MODE_RANGE, DW UNALIGNED, DAR OUTSIDE, RW test */
|
|
wp_addr = (unsigned long)&gstruct.b;
|
|
len = B_LEN;
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_WRITE, wp_addr, len);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RW", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
}
|
|
|
|
static void test_sethwdebug_dawr_max_range(pid_t child_pid)
|
|
{
|
|
struct ppc_hw_breakpoint info;
|
|
unsigned long wp_addr;
|
|
char *name = "PPC_PTRACE_SETHWDEBUG, DAWR_MAX_LEN";
|
|
int len;
|
|
int wh;
|
|
|
|
/* PPC_PTRACE_SETHWDEBUG, DAWR_MAX_LEN, RW test */
|
|
wp_addr = (unsigned long)big_var;
|
|
len = DAWR_MAX_LEN;
|
|
get_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_RW, wp_addr, len);
|
|
wh = ptrace_sethwdebug(child_pid, &info);
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
check_success(child_pid, name, "RW", wp_addr, len);
|
|
ptrace_delhwdebug(child_pid, wh);
|
|
}
|
|
|
|
/* Set the breakpoints and check the child successfully trigger them */
|
|
static void
|
|
run_tests(pid_t child_pid, struct ppc_debug_info *dbginfo, bool dawr)
|
|
{
|
|
test_set_debugreg(child_pid);
|
|
if (dbginfo->features & PPC_DEBUG_FEATURE_DATA_BP_RANGE) {
|
|
test_sethwdebug_exact(child_pid);
|
|
|
|
if (!is_8xx)
|
|
test_sethwdebug_range_aligned(child_pid);
|
|
if (dawr && !is_8xx) {
|
|
test_sethwdebug_range_unaligned(child_pid);
|
|
test_sethwdebug_range_unaligned_dar(child_pid);
|
|
test_sethwdebug_dawr_max_range(child_pid);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int ptrace_hwbreak(void)
|
|
{
|
|
pid_t child_pid;
|
|
struct ppc_debug_info dbginfo;
|
|
bool dawr;
|
|
|
|
child_pid = fork();
|
|
if (!child_pid) {
|
|
test_workload();
|
|
return 0;
|
|
}
|
|
|
|
wait(NULL);
|
|
|
|
get_dbginfo(child_pid, &dbginfo);
|
|
SKIP_IF(dbginfo.num_data_bps == 0);
|
|
|
|
dawr = dawr_present(&dbginfo);
|
|
run_tests(child_pid, &dbginfo, dawr);
|
|
|
|
/* Let the child exit first. */
|
|
ptrace(PTRACE_CONT, child_pid, NULL, 0);
|
|
wait(NULL);
|
|
|
|
/*
|
|
* Testcases exits immediately with -1 on any failure. If
|
|
* it has reached here, it means all tests were successful.
|
|
*/
|
|
return TEST_PASS;
|
|
}
|
|
|
|
int main(int argc, char **argv, char **envp)
|
|
{
|
|
int pvr = 0;
|
|
asm __volatile__ ("mfspr %0,%1" : "=r"(pvr) : "i"(SPRN_PVR));
|
|
if (pvr == PVR_8xx)
|
|
is_8xx = true;
|
|
|
|
return test_harness(ptrace_hwbreak, "ptrace-hwbreak");
|
|
}
|