selftests/powerpc/ptrace: Update ptrace-perf watchpoint selftest
authorBenjamin Gray <bgray@linux.ibm.com>
Tue, 1 Aug 2023 01:17:43 +0000 (11:17 +1000)
committerMichael Ellerman <mpe@ellerman.id.au>
Wed, 16 Aug 2023 13:54:50 +0000 (23:54 +1000)
Now that ptrace and perf are no longer exclusive, update the
test to exercise interesting interactions.

An assembly file is used for the children to allow precise instruction
choice and addresses, while avoiding any compiler quirks.

Signed-off-by: Benjamin Gray <bgray@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://msgid.link/20230801011744.153973-7-bgray@linux.ibm.com
tools/testing/selftests/powerpc/ptrace/Makefile
tools/testing/selftests/powerpc/ptrace/ptrace-perf-asm.S [new file with mode: 0644]
tools/testing/selftests/powerpc/ptrace/ptrace-perf-hwbreak.c

index cbeeaea..1b39b86 100644 (file)
@@ -36,6 +36,7 @@ $(TM_TESTS): CFLAGS += -I../tm -mhtm
 CFLAGS += $(KHDR_INCLUDES) -fno-pie
 
 $(OUTPUT)/ptrace-gpr: ptrace-gpr.S
+$(OUTPUT)/ptrace-perf-hwbreak: ptrace-perf-asm.S
 $(OUTPUT)/ptrace-pkey $(OUTPUT)/core-pkey: LDLIBS += -pthread
 
 $(TEST_GEN_PROGS): ../harness.c ../utils.c ../lib/reg.S
diff --git a/tools/testing/selftests/powerpc/ptrace/ptrace-perf-asm.S b/tools/testing/selftests/powerpc/ptrace/ptrace-perf-asm.S
new file mode 100644 (file)
index 0000000..9aa2e58
--- /dev/null
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include <ppc-asm.h>
+
+.global same_watch_addr_load
+.global same_watch_addr_trap
+
+FUNC_START(same_watch_addr_child)
+       nop
+same_watch_addr_load:
+       ld 0,0(3)
+       nop
+same_watch_addr_trap:
+       trap
+       blr
+FUNC_END(same_watch_addr_child)
+
+
+.global perf_then_ptrace_load1
+.global perf_then_ptrace_load2
+.global perf_then_ptrace_trap
+
+FUNC_START(perf_then_ptrace_child)
+       nop
+perf_then_ptrace_load1:
+       ld 0,0(3)
+perf_then_ptrace_load2:
+       ld 0,0(4)
+       nop
+perf_then_ptrace_trap:
+       trap
+       blr
+FUNC_END(perf_then_ptrace_child)
index d8a9e95..a0a0b9b 100644 (file)
 // 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 <linux/hw_breakpoint.h>
+#include <linux/ptrace.h>
+#include <memory.h>
+#include <stdlib.h>
 #include <sys/wait.h>
-#include "ptrace.h"
 
-char data[16];
+#include "utils.h"
 
-/* Overlapping address range */
-volatile __u64 *ptrace_data1 = (__u64 *)&data[0];
-volatile __u64 *perf_data1 = (__u64 *)&data[4];
+/*
+ * Child subroutine that performs a load on the address, then traps
+ */
+void same_watch_addr_child(unsigned long *addr);
 
-/* Non-overlapping address range */
-volatile __u64 *ptrace_data2 = (__u64 *)&data[0];
-volatile __u64 *perf_data2 = (__u64 *)&data[8];
+/* Address of the ld instruction in same_watch_addr_child() */
+extern char same_watch_addr_load[];
 
-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);
-       }
+/* Address of the end trap instruction in same_watch_addr_child() */
+extern char same_watch_addr_trap[];
 
