| // 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/console/analyze_memory.h" | |
| #include <gtest/gtest.h> | |
| #include "src/developer/debug/zxdb/client/mock_frame.h" | |
| #include "src/developer/debug/zxdb/client/mock_process.h" | |
| #include "src/developer/debug/zxdb/client/session.h" | |
| #include "src/developer/debug/zxdb/client/stack.h" | |
| #include "src/developer/debug/zxdb/common/test_with_loop.h" | |
| #include "src/developer/debug/zxdb/console/output_buffer.h" | |
| #include "src/developer/debug/zxdb/symbols/process_symbols_test_setup.h" | |
| namespace zxdb { | |
| namespace { | |
| using ::zxdb::internal::MemoryAnalysis; | |
| using namespace debug_ipc; | |
| class AnalyzeMemoryTest : public TestWithLoop {}; | |
| } // namespace | |
| TEST_F(AnalyzeMemoryTest, Basic) { | |
| Session session; | |
| ProcessSymbolsTestSetup setup; | |
| MockProcess process(&session); | |
| process.set_symbols(&setup.process()); | |
| constexpr uint64_t kBegin = 0x1000; | |
| constexpr uint32_t kLen = 24; // 3 lines of output (8 bytes each). | |
| AnalyzeMemoryOptions opts; | |
| opts.process = &process; | |
| opts.begin_address = kBegin; | |
| opts.bytes_to_read = kLen; | |
| // The callback just saves the buffer to "output". | |
| OutputBuffer output; | |
| auto analysis = fxl::MakeRefCounted<MemoryAnalysis>( | |
| opts, [&output](const Err& err, OutputBuffer analysis, uint64_t next_addr) { | |
| output = analysis; | |
| debug_ipc::MessageLoop::Current()->QuitNow(); | |
| }); | |
| // Setup address space. Make one region inside another. The innermost one should be the one | |
| // reported. | |
| std::vector<debug_ipc::AddressRegion> aspace; | |
| aspace.resize(2); | |
| aspace[0].name = "root"; | |
| aspace[0].base = 0x1000; | |
| aspace[0].size = 0x800000000000; | |
| aspace[0].depth = 0; | |
| aspace[1].name = "inner"; | |
| aspace[1].base = 0x1000; | |
| aspace[1].size = 0x1000; | |
| aspace[1].depth = 1; | |
| analysis->SetAspace(aspace); | |
| const uint64_t kStack0SP = kBegin; | |
| const uint64_t kStack1SP = kBegin + 8; | |
| constexpr uint64_t kAway = 0xFF00000000000; // Points out of the dump. | |
| std::vector<Register> frame0_regs = {Register(RegisterID::kX64_rax, kBegin), | |
| Register(RegisterID::kX64_rcx, kAway), | |
| Register(RegisterID::kX64_rsp, kStack0SP)}; | |
| // Frame 1 duplicates rax (should not have both in the output), but rcx is different and this | |
| // should be called out in the dump. | |
| std::vector<Register> frame1_regs = {Register(RegisterID::kX64_rax, kBegin), | |
| Register(RegisterID::kX64_rcx, kBegin + 16), | |
| Register(RegisterID::kX64_rsp, kStack1SP)}; | |
| // Setup frames. This creates a top frame, an intermediate inline frame, and a bottom frame. | |
| std::vector<std::unique_ptr<Frame>> frames; | |
| frames.push_back(std::make_unique<MockFrame>(nullptr, nullptr, | |
| Location(Location::State::kSymbolized, 0x1234), | |
| kStack0SP, 0, frame0_regs, kStack0SP)); | |
| auto bottom_frame = | |
| std::make_unique<MockFrame>(nullptr, nullptr, Location(Location::State::kSymbolized, 0x1200), | |
| kStack1SP, 0, frame1_regs, kStack1SP); | |
| // Inline frame (needs to reference the bottom frame below it). | |
| frames.push_back( | |
| std::make_unique<MockFrame>(nullptr, nullptr, Location(Location::State::kSymbolized, 0x1210), | |
| kStack1SP, 0, frame1_regs, kStack1SP, bottom_frame.get())); | |
| frames.push_back(std::move(bottom_frame)); | |
| // Stack to hold our mock frames. This stack doesn't need to do anything other than return the | |
| // frames again, so the delegate can be null. | |
| Stack temp_stack(nullptr); | |
| temp_stack.SetFramesForTest(std::move(frames), true); | |
| analysis->SetStack(temp_stack); | |
| // Setup memory. | |
| std::vector<debug_ipc::MemoryBlock> blocks; | |
| blocks.resize(1); | |
| blocks[0].address = kBegin; | |
| blocks[0].valid = true; | |
| blocks[0].size = 0x24; | |
| blocks[0].data = { | |
| 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Points to inner. | |
| 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // Inside outer. | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Nothing | |
| }; | |
| analysis->SetMemory(MemoryDump(std::move(blocks))); | |
| analysis->Schedule(opts); | |
| debug_ipc::MessageLoop::Current()->Run(); | |
| // The pointer to "inner" aspace entry should be annotated. The "outer" aspace entry is too large | |
| // and so will be omitted. | |
| // | |
| // The "frame 2" registers should be omitted because they were covered by the inline "frame 1" | |
| // registers above it. | |
| EXPECT_EQ( | |
| "Address Data \n" | |
| " 0x1000 0x0000000000001000 ◁ rax, rsp, frame 0 base. ▷ inside map " | |
| "\"inner\"\n" | |
| " 0x1008 0x0000000010000000 ◁ frame 1 rsp, frame 1 base\n" | |
| " 0x1010 0x0000000000000000 ◁ frame 1 rcx\n", | |
| output.AsString()); | |
| } | |
| } // namespace zxdb |