From fc18365e2d91e63acbf9dcceb8e6d3cf4138546b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristi=C3=A1n=20Donoso?= <donosoc@google.com> Date: Sat, 27 Apr 2019 03:40:23 +0000 Subject: [PATCH] [debugger] Watchpoint integration test. This is the stub of the test, that is not joined to the build. This is because a lot of logic for making this test to work is required and the CL is big enough at it is. The following CL will do the actual watchpoint logic and hook up this test to run. TEST=No functional change. Change-Id: Idee83c1e65aa55552242d5679e0ff178b8151fcc --- .../integration_tests/watchpoint_test.cc | 233 ++++++++++++++++++ .../test_data/watchpoint_test_exe.cc | 11 + 2 files changed, 244 insertions(+) create mode 100644 src/developer/debug/debug_agent/integration_tests/watchpoint_test.cc create mode 100644 src/developer/debug/debug_agent/test_data/watchpoint_test_exe.cc diff --git a/src/developer/debug/debug_agent/integration_tests/watchpoint_test.cc b/src/developer/debug/debug_agent/integration_tests/watchpoint_test.cc new file mode 100644 index 00000000000..ccb10a0f5b0 --- /dev/null +++ b/src/developer/debug/debug_agent/integration_tests/watchpoint_test.cc @@ -0,0 +1,233 @@ +// 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 <gtest/gtest.h> + +#include "src/developer/debug/debug_agent/integration_tests/message_loop_wrapper.h" +#include "src/developer/debug/debug_agent/integration_tests/mock_stream_backend.h" +#include "src/developer/debug/debug_agent/integration_tests/so_wrapper.h" +#include "src/developer/debug/shared/logging/logging.h" +#include "src/developer/debug/shared/zx_status.h" + +namespace debug_agent { + +using namespace debug_ipc; + +namespace { + +constexpr int kInvalidReturnCode = 0xdeadbeef; + +// Receives the notification from the DebugAgent. +// The implementation is at the end of the file. +class WatchpointStreamBackend : public MockStreamBackend { + public: + WatchpointStreamBackend(MessageLoop* loop) : loop_(loop) {} + + // API ----------------------------------------------------------------------- + + void ResumeAllThreads(); + void ResumeAllThreadsAndRunLoop(); + + // Notification Interception ------------------------------------------------- + + void HandleNotifyModules(NotifyModules) override; + void HandleNotifyException(NotifyException) override; + void HandleNotifyProcessExiting(NotifyProcessExiting) override; + + // Getters ------------------------------------------------------------------- + + MessageLoop* loop() const { return loop_; } + uint64_t so_test_base_addr() const { return so_test_base_addr_; } + + zx_koid_t process_koid() const { return process_koid_; } + zx_koid_t thread_koid() const { return thread_koid_; } + int return_code() const { return return_code_; } + + const auto& exceptions() const { return exceptions_; } + + private: + // Each trapped notification will forward the decision whether to quit the + // loop to this call according to the TestStage enum. + void ShouldQuitLoop(); + + enum class TestStage { + kWaitingForModules, + kWaitingForException, + kWaitingForExit, + kDone, + }; + + TestStage test_stage_ = TestStage::kWaitingForModules; + + MessageLoop* loop_; + uint64_t so_test_base_addr_ = 0; + + zx_koid_t process_koid_ = 0; + zx_koid_t thread_koid_ = 0; + + std::vector<NotifyException> exceptions_; + int return_code_ = kInvalidReturnCode; +}; + +constexpr uint32_t kWatchpointId = 0x1234; + +std::pair<LaunchRequest, LaunchReply> +GetLaunchRequest(const WatchpointStreamBackend& backend, std::string exe); + +std::pair<AddOrChangeBreakpointRequest, AddOrChangeBreakpointReply> +GetWatchpointRequest(const WatchpointStreamBackend& backend, uint64_t address); + +TEST(Watchpoint, DefaultCase) { + static constexpr const char kTestSo[] = "debug_agent_test_so.so"; + SoWrapper so_wrapper; + ASSERT_TRUE(so_wrapper.Init(kTestSo)) << "Could not load so " << kTestSo; + + uint64_t variable_offset = + so_wrapper.GetSymbolOffset(kTestSo, "gWatchpointVariable"); + ASSERT_NE(variable_offset, 0u); + + MessageLoopWrapper loop_wrapper; + { + auto* loop = loop_wrapper.loop(); + WatchpointStreamBackend backend(loop); + RemoteAPI* remote_api = backend.remote_api(); + + static constexpr const char kExecutable[] = + "/pkg/bin/multithreaded_breakpoint_test_exe"; + auto [lnch_request, lnch_reply] = GetLaunchRequest(backend, kExecutable); + remote_api->OnLaunch(lnch_request, &lnch_reply); + ASSERT_EQ(lnch_reply.status, ZX_OK) << ZxStatusToString(lnch_reply.status); + + backend.ResumeAllThreadsAndRunLoop(); + + // The first thread should've started. + ASSERT_NE(backend.process_koid(), 0u); + ASSERT_NE(backend.thread_koid(), 0u); + + // We should have the correct module by now. + ASSERT_NE(backend.so_test_base_addr(), 0u); + uint64_t address = backend.so_test_base_addr() + variable_offset; + + DEBUG_LOG(Test) << std::hex << "Base: 0x" << backend.so_test_base_addr() + << ", Offset: 0x" << variable_offset + << ", Actual Address: 0x" << address; + + auto [wp_request, wp_reply] = GetWatchpointRequest(backend, address); + remote_api->OnAddOrChangeBreakpoint(wp_request, &wp_reply); + ASSERT_EQ(wp_reply.status, ZX_OK) << ZxStatusToString(wp_reply.status); + + backend.ResumeAllThreadsAndRunLoop(); + + // We should've gotten an exception. + auto& exceptions = backend.exceptions(); + ASSERT_EQ(exceptions.size(), 1u); + + auto& exception = exceptions[0]; + EXPECT_EQ(exception.type, NotifyException::Type::kWatchpoint) + << NotifyException::TypeToString(exception.type); + EXPECT_EQ(exception.thread.process_koid, backend.process_koid()); + EXPECT_EQ(exception.thread.thread_koid, backend.thread_koid()); + + ASSERT_EQ(exception.hit_breakpoints.size(), 1u); + auto& wp = exception.hit_breakpoints[0]; + EXPECT_EQ(wp.id, kWatchpointId); + EXPECT_EQ(wp.hit_count, 1u); + EXPECT_EQ(wp.should_delete, true); + + backend.ResumeAllThreadsAndRunLoop(); + + // The process should've exited correctly. + EXPECT_EQ(backend.return_code(), 0u); + } + +} + +// Helpers --------------------------------------------------------------------- + +std::pair<LaunchRequest, LaunchReply> +GetLaunchRequest(const WatchpointStreamBackend& backend, std::string exe) { + LaunchRequest launch_request = {}; + launch_request.argv = {exe}; + launch_request.inferior_type = InferiorType::kBinary; + return {launch_request, {}}; +} + +std::pair<AddOrChangeBreakpointRequest, AddOrChangeBreakpointReply> +GetWatchpointRequest(const WatchpointStreamBackend& backend, uint64_t address) { + // We add a breakpoint in that address. + debug_ipc::ProcessWatchpointSetings location = {}; + location.process_koid = backend.process_koid(); + location.thread_koid = backend.thread_koid(); + location.range = {address, address}; + + debug_ipc::AddOrChangeBreakpointRequest watchpoint_request = {}; + watchpoint_request.breakpoint.id = kWatchpointId; + watchpoint_request.watchpoint.locations.push_back(location); + + return {watchpoint_request, {}}; +} + +// WatchpointStreamBackend Implementation -------------------------------------- + +// Searches the loaded modules for specific one. +void WatchpointStreamBackend::HandleNotifyModules(NotifyModules modules) { + for (auto& module : modules.modules) { + DEBUG_LOG(Test) << "Received module " << module.name; + if (module.name == "libdebug_agent_test_so.so") { + so_test_base_addr_ = module.base; + break; + } + } + ShouldQuitLoop(); +} + +// Records the exception given from the debug agent. +void WatchpointStreamBackend::HandleNotifyException(NotifyException exception) { + DEBUG_LOG(Test) << "Received " + << NotifyException::TypeToString(exception.type) + << " on Thread: " << exception.thread.thread_koid; + exceptions_.push_back(std::move(exception)); + ShouldQuitLoop(); +} + +void WatchpointStreamBackend::HandleNotifyProcessExiting( + NotifyProcessExiting process) { + DEBUG_LOG(Test) << "Process " << process.process_koid + << " exiting with return code: " << process.return_code; + FXL_DCHECK(process.process_koid == process_koid_); + return_code_ = process.return_code; + ShouldQuitLoop(); +} + +void WatchpointStreamBackend::ShouldQuitLoop() { + if (test_stage_ == TestStage::kWaitingForModules) { + if (so_test_base_addr_ != 0u) { + test_stage_ = TestStage::kWaitingForException; + DEBUG_LOG(Test) << "State changed to WAITING FOR EXCEPTION."; + loop_->QuitNow(); + return; + } + } else if (test_stage_ == TestStage::kWaitingForException) { + if (exceptions_.size() == 1u) { + test_stage_ = TestStage::kWaitingForExit; + DEBUG_LOG(Test) << "State changed to WAITING FOR EXIT."; + loop_->QuitNow(); + return; + } + } else if (test_stage_ == TestStage::kWaitingForExit) { + if (return_code_ != kInvalidReturnCode) { + test_stage_ = TestStage::kDone; + DEBUG_LOG(Test) << "State changed to DONE."; + loop_->QuitNow(); + return; + } + } + + FXL_NOTREACHED() << "Invalid test state."; +} + +} // namespace + +} // namespace debug_agent + diff --git a/src/developer/debug/debug_agent/test_data/watchpoint_test_exe.cc b/src/developer/debug/debug_agent/test_data/watchpoint_test_exe.cc new file mode 100644 index 00000000000..e25d2651426 --- /dev/null +++ b/src/developer/debug/debug_agent/test_data/watchpoint_test_exe.cc @@ -0,0 +1,11 @@ +// 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 "src/developer/debug/debug_agent/test_data/test_so_symbols.h" + +int main() { + // This function will touch a global variable the debugger will set a + // watchpoint on. + WatchpointFunction(); +} -- GitLab