blob: 2818d31eb230152e24edfe95f4ab37364799d9a2 [file] [log] [blame]
//===-- MinidumpFileBuilder.cpp -------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "MinidumpFileBuilder.h"
#include "Plugins/Process/minidump/RegisterContextMinidump_ARM64.h"
#include "Plugins/Process/minidump/RegisterContextMinidump_x86_64.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleList.h"
#include "lldb/Core/Section.h"
#include "lldb/Target/ABI.h"
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/StopInfo.h"
#include "lldb/Target/ThreadList.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/DataExtractor.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/RangeMap.h"
#include "lldb/Utility/RegisterValue.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/BinaryFormat/Minidump.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Error.h"
#include "llvm/TargetParser/Triple.h"
#include "Plugins/Process/minidump/MinidumpTypes.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-forward.h"
#include "lldb/lldb-types.h"
#include <algorithm>
#include <cinttypes>
#include <cstddef>
#include <cstdint>
#include <utility>
using namespace lldb;
using namespace lldb_private;
using namespace llvm::minidump;
Status MinidumpFileBuilder::AddHeaderAndCalculateDirectories() {
// First set the offset on the file, and on the bytes saved
m_saved_data_size = HEADER_SIZE;
// We know we will have at least Misc, SystemInfo, Modules, and ThreadList
// (corresponding memory list for stacks), an additional memory list for
// non-stacks, and a stream to mark this minidump was generated by LLDB.
lldb_private::Target &target = m_process_sp->GetTarget();
m_expected_directories = 6;
// Check if OS is linux and reserve directory space for all linux specific
// breakpad extension directories.
if (target.GetArchitecture().GetTriple().getOS() ==
llvm::Triple::OSType::Linux)
m_expected_directories += 9;
// Go through all of the threads and check for exceptions.
std::vector<lldb::ThreadSP> threads =
m_process_sp->CalculateCoreFileThreadList(m_save_core_options);
for (const ThreadSP &thread_sp : threads) {
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
if (stop_info_sp) {
const StopReason &stop_reason = stop_info_sp->GetStopReason();
if (stop_reason != lldb::eStopReasonInvalid)
m_expected_directories++;
}
}
// Add a generous buffer of directories, these are quite small
// and forks may add new directories upstream LLDB hadn't accounted for
// when we started pre-calculating directory size, so this should account for
// that
m_expected_directories += 100;
m_saved_data_size +=
m_expected_directories * sizeof(llvm::minidump::Directory);
Status error;
offset_t new_offset = m_core_file->SeekFromStart(m_saved_data_size);
if (new_offset != m_saved_data_size)
error = Status::FromErrorStringWithFormat(
"Failed to fill in header and directory "
"sections. Written / Expected (%" PRIx64 " / %" PRIx64 ")",
new_offset, m_saved_data_size);
if (error.Fail())
return error;
return AddLLDBGeneratedStream();
}
Status MinidumpFileBuilder::AddDirectory(StreamType type,
uint64_t stream_size) {
// We explicitly cast type, an 32b enum, to uint32_t to avoid warnings.
Status error;
if (GetCurrentDataEndOffset() > UINT32_MAX) {
error = Status::FromErrorStringWithFormat(
"Unable to add directory for stream type "
"%x, offset is greater then 32 bit limit.",
(uint32_t)type);
return error;
}
if (m_directories.size() + 1 > m_expected_directories) {
error = Status::FromErrorStringWithFormat(
"Unable to add directory for stream type %x, exceeded expected number "
"of directories %zu.",
(uint32_t)type, m_expected_directories);
return error;
}
LocationDescriptor loc;
loc.DataSize = static_cast<llvm::support::ulittle32_t>(stream_size);
// Stream will begin at the current end of data section
loc.RVA = static_cast<llvm::support::ulittle32_t>(GetCurrentDataEndOffset());
Directory dir;
dir.Type = static_cast<llvm::support::little_t<StreamType>>(type);
dir.Location = loc;
m_directories.push_back(dir);
return error;
}
Status MinidumpFileBuilder::AddLLDBGeneratedStream() {
Status error;
StreamType type = StreamType::LLDBGenerated;
return AddDirectory(type, 0);
}
Status MinidumpFileBuilder::AddSystemInfo() {
Status error;
const llvm::Triple &target_triple =
m_process_sp->GetTarget().GetArchitecture().GetTriple();
error =
AddDirectory(StreamType::SystemInfo, sizeof(llvm::minidump::SystemInfo));
if (error.Fail())
return error;
llvm::minidump::ProcessorArchitecture arch;
switch (target_triple.getArch()) {
case llvm::Triple::ArchType::x86_64:
arch = ProcessorArchitecture::AMD64;
break;
case llvm::Triple::ArchType::x86:
arch = ProcessorArchitecture::X86;
break;
case llvm::Triple::ArchType::arm:
arch = ProcessorArchitecture::ARM;
break;
case llvm::Triple::ArchType::aarch64:
arch = ProcessorArchitecture::ARM64;
break;
case llvm::Triple::ArchType::mips64:
case llvm::Triple::ArchType::mips64el:
case llvm::Triple::ArchType::mips:
case llvm::Triple::ArchType::mipsel:
arch = ProcessorArchitecture::MIPS;
break;
case llvm::Triple::ArchType::ppc64:
case llvm::Triple::ArchType::ppc:
case llvm::Triple::ArchType::ppc64le:
arch = ProcessorArchitecture::PPC;
break;
default:
error = Status::FromErrorStringWithFormat(
"Architecture %s not supported.",
target_triple.getArchName().str().c_str());
return error;
};
llvm::support::little_t<OSPlatform> platform_id;
switch (target_triple.getOS()) {
case llvm::Triple::OSType::Linux:
if (target_triple.getEnvironment() ==
llvm::Triple::EnvironmentType::Android)
platform_id = OSPlatform::Android;
else
platform_id = OSPlatform::Linux;
break;
case llvm::Triple::OSType::Win32:
platform_id = OSPlatform::Win32NT;
break;
case llvm::Triple::OSType::MacOSX:
platform_id = OSPlatform::MacOSX;
break;
case llvm::Triple::OSType::IOS:
platform_id = OSPlatform::IOS;
break;
default:
error = Status::FromErrorStringWithFormat(
"OS %s not supported.", target_triple.getOSName().str().c_str());
return error;
};
llvm::minidump::SystemInfo sys_info;
sys_info.ProcessorArch =
static_cast<llvm::support::little_t<ProcessorArchitecture>>(arch);
// Global offset to beginning of a csd_string in a data section
sys_info.CSDVersionRVA = static_cast<llvm::support::ulittle32_t>(
GetCurrentDataEndOffset() + sizeof(llvm::minidump::SystemInfo));
sys_info.PlatformId = platform_id;
m_data.AppendData(&sys_info, sizeof(llvm::minidump::SystemInfo));
std::string csd_string;
error = WriteString(csd_string, &m_data);
if (error.Fail()) {
error =
Status::FromErrorString("Unable to convert the csd string to UTF16.");
return error;
}
return error;
}
Status WriteString(const std::string &to_write,
lldb_private::DataBufferHeap *buffer) {
Status error;
// let the StringRef eat also null termination char
llvm::StringRef to_write_ref(to_write.c_str(), to_write.size() + 1);
llvm::SmallVector<llvm::UTF16, 128> to_write_utf16;
bool converted = convertUTF8ToUTF16String(to_write_ref, to_write_utf16);
if (!converted) {
error = Status::FromErrorStringWithFormat(
"Unable to convert the string to UTF16. Failed to convert %s",
to_write.c_str());
return error;
}
// size of the UTF16 string should be written without the null termination
// character that is stored in 2 bytes
llvm::support::ulittle32_t to_write_size(to_write_utf16.size_in_bytes() - 2);
buffer->AppendData(&to_write_size, sizeof(llvm::support::ulittle32_t));
buffer->AppendData(to_write_utf16.data(), to_write_utf16.size_in_bytes());
return error;
}
llvm::Expected<uint64_t> getModuleFileSize(Target &target,
const ModuleSP &mod) {
// JIT module has the same vm and file size.
uint64_t SizeOfImage = 0;
if (mod->GetObjectFile()->CalculateType() == ObjectFile::Type::eTypeJIT) {
for (const auto &section : *mod->GetObjectFile()->GetSectionList()) {
SizeOfImage += section->GetByteSize();
}
return SizeOfImage;
}
SectionSP sect_sp = mod->GetObjectFile()->GetBaseAddress().GetSection();
if (!sect_sp) {
return llvm::createStringError(std::errc::operation_not_supported,
"Couldn't obtain the section information.");
}
lldb::addr_t sect_addr = sect_sp->GetLoadBaseAddress(&target);
// Use memory size since zero fill sections, like ".bss", will be smaller on
// disk.
lldb::addr_t sect_size = sect_sp->GetByteSize();
// This will usually be zero, but make sure to calculate the BaseOfImage
// offset.
const lldb::addr_t base_sect_offset =
mod->GetObjectFile()->GetBaseAddress().GetLoadAddress(&target) -
sect_addr;
SizeOfImage = sect_size - base_sect_offset;
lldb::addr_t next_sect_addr = sect_addr + sect_size;
Address sect_so_addr;
target.ResolveLoadAddress(next_sect_addr, sect_so_addr);
lldb::SectionSP next_sect_sp = sect_so_addr.GetSection();
while (next_sect_sp &&
next_sect_sp->GetLoadBaseAddress(&target) == next_sect_addr) {
sect_size = sect_sp->GetByteSize();
SizeOfImage += sect_size;
next_sect_addr += sect_size;
target.ResolveLoadAddress(next_sect_addr, sect_so_addr);
next_sect_sp = sect_so_addr.GetSection();
}
return SizeOfImage;
}
// ModuleList stream consists of a number of modules, followed by an array
// of llvm::minidump::Module's structures. Every structure informs about a
// single module. Additional data of variable length, such as module's names,
// are stored just after the ModuleList stream. The llvm::minidump::Module
// structures point to this helper data by global offset.
Status MinidumpFileBuilder::AddModuleList() {
constexpr size_t minidump_module_size = sizeof(llvm::minidump::Module);
Status error;
lldb_private::Target &target = m_process_sp->GetTarget();
const ModuleList &modules = target.GetImages();
llvm::support::ulittle32_t modules_count =
static_cast<llvm::support::ulittle32_t>(modules.GetSize());
// This helps us with getting the correct global offset in minidump
// file later, when we will be setting up offsets from the
// the llvm::minidump::Module's structures into helper data
size_t size_before = GetCurrentDataEndOffset();
// This is the size of the main part of the ModuleList stream.
// It consists of a module number and corresponding number of
// structs describing individual modules
size_t module_stream_size =
sizeof(llvm::support::ulittle32_t) + modules_count * minidump_module_size;
// Adding directory describing this stream.
error = AddDirectory(StreamType::ModuleList, module_stream_size);
if (error.Fail())
return error;
m_data.AppendData(&modules_count, sizeof(llvm::support::ulittle32_t));
// Temporary storage for the helper data (of variable length)
// as these cannot be dumped to m_data before dumping entire
// array of module structures.
DataBufferHeap helper_data;
for (size_t i = 0; i < modules_count; ++i) {
ModuleSP mod = modules.GetModuleAtIndex(i);
std::string module_name = mod->GetSpecificationDescription();
auto maybe_mod_size = getModuleFileSize(target, mod);
if (!maybe_mod_size) {
llvm::Error mod_size_err = maybe_mod_size.takeError();
llvm::handleAllErrors(std::move(mod_size_err),
[&](const llvm::ErrorInfoBase &E) {
error = Status::FromErrorStringWithFormat(
"Unable to get the size of module %s: %s.",
module_name.c_str(), E.message().c_str());
});
return error;
}
uint64_t mod_size = std::move(*maybe_mod_size);
llvm::support::ulittle32_t signature =
static_cast<llvm::support::ulittle32_t>(
static_cast<uint32_t>(minidump::CvSignature::ElfBuildId));
auto uuid = mod->GetUUID().GetBytes();
VSFixedFileInfo info;
info.Signature = static_cast<llvm::support::ulittle32_t>(0u);
info.StructVersion = static_cast<llvm::support::ulittle32_t>(0u);
info.FileVersionHigh = static_cast<llvm::support::ulittle32_t>(0u);
info.FileVersionLow = static_cast<llvm::support::ulittle32_t>(0u);
info.ProductVersionHigh = static_cast<llvm::support::ulittle32_t>(0u);
info.ProductVersionLow = static_cast<llvm::support::ulittle32_t>(0u);
info.FileFlagsMask = static_cast<llvm::support::ulittle32_t>(0u);
info.FileFlags = static_cast<llvm::support::ulittle32_t>(0u);
info.FileOS = static_cast<llvm::support::ulittle32_t>(0u);
info.FileType = static_cast<llvm::support::ulittle32_t>(0u);
info.FileSubtype = static_cast<llvm::support::ulittle32_t>(0u);
info.FileDateHigh = static_cast<llvm::support::ulittle32_t>(0u);
info.FileDateLow = static_cast<llvm::support::ulittle32_t>(0u);
LocationDescriptor ld;
ld.DataSize = static_cast<llvm::support::ulittle32_t>(0u);
ld.RVA = static_cast<llvm::support::ulittle32_t>(0u);
// Setting up LocationDescriptor for uuid string. The global offset into
// minidump file is calculated.
LocationDescriptor ld_cv;
ld_cv.DataSize = static_cast<llvm::support::ulittle32_t>(
sizeof(llvm::support::ulittle32_t) + uuid.size());
ld_cv.RVA = static_cast<llvm::support::ulittle32_t>(
size_before + module_stream_size + helper_data.GetByteSize());
helper_data.AppendData(&signature, sizeof(llvm::support::ulittle32_t));
helper_data.AppendData(uuid.begin(), uuid.size());
llvm::minidump::Module m;
m.BaseOfImage = static_cast<llvm::support::ulittle64_t>(
mod->GetObjectFile()->GetBaseAddress().GetLoadAddress(&target));
m.SizeOfImage = static_cast<llvm::support::ulittle32_t>(mod_size);
m.Checksum = static_cast<llvm::support::ulittle32_t>(0);
m.TimeDateStamp =
static_cast<llvm::support::ulittle32_t>(std::time(nullptr));
m.ModuleNameRVA = static_cast<llvm::support::ulittle32_t>(
size_before + module_stream_size + helper_data.GetByteSize());
m.VersionInfo = info;
m.CvRecord = ld_cv;
m.MiscRecord = ld;
error = WriteString(module_name, &helper_data);
if (error.Fail())
return error;
m_data.AppendData(&m, sizeof(llvm::minidump::Module));
}
m_data.AppendData(helper_data.GetBytes(), helper_data.GetByteSize());
return error;
}
uint16_t read_register_u16_raw(RegisterContext *reg_ctx,
llvm::StringRef reg_name) {
const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
if (!reg_info)
return 0;
lldb_private::RegisterValue reg_value;
bool success = reg_ctx->ReadRegister(reg_info, reg_value);
if (!success)
return 0;
return reg_value.GetAsUInt16();
}
uint32_t read_register_u32_raw(RegisterContext *reg_ctx,
llvm::StringRef reg_name) {
const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
if (!reg_info)
return 0;
lldb_private::RegisterValue reg_value;
bool success = reg_ctx->ReadRegister(reg_info, reg_value);
if (!success)
return 0;
return reg_value.GetAsUInt32();
}
uint64_t read_register_u64_raw(RegisterContext *reg_ctx,
llvm::StringRef reg_name) {
const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
if (!reg_info)
return 0;
lldb_private::RegisterValue reg_value;
bool success = reg_ctx->ReadRegister(reg_info, reg_value);
if (!success)
return 0;
return reg_value.GetAsUInt64();
}
llvm::support::ulittle16_t read_register_u16(RegisterContext *reg_ctx,
llvm::StringRef reg_name) {
return static_cast<llvm::support::ulittle16_t>(
read_register_u16_raw(reg_ctx, reg_name));
}
llvm::support::ulittle32_t read_register_u32(RegisterContext *reg_ctx,
llvm::StringRef reg_name) {
return static_cast<llvm::support::ulittle32_t>(
read_register_u32_raw(reg_ctx, reg_name));
}
llvm::support::ulittle64_t read_register_u64(RegisterContext *reg_ctx,
llvm::StringRef reg_name) {
return static_cast<llvm::support::ulittle64_t>(
read_register_u64_raw(reg_ctx, reg_name));
}
void read_register_u128(RegisterContext *reg_ctx, llvm::StringRef reg_name,
uint8_t *dst) {
const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
if (reg_info) {
lldb_private::RegisterValue reg_value;
if (reg_ctx->ReadRegister(reg_info, reg_value)) {
Status error;
uint32_t bytes_copied = reg_value.GetAsMemoryData(
*reg_info, dst, 16, lldb::ByteOrder::eByteOrderLittle, error);
if (bytes_copied == 16)
return;
}
}
// If anything goes wrong, then zero out the register value.
memset(dst, 0, 16);
}
lldb_private::minidump::MinidumpContext_x86_64
GetThreadContext_x86_64(RegisterContext *reg_ctx) {
lldb_private::minidump::MinidumpContext_x86_64 thread_context = {};
thread_context.p1_home = {};
thread_context.context_flags = static_cast<uint32_t>(
lldb_private::minidump::MinidumpContext_x86_64_Flags::x86_64_Flag |
lldb_private::minidump::MinidumpContext_x86_64_Flags::Control |
lldb_private::minidump::MinidumpContext_x86_64_Flags::Segments |
lldb_private::minidump::MinidumpContext_x86_64_Flags::Integer |
lldb_private::minidump::MinidumpContext_x86_64_Flags::LLDBSpecific);
thread_context.rax = read_register_u64(reg_ctx, "rax");
thread_context.rbx = read_register_u64(reg_ctx, "rbx");
thread_context.rcx = read_register_u64(reg_ctx, "rcx");
thread_context.rdx = read_register_u64(reg_ctx, "rdx");
thread_context.rdi = read_register_u64(reg_ctx, "rdi");
thread_context.rsi = read_register_u64(reg_ctx, "rsi");
thread_context.rbp = read_register_u64(reg_ctx, "rbp");
thread_context.rsp = read_register_u64(reg_ctx, "rsp");
thread_context.r8 = read_register_u64(reg_ctx, "r8");
thread_context.r9 = read_register_u64(reg_ctx, "r9");
thread_context.r10 = read_register_u64(reg_ctx, "r10");
thread_context.r11 = read_register_u64(reg_ctx, "r11");
thread_context.r12 = read_register_u64(reg_ctx, "r12");
thread_context.r13 = read_register_u64(reg_ctx, "r13");
thread_context.r14 = read_register_u64(reg_ctx, "r14");
thread_context.r15 = read_register_u64(reg_ctx, "r15");
thread_context.rip = read_register_u64(reg_ctx, "rip");
// To make our code agnostic to whatever type the register value identifies
// itself as, we read as a u64 and truncate to u32/u16 ourselves.
thread_context.eflags = read_register_u64(reg_ctx, "rflags");
thread_context.cs = read_register_u64(reg_ctx, "cs");
thread_context.fs = read_register_u64(reg_ctx, "fs");
thread_context.gs = read_register_u64(reg_ctx, "gs");
thread_context.ss = read_register_u64(reg_ctx, "ss");
thread_context.ds = read_register_u64(reg_ctx, "ds");
thread_context.fs_base = read_register_u64(reg_ctx, "fs_base");
thread_context.gs_base = read_register_u64(reg_ctx, "gs_base");
return thread_context;
}
minidump::RegisterContextMinidump_ARM64::Context
GetThreadContext_ARM64(RegisterContext *reg_ctx) {
minidump::RegisterContextMinidump_ARM64::Context thread_context = {};
thread_context.context_flags = static_cast<uint32_t>(
minidump::RegisterContextMinidump_ARM64::Flags::ARM64_Flag |
minidump::RegisterContextMinidump_ARM64::Flags::Integer |
minidump::RegisterContextMinidump_ARM64::Flags::FloatingPoint);
char reg_name[16];
for (uint32_t i = 0; i < 31; ++i) {
snprintf(reg_name, sizeof(reg_name), "x%u", i);
thread_context.x[i] = read_register_u64(reg_ctx, reg_name);
}
// Work around a bug in debugserver where "sp" on arm64 doesn't have the alt
// name set to "x31"
thread_context.x[31] = read_register_u64(reg_ctx, "sp");
thread_context.pc = read_register_u64(reg_ctx, "pc");
thread_context.cpsr = read_register_u32(reg_ctx, "cpsr");
thread_context.fpsr = read_register_u32(reg_ctx, "fpsr");
thread_context.fpcr = read_register_u32(reg_ctx, "fpcr");
for (uint32_t i = 0; i < 32; ++i) {
snprintf(reg_name, sizeof(reg_name), "v%u", i);
read_register_u128(reg_ctx, reg_name, &thread_context.v[i * 16]);
}
return thread_context;
}
class ArchThreadContexts {
llvm::Triple::ArchType m_arch;
union {
lldb_private::minidump::MinidumpContext_x86_64 x86_64;
lldb_private::minidump::RegisterContextMinidump_ARM64::Context arm64;
};
public:
ArchThreadContexts(llvm::Triple::ArchType arch) : m_arch(arch) {}
bool prepareRegisterContext(RegisterContext *reg_ctx) {
switch (m_arch) {
case llvm::Triple::ArchType::x86_64:
x86_64 = GetThreadContext_x86_64(reg_ctx);
return true;
case llvm::Triple::ArchType::aarch64:
arm64 = GetThreadContext_ARM64(reg_ctx);
return true;
default:
break;
}
return false;
}
const void *data() const { return &x86_64; }
size_t size() const {
switch (m_arch) {
case llvm::Triple::ArchType::x86_64:
return sizeof(x86_64);
case llvm::Triple::ArchType::aarch64:
return sizeof(arm64);
default:
break;
}
return 0;
}
};
Status MinidumpFileBuilder::FixThreadStacks() {
Status error;
// If we have anything in the heap flush it.
FlushBufferToDisk();
m_core_file->SeekFromStart(m_thread_list_start);
for (auto &pair : m_thread_by_range_end) {
// The thread objects will get a new memory descriptor added
// When we are emitting the memory list and then we write it here
const llvm::minidump::Thread &thread = pair.second;
size_t bytes_to_write = sizeof(llvm::minidump::Thread);
size_t bytes_written = bytes_to_write;
error = m_core_file->Write(&thread, bytes_written);
if (error.Fail() || bytes_to_write != bytes_written) {
error = Status::FromErrorStringWithFormat(
"Wrote incorrect number of bytes to minidump file. (written %zd/%zd)",
bytes_written, bytes_to_write);
return error;
}
}
return error;
}
Status MinidumpFileBuilder::AddThreadList() {
constexpr size_t minidump_thread_size = sizeof(llvm::minidump::Thread);
std::vector<ThreadSP> thread_list =
m_process_sp->CalculateCoreFileThreadList(m_save_core_options);
// size of the entire thread stream consists of:
// number of threads and threads array
size_t thread_stream_size = sizeof(llvm::support::ulittle32_t) +
thread_list.size() * minidump_thread_size;
// save for the ability to set up RVA
size_t size_before = GetCurrentDataEndOffset();
Status error;
error = AddDirectory(StreamType::ThreadList, thread_stream_size);
if (error.Fail())
return error;
llvm::support::ulittle32_t thread_count =
static_cast<llvm::support::ulittle32_t>(thread_list.size());
m_data.AppendData(&thread_count, sizeof(llvm::support::ulittle32_t));
// Take the offset after the thread count.
m_thread_list_start = GetCurrentDataEndOffset();
DataBufferHeap helper_data;
Log *log = GetLog(LLDBLog::Object);
for (const ThreadSP &thread_sp : thread_list) {
RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
if (!reg_ctx_sp) {
error = Status::FromErrorString("Unable to get the register context.");
return error;
}
RegisterContext *reg_ctx = reg_ctx_sp.get();
Target &target = m_process_sp->GetTarget();
const ArchSpec &arch = target.GetArchitecture();
ArchThreadContexts thread_context(arch.GetMachine());
if (!thread_context.prepareRegisterContext(reg_ctx)) {
error = Status::FromErrorStringWithFormat(
"architecture %s not supported.",
arch.GetTriple().getArchName().str().c_str());
return error;
}
uint64_t sp = reg_ctx->GetSP();
MemoryRegionInfo sp_region;
m_process_sp->GetMemoryRegionInfo(sp, sp_region);
// Emit a blank descriptor
MemoryDescriptor stack;
LocationDescriptor empty_label;
empty_label.DataSize = 0;
empty_label.RVA = 0;
stack.Memory = empty_label;
stack.StartOfMemoryRange = 0;
LocationDescriptor thread_context_memory_locator;
thread_context_memory_locator.DataSize =
static_cast<llvm::support::ulittle32_t>(thread_context.size());
thread_context_memory_locator.RVA = static_cast<llvm::support::ulittle32_t>(
size_before + thread_stream_size + helper_data.GetByteSize());
// Cache thie thread context memory so we can reuse for exceptions.
m_tid_to_reg_ctx[thread_sp->GetID()] = thread_context_memory_locator;
LLDB_LOGF(log, "AddThreadList for thread %d: thread_context %zu bytes",
thread_sp->GetIndexID(), thread_context.size());
helper_data.AppendData(thread_context.data(), thread_context.size());
llvm::minidump::Thread t;
t.ThreadId = static_cast<llvm::support::ulittle32_t>(thread_sp->GetID());
t.SuspendCount = static_cast<llvm::support::ulittle32_t>(
(thread_sp->GetState() == StateType::eStateSuspended) ? 1 : 0);
t.PriorityClass = static_cast<llvm::support::ulittle32_t>(0);
t.Priority = static_cast<llvm::support::ulittle32_t>(0);
t.EnvironmentBlock = static_cast<llvm::support::ulittle64_t>(0);
t.Stack = stack, t.Context = thread_context_memory_locator;
// We save off the stack object so we can circle back and clean it up.
m_thread_by_range_end[sp_region.GetRange().GetRangeEnd()] = t;
m_data.AppendData(&t, sizeof(llvm::minidump::Thread));
}
LLDB_LOGF(log, "AddThreadList(): total helper_data %" PRIx64 " bytes",
helper_data.GetByteSize());
m_data.AppendData(helper_data.GetBytes(), helper_data.GetByteSize());
return Status();
}
Status MinidumpFileBuilder::AddExceptions() {
std::vector<ThreadSP> thread_list =
m_process_sp->CalculateCoreFileThreadList(m_save_core_options);
Status error;
for (const ThreadSP &thread_sp : thread_list) {
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
// If we don't have a stop info, or if it's invalid, skip.
if (!stop_info_sp ||
stop_info_sp->GetStopReason() == lldb::eStopReasonInvalid)
continue;
constexpr size_t minidump_exception_size =
sizeof(llvm::minidump::ExceptionStream);
error = AddDirectory(StreamType::Exception, minidump_exception_size);
if (error.Fail())
return error;
RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
Exception exp_record = {};
exp_record.ExceptionCode =
static_cast<llvm::support::ulittle32_t>(stop_info_sp->GetValue());
exp_record.ExceptionFlags =
static_cast<llvm::support::ulittle32_t>(Exception::LLDB_FLAG);
exp_record.ExceptionRecord = static_cast<llvm::support::ulittle64_t>(0);
exp_record.ExceptionAddress = reg_ctx_sp->GetPC();
exp_record.NumberParameters = static_cast<llvm::support::ulittle32_t>(1);
std::string description = stop_info_sp->GetDescription();
// We have 120 bytes to work with and it's unlikely description will
// overflow, but we gotta check.
memcpy(&exp_record.ExceptionInformation, description.c_str(),
std::min(description.size(), Exception::MaxParameterBytes));
exp_record.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
ExceptionStream exp_stream;
exp_stream.ThreadId =
static_cast<llvm::support::ulittle32_t>(thread_sp->GetID());
exp_stream.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
exp_stream.ExceptionRecord = exp_record;
auto Iter = m_tid_to_reg_ctx.find(thread_sp->GetID());
if (Iter != m_tid_to_reg_ctx.end()) {
exp_stream.ThreadContext = Iter->second;
} else {
exp_stream.ThreadContext.DataSize = 0;
exp_stream.ThreadContext.RVA = 0;
}
m_data.AppendData(&exp_stream, minidump_exception_size);
}
return error;
}
lldb_private::Status MinidumpFileBuilder::AddMiscInfo() {
Status error;
error = AddDirectory(StreamType::MiscInfo,
sizeof(lldb_private::minidump::MinidumpMiscInfo));
if (error.Fail())
return error;
lldb_private::minidump::MinidumpMiscInfo misc_info;
misc_info.size = static_cast<llvm::support::ulittle32_t>(
sizeof(lldb_private::minidump::MinidumpMiscInfo));
// Default set flags1 to 0, in case that we will not be able to
// get any information
misc_info.flags1 = static_cast<llvm::support::ulittle32_t>(0);
lldb_private::ProcessInstanceInfo process_info;
m_process_sp->GetProcessInfo(process_info);
if (process_info.ProcessIDIsValid()) {
// Set flags1 to reflect that PID is filled in
misc_info.flags1 =
static_cast<llvm::support::ulittle32_t>(static_cast<uint32_t>(
lldb_private::minidump::MinidumpMiscInfoFlags::ProcessID));
misc_info.process_id =
static_cast<llvm::support::ulittle32_t>(process_info.GetProcessID());
}
m_data.AppendData(&misc_info,
sizeof(lldb_private::minidump::MinidumpMiscInfo));
return error;
}
std::unique_ptr<llvm::MemoryBuffer>
getFileStreamHelper(const std::string &path) {
auto maybe_stream = llvm::MemoryBuffer::getFileAsStream(path);
if (!maybe_stream)
return nullptr;
return std::move(maybe_stream.get());
}
Status MinidumpFileBuilder::AddLinuxFileStreams() {
Status error;
// No-op if we are not on linux.
if (m_process_sp->GetTarget().GetArchitecture().GetTriple().getOS() !=
llvm::Triple::Linux)
return error;
std::vector<std::pair<StreamType, std::string>> files_with_stream_types = {
{StreamType::LinuxCPUInfo, "/proc/cpuinfo"},
{StreamType::LinuxLSBRelease, "/etc/lsb-release"},
};
lldb_private::ProcessInstanceInfo process_info;
m_process_sp->GetProcessInfo(process_info);
if (process_info.ProcessIDIsValid()) {
lldb::pid_t pid = process_info.GetProcessID();
std::string pid_str = std::to_string(pid);
files_with_stream_types.push_back(
{StreamType::LinuxProcStatus, "/proc/" + pid_str + "/status"});
files_with_stream_types.push_back(
{StreamType::LinuxCMDLine, "/proc/" + pid_str + "/cmdline"});
files_with_stream_types.push_back(
{StreamType::LinuxEnviron, "/proc/" + pid_str + "/environ"});
files_with_stream_types.push_back(
{StreamType::LinuxAuxv, "/proc/" + pid_str + "/auxv"});
files_with_stream_types.push_back(
{StreamType::LinuxMaps, "/proc/" + pid_str + "/maps"});
files_with_stream_types.push_back(
{StreamType::LinuxProcStat, "/proc/" + pid_str + "/stat"});
files_with_stream_types.push_back(
{StreamType::LinuxProcFD, "/proc/" + pid_str + "/fd"});
}
for (const auto &entry : files_with_stream_types) {
StreamType stream = entry.first;
std::string path = entry.second;
auto memory_buffer = getFileStreamHelper(path);
if (memory_buffer) {
size_t size = memory_buffer->getBufferSize();
if (size == 0)
continue;
error = AddDirectory(stream, size);
if (error.Fail())
return error;
m_data.AppendData(memory_buffer->getBufferStart(), size);
}
}
return error;
}
Status MinidumpFileBuilder::AddMemoryList() {
Status error;
// We first save the thread stacks to ensure they fit in the first UINT32_MAX
// bytes of the core file. Thread structures in minidump files can only use
// 32 bit memory descriptiors, so we emit them first to ensure the memory is
// in accessible with a 32 bit offset.
std::vector<CoreFileMemoryRange> ranges_32;
std::vector<CoreFileMemoryRange> ranges_64;
CoreFileMemoryRanges all_core_memory_ranges;
error = m_process_sp->CalculateCoreFileSaveRanges(m_save_core_options,
all_core_memory_ranges);
if (error.Fail())
return error;
lldb_private::Progress progress("Saving Minidump File", "",
all_core_memory_ranges.GetSize());
std::vector<CoreFileMemoryRange> all_core_memory_vec;
// Extract all the data into just a vector of data. So we can mutate this in
// place.
for (const auto &core_range : all_core_memory_ranges)
all_core_memory_vec.push_back(core_range.data);
// Start by saving all of the stacks and ensuring they fit under the 32b
// limit.
uint64_t total_size = GetCurrentDataEndOffset();
auto iterator = all_core_memory_vec.begin();
while (iterator != all_core_memory_vec.end()) {
if (m_thread_by_range_end.count(iterator->range.end()) > 0) {
// We don't save stacks twice.
ranges_32.push_back(*iterator);
total_size +=
iterator->range.size() + sizeof(llvm::minidump::MemoryDescriptor);
iterator = all_core_memory_vec.erase(iterator);
} else {
iterator++;
}
}
if (total_size >= UINT32_MAX) {
error = Status::FromErrorStringWithFormat(
"Unable to write minidump. Stack memory "
"exceeds 32b limit. (Num Stacks %zu)",
ranges_32.size());
return error;
}
// After saving the stacks, we start packing as much as we can into 32b.
// We apply a generous padding here so that the Directory, MemoryList and
// Memory64List sections all begin in 32b addressable space.
// Then anything overflow extends into 64b addressable space.
// all_core_memory_vec will either contain all stack regions at this point,
// or be empty if it's a stack only minidump.
if (!all_core_memory_vec.empty())
total_size += 256 + (all_core_memory_vec.size() *
sizeof(llvm::minidump::MemoryDescriptor_64));
for (const auto &core_range : all_core_memory_vec) {
const addr_t range_size = core_range.range.size();
// We don't need to check for stacks here because we already removed them
// from all_core_memory_ranges.
if (total_size + range_size < UINT32_MAX) {
ranges_32.push_back(core_range);
total_size += range_size;
} else {
ranges_64.push_back(core_range);
}
}
error = AddMemoryList_32(ranges_32, progress);
if (error.Fail())
return error;
// Add the remaining memory as a 64b range.
if (!ranges_64.empty()) {
error = AddMemoryList_64(ranges_64, progress);
if (error.Fail())
return error;
}
return FixThreadStacks();
}
Status MinidumpFileBuilder::DumpHeader() const {
// write header
llvm::minidump::Header header;
header.Signature = static_cast<llvm::support::ulittle32_t>(
llvm::minidump::Header::MagicSignature);
header.Version = static_cast<llvm::support::ulittle32_t>(
llvm::minidump::Header::MagicVersion);
header.NumberOfStreams =
static_cast<llvm::support::ulittle32_t>(m_directories.size());
// We write the directories right after the header.
header.StreamDirectoryRVA =
static_cast<llvm::support::ulittle32_t>(HEADER_SIZE);
header.Checksum = static_cast<llvm::support::ulittle32_t>(
0u); // not used in most of the writers
header.TimeDateStamp =
static_cast<llvm::support::ulittle32_t>(std::time(nullptr));
header.Flags =
static_cast<llvm::support::ulittle64_t>(0u); // minidump normal flag
Status error;
size_t bytes_written;
m_core_file->SeekFromStart(0);
bytes_written = HEADER_SIZE;
error = m_core_file->Write(&header, bytes_written);
if (error.Fail() || bytes_written != HEADER_SIZE) {
if (bytes_written != HEADER_SIZE)
error = Status::FromErrorStringWithFormat(
"Unable to write the minidump header (written %zd/%zd)",
bytes_written, HEADER_SIZE);
return error;
}
return error;
}
offset_t MinidumpFileBuilder::GetCurrentDataEndOffset() const {
return m_data.GetByteSize() + m_saved_data_size;
}
Status MinidumpFileBuilder::DumpDirectories() const {
Status error;
size_t bytes_written;
m_core_file->SeekFromStart(HEADER_SIZE);
for (const Directory &dir : m_directories) {
bytes_written = DIRECTORY_SIZE;
error = m_core_file->Write(&dir, bytes_written);
if (error.Fail() || bytes_written != DIRECTORY_SIZE) {
if (bytes_written != DIRECTORY_SIZE)
error = Status::FromErrorStringWithFormat(
"unable to write the directory (written %zd/%zd)", bytes_written,
DIRECTORY_SIZE);
return error;
}
}
return error;
}
Status MinidumpFileBuilder::ReadWriteMemoryInChunks(
lldb_private::DataBufferHeap &data_buffer,
const lldb_private::CoreFileMemoryRange &range, uint64_t &bytes_read) {
const lldb::addr_t addr = range.range.start();
const lldb::addr_t size = range.range.size();
Log *log = GetLog(LLDBLog::Object);
Status addDataError;
Process::ReadMemoryChunkCallback callback =
[&](Status &error, lldb::addr_t current_addr, const void *buf,
uint64_t bytes_read) -> lldb_private::IterationAction {
if (error.Fail() || bytes_read == 0) {
LLDB_LOGF(log,
"Failed to read memory region at: 0x%" PRIx64
". Bytes read: %" PRIx64 ", error: %s",
current_addr, bytes_read, error.AsCString());
// If we failed in a memory read, we would normally want to skip
// this entire region. If we had already written to the minidump
// file, we can't easily rewind that state.
//
// So if we do encounter an error while reading, we return
// immediately, any prior bytes read will still be included but
// any bytes partially read before the error are ignored.
return lldb_private::IterationAction::Stop;
}
// Write to the minidump file with the chunk potentially flushing to
// disk.
// This error will be captured by the outer scope and is considered fatal.
// If we get an error writing to disk we can't easily guarauntee that we
// won't corrupt the minidump.
addDataError = AddData(buf, bytes_read);
if (addDataError.Fail())
return lldb_private::IterationAction::Stop;
// If we have a partial read, report it, but only if the partial read
// didn't finish reading the entire region.
if (bytes_read != data_buffer.GetByteSize() &&
current_addr + bytes_read != size) {
LLDB_LOGF(log,
"Memory region at: %" PRIx64 " partiall read 0x%" PRIx64
" bytes out of %" PRIx64 " bytes.",
current_addr, bytes_read,
data_buffer.GetByteSize() - bytes_read);
// If we've read some bytes, we stop trying to read more and return
// this best effort attempt
return lldb_private::IterationAction::Stop;
}
// No problems, keep going!
return lldb_private::IterationAction::Continue;
};
bytes_read = m_process_sp->ReadMemoryInChunks(
addr, data_buffer.GetBytes(), data_buffer.GetByteSize(), size, callback);
return addDataError;
}
static uint64_t
GetLargestRangeSize(const std::vector<CoreFileMemoryRange> &ranges) {
uint64_t max_size = 0;
for (const auto &core_range : ranges)
max_size = std::max(max_size, core_range.range.size());
return max_size;
}
Status
MinidumpFileBuilder::AddMemoryList_32(std::vector<CoreFileMemoryRange> &ranges,
Progress &progress) {
std::vector<MemoryDescriptor> descriptors;
Status error;
if (ranges.size() == 0)
return error;
Log *log = GetLog(LLDBLog::Object);
size_t region_index = 0;
lldb_private::DataBufferHeap data_buffer(
std::min(GetLargestRangeSize(ranges), MAX_WRITE_CHUNK_SIZE), 0);
for (const auto &core_range : ranges) {
// Take the offset before we write.
const offset_t offset_for_data = GetCurrentDataEndOffset();
const addr_t addr = core_range.range.start();
const addr_t size = core_range.range.size();
const addr_t end = core_range.range.end();
LLDB_LOGF(log,
"AddMemoryList %zu/%zu reading memory for region "
"(%" PRIx64 " bytes) [%" PRIx64 ", %" PRIx64 ")",
region_index, ranges.size(), size, addr, addr + size);
++region_index;
progress.Increment(1, "Adding Memory Range " + core_range.Dump());
uint64_t bytes_read = 0;
error = ReadWriteMemoryInChunks(data_buffer, core_range, bytes_read);
if (error.Fail())
return error;
// If we completely failed to read this range
// we can drop the memory range
if (bytes_read == 0)
continue;
MemoryDescriptor descriptor;
descriptor.StartOfMemoryRange =
static_cast<llvm::support::ulittle64_t>(addr);
descriptor.Memory.DataSize =
static_cast<llvm::support::ulittle32_t>(bytes_read);
descriptor.Memory.RVA =
static_cast<llvm::support::ulittle32_t>(offset_for_data);
descriptors.push_back(descriptor);
if (m_thread_by_range_end.count(end) > 0)
m_thread_by_range_end[end].Stack = descriptor;
}
// Add a directory that references this list
// With a size of the number of ranges as a 32 bit num
// And then the size of all the ranges
error = AddDirectory(StreamType::MemoryList,
sizeof(llvm::minidump::MemoryListHeader) +
descriptors.size() *
sizeof(llvm::minidump::MemoryDescriptor));
if (error.Fail())
return error;
llvm::minidump::MemoryListHeader list_header;
llvm::support::ulittle32_t memory_ranges_num =
static_cast<llvm::support::ulittle32_t>(descriptors.size());
list_header.NumberOfMemoryRanges = memory_ranges_num;
m_data.AppendData(&list_header, sizeof(llvm::minidump::MemoryListHeader));
// For 32b we can get away with writing off the descriptors after the data.
// This means no cleanup loop needed.
m_data.AppendData(descriptors.data(),
descriptors.size() * sizeof(MemoryDescriptor));
return error;
}
Status
MinidumpFileBuilder::AddMemoryList_64(std::vector<CoreFileMemoryRange> &ranges,
Progress &progress) {
Status error;
if (ranges.empty())
return error;
error = AddDirectory(StreamType::Memory64List,
(sizeof(llvm::support::ulittle64_t) * 2) +
ranges.size() *
sizeof(llvm::minidump::MemoryDescriptor_64));
if (error.Fail())
return error;
llvm::minidump::Memory64ListHeader list_header;
llvm::support::ulittle64_t memory_ranges_num =
static_cast<llvm::support::ulittle64_t>(ranges.size());
list_header.NumberOfMemoryRanges = memory_ranges_num;
// Capture the starting offset for all the descriptors so we can clean them up
// if needed.
offset_t starting_offset =
GetCurrentDataEndOffset() + sizeof(llvm::support::ulittle64_t);
// The base_rva needs to start after the directories, which is right after
// this 8 byte variable.
offset_t base_rva =
starting_offset +
(ranges.size() * sizeof(llvm::minidump::MemoryDescriptor_64));
llvm::support::ulittle64_t memory_ranges_base_rva =
static_cast<llvm::support::ulittle64_t>(base_rva);
list_header.BaseRVA = memory_ranges_base_rva;
m_data.AppendData(&list_header, sizeof(llvm::minidump::Memory64ListHeader));
lldb_private::DataBufferHeap data_buffer(
std::min(GetLargestRangeSize(ranges), MAX_WRITE_CHUNK_SIZE), 0);
bool cleanup_required = false;
std::vector<MemoryDescriptor_64> descriptors;
// Enumerate the ranges and create the memory descriptors so we can append
// them first
for (const auto core_range : ranges) {
// Add the space required to store the memory descriptor
MemoryDescriptor_64 memory_desc;
memory_desc.StartOfMemoryRange =
static_cast<llvm::support::ulittle64_t>(core_range.range.start());
memory_desc.DataSize =
static_cast<llvm::support::ulittle64_t>(core_range.range.size());
descriptors.push_back(memory_desc);
// Now write this memory descriptor to the buffer.
m_data.AppendData(&memory_desc, sizeof(MemoryDescriptor_64));
}
Log *log = GetLog(LLDBLog::Object);
size_t region_index = 0;
for (const auto &core_range : ranges) {
const addr_t addr = core_range.range.start();
const addr_t size = core_range.range.size();
LLDB_LOGF(log,
"AddMemoryList_64 %zu/%zu reading memory for region "
"(%" PRIx64 "bytes) "
"[%" PRIx64 ", %" PRIx64 ")",
region_index, ranges.size(), size, addr, addr + size);
++region_index;
progress.Increment(1, "Adding Memory Range " + core_range.Dump());
uint64_t bytes_read = 0;
error = ReadWriteMemoryInChunks(data_buffer, core_range, bytes_read);
if (error.Fail())
return error;
if (bytes_read == 0) {
cleanup_required = true;
descriptors[region_index].DataSize = 0;
}
if (bytes_read != size) {
cleanup_required = true;
descriptors[region_index].DataSize = bytes_read;
}
}
// Early return if there is no cleanup needed.
if (!cleanup_required) {
return error;
} else {
// Flush to disk we can make the fixes in place.
FlushBufferToDisk();
// Fixup the descriptors that were not read correctly.
m_core_file->SeekFromStart(starting_offset);
size_t bytes_written = sizeof(MemoryDescriptor_64) * descriptors.size();
error = m_core_file->Write(descriptors.data(), bytes_written);
if (error.Fail() ||
bytes_written != sizeof(MemoryDescriptor_64) * descriptors.size()) {
error = Status::FromErrorStringWithFormat(
"unable to write the memory descriptors (written %zd/%zd)",
bytes_written, sizeof(MemoryDescriptor_64) * descriptors.size());
}
return error;
}
}
Status MinidumpFileBuilder::AddData(const void *data, uint64_t size) {
// Append the data to the buffer, if the buffer spills over, flush it to disk
m_data.AppendData(data, size);
if (m_data.GetByteSize() > MAX_WRITE_CHUNK_SIZE)
return FlushBufferToDisk();
return Status();
}
Status MinidumpFileBuilder::FlushBufferToDisk() {
Status error;
// Set the stream to it's end.
m_core_file->SeekFromStart(m_saved_data_size);
addr_t starting_size = m_data.GetByteSize();
addr_t remaining_bytes = starting_size;
offset_t offset = 0;
while (remaining_bytes > 0) {
size_t bytes_written = remaining_bytes;
// We don't care how many bytes we wrote unless we got an error
// so just decrement the remaining bytes.
error = m_core_file->Write(m_data.GetBytes() + offset, bytes_written);
if (error.Fail()) {
error = Status::FromErrorStringWithFormat(
"Wrote incorrect number of bytes to minidump file. (written %" PRIx64
"/%" PRIx64 ")",
starting_size - remaining_bytes, starting_size);
return error;
}
offset += bytes_written;
remaining_bytes -= bytes_written;
}
m_saved_data_size += starting_size;
m_data.Clear();
return error;
}
Status MinidumpFileBuilder::DumpFile() {
Status error;
// If anything is left unsaved, dump it.
error = FlushBufferToDisk();
if (error.Fail())
return error;
// Overwrite the header which we filled in earlier.
error = DumpHeader();
if (error.Fail())
return error;
// Overwrite the space saved for directories
error = DumpDirectories();
if (error.Fail())
return error;
return error;
}
void MinidumpFileBuilder::DeleteFile() noexcept {
Log *log = GetLog(LLDBLog::Object);
if (m_core_file) {
Status error = m_core_file->Close();
if (error.Fail())
LLDB_LOGF(log, "Failed to close minidump file: %s", error.AsCString());
m_core_file.reset();
}
}