[lib/inferior_control] Add Thread::TryNext
Plus add basic breakpoint,register support to debugger_utils.
These can be used outside of inferior_control, e.g., testcases.
Tested: New tests added
Change-Id: I019ab124a7cd2ace2b1b48e1a5d70aa3917213ab
diff --git a/garnet/lib/debugger_utils/BUILD.gn b/garnet/lib/debugger_utils/BUILD.gn
index 41b04fe..45ef4ae 100644
--- a/garnet/lib/debugger_utils/BUILD.gn
+++ b/garnet/lib/debugger_utils/BUILD.gn
@@ -9,6 +9,7 @@
static_library("debugger_utils") {
sources = [
+ "breakpoints.h",
"build_ids.cc",
"build_ids.h",
"byte_block.h",
@@ -28,10 +29,13 @@
if (is_fuchsia) {
sources += [
+ "breakpoints.cc",
"dso_list.cc",
"dso_list.h",
"jobs.cc",
"jobs.h",
+ "registers.cc",
+ "registers.h",
"sysinfo.cc",
"sysinfo.h",
"threads.cc",
diff --git a/garnet/lib/debugger_utils/breakpoints.cc b/garnet/lib/debugger_utils/breakpoints.cc
new file mode 100644
index 0000000..0ad0529
--- /dev/null
+++ b/garnet/lib/debugger_utils/breakpoints.cc
@@ -0,0 +1,42 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "breakpoints.h"
+
+#include <zircon/syscalls/debug.h>
+
+#include <lib/fxl/logging.h>
+
+#include "garnet/lib/debugger_utils/registers.h"
+#include "garnet/lib/debugger_utils/util.h"
+
+namespace debugger_utils {
+
+zx_status_t ResumeAfterSoftwareBreakpointInstruction(
+ zx_handle_t thread, zx_handle_t eport) {
+ // Adjust the PC to point after the breakpoint instruction.
+ zx_thread_state_general_regs_t regs;
+ zx_status_t status = ReadGeneralRegisters(thread, ®s);
+ if (status != ZX_OK) {
+ return status;
+ }
+ zx_vaddr_t pc = GetPcFromGeneralRegisters(®s);
+ pc = IncrementPcAfterBreak(pc);
+ SetPcInGeneralRegisters(®s, pc);
+ status = WriteGeneralRegisters(thread, ®s);
+ if (status != ZX_OK) {
+ return status;
+ }
+
+ // Now we can resume the thread.
+ status = zx_task_resume_from_exception(thread, eport, 0);
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Failed to resume thread " << GetKoid(thread)
+ << " after exception: "
+ << debugger_utils::ZxErrorString(status);
+ }
+ return status;
+}
+
+} // namespace debugger_utils
diff --git a/garnet/lib/debugger_utils/breakpoints.h b/garnet/lib/debugger_utils/breakpoints.h
new file mode 100644
index 0000000..8ad2e77
--- /dev/null
+++ b/garnet/lib/debugger_utils/breakpoints.h
@@ -0,0 +1,109 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_LIB_DEBUGGER_UTILS_BREAKPOINTS_H_
+#define GARNET_LIB_DEBUGGER_UTILS_BREAKPOINTS_H_
+
+#include <stdint.h>
+#include <zircon/types.h>
+
+namespace debugger_utils {
+
+constexpr const uint8_t kX64BreakpointInstruction[] = { 0xcc };
+
+// ARM64 break instruction (little-endian)
+constexpr const uint8_t kArm64BreakpointInstruction[] =
+ { 0x00, 0x00, 0x20, 0xd4 };
+
+// For native debugging.
+static inline const uint8_t* GetBreakpointInstruction() {
+#if defined(__x86_64__)
+ return kX64BreakpointInstruction;
+#elif defined(__aarch64__)
+ return kArm64BreakpointInstruction;
+#else
+#error "unsupported architecture"
+#endif
+}
+
+// For native debugging.
+static inline uint32_t GetBreakpointInstructionSize() {
+#if defined(__x86_64__)
+ return sizeof(kX64BreakpointInstruction);
+#elif defined(__aarch64__)
+ return sizeof(kArm64BreakpointInstruction);
+#else
+#error "unsupported architecture"
+#endif
+}
+
+// Given the reported PC after a s/w breakpoint instruction, return the
+// address of that instruction.
+
+static inline zx_vaddr_t X64DecrementPcAfterBreak(zx_vaddr_t pc) {
+ return pc - sizeof(kX64BreakpointInstruction);
+}
+
+static inline zx_vaddr_t Arm64DecrementPcAfterBreak(zx_vaddr_t pc) {
+ return pc;
+}
+
+// For native debugging.
+static inline zx_vaddr_t DecrementPcAfterBreak(zx_vaddr_t pc) {
+#if defined(__x86_64__)
+ return X64DecrementPcAfterBreak(pc);
+#elif defined(__aarch64__)
+ return Arm64DecrementPcAfterBreak(pc);
+#else
+#error "unsupported architecture"
+#endif
+}
+
+// Given the reported PC after a s/w breakpoint instruction, return the
+// address of the next instruction.
+
+static inline zx_vaddr_t X64IncrementPcAfterBreak(zx_vaddr_t pc) {
+ return pc;
+}
+
+static inline zx_vaddr_t Arm64IncrementPcAfterBreak(zx_vaddr_t pc) {
+ return pc + sizeof(kArm64BreakpointInstruction);;
+}
+
+// For native debugging.
+static inline zx_vaddr_t IncrementPcAfterBreak(zx_vaddr_t pc) {
+#if defined(__x86_64__)
+ return X64IncrementPcAfterBreak(pc);
+#elif defined(__aarch64__)
+ return Arm64IncrementPcAfterBreak(pc);
+#else
+#error "unsupported architecture"
+#endif
+}
+
+// Trigger a s/w breakpoint instruction.
+
+// For native debugging.
+static void inline TriggerSoftwareBreakpoint() {
+#if defined(__x86_64__)
+ __asm__ volatile("int3");
+#elif defined(__aarch64__)
+ __asm__ volatile("brk 0");
+#else
+#error "unsupported architecture"
+#endif
+}
+
+// Resume |thread| after it has executed a s/w breakpoint instruction.
+
+#ifdef __Fuchsia__
+
+zx_status_t ResumeAfterSoftwareBreakpointInstruction(zx_handle_t thread,
+ zx_handle_t eport);
+
+#endif
+
+} // namespace debugger_utils
+
+#endif // GARNET_LIB_DEBUGGER_UTILS_BREAKPOINTS_H_
diff --git a/garnet/lib/debugger_utils/registers.cc b/garnet/lib/debugger_utils/registers.cc
new file mode 100644
index 0000000..d6edb96
--- /dev/null
+++ b/garnet/lib/debugger_utils/registers.cc
@@ -0,0 +1,39 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "registers.h"
+
+#include <lib/fxl/logging.h>
+
+#include "util.h"
+
+namespace debugger_utils {
+
+zx_status_t ReadGeneralRegisters(zx_handle_t thread,
+ zx_thread_state_general_regs_t* regs) {
+ zx_status_t status =
+ zx_thread_read_state(thread, ZX_THREAD_STATE_GENERAL_REGS,
+ regs, sizeof(*regs));
+ if (status < 0) {
+ FXL_LOG(ERROR) << "Failed to read general registers for thread "
+ << debugger_utils::GetKoid(thread) << ": "
+ << debugger_utils::ZxErrorString(status);
+ }
+ return status;
+}
+
+zx_status_t WriteGeneralRegisters(zx_handle_t thread,
+ const zx_thread_state_general_regs_t* regs) {
+ zx_status_t status =
+ zx_thread_write_state(thread, ZX_THREAD_STATE_GENERAL_REGS,
+ regs, sizeof(*regs));
+ if (status < 0) {
+ FXL_LOG(ERROR) << "Failed to write general registers for thread "
+ << debugger_utils::GetKoid(thread) << ": "
+ << debugger_utils::ZxErrorString(status);
+ }
+ return status;
+}
+
+} // namespace debugger_utils
diff --git a/garnet/lib/debugger_utils/registers.h b/garnet/lib/debugger_utils/registers.h
new file mode 100644
index 0000000..0456ff6
--- /dev/null
+++ b/garnet/lib/debugger_utils/registers.h
@@ -0,0 +1,88 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef GARNET_LIB_DEBUGGER_UTILS_REGISTERS_H_
+#define GARNET_LIB_DEBUGGER_UTILS_REGISTERS_H_
+
+#include <stdint.h>
+#include <zircon/types.h>
+#include <zircon/syscalls/debug.h>
+
+namespace debugger_utils {
+
+zx_status_t ReadGeneralRegisters(zx_handle_t thread,
+ zx_thread_state_general_regs_t* regs);
+
+zx_status_t WriteGeneralRegisters(zx_handle_t thread,
+ const zx_thread_state_general_regs_t* regs);
+
+static inline zx_vaddr_t GetPcFromGeneralRegisters(
+ const zx_thread_state_general_regs_t* regs) {
+#if defined(__x86_64__)
+ return regs->rip;
+#elif defined(__aarch64__)
+ return regs->pc;
+#else
+#error "unsupported architecture"
+#endif
+}
+
+static inline void SetPcInGeneralRegisters(
+ zx_thread_state_general_regs_t* regs, zx_vaddr_t pc) {
+#if defined(__x86_64__)
+ regs->rip = pc;
+#elif defined(__aarch64__)
+ regs->pc = pc;
+#else
+#error "unsupported architecture"
+#endif
+}
+
+static inline zx_vaddr_t GetSpFromGeneralRegisters(
+ const zx_thread_state_general_regs_t* regs) {
+#if defined(__x86_64__)
+ return regs->rsp;
+#elif defined(__aarch64__)
+ return regs->sp;
+#else
+#error "unsupported architecture"
+#endif
+}
+
+static inline void SetSpInGeneralRegisters(
+ zx_thread_state_general_regs_t* regs, zx_vaddr_t sp) {
+#if defined(__x86_64__)
+ regs->rsp = sp;
+#elif defined(__aarch64__)
+ regs->sp = sp;
+#else
+#error "unsupported architecture"
+#endif
+}
+
+static inline zx_vaddr_t GetFpFromGeneralRegisters(
+ const zx_thread_state_general_regs_t* regs) {
+#if defined(__x86_64__)
+ return regs->rbp;
+#elif defined(__aarch64__)
+ return regs->r[29];
+#else
+#error "unsupported architecture"
+#endif
+}
+
+static inline void SetFpInGeneralRegisters(
+ zx_thread_state_general_regs_t* regs, zx_vaddr_t fp) {
+#if defined(__x86_64__)
+ regs->rbp = fp;
+#elif defined(__aarch64__)
+ regs->r[29] = fp;
+#else
+#error "unsupported architecture"
+#endif
+}
+
+} // namespace debugger_utils
+
+#endif // GARNET_LIB_DEBUGGER_UTILS_BREAKPOINTS_H_
diff --git a/garnet/lib/inferior_control/BUILD.gn b/garnet/lib/inferior_control/BUILD.gn
index 3b72455..b793c2c 100644
--- a/garnet/lib/inferior_control/BUILD.gn
+++ b/garnet/lib/inferior_control/BUILD.gn
@@ -76,6 +76,7 @@
"process_unittest.cc",
"test_server.cc",
"test_server.h",
+ "thread_unittest.cc",
]
deps = [
@@ -96,6 +97,10 @@
"test_helper.cc",
]
+ deps = [
+ "//garnet/lib/debugger_utils",
+ ]
+
libs = [ "zircon" ]
}
diff --git a/garnet/lib/inferior_control/test_helper.cc b/garnet/lib/inferior_control/test_helper.cc
index be09a4c..679161e 100644
--- a/garnet/lib/inferior_control/test_helper.cc
+++ b/garnet/lib/inferior_control/test_helper.cc
@@ -4,22 +4,96 @@
#include <cstdio>
#include <cstring>
+#include <thread>
#include <zircon/process.h>
#include <zircon/processargs.h>
-#include <zircon/status.h>
#include <zircon/syscalls.h>
+#include <zircon/syscalls/port.h>
+#include <zx/event.h>
+#include <zx/port.h>
-static int test_attach() {
- auto channel = zx_take_startup_handle(PA_HND(PA_USER0, 0));
- auto status = zx_object_wait_one(channel, ZX_CHANNEL_PEER_CLOSED,
- ZX_TIME_INFINITE, nullptr);
- if (status != ZX_OK) {
- fprintf(stderr, "zx_object_wait_one failed: %d/%s\n", status,
- zx_status_get_string(status));
- return 1;
+#include "garnet/lib/debugger_utils/breakpoints.h"
+#include "garnet/lib/debugger_utils/util.h"
+
+using debugger_utils::ZxErrorString;
+
+static void ExceptionHandlerThreadFunc(
+ zx_handle_t thread, zx::port* eport, zx::event* event) {
+ zx_koid_t tid = debugger_utils::GetKoid(thread);
+ zx_status_t status = zx_task_bind_exception_port(
+ thread, eport->get(), tid, 0);
+ FXL_CHECK(status == ZX_OK) << "status: " << ZxErrorString(status);
+
+ // Now that we've bound to the thread, notify the test.
+ status = event->signal(0, ZX_EVENT_SIGNALED);
+ FXL_CHECK(status == ZX_OK) << "status: " << ZxErrorString(status);
+
+ for (;;) {
+ zx_port_packet_t packet;
+ zx_status_t status = eport->wait(zx::time::infinite(), &packet);
+ FXL_CHECK(status == ZX_OK) << "status: " << ZxErrorString(status);
+
+ if (packet.type == ZX_PKT_TYPE_USER) {
+ // Sent to trigger loop exit.
+ break;
+ }
+
+ FXL_CHECK(ZX_PKT_IS_EXCEPTION(packet.type));
+ FXL_CHECK(packet.type == ZX_EXCP_SW_BREAKPOINT);
+ FXL_CHECK(packet.key == tid);
+ status = debugger_utils::ResumeAfterSoftwareBreakpointInstruction(
+ thread, eport->get());
+ FXL_CHECK(status == ZX_OK);
}
- printf("test-attach complete, peer channel closed\n");
+}
+
+static void WaitPeerClosed() {
+ zx_handle_t channel = zx_take_startup_handle(PA_HND(PA_USER0, 0));
+ // If no channel was passed we're running standalone.
+ if (channel == ZX_HANDLE_INVALID) {
+ return;
+ }
+ zx_status_t status = zx_object_wait_one(channel, ZX_CHANNEL_PEER_CLOSED,
+ ZX_TIME_INFINITE, nullptr);
+ FXL_CHECK(status == ZX_OK) << "status: " << ZxErrorString(status);
+}
+
+static int TestAttach() {
+ WaitPeerClosed();
+ printf("test-attach complete\n");
+ return 0;
+}
+
+static int TestTryNext() {
+ zx::port eport;
+ zx_status_t status = zx::port::create(0, &eport);
+ FXL_CHECK(status == ZX_OK) << "status: " << ZxErrorString(status);
+
+ zx::event event;
+ status = zx::event::create(0, &event);
+ FXL_CHECK(status == ZX_OK) << "status: " << ZxErrorString(status);
+
+ zx_handle_t self_thread = zx_thread_self();
+ std::thread exception_thread(
+ &ExceptionHandlerThreadFunc, self_thread, &eport, &event);
+
+ // Don't trigger the s/w breakpoint until the exception loop is ready
+ // to handle it.
+ status = event.wait_one(ZX_EVENT_SIGNALED, zx::time::infinite(), nullptr);
+ FXL_CHECK(status == ZX_OK) << "status: " << ZxErrorString(status);
+
+ debugger_utils::TriggerSoftwareBreakpoint();
+
+ WaitPeerClosed();
+
+ // The the exception thread to exit.
+ zx_port_packet_t packet{};
+ status = eport.queue(&packet);
+ FXL_CHECK(status == ZX_OK) << "status: " << ZxErrorString(status);
+ exception_thread.join();
+
+ printf("test-try-next complete\n");
return 0;
}
@@ -32,7 +106,10 @@
if (argc == 2) {
const char* cmd = argv[1];
if (strcmp(cmd, "test-attach") == 0) {
- return test_attach();
+ return TestAttach();
+ }
+ if (strcmp(cmd, "test-try-next") == 0) {
+ return TestTryNext();
}
fprintf(stderr, "Unrecognized command: %s\n", cmd);
return 1;
diff --git a/garnet/lib/inferior_control/thread.cc b/garnet/lib/inferior_control/thread.cc
index 923d9f2..af9e8a3 100644
--- a/garnet/lib/inferior_control/thread.cc
+++ b/garnet/lib/inferior_control/thread.cc
@@ -135,6 +135,28 @@
}
}
+bool Thread::TryNext() {
+ if (state() != State::kInException && state() != State::kNew) {
+ FXL_LOG(ERROR) << "Cannot try-next a thread " << GetName()
+ << " while in state: " << StateName(state());
+ return false;
+ }
+
+ FXL_VLOG(2) << "Thread " << GetName() << ": trying next exception handler";
+
+ zx_status_t status =
+ zx_task_resume_from_exception(handle_, GetExceptionPortHandle(),
+ ZX_RESUME_TRY_NEXT);
+ if (status < 0) {
+ FXL_LOG(ERROR) << "Failed to try-next thread "
+ << GetName() << ": "
+ << debugger_utils::ZxErrorString(status);
+ return false;
+ }
+
+ return true;
+}
+
bool Thread::ResumeFromException() {
if (state() != State::kInException && state() != State::kNew) {
FXL_LOG(ERROR) << "Cannot resume a thread while in state: "
diff --git a/garnet/lib/inferior_control/thread.h b/garnet/lib/inferior_control/thread.h
index 9c9178b..56a3700 100644
--- a/garnet/lib/inferior_control/thread.h
+++ b/garnet/lib/inferior_control/thread.h
@@ -73,6 +73,9 @@
void OnException(const zx_excp_type_t type,
const zx_exception_context_t& context);
+ // Pass the exception on to the next handler.
+ bool TryNext();
+
// Resumes the thread from a "stopped in exception" state. Returns true on
// success, false on failure. The thread state on return is kRunning.
bool ResumeFromException();
diff --git a/garnet/lib/inferior_control/thread_unittest.cc b/garnet/lib/inferior_control/thread_unittest.cc
new file mode 100644
index 0000000..7f8bf29
--- /dev/null
+++ b/garnet/lib/inferior_control/thread_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <lib/async/cpp/task.h>
+#include <lib/zx/channel.h>
+
+#include "garnet/lib/inferior_control/process.h"
+#include "garnet/lib/inferior_control/test_server.h"
+
+#include "gtest/gtest.h"
+
+namespace inferior_control {
+namespace {
+
+// TODO(dje): Obtain path more cleanly.
+const char helper_program[] =
+ "/pkgfs/packages/inferior_control_tests/0/bin/"
+ "inferior_control_test_helper";
+
+// Test resume from exception and try-next.
+// Note: Exceptions are handled in the same thread as server.Run().
+
+class ThreadTest : public TestServer {
+ public:
+ ThreadTest() = default;
+
+ bool got_sw_breakpoint() const { return got_sw_breakpoint_; }
+ bool got_unexpected_exception() const { return got_unexpected_exception_; }
+
+ void OnArchitecturalException(
+ Process* process, Thread* thread, const zx_excp_type_t type,
+ const zx_exception_context_t& context) {
+ FXL_LOG(INFO) << "Got exception 0x" << std::hex << type;
+ if (type == ZX_EXCP_SW_BREAKPOINT) {
+ got_sw_breakpoint_ = true;
+ thread->TryNext();
+ } else {
+ // We shouldn't get here, test has failed.
+ // Record the fact for the test, and terminate the inferior, we don't
+ // want the exception propagating to the system exception handler.
+ got_unexpected_exception_ = true;
+ zx_task_kill(process->handle());
+ }
+ }
+
+ private:
+ bool got_sw_breakpoint_ = false;
+ bool got_unexpected_exception_ = false;
+};
+
+TEST_F(ThreadTest, ResumeTryNextTest) {
+ std::vector<std::string> argv{
+ helper_program,
+ "test-try-next",
+ };
+ ASSERT_TRUE(SetupInferior(argv));
+
+ zx::channel our_channel, their_channel;
+ auto status = zx::channel::create(0, &our_channel, &their_channel);
+ ASSERT_EQ(status, ZX_OK);
+ EXPECT_TRUE(RunHelperProgram(std::move(their_channel)));
+
+ // The inferior is waiting for us to close our side of the channel.
+ our_channel.reset();
+
+ EXPECT_TRUE(Run());
+ EXPECT_TRUE(TestSuccessfulExit());
+ EXPECT_TRUE(got_sw_breakpoint());
+ EXPECT_FALSE(got_unexpected_exception());
+}
+
+} // namespace
+} // namespace inferior_control