-       while (getline(&line, &len, fp) != -1) {
-               if (!strstr(line, "pid_max") || strstr(line, "pid_max_max") ||
-                   strstr(line, "pid_max_min"))
-                       continue;
+/*
+ * Child subroutine that performs a load on the first address, then a load on
+ * the second address (with no instructions separating this from the first
+ * load), then traps.
+ */
+void perf_then_ptrace_child(unsigned long *first_addr, unsigned long *second_addr);
 
-               strncpy(addr, line, len < 100 ? len : 100);
-               c = strchr(addr, ' ');
-               *c = '\0';
-               return strtoul(addr, &c, 16);
-       }
-       fclose(fp);
-       printf("Could not find pid_max. Exiting..\n");
-       exit(EXIT_FAILURE);
-       return -1;
-}
+/* Address of the first ld instruction in perf_then_ptrace_child() */
+extern char perf_then_ptrace_load1[];
 
-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;
-}
+/* Address of the second ld instruction in perf_then_ptrace_child() */
+extern char perf_then_ptrace_load2[];
 
-static void perf_kernel_event_attr_set(struct perf_event_attr *attr)
+/* Address of the end trap instruction in perf_then_ptrace_child() */
+extern char perf_then_ptrace_trap[];
+
+static inline long sys_ptrace(long request, pid_t pid, unsigned long addr, unsigned long data)
 {
-       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;
+       return syscall(__NR_ptrace, request, pid, addr, data);
 }
 
-static int perf_cpu_event_open(int cpu, __u64 addr, __u64 len)
+static long ptrace_traceme(void)
 {
-       struct perf_event_attr attr;
-
-       perf_user_event_attr_set(&attr, addr, len);
-       return syscall(__NR_perf_event_open, &attr, -1, cpu, -1, 0);
+       return sys_ptrace(PTRACE_TRACEME, 0, 0, 0);
 }
 
-static int perf_thread_event_open(pid_t child_pid, __u64 addr, __u64 len)
+static long ptrace_getregs(pid_t pid, struct pt_regs *result)
 {
-       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);
+       return sys_ptrace(PTRACE_GETREGS, pid, 0, (unsigned long)result);
 }
 
-static int perf_thread_cpu_event_open(pid_t child_pid, int cpu, __u64 addr, __u64 len)
+static long ptrace_setregs(pid_t pid, struct pt_regs *result)
 {
-       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);
+       return sys_ptrace(PTRACE_SETREGS, pid, 0, (unsigned long)result);
 }
 
-static int perf_thread_kernel_event_open(pid_t child_pid)
+static long ptrace_cont(pid_t pid, long signal)
 {
-       struct perf_event_attr attr;
-
-       perf_kernel_event_attr_set(&attr);
-       return syscall(__NR_perf_event_open, &attr, child_pid, -1, -1, 0);
+       return sys_ptrace(PTRACE_CONT, pid, 0, signal);
 }
 
-static int perf_cpu_kernel_event_open(int cpu)
+static long ptrace_singlestep(pid_t pid, long signal)
 {
-       struct perf_event_attr attr;
-
-       perf_kernel_event_attr_set(&attr);
-       return syscall(__NR_perf_event_open, &attr, -1, cpu, -1, 0);
+       return sys_ptrace(PTRACE_SINGLESTEP, pid, 0, signal);
 }
 
-static int child(void)
+static long ppc_ptrace_gethwdbginfo(pid_t pid, struct ppc_debug_info *dbginfo)
 {
-       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;
+       return sys_ptrace(PPC_PTRACE_GETHWDBGINFO, pid, 0, (unsigned long)dbginfo);
 }
 
-static void ptrace_ppc_hw_breakpoint(struct ppc_hw_breakpoint *info, int type,
-                                    __u64 addr, int len)
+static long ppc_ptrace_sethwdbg(pid_t pid, struct ppc_hw_breakpoint *bp_info)
 {
-       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;
+       return sys_ptrace(PPC_PTRACE_SETHWDEBUG, pid, 0, (unsigned long)bp_info);
 }
 
-static int ptrace_open(pid_t child_pid, __u64 wp_addr, int len)
+static long ppc_ptrace_delhwdbg(pid_t pid, int bp_id)
 {
-       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);
+       return sys_ptrace(PPC_PTRACE_DELHWDEBUG, pid, 0L, bp_id);
 }
 
