| // SPDX-License-Identifier: GPL-2.0+ |
| #include <stdio.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <linux/hw_breakpoint.h> |
| #include <linux/perf_event.h> |
| #include <asm/unistd.h> |
| #include <sys/ptrace.h> |
| #include <sys/wait.h> |
| #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"); |
| } |