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