-static int test1(pid_t child_pid)
+static long ptrace_getreg_pc(pid_t pid, void **pc)
 {
-       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;
-        */
+       struct pt_regs regs;
+       long err;
 
-       perf_fd = perf_cpu_event_open(0, (__u64)perf_data1, sizeof(*perf_data1));
-       if (perf_fd < 0)
-               return -1;
+       err = ptrace_getregs(pid, &regs);
+       if (err)
+               return err;
 
-       ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1));
-       if (ptrace_fd > 0 || errno != ENOSPC)
-               ret = -1;
+       *pc = (void *)regs.nip;
 
-       close(perf_fd);
-       return ret;
+       return 0;
 }
 
-static int test2(pid_t child_pid)
+static long ptrace_setreg_pc(pid_t pid, void *pc)
 {
-       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;
-        */
+       struct pt_regs regs;
+       long err;
 
-       perf_fd = perf_cpu_event_open(0, (__u64)perf_data2, sizeof(*perf_data2));
-       if (perf_fd < 0)
-               return -1;
+       err = ptrace_getregs(pid, &regs);
+       if (err)
+               return err;
 
-       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);
+       regs.nip = (unsigned long)pc;
 
-perf_close:
-       close(perf_fd);
-       return ret;
-}
+       err = ptrace_setregs(pid, &regs);
+       if (err)
+               return err;
 
-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;
+       return 0;
 }
 
-static int test4(pid_t child_pid)
+static int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu,
+                          int group_fd, unsigned long flags)
 {
-       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;
+       return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
 }
 
-static int test5(pid_t child_pid)
+static void perf_user_event_attr_set(struct perf_event_attr *attr, void *addr, u64 len)
 {
-       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;
-       }
+       memset(attr, 0, sizeof(struct perf_event_attr));
 
-       ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd);
-perf_close:
-       close(perf_fd);
-kill_child:
-       kill(cpid, SIGINT);
-       return ret;
+       attr->type              = PERF_TYPE_BREAKPOINT;
+       attr->size              = sizeof(struct perf_event_attr);
+       attr->bp_type           = HW_BREAKPOINT_R;
+       attr->bp_addr           = (u64)addr;
+       attr->bp_len            = len;
+       attr->exclude_kernel    = 1;
+       attr->exclude_hv        = 1;
 }
 
-static int test6(pid_t child_pid)
+static int perf_watchpoint_open(pid_t child_pid, void *addr, u64 len)
 {
-       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);
+       struct perf_event_attr attr;
 
-ptrace_close:
-       ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd);
-       return ret;
+       perf_user_event_attr_set(&attr, addr, len);
+       return perf_event_open(&attr, child_pid, -1, -1, 0);
 }
 
-static int test7(pid_t child_pid)
+static int perf_read_counter(int perf_fd, u64 *count)
 {
-       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;
+       /*
+        * A perf counter is retrieved by the read() syscall. It contains
+        * the current count as 8 bytes that are interpreted as a u64
         */
-       ptrace_fd = ptrace_open(child_pid, (__u64)ptrace_data1, sizeof(*ptrace_data1));
-       if (ptrace_fd < 0)
-               return -1;
+       ssize_t len = read(perf_fd, count, sizeof(*count));
 
-       perf_fd = perf_thread_event_open(child_pid, (__u64)perf_data1,
-                                        sizeof(*perf_data1));
-       if (perf_fd > 0 || errno != ENOSPC)
-               ret = -1;
+       if (len != sizeof(*count))
+               return -1;
 
-       ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd);
-       return ret;
+       return 0;
 }
 
-static int test8(pid_t child_pid)
+static void ppc_ptrace_init_breakpoint(struct ppc_hw_breakpoint *info,
+                                      int type, void *addr, int len)
 {
-       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;
+       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 int test9(pid_t child_pid)
+/*
+ * Checks if we can place at least 2 watchpoints on the child process
+ */
+static int check_watchpoints(pid_t 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);
-       }
+       struct ppc_debug_info dbginfo;
 
