| // 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 |