| // Copyright 2020 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/so_wrapper.h" |
| #include "src/developer/debug/debug_agent/local_stream_backend.h" |
| #include "src/developer/debug/debug_agent/zircon_system_interface.h" |
| #include "src/developer/debug/shared/zx_status.h" |
| |
| namespace debug_agent { |
| |
| namespace { |
| |
| const char kSoName[] = "debug_agent_test_so.so"; |
| |
| class DynamicLoaderStreamBackend : public LocalStreamBackend { |
| public: |
| DynamicLoaderStreamBackend(debug_ipc::MessageLoop* loop) : loop_(loop) {} |
| |
| void set_remote_api(RemoteAPI* remote_api) { remote_api_ = remote_api; } |
| |
| // LocalStreamBackend implementation: |
| void HandleNotifyThreadStarting(debug_ipc::NotifyThread thread) override; |
| void HandleNotifyModules(debug_ipc::NotifyModules modules) override; |
| void HandleNotifyProcessExiting(debug_ipc::NotifyProcessExiting) override; |
| |
| private: |
| void ResumeAll(); |
| |
| bool HasModuleForFile(const debug_ipc::NotifyModules& modules, const char* file_name); |
| |
| enum class Stage { |
| kWaitingForThread, |
| kWaitingForModules, |
| kWaitingForLoad, |
| kWaitingForUnload, |
| kWaitingForExit, |
| kDone, |
| }; |
| |
| Stage stage_ = Stage::kWaitingForThread; |
| |
| debug_ipc::MessageLoop* loop_ = nullptr; |
| RemoteAPI* remote_api_ = nullptr; |
| |
| zx_koid_t process_koid_ = 0; |
| zx_koid_t thread_koid_ = 0; |
| }; |
| |
| void DynamicLoaderStreamBackend::HandleNotifyThreadStarting(debug_ipc::NotifyThread thread) { |
| if (stage_ == Stage::kWaitingForThread) { |
| process_koid_ = thread.record.process_koid; |
| thread_koid_ = thread.record.thread_koid; |
| stage_ = Stage::kWaitingForModules; |
| ResumeAll(); |
| } else { |
| ADD_FAILURE() << "Got starting message when not expected"; |
| loop_->QuitNow(); |
| } |
| } |
| |
| void DynamicLoaderStreamBackend::HandleNotifyModules(debug_ipc::NotifyModules modules) { |
| if (stage_ == Stage::kWaitingForModules) { |
| EXPECT_FALSE(HasModuleForFile(modules, kSoName)); |
| stage_ = Stage::kWaitingForLoad; |
| ResumeAll(); |
| } else if (stage_ == Stage::kWaitingForLoad) { |
| EXPECT_TRUE(HasModuleForFile(modules, kSoName)); |
| |
| // TODO(bug 58371) our dynamic loader does not implement dlclose() so that we never get a |
| // notification that dynamic libraries are unloaded. When that's implemented, this corresponding |
| // code should be used to test the debugger's behavior in that context. |
| #if 0 |
| stage_ = Stage::kWaitingForUnload; |
| ResumeAll(); |
| } else if (stage_ == Stage::kWaitingForUnload) { |
| EXPECT_FALSE(HasModuleForFile(modules, kSoName)); |
| #endif |
| |
| stage_ = Stage::kWaitingForExit; |
| ResumeAll(); |
| } else { |
| ADD_FAILURE() << "Unexpected NotifyModules call."; |
| loop_->QuitNow(); |
| } |
| } |
| |
| void DynamicLoaderStreamBackend::HandleNotifyProcessExiting(debug_ipc::NotifyProcessExiting) { |
| if (stage_ == Stage::kWaitingForExit) { |
| stage_ = Stage::kDone; |
| } else { |
| ADD_FAILURE() << "Process exited before getting the right notifications."; |
| } |
| |
| loop_->QuitNow(); |
| } |
| |
| void DynamicLoaderStreamBackend::ResumeAll() { |
| debug_ipc::ResumeRequest resume_request; |
| debug_ipc::ResumeReply resume_reply; |
| remote_api_->OnResume(resume_request, &resume_reply); |
| } |
| |
| bool DynamicLoaderStreamBackend::HasModuleForFile(const debug_ipc::NotifyModules& modules, |
| const char* file_name) { |
| for (const auto& m : modules.modules) { |
| if (m.name == file_name) |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // Tests that dynamic library load and unload events are caught by the debug agent and the proper |
| // notifications are issued. |
| TEST(DynamicLoader, LoadUnload) { |
| MessageLoopWrapper loop_wrapper; |
| { |
| auto* loop = loop_wrapper.loop(); |
| |
| DynamicLoaderStreamBackend backend(loop); |
| DebugAgent agent(std::make_unique<ZirconSystemInterface>()); |
| |
| agent.Connect(&backend.stream()); |
| backend.set_remote_api(&agent); |
| |
| debug_ipc::LaunchRequest launch_request = {}; |
| launch_request.argv = {"/pkg/bin/load_so_exe"}; |
| launch_request.inferior_type = debug_ipc::InferiorType::kBinary; |
| |
| debug_ipc::LaunchReply launch_reply; |
| agent.OnLaunch(launch_request, &launch_reply); |
| ASSERT_EQ(launch_reply.status, ZX_OK) << debug_ipc::ZxStatusToString(launch_reply.status); |
| |
| loop->Run(); |
| } |
| } |
| |
| } // namespace debug_agent |