-       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);
+       FAIL_IF_MSG(ppc_ptrace_gethwdbginfo(pid, &dbginfo), "PPC_PTRACE_GETHWDBGINFO failed");
+       SKIP_IF_MSG(dbginfo.num_data_bps <= 1, "Not enough data watchpoints (need at least 2)");
 
-kill_child:
-       kill(cpid, SIGINT);
-       ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd);
-       return ret;
+       return 0;
 }
 
-static int test10(pid_t child_pid)
+/*
+ * Wrapper around a plain fork() call that sets up the child for
+ * ptrace-ing. Both the parent and child return from this, though
+ * the child is stopped until ptrace_cont(pid) is run by the parent.
+ */
+static int ptrace_fork_child(pid_t *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;
+       int status;
 
-       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;
-}
+       *pid = fork();
 
-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;
+       if (*pid < 0)
+               FAIL_IF_MSG(1, "Failed to fork child");
 
-       perf_fd = perf_cpu_event_open(0, (__u64)perf_data2, sizeof(*perf_data2));
-       if (perf_fd < 0) {
-               ret = -1;
-               goto ptrace_close;
+       if (!*pid) {
+               FAIL_IF_EXIT_MSG(ptrace_traceme(), "PTRACE_TRACEME failed");
+               FAIL_IF_EXIT_MSG(raise(SIGSTOP), "Child failed to raise SIGSTOP");
+       } else {
+               /* Synchronise on child SIGSTOP */
+               FAIL_IF_MSG(waitpid(*pid, &status, 0) == -1, "Failed to wait for child");
+               FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
        }
-       close(perf_fd);
 
-ptrace_close:
-       ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd);
-       return ret;
+       return 0;
 }
 
-static int test12(pid_t child_pid)
+/*
+ * Tests the interaction between ptrace and perf watching the same data.
+ *
+ * We expect ptrace to take 'priority', as it is has before-execute
+ * semantics.
+ *
+ * The perf counter should not be incremented yet because perf has after-execute
+ * semantics. E.g., if ptrace changes the child PC, we don't even execute the
+ * instruction at all.
+ *
+ * When the child is stopped for ptrace, we test both continue and single step.
+ * Both should increment the perf counter. We also test changing the PC somewhere
+ * different and stepping, which should not increment the perf counter.
+ */
+int same_watch_addr_test(void)
 {
-       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;
+       struct ppc_hw_breakpoint bp_info;       /* ptrace breakpoint info */
+       int bp_id;      /* Breakpoint handle of ptrace watchpoint */
+       int perf_fd;    /* File descriptor of perf performance counter */
+       u64 perf_count; /* Most recently fetched perf performance counter value */
+       pid_t pid;      /* PID of child process */
+       void *pc;       /* Most recently fetched child PC value */
+       int status;     /* Stop status of child after waitpid */
+       unsigned long value;    /* Dummy value to be read/written to by child */
+       int err;
+
+       err = ptrace_fork_child(&pid);
+       if (err)
+               return err;
+
+       if (!pid) {
+               same_watch_addr_child(&value);
+               exit(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;
+       err = check_watchpoints(pid);
+       if (err)
+               return err;
 
-       ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd);
-       return ret;
-}
+       /* Place a perf watchpoint counter on value */
+       perf_fd = perf_watchpoint_open(pid, &value, sizeof(value));
+       FAIL_IF_MSG(perf_fd < 0, "Failed to open perf performance counter");
 
-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;
+       /* Place a ptrace watchpoint on value */
+       ppc_ptrace_init_breakpoint(&bp_info, PPC_BREAKPOINT_TRIGGER_READ, &value, sizeof(value));
+       bp_id = ppc_ptrace_sethwdbg(pid, &bp_info);
+       FAIL_IF_MSG(bp_id < 0, "Failed to set ptrace watchpoint");
 
-       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);
+       /* Let the child run. It should stop on the ptrace watchpoint */
+       FAIL_IF_MSG(ptrace_cont(pid, 0), "Failed to continue child");
 
