775 lines
30 KiB
C++
775 lines
30 KiB
C++
//===-- 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_x86_64.h"
|
|
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/ModuleList.h"
|
|
#include "lldb/Core/Section.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/DataExtractor.h"
|
|
#include "lldb/Utility/RegisterValue.h"
|
|
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/BinaryFormat/Minidump.h"
|
|
#include "llvm/Support/ConvertUTF.h"
|
|
#include "llvm/Support/Error.h"
|
|
|
|
#include "Plugins/Process/minidump/MinidumpTypes.h"
|
|
|
|
#include <cinttypes>
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
using namespace llvm::minidump;
|
|
|
|
void MinidumpFileBuilder::AddDirectory(StreamType type, size_t stream_size) {
|
|
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);
|
|
}
|
|
|
|
Status MinidumpFileBuilder::AddSystemInfo(const llvm::Triple &target_triple) {
|
|
Status error;
|
|
AddDirectory(StreamType::SystemInfo, sizeof(llvm::minidump::SystemInfo));
|
|
|
|
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.SetErrorStringWithFormat("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.SetErrorStringWithFormat("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.SetErrorString("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.SetErrorStringWithFormat(
|
|
"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) {
|
|
SectionSP sect_sp = mod->GetObjectFile()->GetBaseAddress().GetSection();
|
|
uint64_t SizeOfImage = 0;
|
|
|
|
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(Target &target) {
|
|
constexpr size_t minidump_module_size = sizeof(llvm::minidump::Module);
|
|
Status error;
|
|
|
|
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.
|
|
AddDirectory(StreamType::ModuleList, module_stream_size);
|
|
|
|
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) {
|
|
error.SetErrorStringWithFormat("Unable to get the size of module %s.",
|
|
module_name.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,
|
|
const std::string ®_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,
|
|
const std::string ®_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,
|
|
const std::string ®_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,
|
|
const std::string ®_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,
|
|
const std::string ®_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,
|
|
const std::string ®_name) {
|
|
return static_cast<llvm::support::ulittle64_t>(
|
|
read_register_u64_raw(reg_ctx, reg_name));
|
|
}
|
|
|
|
lldb_private::minidump::MinidumpContext_x86_64
|
|
GetThreadContext_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);
|
|
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");
|
|
thread_context.eflags = read_register_u32(reg_ctx, "rflags");
|
|
thread_context.cs = read_register_u16(reg_ctx, "cs");
|
|
thread_context.fs = read_register_u16(reg_ctx, "fs");
|
|
thread_context.gs = read_register_u16(reg_ctx, "gs");
|
|
thread_context.ss = read_register_u16(reg_ctx, "ss");
|
|
thread_context.ds = read_register_u16(reg_ctx, "ds");
|
|
return thread_context;
|
|
}
|
|
|
|
// Function returns start and size of the memory region that contains
|
|
// memory location pointed to by the current stack pointer.
|
|
llvm::Expected<std::pair<addr_t, addr_t>>
|
|
findStackHelper(const lldb::ProcessSP &process_sp, uint64_t rsp) {
|
|
MemoryRegionInfo range_info;
|
|
Status error = process_sp->GetMemoryRegionInfo(rsp, range_info);
|
|
// Skip failed memory region requests or any regions with no permissions.
|
|
if (error.Fail() || range_info.GetLLDBPermissions() == 0)
|
|
return llvm::createStringError(
|
|
std::errc::not_supported,
|
|
"unable to load stack segment of the process");
|
|
|
|
const addr_t addr = range_info.GetRange().GetRangeBase();
|
|
const addr_t size = range_info.GetRange().GetByteSize();
|
|
|
|
if (size == 0)
|
|
return llvm::createStringError(std::errc::not_supported,
|
|
"stack segment of the process is empty");
|
|
|
|
return std::make_pair(addr, size);
|
|
}
|
|
|
|
Status MinidumpFileBuilder::AddThreadList(const lldb::ProcessSP &process_sp) {
|
|
constexpr size_t minidump_thread_size = sizeof(llvm::minidump::Thread);
|
|
lldb_private::ThreadList thread_list = process_sp->GetThreadList();
|
|
|
|
// 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.GetSize() * minidump_thread_size;
|
|
// save for the ability to set up RVA
|
|
size_t size_before = GetCurrentDataEndOffset();
|
|
|
|
AddDirectory(StreamType::ThreadList, thread_stream_size);
|
|
|
|
llvm::support::ulittle32_t thread_count =
|
|
static_cast<llvm::support::ulittle32_t>(thread_list.GetSize());
|
|
m_data.AppendData(&thread_count, sizeof(llvm::support::ulittle32_t));
|
|
|
|
DataBufferHeap helper_data;
|
|
|
|
const uint32_t num_threads = thread_list.GetSize();
|
|
|
|
for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) {
|
|
ThreadSP thread_sp(thread_list.GetThreadAtIndex(thread_idx));
|
|
RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
|
|
Status error;
|
|
|
|
if (!reg_ctx_sp) {
|
|
error.SetErrorString("Unable to get the register context.");
|
|
return error;
|
|
}
|
|
RegisterContext *reg_ctx = reg_ctx_sp.get();
|
|
auto thread_context = GetThreadContext_64(reg_ctx);
|
|
uint64_t rsp = read_register_u64_raw(reg_ctx, "rsp");
|
|
auto expected_address_range = findStackHelper(process_sp, rsp);
|
|
|
|
if (!expected_address_range) {
|
|
error.SetErrorString("Unable to get the stack address.");
|
|
return error;
|
|
}
|
|
|
|
std::pair<uint64_t, uint64_t> range = std::move(*expected_address_range);
|
|
uint64_t addr = range.first;
|
|
uint64_t size = range.second;
|
|
|
|
auto data_up = std::make_unique<DataBufferHeap>(size, 0);
|
|
const size_t stack_bytes_read =
|
|
process_sp->ReadMemory(addr, data_up->GetBytes(), size, error);
|
|
|
|
if (error.Fail())
|
|
return error;
|
|
|
|
LocationDescriptor stack_memory;
|
|
stack_memory.DataSize =
|
|
static_cast<llvm::support::ulittle32_t>(stack_bytes_read);
|
|
stack_memory.RVA = static_cast<llvm::support::ulittle32_t>(
|
|
size_before + thread_stream_size + helper_data.GetByteSize());
|
|
|
|
MemoryDescriptor stack;
|
|
stack.StartOfMemoryRange = static_cast<llvm::support::ulittle64_t>(addr);
|
|
stack.Memory = stack_memory;
|
|
|
|
helper_data.AppendData(data_up->GetBytes(), stack_bytes_read);
|
|
|
|
LocationDescriptor thread_context_memory_locator;
|
|
thread_context_memory_locator.DataSize =
|
|
static_cast<llvm::support::ulittle32_t>(sizeof(thread_context));
|
|
thread_context_memory_locator.RVA = static_cast<llvm::support::ulittle32_t>(
|
|
size_before + thread_stream_size + helper_data.GetByteSize());
|
|
|
|
helper_data.AppendData(
|
|
&thread_context,
|
|
sizeof(lldb_private::minidump::MinidumpContext_x86_64));
|
|
|
|
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;
|
|
|
|
m_data.AppendData(&t, sizeof(llvm::minidump::Thread));
|
|
}
|
|
|
|
m_data.AppendData(helper_data.GetBytes(), helper_data.GetByteSize());
|
|
return Status();
|
|
}
|
|
|
|
Status MinidumpFileBuilder::AddException(const lldb::ProcessSP &process_sp) {
|
|
Status error;
|
|
lldb_private::ThreadList thread_list = process_sp->GetThreadList();
|
|
|
|
const uint32_t num_threads = thread_list.GetSize();
|
|
uint32_t stop_reason_thread_idx = 0;
|
|
for (stop_reason_thread_idx = 0; stop_reason_thread_idx < num_threads;
|
|
++stop_reason_thread_idx) {
|
|
ThreadSP thread_sp(thread_list.GetThreadAtIndex(stop_reason_thread_idx));
|
|
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
|
|
|
|
if (stop_info_sp && stop_info_sp->IsValid())
|
|
break;
|
|
}
|
|
|
|
if (stop_reason_thread_idx == num_threads) {
|
|
error.SetErrorString("No stop reason thread found.");
|
|
return error;
|
|
}
|
|
|
|
constexpr size_t minidump_exception_size =
|
|
sizeof(llvm::minidump::ExceptionStream);
|
|
AddDirectory(StreamType::Exception, minidump_exception_size);
|
|
size_t size_before = GetCurrentDataEndOffset();
|
|
|
|
ThreadSP thread_sp(thread_list.GetThreadAtIndex(stop_reason_thread_idx));
|
|
RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
|
|
RegisterContext *reg_ctx = reg_ctx_sp.get();
|
|
auto thread_context = GetThreadContext_64(reg_ctx);
|
|
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
|
|
|
|
DataBufferHeap helper_data;
|
|
|
|
LocationDescriptor thread_context_memory_locator;
|
|
thread_context_memory_locator.DataSize =
|
|
static_cast<llvm::support::ulittle32_t>(sizeof(thread_context));
|
|
thread_context_memory_locator.RVA = static_cast<llvm::support::ulittle32_t>(
|
|
size_before + minidump_exception_size + helper_data.GetByteSize());
|
|
|
|
helper_data.AppendData(
|
|
&thread_context, sizeof(lldb_private::minidump::MinidumpContext_x86_64));
|
|
|
|
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>(0);
|
|
exp_record.ExceptionRecord = static_cast<llvm::support::ulittle64_t>(0);
|
|
exp_record.ExceptionAddress = read_register_u64(reg_ctx, "rip");
|
|
exp_record.NumberParameters = static_cast<llvm::support::ulittle32_t>(0);
|
|
exp_record.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
|
|
// exp_record.ExceptionInformation;
|
|
|
|
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;
|
|
exp_stream.ThreadContext = thread_context_memory_locator;
|
|
|
|
m_data.AppendData(&exp_stream, minidump_exception_size);
|
|
m_data.AppendData(helper_data.GetBytes(), helper_data.GetByteSize());
|
|
return error;
|
|
}
|
|
|
|
lldb_private::Status
|
|
MinidumpFileBuilder::AddMemoryList(const lldb::ProcessSP &process_sp) {
|
|
Status error;
|
|
|
|
if (error.Fail()) {
|
|
error.SetErrorString("Process doesn't support getting memory region info.");
|
|
return error;
|
|
}
|
|
|
|
// Get interesting addresses
|
|
std::vector<size_t> interesting_addresses;
|
|
auto thread_list = process_sp->GetThreadList();
|
|
for (size_t i = 0; i < thread_list.GetSize(); ++i) {
|
|
ThreadSP thread_sp(thread_list.GetThreadAtIndex(i));
|
|
RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
|
|
RegisterContext *reg_ctx = reg_ctx_sp.get();
|
|
|
|
interesting_addresses.push_back(read_register_u64(reg_ctx, "rsp"));
|
|
interesting_addresses.push_back(read_register_u64(reg_ctx, "rip"));
|
|
}
|
|
|
|
DataBufferHeap helper_data;
|
|
std::vector<MemoryDescriptor> mem_descriptors;
|
|
|
|
std::set<addr_t> visited_region_base_addresses;
|
|
for (size_t interesting_address : interesting_addresses) {
|
|
MemoryRegionInfo range_info;
|
|
error = process_sp->GetMemoryRegionInfo(interesting_address, range_info);
|
|
// Skip failed memory region requests or any regions with no permissions.
|
|
if (error.Fail() || range_info.GetLLDBPermissions() == 0)
|
|
continue;
|
|
const addr_t addr = range_info.GetRange().GetRangeBase();
|
|
// Skip any regions we have already saved out.
|
|
if (visited_region_base_addresses.insert(addr).second == false)
|
|
continue;
|
|
const addr_t size = range_info.GetRange().GetByteSize();
|
|
if (size == 0)
|
|
continue;
|
|
auto data_up = std::make_unique<DataBufferHeap>(size, 0);
|
|
const size_t bytes_read =
|
|
process_sp->ReadMemory(addr, data_up->GetBytes(), size, error);
|
|
if (bytes_read == 0)
|
|
continue;
|
|
// We have a good memory region with valid bytes to store.
|
|
LocationDescriptor memory_dump;
|
|
memory_dump.DataSize = static_cast<llvm::support::ulittle32_t>(bytes_read);
|
|
memory_dump.RVA =
|
|
static_cast<llvm::support::ulittle32_t>(GetCurrentDataEndOffset());
|
|
MemoryDescriptor memory_desc;
|
|
memory_desc.StartOfMemoryRange =
|
|
static_cast<llvm::support::ulittle64_t>(addr);
|
|
memory_desc.Memory = memory_dump;
|
|
mem_descriptors.push_back(memory_desc);
|
|
m_data.AppendData(data_up->GetBytes(), bytes_read);
|
|
}
|
|
|
|
AddDirectory(StreamType::MemoryList,
|
|
sizeof(llvm::support::ulittle32_t) +
|
|
mem_descriptors.size() *
|
|
sizeof(llvm::minidump::MemoryDescriptor));
|
|
llvm::support::ulittle32_t memory_ranges_num(mem_descriptors.size());
|
|
|
|
m_data.AppendData(&memory_ranges_num, sizeof(llvm::support::ulittle32_t));
|
|
for (auto memory_descriptor : mem_descriptors) {
|
|
m_data.AppendData(&memory_descriptor,
|
|
sizeof(llvm::minidump::MemoryDescriptor));
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void MinidumpFileBuilder::AddMiscInfo(const lldb::ProcessSP &process_sp) {
|
|
AddDirectory(StreamType::MiscInfo,
|
|
sizeof(lldb_private::minidump::MinidumpMiscInfo));
|
|
|
|
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;
|
|
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));
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
void MinidumpFileBuilder::AddLinuxFileStreams(
|
|
const lldb::ProcessSP &process_sp) {
|
|
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;
|
|
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;
|
|
AddDirectory(stream, size);
|
|
m_data.AppendData(memory_buffer->getBufferStart(), size);
|
|
}
|
|
}
|
|
}
|
|
|
|
Status MinidumpFileBuilder::Dump(lldb::FileUP &core_file) const {
|
|
constexpr size_t header_size = sizeof(llvm::minidump::Header);
|
|
constexpr size_t directory_size = sizeof(llvm::minidump::Directory);
|
|
|
|
// 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>(GetDirectoriesNum());
|
|
header.StreamDirectoryRVA =
|
|
static_cast<llvm::support::ulittle32_t>(GetCurrentDataEndOffset());
|
|
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;
|
|
|
|
bytes_written = header_size;
|
|
error = core_file->Write(&header, bytes_written);
|
|
if (error.Fail() || bytes_written != header_size) {
|
|
if (bytes_written != header_size)
|
|
error.SetErrorStringWithFormat(
|
|
"unable to write the header (written %zd/%zd)", bytes_written,
|
|
header_size);
|
|
return error;
|
|
}
|
|
|
|
// write data
|
|
bytes_written = m_data.GetByteSize();
|
|
error = core_file->Write(m_data.GetBytes(), bytes_written);
|
|
if (error.Fail() || bytes_written != m_data.GetByteSize()) {
|
|
if (bytes_written != m_data.GetByteSize())
|
|
error.SetErrorStringWithFormat(
|
|
"unable to write the data (written %zd/%" PRIu64 ")", bytes_written,
|
|
m_data.GetByteSize());
|
|
return error;
|
|
}
|
|
|
|
// write directories
|
|
for (const Directory &dir : m_directories) {
|
|
bytes_written = directory_size;
|
|
error = core_file->Write(&dir, bytes_written);
|
|
if (error.Fail() || bytes_written != directory_size) {
|
|
if (bytes_written != directory_size)
|
|
error.SetErrorStringWithFormat(
|
|
"unable to write the directory (written %zd/%zd)", bytes_written,
|
|
directory_size);
|
|
return error;
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
size_t MinidumpFileBuilder::GetDirectoriesNum() const {
|
|
return m_directories.size();
|
|
}
|
|
|
|
size_t MinidumpFileBuilder::GetCurrentDataEndOffset() const {
|
|
return sizeof(llvm::minidump::Header) + m_data.GetByteSize();
|
|
}
|