| // Copyright 2021 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/minidump_memory.h" |
| |
| #include <memory> |
| |
| #include "src/developer/debug/zxdb/client/elf_memory_region.h" |
| #include "src/developer/debug/zxdb/client/file_memory_region.h" |
| #include "src/lib/elflib/elflib.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| #include "src/lib/unwinder/module.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // Helper to make crashpad::MemorySnapshot::Read easier to use. |
| bool ReadMinidumpMemorySnapshot(const crashpad::MemorySnapshot& memory, |
| std::function<bool(void*, size_t)> callback) { |
| class Delegate : public crashpad::MemorySnapshot::Delegate { |
| public: |
| explicit Delegate(std::function<bool(void*, size_t)> cb) : cb_(std::move(cb)) {} |
| bool MemorySnapshotDelegateRead(void* data, size_t size) override { return cb_(data, size); } |
| std::function<bool(void*, size_t)> cb_; |
| }; |
| Delegate delegate(std::move(callback)); |
| return memory.Read(&delegate); |
| } |
| |
| } // namespace |
| |
| MinidumpMemory::MinidumpMemory(const crashpad::ProcessSnapshotMinidump& minidump, |
| BuildIDIndex& build_id_index) { |
| for (const auto& thread : minidump.Threads()) { |
| auto stack = thread->Stack(); |
| |
| if (!stack) { |
| continue; |
| } |
| |
| regions_.emplace_back(stack->Address(), stack->Address() + stack->Size(), |
| std::make_shared<SnapshotMemoryRegion>(stack)); |
| } |
| |
| for (const auto& minidump_mod : minidump.Modules()) { |
| uint64_t base = minidump_mod->Address(); |
| auto entry = build_id_index.EntryForBuildID(MinidumpGetBuildId(*minidump_mod)); |
| std::optional<ElfMemoryRegion> debug_info; |
| std::optional<ElfMemoryRegion> binary; |
| |
| if (!entry.debug_info.empty()) { |
| debug_info = ElfMemoryRegion(base, entry.debug_info); |
| } |
| |
| if (!entry.binary.empty()) { |
| binary = ElfMemoryRegion(base, entry.binary); |
| } |
| |
| debug_modules_.emplace(base, |
| Entry{.binary = std::move(binary), .debug_info = std::move(debug_info)}); |
| |
| if (entry.binary.empty()) { |
| continue; |
| } |
| |
| auto elf = elflib::ElfLib::Create(entry.binary); |
| if (!elf) { |
| continue; |
| } |
| auto module = std::make_shared<FileMemoryRegion>(base, entry.binary); |
| for (const auto& segment : elf->GetSegmentHeaders()) { |
| // Only PT_LOAD segments are actually mapped. The rest are informational. |
| if (segment.p_type != elflib::PT_LOAD) { |
| continue; |
| } |
| if (segment.p_flags & elflib::PF_W) { |
| // Writable segment. Data in the ELF file might not match what was present at the time of |
| // the crash. |
| continue; |
| } |
| regions_.emplace_back(base + segment.p_vaddr, base + segment.p_vaddr + segment.p_memsz, |
| module); |
| } |
| } |
| std::sort(regions_.begin(), regions_.end()); |
| |
| // Sanity check. |
| uint64_t last_end = 0; |
| for (auto& [start, end, mem] : regions_) { |
| FX_CHECK(start >= last_end); |
| last_end = end; |
| } |
| } |
| |
| std::vector<debug_ipc::MemoryBlock> MinidumpMemory::ReadMemoryBlocks(uint64_t address, |
| uint64_t size) { |
| uint64_t end = address + size; |
| std::vector<debug_ipc::MemoryBlock> res; |
| if (address == end) { |
| return res; |
| } |
| for (auto& [region_start, region_end, region_memory] : regions_) { |
| // Space before the first region and between any two regions. |
| if (address < region_start) { |
| auto& block = res.emplace_back(); |
| block.address = address; |
| block.size = std::min(end, region_start) - address; |
| block.valid = false; |
| if (end <= region_start) { |
| address = end; |
| break; |
| } |
| address = region_start; |
| } |
| // Now we have address >= region_start. |
| if (address < region_end) { |
| auto& block = res.emplace_back(); |
| block.address = address; |
| block.size = std::min(end, region_end) - address; |
| block.data.resize(block.size); |
| block.valid = region_memory->ReadBytes(address, block.size, block.data.data()).ok(); |
| if (!block.valid) { |
| block.data.clear(); |
| } |
| if (end <= region_end) { |
| address = end; |
| break; |
| } |
| address = region_end; |
| } |
| } |
| // Space after the last region. |
| if (address < end) { |
| auto& block = res.emplace_back(); |
| block.address = address; |
| block.size = end - address; |
| block.valid = false; |
| } |
| return res; |
| } |
| |
| unwinder::Memory* MinidumpMemory::GetMemoryRegion(uint64_t address) { |
| for (auto& [start, end, memory] : regions_) { |
| if (address >= start && address < end) { |
| return memory.get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| std::vector<unwinder::Module> MinidumpMemory::GetUnwinderModules() { |
| std::vector<unwinder::Module> res; |
| res.reserve(debug_modules_.size()); |
| for (auto& [addr, entry] : debug_modules_) { |
| unwinder::Memory* binary_memory = nullptr; |
| unwinder::Memory* debug_info_memory = nullptr; |
| |
| if (entry.binary) { |
| binary_memory = &entry.binary.value(); |
| } |
| |
| if (entry.debug_info) { |
| debug_info_memory = &entry.debug_info.value(); |
| } |
| |
| res.emplace_back(addr, binary_memory, debug_info_memory, unwinder::Module::AddressMode::kFile); |
| } |
| return res; |
| } |
| |
| unwinder::Error MinidumpMemory::SnapshotMemoryRegion::ReadBytes(uint64_t addr, uint64_t size, |
| void* dst) { |
| if (addr < snapshot_->Address() || addr + size > snapshot_->Address() + snapshot_->Size()) { |
| return unwinder::Error("out of boundary"); |
| } |
| uint64_t offset = addr - snapshot_->Address(); |
| bool ok = ReadMinidumpMemorySnapshot(*snapshot_, [&](void* data, uint64_t actual_size) { |
| if (offset + size > actual_size) { |
| return false; |
| } |
| memcpy(dst, reinterpret_cast<uint8_t*>(data) + offset, size); |
| return true; |
| }); |
| if (ok) { |
| return unwinder::Success(); |
| } |
| return unwinder::Error("error reading from the memory snapshot"); |
| } |
| |
| std::string MinidumpGetBuildId(const crashpad::ModuleSnapshot& mod) { |
| auto build_id = mod.BuildID(); |
| |
| if (build_id.empty()) { |
| return ""; |
| } |
| |
| // 2 hex characters per 1 byte, so the string size is twice the data size. |
| std::string ret; |
| ret.reserve(2 * build_id.size()); |
| for (const auto& byte : build_id) { |
| ret.append(fxl::StringPrintf("%02hhx", byte)); |
| } |
| |
| return ret; |
| } |
| |
| } // namespace zxdb |