blob: 185b05fa7d44ced2ee37e8316bfe4748f7e06bd6 [file] [log] [blame]
// 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