-ptrace_close:
-       ptrace(PPC_PTRACE_DELHWDEBUG, child_pid, 0, ptrace_fd);
-       return ret;
-}
+       FAIL_IF_MSG(waitpid(pid, &status, 0) == -1, "Failed to wait for child");
+       FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
+       FAIL_IF_MSG(ptrace_getreg_pc(pid, &pc), "Failed to get child PC");
+       FAIL_IF_MSG(pc != same_watch_addr_load, "Child did not stop on load instruction");
 
-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;
+       /*
+        * We stopped before executing the load, so perf should not have
+        * recorded any events yet
         */
-       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;
-}
+       FAIL_IF_MSG(perf_read_counter(perf_fd, &perf_count), "Failed to read perf counter");
+       FAIL_IF_MSG(perf_count != 0, "perf recorded unexpected event");
+
+       /* Single stepping over the load should increment the perf counter */
+       FAIL_IF_MSG(ptrace_singlestep(pid, 0), "Failed to single step child");
+
+       FAIL_IF_MSG(waitpid(pid, &status, 0) == -1, "Failed to wait for child");
+       FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
+       FAIL_IF_MSG(ptrace_getreg_pc(pid, &pc), "Failed to get child PC");
+       FAIL_IF_MSG(pc != same_watch_addr_load + 4, "Failed to single step load instruction");
+       FAIL_IF_MSG(perf_read_counter(perf_fd, &perf_count), "Failed to read perf counter");
+       FAIL_IF_MSG(perf_count != 1, "perf counter did not increment");
+
+       /*
+        * Set up a ptrace watchpoint on the value again and trigger it.
+        * The perf counter should not have incremented because we do not
+        * execute the load yet.
+        */
+       FAIL_IF_MSG(ppc_ptrace_delhwdbg(pid, bp_id), "Failed to remove old ptrace watchpoint");
+       bp_id = ppc_ptrace_sethwdbg(pid, &bp_info);
+       FAIL_IF_MSG(bp_id < 0, "Failed to set ptrace watchpoint");
+       FAIL_IF_MSG(ptrace_setreg_pc(pid, same_watch_addr_load), "Failed to set child PC");
+       FAIL_IF_MSG(ptrace_cont(pid, 0), "Failed to continue child");
+
+       FAIL_IF_MSG(waitpid(pid, &status, 0) == -1, "Failed to wait for child");
+       FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
+       FAIL_IF_MSG(ptrace_getreg_pc(pid, &pc), "Failed to get child PC");
+       FAIL_IF_MSG(pc != same_watch_addr_load, "Child did not stop on load trap");
+       FAIL_IF_MSG(perf_read_counter(perf_fd, &perf_count), "Failed to read perf counter");
+       FAIL_IF_MSG(perf_count != 1, "perf counter should not have changed");
+
+       /* Continuing over the load should increment the perf counter */
+       FAIL_IF_MSG(ptrace_cont(pid, 0), "Failed to continue child");
+
+       FAIL_IF_MSG(waitpid(pid, &status, 0) == -1, "Failed to wait for child");
+       FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
+       FAIL_IF_MSG(ptrace_getreg_pc(pid, &pc), "Failed to get child PC");
+       FAIL_IF_MSG(pc != same_watch_addr_trap, "Child did not stop on end trap");
+       FAIL_IF_MSG(perf_read_counter(perf_fd, &perf_count), "Failed to read perf counter");
+       FAIL_IF_MSG(perf_count != 2, "perf counter did not increment");
+
+       /*
+        * If we set the child PC back to the load instruction, then continue,
+        * we should reach the end trap (because ptrace is one-shot) and have
+        * another perf event.
+        */
+       FAIL_IF_MSG(ptrace_setreg_pc(pid, same_watch_addr_load), "Failed to set child PC");
+       FAIL_IF_MSG(ptrace_cont(pid, 0), "Failed to continue child");
+
+       FAIL_IF_MSG(waitpid(pid, &status, 0) == -1, "Failed to wait for child");
+       FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
+       FAIL_IF_MSG(ptrace_getreg_pc(pid, &pc), "Failed to get child PC");
+       FAIL_IF_MSG(pc != same_watch_addr_trap, "Child did not stop on end trap");
+       FAIL_IF_MSG(perf_read_counter(perf_fd, &perf_count), "Failed to read perf counter");
+       FAIL_IF_MSG(perf_count != 3, "perf counter did not increment");
+
+       /*
+        * If we set the child PC back to the load instruction, set a ptrace
+        * watchpoint on the load, then continue, we should immediately get
+        * the ptrace trap without incrementing the perf counter
+        */
+       FAIL_IF_MSG(ppc_ptrace_delhwdbg(pid, bp_id), "Failed to remove old ptrace watchpoint");
+       bp_id = ppc_ptrace_sethwdbg(pid, &bp_info);
+       FAIL_IF_MSG(bp_id < 0, "Failed to set ptrace watchpoint");
+       FAIL_IF_MSG(ptrace_setreg_pc(pid, same_watch_addr_load), "Failed to set child PC");
+       FAIL_IF_MSG(ptrace_cont(pid, 0), "Failed to continue child");
+
+       FAIL_IF_MSG(waitpid(pid, &status, 0) == -1, "Failed to wait for child");
+       FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
+       FAIL_IF_MSG(ptrace_getreg_pc(pid, &pc), "Failed to get child PC");
+       FAIL_IF_MSG(pc != same_watch_addr_load, "Child did not stop on load instruction");
+       FAIL_IF_MSG(perf_read_counter(perf_fd, &perf_count), "Failed to read perf counter");
+       FAIL_IF_MSG(perf_count != 3, "perf counter should not have changed");
+
+       /*
+        * If we change the PC while stopped on the load instruction, we should
+        * not increment the perf counter (because ptrace is before-execute,
+        * perf is after-execute).
+        */
+       FAIL_IF_MSG(ptrace_setreg_pc(pid, same_watch_addr_load + 4), "Failed to set child PC");
+       FAIL_IF_MSG(ptrace_cont(pid, 0), "Failed to continue child");
 
