Add hardware breakpoint ptrace test
This adds a ptrace test which tests the hardware breakpoint
functionality of the ptrace api.
I've also renamed the test case to sys_ptrace to better match the naming
scheme in the other files. I've ran the tests on angler (32 and 64 bit)
and fugu.
Test: run the test
Change-Id: I7d8a7d79585477d78da1f033c85f8d2cc3b34340
diff --git a/tests/sys_ptrace_test.cpp b/tests/sys_ptrace_test.cpp
index bdd6a89..bee43ab 100644
--- a/tests/sys_ptrace_test.cpp
+++ b/tests/sys_ptrace_test.cpp
@@ -30,8 +30,8 @@
#define TRAP_HWBKPT 4
#endif
-template<typename T>
-static void __attribute__((noreturn)) fork_child(unsigned cpu, T &data) {
+template <typename T>
+static void __attribute__((noreturn)) watchpoint_fork_child(unsigned cpu, T& data) {
// Extra precaution: make sure we go away if anything happens to our parent.
if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) == -1) {
perror("prctl(PR_SET_PDEATHSIG)");
@@ -71,7 +71,9 @@
pid_t pid;
};
-static bool are_watchpoints_supported(pid_t child) {
+enum class HwFeature { Watchpoint, Breakpoint };
+
+static bool is_hw_feature_supported(pid_t child, HwFeature feature) {
#if defined(__arm__)
long capabilities;
long result = ptrace(PTRACE_GETHBPREGS, child, 0, &capabilities);
@@ -79,26 +81,34 @@
EXPECT_EQ(EIO, errno);
return false;
}
- return ((capabilities >> 8) & 0xff) > 0;
+ switch (feature) {
+ case HwFeature::Watchpoint:
+ return ((capabilities >> 8) & 0xff) > 0;
+ case HwFeature::Breakpoint:
+ return (capabilities & 0xff) > 0;
+ }
#elif defined(__aarch64__)
user_hwdebug_state dreg_state;
iovec iov;
iov.iov_base = &dreg_state;
iov.iov_len = sizeof(dreg_state);
- long result = ptrace(PTRACE_GETREGSET, child, NT_ARM_HW_WATCH, &iov);
+ long result = ptrace(PTRACE_GETREGSET, child,
+ feature == HwFeature::Watchpoint ? NT_ARM_HW_WATCH : NT_ARM_HW_BREAK, &iov);
if (result == -1) {
EXPECT_EQ(EINVAL, errno);
return false;
}
return (dreg_state.dbg_info & 0xff) > 0;
#elif defined(__i386__) || defined(__x86_64__)
- // We assume watchpoints are always supported on x86.
+ // We assume watchpoints and breakpoints are always supported on x86.
(void) child;
+ (void)feature;
return true;
#else
// TODO: mips support.
(void) child;
+ (void)feature;
return false;
#endif
}
@@ -153,7 +163,7 @@
pid_t child = fork();
ASSERT_NE(-1, child) << strerror(errno);
- if (child == 0) fork_child(cpu, data);
+ if (child == 0) watchpoint_fork_child(cpu, data);
ChildGuard guard(child);
@@ -162,7 +172,10 @@
ASSERT_TRUE(WIFSTOPPED(status)) << "Status was: " << status;
ASSERT_EQ(SIGSTOP, WSTOPSIG(status)) << "Status was: " << status;
- if (!are_watchpoints_supported(child)) return;
+ if (!is_hw_feature_supported(child, HwFeature::Watchpoint)) {
+ GTEST_LOG_(INFO) << "Skipping test because hardware support is not available.\n";
+ return;
+ }
set_watchpoint(child, &data, sizeof data);
@@ -191,7 +204,7 @@
// Test watchpoint API. The test is considered successful if our watchpoints get hit OR the
// system reports that watchpoint support is not present. We run the test for different
// watchpoint sizes, while pinning the process to each cpu in turn, for better coverage.
-TEST(ptrace, watchpoint_stress) {
+TEST(sys_ptrace, watchpoint_stress) {
cpu_set_t available_cpus;
ASSERT_EQ(0, sched_getaffinity(0, sizeof available_cpus, &available_cpus));
@@ -200,3 +213,102 @@
run_watchpoint_test(cpu);
}
}
+
+static void __attribute__((noinline)) breakpoint_func() {
+ asm volatile("");
+}
+
+static void __attribute__((noreturn)) breakpoint_fork_child() {
+ // Extra precaution: make sure we go away if anything happens to our parent.
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) == -1) {
+ perror("prctl(PR_SET_PDEATHSIG)");
+ _exit(1);
+ }
+
+ if (ptrace(PTRACE_TRACEME, 0, nullptr, nullptr) == -1) {
+ perror("ptrace(PTRACE_TRACEME)");
+ _exit(2);
+ }
+
+ raise(SIGSTOP); // Synchronize with the tracer, let it set the breakpoint.
+
+ breakpoint_func(); // Now trigger the breakpoint.
+
+ _exit(0);
+}
+
+static void set_breakpoint(pid_t child) {
+ uintptr_t address = uintptr_t(breakpoint_func);
+#if defined(__arm__) || defined(__aarch64__)
+ address &= ~3;
+ const unsigned byte_mask = 0xf;
+ const unsigned enable = 1;
+ const unsigned control = byte_mask << 5 | enable;
+
+#ifdef __arm__
+ ASSERT_EQ(0, ptrace(PTRACE_SETHBPREGS, child, 1, &address)) << strerror(errno);
+ ASSERT_EQ(0, ptrace(PTRACE_SETHBPREGS, child, 2, &control)) << strerror(errno);
+#else // aarch64
+ user_hwdebug_state dreg_state;
+ memset(&dreg_state, 0, sizeof dreg_state);
+ dreg_state.dbg_regs[0].addr = reinterpret_cast<uintptr_t>(address);
+ dreg_state.dbg_regs[0].ctrl = control;
+
+ iovec iov;
+ iov.iov_base = &dreg_state;
+ iov.iov_len = offsetof(user_hwdebug_state, dbg_regs) + sizeof(dreg_state.dbg_regs[0]);
+
+ ASSERT_EQ(0, ptrace(PTRACE_SETREGSET, child, NT_ARM_HW_BREAK, &iov)) << strerror(errno);
+#endif
+#elif defined(__i386__) || defined(__x86_64__)
+ ASSERT_EQ(0, ptrace(PTRACE_POKEUSER, child, offsetof(user, u_debugreg[0]), address))
+ << strerror(errno);
+ errno = 0;
+ unsigned data = ptrace(PTRACE_PEEKUSER, child, offsetof(user, u_debugreg[7]), nullptr);
+ ASSERT_EQ(0, errno);
+
+ const unsigned size = 0;
+ const unsigned enable = 1;
+ const unsigned type = 0; // Execute
+
+ const unsigned mask = 3 << 18 | 3 << 16 | 1;
+ const unsigned value = size << 18 | type << 16 | enable;
+ data &= mask;
+ data |= value;
+ ASSERT_EQ(0, ptrace(PTRACE_POKEUSER, child, offsetof(user, u_debugreg[7]), data))
+ << strerror(errno);
+#else
+ (void)child;
+#endif
+}
+
+// Test hardware breakpoint API. The test is considered successful if the breakpoints get hit OR the
+// system reports that hardware breakpoint support is not present.
+TEST(sys_ptrace, hardware_breakpoint) {
+ pid_t child = fork();
+ ASSERT_NE(-1, child) << strerror(errno);
+ if (child == 0) breakpoint_fork_child();
+
+ ChildGuard guard(child);
+
+ int status;
+ ASSERT_EQ(child, waitpid(child, &status, __WALL)) << strerror(errno);
+ ASSERT_TRUE(WIFSTOPPED(status)) << "Status was: " << status;
+ ASSERT_EQ(SIGSTOP, WSTOPSIG(status)) << "Status was: " << status;
+
+ if (!is_hw_feature_supported(child, HwFeature::Breakpoint)) {
+ GTEST_LOG_(INFO) << "Skipping test because hardware support is not available.\n";
+ return;
+ }
+
+ set_breakpoint(child);
+
+ ASSERT_EQ(0, ptrace(PTRACE_CONT, child, nullptr, nullptr)) << strerror(errno);
+ ASSERT_EQ(child, waitpid(child, &status, __WALL)) << strerror(errno);
+ ASSERT_TRUE(WIFSTOPPED(status)) << "Status was: " << status;
+ ASSERT_EQ(SIGTRAP, WSTOPSIG(status)) << "Status was: " << status;
+
+ siginfo_t siginfo;
+ ASSERT_EQ(0, ptrace(PTRACE_GETSIGINFO, child, nullptr, &siginfo)) << strerror(errno);
+ ASSERT_EQ(TRAP_HWBKPT, siginfo.si_code);
+}