| // 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 "src/developer/debug/zxdb/client/breakpoint_impl.h" |
| |
| #include <utility> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/developer/debug/shared/platform_message_loop.h" |
| #include "src/developer/debug/zxdb/client/breakpoint_observer.h" |
| #include "src/developer/debug/zxdb/client/process_impl.h" |
| #include "src/developer/debug/zxdb/client/remote_api_test.h" |
| #include "src/developer/debug/zxdb/client/session.h" |
| #include "src/developer/debug/zxdb/client/setting_schema_definition.h" |
| #include "src/developer/debug/zxdb/client/target_impl.h" |
| #include "src/developer/debug/zxdb/symbols/function.h" |
| #include "src/developer/debug/zxdb/symbols/index_test_support.h" |
| #include "src/developer/debug/zxdb/symbols/mock_module_symbols.h" |
| #include "src/developer/debug/zxdb/symbols/process_symbols_test_setup.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| using debug::MessageLoop; |
| |
| class BreakpointSink : public RemoteAPI { |
| public: |
| void AddOrChangeBreakpoint( |
| const debug_ipc::AddOrChangeBreakpointRequest& request, |
| fit::callback<void(const Err&, debug_ipc::AddOrChangeBreakpointReply)> cb) override { |
| adds.push_back(request); |
| |
| MessageLoop::Current()->PostTask(FROM_HERE, [cb = std::move(cb)]() mutable { |
| cb(Err(), debug_ipc::AddOrChangeBreakpointReply()); |
| }); |
| } |
| |
| void RemoveBreakpoint( |
| const debug_ipc::RemoveBreakpointRequest& request, |
| fit::callback<void(const Err&, debug_ipc::RemoveBreakpointReply)> cb) override { |
| removes.push_back(request); |
| |
| MessageLoop::Current()->PostTask(FROM_HERE, [cb = std::move(cb)]() mutable { |
| cb(Err(), debug_ipc::RemoveBreakpointReply()); |
| }); |
| } |
| |
| // No-op. |
| void Threads(const debug_ipc::ThreadsRequest& request, |
| fit::callback<void(const Err&, debug_ipc::ThreadsReply)> cb) override { |
| thread_request_made = true; |
| } |
| |
| std::vector<debug_ipc::AddOrChangeBreakpointRequest> adds; |
| std::vector<debug_ipc::RemoveBreakpointRequest> removes; |
| |
| bool thread_request_made = false; |
| }; |
| |
| class BreakpointImplTest : public RemoteAPITest { |
| public: |
| BreakpointImplTest() = default; |
| ~BreakpointImplTest() override = default; |
| |
| BreakpointSink& sink() { return *sink_; } |
| |
| protected: |
| std::unique_ptr<RemoteAPI> GetRemoteAPIImpl() override { |
| auto sink = std::make_unique<BreakpointSink>(); |
| sink_ = sink.get(); |
| return std::move(sink); |
| } |
| |
| private: |
| BreakpointSink* sink_; // Owned by the session. |
| }; |
| |
| class BreakpointObserverSink : public BreakpointObserver { |
| public: |
| // The Session must outlive this object. |
| explicit BreakpointObserverSink(Session* session) : session_(session) { |
| session->AddBreakpointObserver(this); |
| } |
| ~BreakpointObserverSink() { session_->RemoveBreakpointObserver(this); } |
| |
| void OnBreakpointMatched(Breakpoint* breakpoint, bool user_requested) override { |
| notification_count++; |
| last_breakpoint = breakpoint; |
| last_user_requested = user_requested; |
| } |
| |
| int notification_count = 0; |
| |
| // Parameters of last notification. |
| Breakpoint* last_breakpoint = nullptr; |
| bool last_user_requested = false; |
| |
| private: |
| Session* session_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(BreakpointImplTest, DynamicLoading) { |
| BreakpointObserverSink observer_sink(&session()); |
| |
| BreakpointImpl bp(&session(), false); |
| EXPECT_EQ(0, observer_sink.notification_count); |
| |
| ProcessSymbolsTestSetup setup; |
| auto module_symbols1 = fxl::MakeRefCounted<MockModuleSymbols>("myfile1.so"); |
| auto module_symbols2 = fxl::MakeRefCounted<MockModuleSymbols>("myfile2.so"); |
| |
| // The function to find the breakpoint for is in module1. |
| const std::string kFunctionName = "DoThings"; |
| auto function_symbol = fxl::MakeRefCounted<Function>(DwarfTag::kSubprogram); |
| function_symbol->set_assigned_name(kFunctionName); |
| TestIndexedSymbol function_indexed(module_symbols1.get(), &module_symbols1->index().root(), |
| kFunctionName, function_symbol); |
| |
| // Make a disabled symbolic breakpoint. |
| BreakpointSettings in; |
| in.enabled = false; |
| in.locations.emplace_back(Identifier(IdentifierComponent(kFunctionName))); |
| |
| // Setting the disabled settings shouldn't update the backend. |
| bp.SetSettings(in); |
| EXPECT_EQ(0, observer_sink.notification_count); // No calls because it matched no places. |
| ASSERT_TRUE(sink().adds.empty()); |
| |
| // Setting enabled settings with no processes should not update the backend. |
| in.enabled = true; |
| bp.SetSettings(in); |
| EXPECT_EQ(0, observer_sink.notification_count); |
| ASSERT_TRUE(sink().adds.empty()); |
| |
| TargetImpl* target = session().system().GetTargetImpls()[0]; |
| |
| // Create a process for it. Since the process doesn't resolve the symbol yet, no messages should |
| // be sent. |
| const uint64_t koid = 5678; |
| target->CreateProcessForTesting(koid, "test"); |
| ASSERT_TRUE(sink().adds.empty()); |
| |
| // Make two fake modules. The first will resolve the function to two locations, the second will |
| // resolve nothing. They must be larger than the module base. |
| const uint64_t kModule1Base = 0x1000000; |
| const uint64_t kAddress1 = 0x78456345; |
| const uint64_t kAddress2 = 0x12345678; |
| module_symbols1->AddSymbolLocations(kFunctionName, |
| {Location(Location::State::kSymbolized, kAddress1), |
| Location(Location::State::kSymbolized, kAddress2)}); |
| |
| // Cause the process to load the module. We have to keep the module_ref alive for this to stay |
| // cached in the SystemSymbols. |
| const std::string kBuildID1 = "abcd"; |
| const std::string kBuildID2 = "zyxw"; |
| session().system().GetSymbols()->InjectModuleForTesting(kBuildID1, module_symbols1.get()); |
| session().system().GetSymbols()->InjectModuleForTesting(kBuildID2, module_symbols2.get()); |
| |
| // Before modules no thread request should be made. |
| EXPECT_FALSE(sink().thread_request_made); |
| |
| // Cause the process to load module 1. |
| std::vector<debug_ipc::Module> modules; |
| debug_ipc::Module load1; |
| load1.name = "test"; |
| load1.base = kModule1Base; |
| load1.build_id = kBuildID1; |
| modules.push_back(load1); |
| target->process()->OnModules(modules, {}); |
| |
| // After adding modules, the client should have asked for threads. |
| EXPECT_TRUE(sink().thread_request_made); |
| |
| // That should have notified the breakpoint which should have added the two addresses to the |
| // backend. |
| ASSERT_FALSE(sink().adds.empty()); |
| debug_ipc::AddOrChangeBreakpointRequest out = sink().adds[0]; |
| EXPECT_FALSE(out.breakpoint.one_shot); |
| EXPECT_EQ(debug_ipc::Stop::kAll, out.breakpoint.stop); |
| |
| // Both locations should be for the same process, with no thread restriction. |
| ASSERT_EQ(2u, out.breakpoint.locations.size()); |
| EXPECT_EQ(koid, out.breakpoint.locations[0].id.process); |
| EXPECT_EQ(koid, out.breakpoint.locations[1].id.process); |
| EXPECT_EQ(0u, out.breakpoint.locations[0].id.thread); |
| EXPECT_EQ(0u, out.breakpoint.locations[1].id.thread); |
| |
| // Addresses could be in either order. They should be absolute. |
| EXPECT_TRUE((out.breakpoint.locations[0].address == kAddress1 && |
| out.breakpoint.locations[1].address == kAddress2) || |
| (out.breakpoint.locations[0].address == kAddress2 && |
| out.breakpoint.locations[1].address == kAddress1)); |
| |
| // Adding another module with nothing to resolve should send no messages. |
| sink().adds.clear(); |
| const uint64_t kModule2Base = 0x2000000; |
| debug_ipc::Module load2; |
| load2.name = "test2"; |
| load2.base = kModule2Base; |
| load2.build_id = kBuildID2; |
| modules.push_back(load2); |
| target->process()->OnModules(modules, {}); |
| ASSERT_TRUE(sink().adds.empty()); |
| |
| // Should have sent a notification. It should not be marked user-requested since this was a |
| // result of loading a new process. |
| EXPECT_EQ(1, observer_sink.notification_count); |
| EXPECT_EQ(&bp, observer_sink.last_breakpoint); |
| EXPECT_EQ(false, observer_sink.last_user_requested); |
| |
| // Disabling should send the delete message. |
| ASSERT_TRUE(sink().removes.empty()); // Should have none so far. |
| in.enabled = false; |
| bp.SetSettings(in); |
| ASSERT_TRUE(sink().adds.empty()); |
| ASSERT_EQ(1u, sink().removes.size()); |
| EXPECT_EQ(out.breakpoint.id, sink().removes[0].breakpoint_id); |
| } |
| |
| // Tests that address breakpoints are enabled immediately even when no symbols are available. |
| TEST_F(BreakpointImplTest, Address) { |
| // TargetImpl target(&session().system()); |
| auto target_impls = session().system().GetTargetImpls(); |
| ASSERT_EQ(1u, target_impls.size()); |
| TargetImpl* target = target_impls[0]; |
| const uint64_t kProcessKoid = 6789; |
| target->CreateProcessForTesting(kProcessKoid, "test"); |
| |
| BreakpointImpl bp(&session(), false); |
| |
| const uint64_t kAddress = 0x123456780; |
| BreakpointSettings in; |
| in.enabled = true; |
| in.scope = ExecutionScope(target); |
| in.locations.emplace_back(kAddress); |
| |
| bp.SetSettings(in); |
| |
| // Check the message was sent. |
| ASSERT_EQ(1u, sink().adds.size()); |
| debug_ipc::AddOrChangeBreakpointRequest out = sink().adds[0]; |
| EXPECT_FALSE(out.breakpoint.one_shot); |
| EXPECT_EQ(debug_ipc::Stop::kAll, out.breakpoint.stop); |
| EXPECT_EQ(1u, out.breakpoint.locations.size()); |
| } |
| |
| TEST_F(BreakpointImplTest, Watchpoint) { |
| auto target_impls = session().system().GetTargetImpls(); |
| ASSERT_EQ(1u, target_impls.size()); |
| TargetImpl* target = target_impls[0]; |
| const uint64_t kProcessKoid = 6789; |
| target->CreateProcessForTesting(kProcessKoid, "test"); |
| |
| BreakpointImpl bp(&session(), false); |
| |
| const uint64_t kAddress = 0x123456780; |
| const uint32_t kSize = 4; |
| BreakpointSettings in; |
| in.enabled = true; |
| in.type = debug_ipc::BreakpointType::kWrite; |
| in.byte_size = kSize; |
| in.scope = ExecutionScope(target); |
| in.locations.emplace_back(kAddress); |
| |
| bp.SetSettings(in); |
| |
| // Check the message was sent. |
| ASSERT_EQ(1u, sink().adds.size()); |
| debug_ipc::AddOrChangeBreakpointRequest& out = sink().adds[0]; |
| EXPECT_EQ(out.breakpoint.type, debug_ipc::BreakpointType::kWrite); |
| EXPECT_FALSE(out.breakpoint.one_shot); |
| EXPECT_EQ(debug_ipc::Stop::kAll, out.breakpoint.stop); |
| |
| ASSERT_EQ(1u, out.breakpoint.locations.size()); |
| EXPECT_EQ(out.breakpoint.locations[0].address, 0u); |
| // For now, the debugger will send the same address as a range/begin. |
| EXPECT_EQ(out.breakpoint.locations[0].address_range.begin(), kAddress); |
| EXPECT_EQ(out.breakpoint.locations[0].address_range.end(), kAddress + kSize); |
| } |
| |
| // Tests the SettingStore integration and error checking of sizes. |
| TEST_F(BreakpointImplTest, SetSize) { |
| BreakpointImpl bp(&session(), false); |
| |
| SettingStore& setting_store = bp.settings(); |
| EXPECT_EQ(0, setting_store.GetInt(ClientSettings::Breakpoint::kSize)); |
| |
| // Setting the size at this point should be invalid because the type isn't hardware. |
| Err err = setting_store.SetInt(ClientSettings::Breakpoint::kSize, 1); |
| EXPECT_EQ("Breakpoints of type 'software' don't have sizes associated with them.", err.msg()); |
| err = setting_store.SetInt(ClientSettings::Breakpoint::kSize, 0); |
| EXPECT_TRUE(err.ok()); |
| |
| // Se thte settings for a 4-byte write breakpoint. |
| BreakpointSettings in; |
| in.enabled = true; |
| in.type = debug_ipc::BreakpointType::kWrite; |
| in.byte_size = 4; |
| in.locations.emplace_back(0x1234); |
| bp.SetSettings(in); |
| |
| // The setting store should provide the new value. |
| EXPECT_EQ(4, setting_store.GetInt(ClientSettings::Breakpoint::kSize)); |
| |
| // Odd size. |
| err = setting_store.SetInt(ClientSettings::Breakpoint::kSize, 3); |
| EXPECT_FALSE(err.ok()); |
| |
| // Large size. |
| err = setting_store.SetInt(ClientSettings::Breakpoint::kSize, 200); |
| EXPECT_FALSE(err.ok()); |
| |
| // Good new size. |
| err = setting_store.SetInt(ClientSettings::Breakpoint::kSize, 8); |
| EXPECT_TRUE(err.ok()); |
| EXPECT_EQ(8, setting_store.GetInt(ClientSettings::Breakpoint::kSize)); |
| } |
| |
| } // namespace zxdb |