// 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 #include #include #include #include #include #include #include #include #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"); }