// SPDX-License-Identifier: GPL-2.0+ #include #include #include #include #include #include #include #include #include #include #include #include "ptrace.h" char data[16]; /* Overlapping address range */ volatile __u64 *ptrace_data1 = (__u64 *)&data[0]; volatile __u64 *perf_data1 = (__u64 *)&data[4]; /* Non-overlapping address range */ volatile __u64 *ptrace_data2 = (__u64 *)&data[0]; volatile __u64 *perf_data2 = (__u64 *)&data[8]; static unsigned long pid_max_addr(void) { FILE *fp; char *line, *c; char addr[100]; size_t len = 0; fp = fopen("/proc/kallsyms", "r"); if (!fp) { printf("Failed to read /proc/kallsyms. Exiting..\n"); exit(EXIT_FAILURE); } while (getline(&line, &len, fp) != -1) { if (!strstr(line, "pid_max") || strstr(line, "pid_max_max") || strstr(line, "pid_max_min")) continue; strncpy(addr, line, len < 100 ? len : 100); c = strchr(addr, ' '); *c = '\0'; return strtoul(addr, &c, 16); } fclose(fp); printf("Could not find pix_max. Exiting..\n"); exit(EXIT_FAILURE); return -1; } static void perf_user_event_attr_set(struct perf_event_attr *attr, __u64 addr, __u64 len) { memset(attr, 0, sizeof(struct perf_event_attr)); attr->type = PERF_TYPE_BREAKPOINT; attr->size = sizeof(struct perf_event_attr); attr->bp_type = HW_BREAKPOINT_R; attr->bp_addr = addr; attr->bp_len = len; attr->exclude_kernel = 1; attr->exclude_hv = 1; } static void perf_kernel_event_attr_set(struct perf_event_attr *attr) { memset(attr, 0, sizeof(struct perf_event_attr)); attr->type = PERF_TYPE_BREAKPOINT; attr->size = sizeof(struct perf_event_attr); attr->bp_type = HW_BREAKPOINT_R; attr->bp_addr = pid_max_addr(); attr->bp_len = sizeof(unsigned long); attr->exclude_user = 1; attr->exclude_hv = 1; } static int perf_cpu_event_open(int cpu, __u64 addr, __u64 len) { struct perf_event_attr attr; perf_user_event_attr_set(&attr, addr, len); return syscall(__NR_perf_event_open, &attr, -1, cpu, -1, 0); } static int perf_thread_event_open(pid_t child_pid, __u64 addr, __u64 len) { struct perf_event_attr attr; perf_user_event_attr_set(&attr, addr, len); return syscall(__NR_perf_event_open, &attr, child_pid, -1, -1, 0); } static int perf_thread_cpu_event_open(pid_t child_pid, int cpu, __u64 addr, __u64 len) { struct perf_event_attr attr; perf_user_event_attr_set(&attr, addr, len); return syscall(__NR_perf_event_open, &attr, child_pid, cpu, -1, 0); } static int perf_thread_kernel_event_open(pid_t child_pid) { struct perf_event_attr attr; perf_kernel_event_attr_set(&attr); return syscall(__NR_perf_event_open, &attr, child_pid, -1, -1, 0); } static int perf_cpu_kernel_event_open(int cpu) { struct perf_event_attr attr; perf_kernel_event_attr_set(&attr); return syscall(__NR_perf_event_open, &attr, -1, cpu, -1, 0); } static int child(void) { int ret; ret = ptrace(PTRACE_TRACEME, 0, NULL, 0); if (ret) { printf("Error: PTRACE_TRACEME failed\n"); return 0; } kill(getpid(), SIGUSR1); /* --> parent (SIGUSR1) */ return 0; } static void ptrace_ppc_hw_breakpoint(struct ppc_hw_breakpoint *info, int type, __u64 addr, int len) { info->version = 1; info->trigger_type = type; info->condition_mode = PPC_BREAKPOINT_CONDITION_NONE; info->addr = addr; info->addr2 = 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 int ptrace_open(pid_t child_pid, __u64 wp_addr, int len) { struct ppc_hw_breakpoint info; ptrace_ppc_hw_breakpoint(&info, PPC_BREAKPOINT_TRIGGER_RW, wp_addr, len); return ptrace(PPC_PTRACE_SETHWDEBUG, child_pid, 0, &info); } static int test1(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread event by ptrace) * if (existing cpu event by perf) * if (addr range overlaps) * fail; */ perf_fd = perf_cpu_event_open(0, (__u64)perf_data1, sizeof(*perf_data1)); if (perf_fd < 0) return -1; ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd > 0 || errno != ENOSPC) ret = -1; close(perf_fd); return ret; } static int test2(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread event by ptrace) * if (existing cpu event by perf) * if (addr range does not overlaps) * allow; */ perf_fd = perf_cpu_event_open(0, (__u64)perf_data2, sizeof(*perf_data2)); if (perf_fd < 0) return -1; ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data2, sizeof(*ptrace_data2)); if (ptrace_fd < 0) { ret = -1; goto perf_close; } ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); perf_close: close(perf_fd); return ret; } static int test3(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread event by ptrace) * if (existing thread event by perf on the same thread) * if (addr range overlaps) * fail; */ perf_fd = perf_thread_event_open(child_pid, (__u64)perf_data1, sizeof(*perf_data1)); if (perf_fd < 0) return -1; ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd > 0 || errno != ENOSPC) ret = -1; close(perf_fd); return ret; } static int test4(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread event by ptrace) * if (existing thread event by perf on the same thread) * if (addr range does not overlaps) * fail; */ perf_fd = perf_thread_event_open(child_pid, (__u64)perf_data2, sizeof(*perf_data2)); if (perf_fd < 0) return -1; ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data2, sizeof(*ptrace_data2)); if (ptrace_fd < 0) { ret = -1; goto perf_close; } ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); perf_close: close(perf_fd); return ret; } static int test5(pid_t child_pid) { int perf_fd; int ptrace_fd; int cpid; int ret = 0; /* Test: * if (new per thread event by ptrace) * if (existing thread event by perf on the different thread) * allow; */ cpid = fork(); if (!cpid) { /* Temporary Child */ pause(); exit(EXIT_SUCCESS); } perf_fd = perf_thread_event_open(cpid, (__u64)perf_data1, sizeof(*perf_data1)); if (perf_fd < 0) { ret = -1; goto kill_child; } ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd < 0) { ret = -1; goto perf_close; } ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); perf_close: close(perf_fd); kill_child: kill(cpid, SIGINT); return ret; } static int test6(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread kernel event by perf) * if (existing thread event by ptrace on the same thread) * allow; * -- OR -- * if (new per cpu kernel event by perf) * if (existing thread event by ptrace) * allow; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd < 0) return -1; perf_fd = perf_thread_kernel_event_open(child_pid); if (perf_fd < 0) { ret = -1; goto ptrace_close; } close(perf_fd); perf_fd = perf_cpu_kernel_event_open(0); if (perf_fd < 0) { ret = -1; goto ptrace_close; } close(perf_fd); ptrace_close: ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int test7(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread event by perf) * if (existing thread event by ptrace on the same thread) * if (addr range overlaps) * fail; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd < 0) return -1; perf_fd = perf_thread_event_open(child_pid, (__u64)perf_data1, sizeof(*perf_data1)); if (perf_fd > 0 || errno != ENOSPC) ret = -1; ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int test8(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread event by perf) * if (existing thread event by ptrace on the same thread) * if (addr range does not overlaps) * allow; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data2, sizeof(*ptrace_data2)); if (ptrace_fd < 0) return -1; perf_fd = perf_thread_event_open(child_pid, (__u64)perf_data2, sizeof(*perf_data2)); if (perf_fd < 0) { ret = -1; goto ptrace_close; } close(perf_fd); ptrace_close: ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int test9(pid_t child_pid) { int perf_fd; int ptrace_fd; int cpid; int ret = 0; /* Test: * if (new per thread event by perf) * if (existing thread event by ptrace on the other thread) * allow; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd < 0) return -1; cpid = fork(); if (!cpid) { /* Temporary Child */ pause(); exit(EXIT_SUCCESS); } perf_fd = perf_thread_event_open(cpid, (__u64)perf_data1, sizeof(*perf_data1)); if (perf_fd < 0) { ret = -1; goto kill_child; } close(perf_fd); kill_child: kill(cpid, SIGINT); ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int test10(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per cpu event by perf) * if (existing thread event by ptrace on the same thread) * if (addr range overlaps) * fail; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd < 0) return -1; perf_fd = perf_cpu_event_open(0, (__u64)perf_data1, sizeof(*perf_data1)); if (perf_fd > 0 || errno != ENOSPC) ret = -1; ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int test11(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per cpu event by perf) * if (existing thread event by ptrace on the same thread) * if (addr range does not overlap) * allow; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data2, sizeof(*ptrace_data2)); if (ptrace_fd < 0) return -1; perf_fd = perf_cpu_event_open(0, (__u64)perf_data2, sizeof(*perf_data2)); if (perf_fd < 0) { ret = -1; goto ptrace_close; } close(perf_fd); ptrace_close: ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int test12(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread and per cpu event by perf) * if (existing thread event by ptrace on the same thread) * if (addr range overlaps) * fail; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd < 0) return -1; perf_fd = perf_thread_cpu_event_open(child_pid, 0, (__u64)perf_data1, sizeof(*perf_data1)); if (perf_fd > 0 || errno != ENOSPC) ret = -1; ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int test13(pid_t child_pid) { int perf_fd; int ptrace_fd; int ret = 0; /* Test: * if (new per thread and per cpu event by perf) * if (existing thread event by ptrace on the same thread) * if (addr range does not overlap) * allow; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data2, sizeof(*ptrace_data2)); if (ptrace_fd < 0) return -1; perf_fd = perf_thread_cpu_event_open(child_pid, 0, (__u64)perf_data2, sizeof(*perf_data2)); if (perf_fd < 0) { ret = -1; goto ptrace_close; } close(perf_fd); ptrace_close: ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int test14(pid_t child_pid) { int perf_fd; int ptrace_fd; int cpid; int ret = 0; /* Test: * if (new per thread and per cpu event by perf) * if (existing thread event by ptrace on the other thread) * allow; */ ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1)); if (ptrace_fd < 0) return -1; cpid = fork(); if (!cpid) { /* Temporary Child */ pause(); exit(EXIT_SUCCESS); } perf_fd = perf_thread_cpu_event_open(cpid, 0, (__u64)perf_data1, sizeof(*perf_data1)); if (perf_fd < 0) { ret = -1; goto kill_child; } close(perf_fd); kill_child: kill(cpid, SIGINT); ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd); return ret; } static int do_test(const char *msg, int (*fun)(pid_t arg), pid_t arg) { int ret; ret = fun(arg); if (ret) printf("%s: Error\n", msg); else printf("%s: Ok\n", msg); return ret; } char *desc[14] = { "perf cpu event -> ptrace thread event (Overlapping)", "perf cpu event -> ptrace thread event (Non-overlapping)", "perf thread event -> ptrace same thread event (Overlapping)", "perf thread event -> ptrace same thread event (Non-overlapping)", "perf thread event -> ptrace other thread event", "ptrace thread event -> perf kernel event", "ptrace thread event -> perf same thread event (Overlapping)", "ptrace thread event -> perf same thread event (Non-overlapping)", "ptrace thread event -> perf other thread event", "ptrace thread event -> perf cpu event (Overlapping)", "ptrace thread event -> perf cpu event (Non-overlapping)", "ptrace thread event -> perf same thread & cpu event (Overlapping)", "ptrace thread event -> perf same thread & cpu event (Non-overlapping)", "ptrace thread event -> perf other thread & cpu event", }; static int test(pid_t child_pid) { int ret = TEST_PASS; ret |= do_test(desc[0], test1, child_pid); ret |= do_test(desc[1], test2, child_pid); ret |= do_test(desc[2], test3, child_pid); ret |= do_test(desc[3], test4, child_pid); ret |= do_test(desc[4], test5, child_pid); ret |= do_test(desc[5], test6, child_pid); ret |= do_test(desc[6], test7, child_pid); ret |= do_test(desc[7], test8, child_pid); ret |= do_test(desc[8], test9, child_pid); ret |= do_test(desc[9], test10, child_pid); ret |= do_test(desc[10], test11, child_pid); ret |= do_test(desc[11], test12, child_pid); ret |= do_test(desc[12], test13, child_pid); ret |= do_test(desc[13], test14, child_pid); return ret; } 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 int ptrace_perf_hwbreak(void) { int ret; pid_t child_pid; struct ppc_debug_info dbginfo; child_pid = fork(); if (!child_pid) return child(); /* parent */ wait(NULL); /* <-- child (SIGUSR1) */ get_dbginfo(child_pid, &dbginfo); SKIP_IF(dbginfo.num_data_bps <= 1); ret = perf_cpu_event_open(0, (__u64)perf_data1, sizeof(*perf_data1)); SKIP_IF(ret < 0); close(ret); ret = test(child_pid); ptrace(PTRACE_CONT, child_pid, NULL, 0); return ret; } int main(int argc, char *argv[]) { return test_harness(ptrace_perf_hwbreak, "ptrace-perf-hwbreak"); }