| // 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_ipc::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, std::vector<uint64_t>()); | |
| // 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].process_koid); | |
| EXPECT_EQ(koid, out.breakpoint.locations[1].process_koid); | |
| EXPECT_EQ(0u, out.breakpoint.locations[0].thread_koid); | |
| EXPECT_EQ(0u, out.breakpoint.locations[1].thread_koid); | |
| // 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, std::vector<uint64_t>()); | |
| 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 |