[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, &regs);
+  if (status != ZX_OK) {
+    return status;
+  }
+  zx_vaddr_t pc = GetPcFromGeneralRegisters(&regs);
+  pc = IncrementPcAfterBreak(pc);
+  SetPcInGeneralRegisters(&regs, pc);
+  status = WriteGeneralRegisters(thread, &regs);
+  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