-static int do_test(const char *msg, int (*fun)(pid_t arg), pid_t arg)
-{
-       int ret;
+       FAIL_IF_MSG(waitpid(pid, &status, 0) == -1, "Failed to wait for child");
+       FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
+       FAIL_IF_MSG(ptrace_getreg_pc(pid, &pc), "Failed to get child PC");
+       FAIL_IF_MSG(pc != same_watch_addr_trap, "Child did not stop on end trap");
+       FAIL_IF_MSG(perf_read_counter(perf_fd, &perf_count), "Failed to read perf counter");
+       FAIL_IF_MSG(perf_count != 3, "perf counter should not have changed");
 
-       ret = fun(arg);
-       if (ret)
-               printf("%s: Error\n", msg);
-       else
-               printf("%s: Ok\n", msg);
-       return ret;
-}
+       /* Clean up child */
+       FAIL_IF_MSG(kill(pid, SIGKILL) != 0, "Failed to kill child");
 
-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;
+       return 0;
 }
 
-static void get_dbginfo(pid_t child_pid, struct ppc_debug_info *dbginfo)
+/*
+ * Tests the interaction between ptrace and perf when:
+ * 1. perf watches a value
+ * 2. ptrace watches a different value
+ * 3. The perf value is read, then the ptrace value is read immediately after
+ *
+ * A breakpoint implementation may accidentally misattribute/skip one of
+ * the ptrace or perf handlers, as interrupt based work is done after perf
+ * and before ptrace.
+ *
+ * We expect the perf counter to increment before the ptrace watchpoint
+ * triggers.
+ */
+int perf_then_ptrace_test(void)
 {
-       if (ptrace(PPC_PTRACE_GETHWDBGINFO, child_pid, NULL, dbginfo)) {
-               perror("Can't get breakpoint info");
-               exit(-1);
+       struct ppc_hw_breakpoint bp_info;       /* ptrace breakpoint info */
+       int bp_id;      /* Breakpoint handle of ptrace watchpoint */
+       int perf_fd;    /* File descriptor of perf performance counter */
+       u64 perf_count; /* Most recently fetched perf performance counter value */
+       pid_t pid;      /* PID of child process */
+       void *pc;       /* Most recently fetched child PC value */
+       int status;     /* Stop status of child after waitpid */
+       unsigned long perf_value;       /* Dummy value to be watched by perf */
+       unsigned long ptrace_value;     /* Dummy value to be watched by ptrace */
+       int err;
+
+       err = ptrace_fork_child(&pid);
+       if (err)
+               return err;
+
+       /*
+        * If we are the child, run a subroutine that reads the perf value,
+        * then reads the ptrace value with consecutive load instructions
+        */
+       if (!pid) {
+               perf_then_ptrace_child(&perf_value, &ptrace_value);
+               exit(0);
        }
-}
 
-static int ptrace_perf_hwbreak(void)
-{
-       int ret;
-       pid_t child_pid;
-       struct ppc_debug_info dbginfo;
+       err = check_watchpoints(pid);
+       if (err)
+               return err;
 
-       child_pid = fork();
-       if (!child_pid)
-               return child();
+       /* Place a perf watchpoint counter */
+       perf_fd = perf_watchpoint_open(pid, &perf_value, sizeof(perf_value));
+       FAIL_IF_MSG(perf_fd < 0, "Failed to open perf performance counter");
 
-       /* parent */
-       wait(NULL); /* <-- child (SIGUSR1) */
+       /* Place a ptrace watchpoint */
+       ppc_ptrace_init_breakpoint(&bp_info, PPC_BREAKPOINT_TRIGGER_READ,
+                                  &ptrace_value, sizeof(ptrace_value));
+       bp_id = ppc_ptrace_sethwdbg(pid, &bp_info);
+       FAIL_IF_MSG(bp_id < 0, "Failed to set ptrace watchpoint");
 
-       get_dbginfo(child_pid, &dbginfo);
-       SKIP_IF_MSG(dbginfo.num_data_bps <= 1, "Not enough data watchpoints (need at least 2)");
+       /* Let the child run. It should stop on the ptrace watchpoint */
+       FAIL_IF_MSG(ptrace_cont(pid, 0), "Failed to continue child");
 
-       ret = perf_cpu_event_open(0, (__u64)perf_data1, sizeof(*perf_data1));
-       SKIP_IF_MSG(ret < 0, "perf_event_open syscall failed");
-       close(ret);
+       FAIL_IF_MSG(waitpid(pid, &status, 0) == -1, "Failed to wait for child");
+       FAIL_IF_MSG(!WIFSTOPPED(status), "Child is not stopped");
+       FAIL_IF_MSG(ptrace_getreg_pc(pid, &pc), "Failed to get child PC");
+       FAIL_IF_MSG(pc != perf_then_ptrace_load2, "Child did not stop on ptrace load");
 
-       ret = test(child_pid);
+       /* perf should have recorded the first load */
+       FAIL_IF_MSG(perf_read_counter(perf_fd, &perf_count), "Failed to read perf counter");
+       FAIL_IF_MSG(perf_count != 1, "perf counter did not increment");
 
-       ptrace(PTRACE_CONT, child_pid, NULL, 0);
-       return ret;
+       /* Clean up child */
+       FAIL_IF_MSG(kill(pid, SIGKILL) != 0, "Failed to kill child");
+
+       return 0;
 }
 
 int main(int argc, char *argv[])
 {
-       return test_harness(ptrace_perf_hwbreak, "ptrace-perf-hwbreak");
+       int err = 0;
+
+       err |= test_harness(same_watch_addr_test, "same_watch_addr");
+       err |= test_harness(perf_then_ptrace_test, "perf_then_ptrace");
+
+       return err;
 }