[debugger] HW breakpoint test

NOTE: The testing for arm64 is disabled until the debug agent supports
setting those breakpoint in.

This CL extracts a lot of the common functionality used for the SW
breakpoint classes and separates them into their own objects so that
they can be more easily reused in different tests.

This will make testing of other constructs, such as watchpoints, much
easier to do.

This also adds a test to verify that there is a HW breakpoint
notification in place.

TEST=Unit

Change-Id: I5525d1a6ddb89de144683d65acf7a23cb36cfa24
diff --git a/bin/debug_agent/BUILD.gn b/bin/debug_agent/BUILD.gn
index fc951f9..2c78eb5 100644
--- a/bin/debug_agent/BUILD.gn
+++ b/bin/debug_agent/BUILD.gn
@@ -111,7 +111,7 @@
   binary = "debug_agent"
 }
 
-executable("unittests") {
+executable("tests") {
   testonly = true
   output_name = "debug_agent_tests"
 
@@ -119,7 +119,6 @@
     "breakpoint_unittest.cc",
     "debugged_job_unittest.cc",
     "debugged_thread_unittest.cc",
-    "integration_tests/breakpoint_test.cc",
     "process_breakpoint_unittest.cc",
     "process_info_unittests.cc",
     "system_info_unittests.cc",
@@ -131,6 +130,7 @@
 
   deps = [
     ":lib",
+    "//garnet/bin/debug_agent/integration_tests",
     "//garnet/lib/debug_ipc:tests",
     "//garnet/public/lib/component/cpp:environment_services",
     "//garnet/public/lib/fxl/test:gtest_main",
@@ -145,7 +145,7 @@
   package_name = "debug_agent_tests"
 
   deps = [
-    ":unittests",
+    ":tests",
     "//garnet/bin/debug_agent/test_data:debug_agent_so_test_exe",
     "//garnet/bin/debug_agent/test_data:debug_agent_test_so",
     "//garnet/bin/debug_agent/test_data:process_loop",
diff --git a/bin/debug_agent/integration_tests/BUILD.gn b/bin/debug_agent/integration_tests/BUILD.gn
new file mode 100644
index 0000000..1443a7d
--- /dev/null
+++ b/bin/debug_agent/integration_tests/BUILD.gn
@@ -0,0 +1,25 @@
+# Copyright 2017 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.
+
+source_set("integration_tests") {
+  testonly = true
+
+  sources = [
+    "breakpoint_test.cc",
+    "mock_stream_backend.cc",
+    "mock_stream_backend.h",
+    "so_wrapper.cc",
+    "so_wrapper.h",
+  ]
+
+  deps = [
+    "//garnet/bin/debug_agent:lib",
+    "//garnet/lib/debug_ipc:client",
+    "//garnet/public/lib/component/cpp:environment_services",
+    "//garnet/public/lib/fxl",
+    "//garnet/public/lib/svc/cpp",
+    "//third_party/googletest:gtest",
+    "//zircon/public/lib/zx",
+  ]
+}
diff --git a/bin/debug_agent/integration_tests/breakpoint_test.cc b/bin/debug_agent/integration_tests/breakpoint_test.cc
index 4fa058c..07994df 100644
--- a/bin/debug_agent/integration_tests/breakpoint_test.cc
+++ b/bin/debug_agent/integration_tests/breakpoint_test.cc
@@ -1,21 +1,15 @@
 // Copyright 2018 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 <dlfcn.h>
-#include <link.h>
-#include <stdio.h>
-
 #include <gtest/gtest.h>
 
-#include "garnet/bin/debug_agent/arch.h"
-#include "garnet/bin/debug_agent/debug_agent.h"
-#include "garnet/lib/debug_ipc/client_protocol.h"
-#include "garnet/lib/debug_ipc/helper/message_loop_zircon.h"
+#include "garnet/bin/debug_agent/integration_tests/mock_stream_backend.h"
+#include "garnet/bin/debug_agent/integration_tests/so_wrapper.h"
 #include "garnet/lib/debug_ipc/message_reader.h"
+#include "garnet/lib/debug_ipc/helper/message_loop_zircon.h"
+#include "garnet/lib/debug_ipc/helper/zx_status.h"
 
-#include "lib/component/cpp/environment_services_helper.h"
 #include "lib/fxl/logging.h"
 
 namespace debug_agent {
@@ -58,61 +52,19 @@
 const char* kTestExecutableName = "debug_agent_so_test";
 const char* kTestExecutablePath = "/pkg/bin/debug_agent_so_test";
 
-// This class is meant to receive the raw messages outputted by the debug agent.
-// The agent's stream calls this backend to output the data and verifies that
-// all the content is sent.
-//
-// We use this class to intercept the messages sent back from the agent and
-// react accordingly. This class is kinda hardcoded for this tests, as different
-// integration tests care about different messages. If there are more tests that
-// require this kind of interception, this class should be separated and
-// generalized.
-class MockStreamBackend : public debug_ipc::StreamBuffer::Writer {
+class BreakpointStreamBackend : public MockStreamBackend {
  public:
-  MockStreamBackend(debug_ipc::MessageLoop* loop) : loop_(loop) {}
+  BreakpointStreamBackend(debug_ipc::MessageLoop* loop) : loop_(loop) {}
 
   uint64_t so_test_base_addr() const { return so_test_base_addr_; }
   debug_ipc::NotifyException exception() const { return exception_; }
 
-  // The stream will call this function to send the data to whatever backend it
-  // is connected to. It returns how much of the input message it could actually
-  // write. For this tests purposes, we always read the whole message.
-  size_t ConsumeStreamBufferData(const char* data, size_t len) override {
-    // We assume we always get a header.
-    debug_ipc::MsgHeader header = *(const debug_ipc::MsgHeader*)data;
-
-    // Buffer for the message.
-    std::vector<char> msg_buffer;
-    msg_buffer.reserve(len);
-    msg_buffer.insert(msg_buffer.end(), data, data + len);
-
-    // Dispatch the messages we find interesting.
-    debug_ipc::MessageReader reader(std::move(msg_buffer));
-    switch (header.type) {
-      case debug_ipc::MsgHeader::Type::kNotifyModules:
-        HandleNotifyModules(std::move(reader));
-        // We make the test continue.
-        loop_->QuitNow();
-        break;
-      case debug_ipc::MsgHeader::Type::kNotifyException:
-        HandleNotifyException(std::move(reader));
-        // We make the test continue.
-        loop_->QuitNow();
-        break;
-      default:
-        // We are not interested in breaking out of the loop for other
-        // notifications.
-        break;
-    }
-
-    // Say we read the whole message.
-    return len;
-  }
+  // The messages we're interested in handling ---------------------------------
 
   // Searches the loaded modules for specific one.
-  void HandleNotifyModules(debug_ipc::MessageReader reader) {
+  void HandleNotifyModules(debug_ipc::MessageReader* reader) override {
     debug_ipc::NotifyModules modules;
-    if (!debug_ipc::ReadNotifyModules(&reader, &modules))
+    if (!debug_ipc::ReadNotifyModules(reader, &modules))
       return;
     for (auto& module : modules.modules) {
       if (module.name.find(kTestExecutableName) != std::string::npos) {
@@ -120,108 +72,44 @@
         break;
       }
     }
+    loop_->QuitNow();
   }
 
   // Records the exception given from the debug agent.
-  void HandleNotifyException(debug_ipc::MessageReader reader) {
+  void HandleNotifyException(debug_ipc::MessageReader* reader) override {
     debug_ipc::NotifyException exception;
-    if (!debug_ipc::ReadNotifyException(&reader, &exception))
+    if (!debug_ipc::ReadNotifyException(reader, &exception))
       return;
     exception_ = exception;
+    loop_->QuitNow();
   }
 
  private:
+  debug_ipc::MessageLoop* loop_;
   uint64_t so_test_base_addr_ = 0;
   debug_ipc::NotifyException exception_ = {};
-
-  debug_ipc::MessageLoop* loop_;
 };
 
-struct IteratePhdrCallbackControl {
-  const char* searched_so_name;
-  uint64_t so_base_address;
-};
-
-// This callback will be called by dl_iterate_phdr for each module loaded into
-// the current process. We use this to search for the module opened through
-// dlopen.
-//
-// dl_iterate_phdr iterates over all the modules until one of them returns
-// non-zero (signal to stop) or when there are no more modules left.
-int IteratePhdrCallback(struct dl_phdr_info* info, size_t size, void* user) {
-  IteratePhdrCallbackControl* control =
-      reinterpret_cast<IteratePhdrCallbackControl*>(user);
-
-  // We verify the current .so being iterated vs the one we're searching for.
-  std::string so_name(info->dlpi_name);
-  if (so_name.find(control->searched_so_name) != std::string::npos) {
-    (*control).so_base_address = info->dlpi_addr;
-    return 1;   // We end the iteration.
-  }
-
-  // Continue the iteration.
-  return 0;
-}
-
-// Minor utility to ensure loaded .so are freed.
-class SoWrapper {
- public:
-  explicit SoWrapper(const char* so_name) {
-    so_ = dlopen(so_name, RTLD_GLOBAL);
-  }
-  ~SoWrapper() {
-    if (so_)
-      dlclose(so_);
-  }
-
-  operator bool() const { return so_ != nullptr; }
-  void* operator*() const { return so_; }
-
- private:
-  void* so_;
-};
-
-TEST(BreakpointIntegration, CorrectSetsSWBreakpoint) {
+TEST(BreakpointIntegration, SWBreakpoint) {
   // We attempt to load the pre-made .so.
-  SoWrapper so(kTestSo);
-  if (!so)
-    FAIL() << "Could not load " << kTestSo << ": " << dlerror();
+  SoWrapper so_wrapper;
+  ASSERT_TRUE(so_wrapper.Init(kTestSo)) << "Could not load so " << kTestSo;
 
-  // We iterate over the elf headers of the loaded so and search for a
-  // particular module within it. If we find it, we record the base address of
-  // the module for later canculating the offset of a symbol from this base.
-  IteratePhdrCallbackControl control = {};
-  control.searched_so_name = kTestSo;
-  if (dl_iterate_phdr(IteratePhdrCallback, &control) == 0)
-    FAIL() << "Did not find debug_agent_test_so.";
+  uint64_t module_base = so_wrapper.GetModuleStartAddress(kTestSo);
+  ASSERT_NE(module_base, 0u);
 
-  // We search for a particular symbol within the .so.
-  void* function_ptr = dlsym(*so, kExportedFunctionName);
-  if (!function_ptr)
-    FAIL() << "Could not find symbol \"" << kExportedFunctionName << "\"";
+  uint64_t symbol_addr = so_wrapper.GetSymbolAddress(kExportedFunctionName);
+  ASSERT_NE(symbol_addr, 0u);
 
-  // We calculate the offset of the searched symbol within the .so. This offset
-  // will be same in a binary that has linked with the same module. We only need
-  // to know the base address of that module. We get that through the notify
-  // modules message from the debug agent.
-  uint64_t function_offset = (uint64_t)function_ptr - control.so_base_address;
+  uint64_t function_offset = symbol_addr - module_base;
 
   debug_ipc::MessageLoopZircon loop;
   loop.Init();
   {
-    // Create a mock backed the debug agent's stream will write to. This is
-    // mocking what the socket would do in the normal environment.
-    MockStreamBackend mock_stream_backend(&loop);
-    debug_ipc::StreamBuffer stream;
-    stream.set_writer(&mock_stream_backend);
-
-    // Create a debug agent that's "connected" to our mock environment.
-    // This will have the correct setup to talk to Zircon through the component
-    // environment.
-    auto environment_services = component::GetEnvironmentServices();
-    DebugAgent debug_agent(&stream, std::move(environment_services));
-    // The RemoteAPI is needed because the debug agent API is private.
-    RemoteAPI* remote_api = &debug_agent;
+    // This stream backend will take care of intercepting the calls from the
+    // debug agent.
+    BreakpointStreamBackend mock_stream_backend(&loop);
+    RemoteAPI* remote_api = mock_stream_backend.remote_api();
 
     // We launch the test binary.
     debug_ipc::LaunchRequest launch_request = {};
@@ -283,4 +171,90 @@
   loop.Cleanup();
 }
 
+TEST(BreakpointIntegration, HWBreakpoint) {
+#if defined(__aarch64__)
+  return;
+#endif
+  // We attempt to load the pre-made .so.
+  SoWrapper so_wrapper;
+  ASSERT_TRUE(so_wrapper.Init(kTestSo)) << "Could not load so " << kTestSo;
+
+  uint64_t module_base = so_wrapper.GetModuleStartAddress(kTestSo);
+  ASSERT_NE(module_base, 0u);
+
+  uint64_t symbol_addr = so_wrapper.GetSymbolAddress(kExportedFunctionName);
+  ASSERT_NE(symbol_addr, 0u);
+
+  uint64_t function_offset = symbol_addr - module_base;
+
+  debug_ipc::MessageLoopZircon loop;
+  loop.Init();
+  {
+    // This stream backend will take care of intercepting the calls from the
+    // debug agent.
+    BreakpointStreamBackend mock_stream_backend(&loop);
+    RemoteAPI* remote_api = mock_stream_backend.remote_api();
+
+    // We launch the test binary.
+    debug_ipc::LaunchRequest launch_request = {};
+    launch_request.argv.push_back(kTestExecutablePath);
+    debug_ipc::LaunchReply launch_reply;
+    remote_api->OnLaunch(launch_request, &launch_reply);
+    ASSERT_EQ(launch_reply.status, static_cast<uint32_t>(ZX_OK));
+
+    // We run the look to get the notifications sent by the agent.
+    // The stream backend will stop the loop once it has received the modules
+    // notification.
+    loop.Run();
+
+    // We should have found the correct module by now.
+    ASSERT_NE(mock_stream_backend.so_test_base_addr(), 0u);
+
+    // We get the offset of the loaded function within the process space.
+    uint64_t module_base = mock_stream_backend.so_test_base_addr();
+    uint64_t module_function = module_base + function_offset;
+
+    // We add a breakpoint in that address.
+    constexpr uint32_t kBreakpointId = 1234u;
+    debug_ipc::ProcessBreakpointSettings location = {};
+    location.process_koid = launch_reply.process_koid;
+    location.address = module_function;
+
+    debug_ipc::AddOrChangeBreakpointRequest breakpoint_request = {};
+    breakpoint_request.breakpoint.breakpoint_id = kBreakpointId;
+    breakpoint_request.breakpoint.one_shot = true;
+    breakpoint_request.breakpoint.type = debug_ipc::BreakpointType::kHardware;
+    breakpoint_request.breakpoint.locations.push_back(location);
+
+    debug_ipc::AddOrChangeBreakpointReply breakpoint_reply;
+    remote_api->OnAddOrChangeBreakpoint(breakpoint_request,
+                                                   &breakpoint_reply);
+    ASSERT_EQ(breakpoint_reply.status, static_cast<uint32_t>(ZX_OK))
+        << "Received: " << debug_ipc::ZxStatusToString(breakpoint_reply.status);
+
+    // Resume the process now that the breakpoint is installed.
+    debug_ipc::ResumeRequest resume_request;
+    resume_request.process_koid = launch_reply.process_koid;
+    debug_ipc::ResumeReply resume_reply;
+    remote_api->OnResume(resume_request, &resume_reply);
+
+    // The loop will run until the stream backend receives an exception
+    // notification.
+    loop.Run();
+
+    // We should have received an exception now.
+    debug_ipc::NotifyException exception = mock_stream_backend.exception();
+    EXPECT_EQ(exception.process_koid, launch_reply.process_koid);
+    EXPECT_EQ(exception.type, debug_ipc::NotifyException::Type::kHardware);
+    ASSERT_EQ(exception.hit_breakpoints.size(), 1u);
+
+    // Verify that the correct breakpoint was hit.
+    auto& breakpoint = exception.hit_breakpoints[0];
+    EXPECT_EQ(breakpoint.breakpoint_id, kBreakpointId);
+    EXPECT_EQ(breakpoint.hit_count, 1u);
+    EXPECT_TRUE(breakpoint.should_delete);
+  }
+  loop.Cleanup();
+}
+
 }  // namespace debug_agent
diff --git a/bin/debug_agent/integration_tests/mock_stream_backend.cc b/bin/debug_agent/integration_tests/mock_stream_backend.cc
new file mode 100644
index 0000000..28bdf3d
--- /dev/null
+++ b/bin/debug_agent/integration_tests/mock_stream_backend.cc
@@ -0,0 +1,51 @@
+// Copyright 2018 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 "garnet/bin/debug_agent/integration_tests/mock_stream_backend.h"
+
+#include "garnet/lib/debug_ipc/message_reader.h"
+#include "lib/component/cpp/environment_services_helper.h"
+
+namespace debug_agent {
+
+MockStreamBackend::MockStreamBackend() {
+  // We initialize the stream and pass it on to the debug agent, which will
+  // think it's correctly connected to a client.
+  stream_.set_writer(this);
+  auto environment_services = component::GetEnvironmentServices();
+  agent_ = std::make_unique<DebugAgent>(&stream_,
+                                        std::move(environment_services));
+}
+
+size_t MockStreamBackend::ConsumeStreamBufferData(const char* data,
+                                                  size_t len) {
+  // We assume we always get a header.
+  const debug_ipc::MsgHeader* header =
+      reinterpret_cast<const debug_ipc::MsgHeader*>(data);
+
+  // Buffer for the message.
+  std::vector<char> msg_buffer;
+  msg_buffer.reserve(len);
+  msg_buffer.insert(msg_buffer.end(), data, data + len);
+
+  // Dispatch the messages we find interesting.
+  debug_ipc::MessageReader reader(std::move(msg_buffer));
+  switch (header->type) {
+    case debug_ipc::MsgHeader::Type::kNotifyModules:
+      HandleNotifyModules(&reader);
+      break;
+    case debug_ipc::MsgHeader::Type::kNotifyException:
+      HandleNotifyException(&reader);
+      break;
+    default:
+      // NOTE: Here is where you add more notification handlers as they are
+      //       sent by the debug agent.
+      break;
+  }
+
+  // Say we read the whole message.
+  return len;
+}
+
+}  // namespace debug_agent
diff --git a/bin/debug_agent/integration_tests/mock_stream_backend.h b/bin/debug_agent/integration_tests/mock_stream_backend.h
new file mode 100644
index 0000000..334c1b1
--- /dev/null
+++ b/bin/debug_agent/integration_tests/mock_stream_backend.h
@@ -0,0 +1,54 @@
+// Copyright 2018 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.
+
+#pragma once
+
+#include "garnet/bin/debug_agent/debug_agent.h"
+#include "garnet/lib/debug_ipc/client_protocol.h"
+#include "garnet/lib/debug_ipc/helper/message_loop.h"
+#include "garnet/lib/debug_ipc/protocol.h"
+#include "garnet/lib/debug_ipc/helper/stream_buffer.h"
+
+#include "garnet/public/lib/fxl/logging.h"
+
+
+namespace debug_ipc {
+class MessageReader;
+}  // namespace debug_ipc
+
+namespace debug_agent {
+
+// This class is meant to receive the raw messages outputted by the debug agent.
+// The agent's stream calls this backend to output the data and verifies that
+// all the content is sent.
+//
+// We use this class to intercept the messages sent back from the agent and
+// react accordingly. This class is kinda hardcoded for this tests, as different
+// integration tests care about different messages. If there are more tests that
+// require this kind of interception, this class should be separated and
+// generalized.
+class MockStreamBackend : public debug_ipc::StreamBuffer::Writer {
+ public:
+  MockStreamBackend();
+  RemoteAPI* remote_api() { return agent_.get(); }
+
+  // Message dispatcher interface.
+  // This should be overriden by every test interested in a particular set of
+  // messages. By default they do nothing.
+  virtual void HandleNotifyModules(debug_ipc::MessageReader*) {}
+  virtual void HandleNotifyException(debug_ipc::MessageReader*) {}
+
+  // The stream will call this function to send the data to whatever backend it
+  // is connected to. It returns how much of the input message it could actually
+  // write. For this tests purposes, we always read the whole message.
+  size_t ConsumeStreamBufferData(const char* data, size_t len) override;
+
+
+ private:
+  // This is the stream the debug agent will be given to write to.
+  debug_ipc::StreamBuffer stream_;
+  std::unique_ptr<DebugAgent> agent_;
+};
+
+}  // namespace debug_agent
diff --git a/bin/debug_agent/integration_tests/so_wrapper.cc b/bin/debug_agent/integration_tests/so_wrapper.cc
new file mode 100644
index 0000000..91170aa
--- /dev/null
+++ b/bin/debug_agent/integration_tests/so_wrapper.cc
@@ -0,0 +1,64 @@
+// Copyright 2018 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 "garnet/bin/debug_agent/integration_tests/so_wrapper.h"
+
+#include <dlfcn.h>
+#include <link.h>
+#include <stdlib.h>
+
+#include <string>
+
+#include "lib/fxl/logging.h"
+#include "lib/fxl/strings/string_printf.h"
+
+namespace debug_agent {
+
+// This callback will be called by dl_iterate_phdr for each module loaded into
+// the current process. We use this to search for the module opened through
+// dlopen.
+//
+// dl_iterate_phdr iterates over all the modules until one of them returns
+// non-zero (signal to stop) or when there are no more modules left.
+int SoWrapper::IteratePhdrCallback(struct ::dl_phdr_info* info, size_t size,
+                                   void* user) {
+  SoWrapper* so_wrapper = reinterpret_cast<SoWrapper*>(user);
+  so_wrapper->module_offsets_[info->dlpi_name] = info->dlpi_addr;
+
+  // Continue the iteration.
+  return 0;
+}
+
+bool SoWrapper::Init(std::string so_name) {
+  so_ = dlopen(so_name.data(), RTLD_GLOBAL);
+  if (!so_)
+    return false;
+  so_name_ = std::move(so_name);
+
+  // Load all the libraries
+  dl_iterate_phdr(SoWrapper::IteratePhdrCallback, this);
+
+  return true;
+}
+
+SoWrapper::~SoWrapper() {
+  if (so_)
+    dlclose(so_);
+}
+
+uint64_t SoWrapper::GetModuleStartAddress(const char* module_name) const {
+  // We try to get the module we're asking for.
+  auto module_it = module_offsets_.find(module_name);
+  if (module_it == module_offsets_.end())
+    return 0;
+  return module_it->second;
+}
+
+uint64_t SoWrapper::GetSymbolAddress(const char* symbol_name) const {
+  // We look for the symbol in our address space.
+  void* symbol = dlsym(so_, symbol_name);
+  return reinterpret_cast<uint64_t>(symbol);
+}
+
+}  // namespace debug_agent
diff --git a/bin/debug_agent/integration_tests/so_wrapper.h b/bin/debug_agent/integration_tests/so_wrapper.h
new file mode 100644
index 0000000..4313727
--- /dev/null
+++ b/bin/debug_agent/integration_tests/so_wrapper.h
@@ -0,0 +1,57 @@
+// Copyright 2018 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.
+
+#pragma once
+
+#include <stdint.h>
+
+#include <map>
+#include <optional>
+#include <string>
+#include <vector>
+
+struct dl_phdr_info;
+
+namespace debug_agent {
+
+// SoWrapper functionalities are two-fold:
+// - Manages a given .so as a resource and exposes a way to expose the addresses
+//   of symbols within that .so.
+// - Queries all the modules loaded within the current process. The main purpose
+//   of this is to find the same module that was loaded by Init and be able to
+//   start address.
+//
+// With that the offset is calculable and we can know how far inside a
+// particular module a symbol is. That can then be used to place breakpoints or
+// other address specific tools.
+class SoWrapper {
+ public:
+  // Fails if |so_name| doesn't point to a valid .so.
+  bool Init(std::string so_name);
+  ~SoWrapper();
+
+  // Gets the start address of a module loaded in the current process.
+  // Returns 0 if not found.
+  uint64_t GetModuleStartAddress(const char* module_name) const;
+
+  // Looks for the address where a particular symbol from the loaded .so is
+  // loaded in the current address space.
+  // Returns 0 if not found.
+  uint64_t GetSymbolAddress(const char* symbol_name) const;
+
+  const std::string& so_name() const { return so_name_; }
+
+ private:
+  // Callback to be used by dl_iterate_phdr to find the module offsets.
+  // This callback is called for each module loaded into the current address
+  // space. This will log each module name and address start into an instance
+  // of SoWrapper given in through |user|.
+  static int IteratePhdrCallback(struct ::dl_phdr_info*, size_t, void* user);
+
+  std::string so_name_;
+  void* so_;
+  std::map<std::string, uint64_t> module_offsets_;
+};
+
+}  // namespace debug_agent
diff --git a/lib/debug_ipc/helper/zx_status.cc b/lib/debug_ipc/helper/zx_status.cc
index a51c6d2..46356a5 100644
--- a/lib/debug_ipc/helper/zx_status.cc
+++ b/lib/debug_ipc/helper/zx_status.cc
@@ -18,6 +18,9 @@
     case kZxErrInternal:
       status_name = "ZX_ERR_INTERNAL";
       break;
+    case kZxErrNotSupported:
+      status_name = "ZX_ERR_NOT_SUPPORTED";
+      break;
     case kZxErrInvalidArgs:
       status_name = "ZX_ERR_INVALID_ARGS";
       break;
diff --git a/public/lib/component/cpp/BUILD.gn b/public/lib/component/cpp/BUILD.gn
index df591a4..df7d277 100644
--- a/public/lib/component/cpp/BUILD.gn
+++ b/public/lib/component/cpp/BUILD.gn
@@ -45,7 +45,8 @@
     "//garnet/bin/debugserver:bin",
     "//garnet/bin/insntrace:bin",
     "//garnet/bin/debug_agent:bin",
-    "//garnet/bin/debug_agent:unittests",
+    "//garnet/bin/debug_agent:tests",
+    "//garnet/bin/debug_agent/integration_tests",
     "//garnet/drivers/wlan/wlan:*",
     "testing",
   ]