blob: 37ee5466c5b98ec3ab18ce8db7f0cd27bf28a72d [file] [log] [blame]
//===-- ProcessMinidump.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 "ProcessMinidump.h"
#include "ThreadMinidump.h"
#include "lldb/Core/DumpDataExtractor.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Core/Section.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandObject.h"
#include "lldb/Interpreter/CommandObjectMultiword.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/OptionArgParser.h"
#include "lldb/Interpreter/OptionGroupBoolean.h"
#include "lldb/Target/JITLoaderList.h"
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Target/SectionLoadList.h"
#include "lldb/Target/Target.h"
#include "lldb/Target/UnixSignals.h"
#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/State.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Threading.h"
#include "Plugins/Process/Utility/StopInfoMachException.h"
#include <memory>
using namespace lldb;
using namespace lldb_private;
using namespace minidump;
LLDB_PLUGIN_DEFINE(ProcessMinidump)
namespace {
/// A minimal ObjectFile implementation providing a dummy object file for the
/// cases when the real module binary is not available. This allows the module
/// to show up in "image list" and symbols to be added to it.
class PlaceholderObjectFile : public ObjectFile {
public:
PlaceholderObjectFile(const lldb::ModuleSP &module_sp,
const ModuleSpec &module_spec, lldb::addr_t base,
lldb::addr_t size)
: ObjectFile(module_sp, &module_spec.GetFileSpec(), /*file_offset*/ 0,
/*length*/ 0, /*data_sp*/ nullptr, /*data_offset*/ 0),
m_arch(module_spec.GetArchitecture()), m_uuid(module_spec.GetUUID()),
m_base(base), m_size(size) {
m_symtab_up = std::make_unique<Symtab>(this);
}
static ConstString GetStaticPluginName() {
return ConstString("placeholder");
}
llvm::StringRef GetPluginName() override {
return GetStaticPluginName().GetStringRef();
}
bool ParseHeader() override { return true; }
Type CalculateType() override { return eTypeUnknown; }
Strata CalculateStrata() override { return eStrataUnknown; }
uint32_t GetDependentModules(FileSpecList &file_list) override { return 0; }
bool IsExecutable() const override { return false; }
ArchSpec GetArchitecture() override { return m_arch; }
UUID GetUUID() override { return m_uuid; }
void ParseSymtab(lldb_private::Symtab &symtab) override {}
bool IsStripped() override { return true; }
ByteOrder GetByteOrder() const override { return m_arch.GetByteOrder(); }
uint32_t GetAddressByteSize() const override {
return m_arch.GetAddressByteSize();
}
Address GetBaseAddress() override {
return Address(m_sections_up->GetSectionAtIndex(0), 0);
}
void CreateSections(SectionList &unified_section_list) override {
m_sections_up = std::make_unique<SectionList>();
auto section_sp = std::make_shared<Section>(
GetModule(), this, /*sect_id*/ 0, ConstString(".module_image"),
eSectionTypeOther, m_base, m_size, /*file_offset*/ 0, /*file_size*/ 0,
/*log2align*/ 0, /*flags*/ 0);
section_sp->SetPermissions(ePermissionsReadable | ePermissionsExecutable);
m_sections_up->AddSection(section_sp);
unified_section_list.AddSection(std::move(section_sp));
}
bool SetLoadAddress(Target &target, addr_t value,
bool value_is_offset) override {
assert(!value_is_offset);
assert(value == m_base);
// Create sections if they haven't been created already.
GetModule()->GetSectionList();
assert(m_sections_up->GetNumSections(0) == 1);
target.GetSectionLoadList().SetSectionLoadAddress(
m_sections_up->GetSectionAtIndex(0), m_base);
return true;
}
void Dump(Stream *s) override {
s->Format("Placeholder object file for {0} loaded at [{1:x}-{2:x})\n",
GetFileSpec(), m_base, m_base + m_size);
}
lldb::addr_t GetBaseImageAddress() const { return m_base; }
private:
ArchSpec m_arch;
UUID m_uuid;
lldb::addr_t m_base;
lldb::addr_t m_size;
};
/// Duplicate the HashElfTextSection() from the breakpad sources.
///
/// Breakpad, a Google crash log reporting tool suite, creates minidump files
/// for many different architectures. When using Breakpad to create ELF
/// minidumps, it will check for a GNU build ID when creating a minidump file
/// and if one doesn't exist in the file, it will say the UUID of the file is a
/// checksum of up to the first 4096 bytes of the .text section. Facebook also
/// uses breakpad and modified this hash to avoid collisions so we can
/// calculate and check for this as well.
///
/// The breakpad code might end up hashing up to 15 bytes that immediately
/// follow the .text section in the file, so this code must do exactly what it
/// does so we can get an exact match for the UUID.
///
/// \param[in] module_sp The module to grab the .text section from.
///
/// \param[in,out] breakpad_uuid A vector that will receive the calculated
/// breakpad .text hash.
///
/// \param[in,out] facebook_uuid A vector that will receive the calculated
/// facebook .text hash.
///
void HashElfTextSection(ModuleSP module_sp, std::vector<uint8_t> &breakpad_uuid,
std::vector<uint8_t> &facebook_uuid) {
SectionList *sect_list = module_sp->GetSectionList();
if (sect_list == nullptr)
return;
SectionSP sect_sp = sect_list->FindSectionByName(ConstString(".text"));
if (!sect_sp)
return;
constexpr size_t kMDGUIDSize = 16;
constexpr size_t kBreakpadPageSize = 4096;
// The breakpad code has a bug where it might access beyond the end of a
// .text section by up to 15 bytes, so we must ensure we round up to the
// next kMDGUIDSize byte boundary.
DataExtractor data;
const size_t text_size = sect_sp->GetFileSize();
const size_t read_size = std::min<size_t>(
llvm::alignTo(text_size, kMDGUIDSize), kBreakpadPageSize);
sect_sp->GetObjectFile()->GetData(sect_sp->GetFileOffset(), read_size, data);
breakpad_uuid.assign(kMDGUIDSize, 0);
facebook_uuid.assign(kMDGUIDSize, 0);
// The only difference between the breakpad hash and the facebook hash is the
// hashing of the text section size into the hash prior to hashing the .text
// contents.
for (size_t i = 0; i < kMDGUIDSize; i++)
facebook_uuid[i] ^= text_size % 255;
// This code carefully duplicates how the hash was created in Breakpad
// sources, including the error where it might has an extra 15 bytes past the
// end of the .text section if the .text section is less than a page size in
// length.
const uint8_t *ptr = data.GetDataStart();
const uint8_t *ptr_end = data.GetDataEnd();
while (ptr < ptr_end) {
for (unsigned i = 0; i < kMDGUIDSize; i++) {
breakpad_uuid[i] ^= ptr[i];
facebook_uuid[i] ^= ptr[i];
}
ptr += kMDGUIDSize;
}
}
} // namespace
llvm::StringRef ProcessMinidump::GetPluginDescriptionStatic() {
return "Minidump plug-in.";
}
lldb::ProcessSP ProcessMinidump::CreateInstance(lldb::TargetSP target_sp,
lldb::ListenerSP listener_sp,
const FileSpec *crash_file,
bool can_connect) {
if (!crash_file || can_connect)
return nullptr;
lldb::ProcessSP process_sp;
// Read enough data for the Minidump header
constexpr size_t header_size = sizeof(Header);
auto DataPtr = FileSystem::Instance().CreateDataBuffer(crash_file->GetPath(),
header_size, 0);
if (!DataPtr)
return nullptr;
lldbassert(DataPtr->GetByteSize() == header_size);
if (identify_magic(toStringRef(DataPtr->GetData())) != llvm::file_magic::minidump)
return nullptr;
auto AllData =
FileSystem::Instance().CreateDataBuffer(crash_file->GetPath(), -1, 0);
if (!AllData)
return nullptr;
return std::make_shared<ProcessMinidump>(target_sp, listener_sp, *crash_file,
std::move(AllData));
}
bool ProcessMinidump::CanDebug(lldb::TargetSP target_sp,
bool plugin_specified_by_name) {
return true;
}
ProcessMinidump::ProcessMinidump(lldb::TargetSP target_sp,
lldb::ListenerSP listener_sp,
const FileSpec &core_file,
DataBufferSP core_data)
: PostMortemProcess(target_sp, listener_sp), m_core_file(core_file),
m_core_data(std::move(core_data)), m_is_wow64(false) {}
ProcessMinidump::~ProcessMinidump() {
Clear();
// We need to call finalize on the process before destroying ourselves to
// make sure all of the broadcaster cleanup goes as planned. If we destruct
// this class, then Process::~Process() might have problems trying to fully
// destroy the broadcaster.
Finalize();
}
void ProcessMinidump::Initialize() {
static llvm::once_flag g_once_flag;
llvm::call_once(g_once_flag, []() {
PluginManager::RegisterPlugin(GetPluginNameStatic(),
GetPluginDescriptionStatic(),
ProcessMinidump::CreateInstance);
});
}
void ProcessMinidump::Terminate() {
PluginManager::UnregisterPlugin(ProcessMinidump::CreateInstance);
}
Status ProcessMinidump::DoLoadCore() {
auto expected_parser = MinidumpParser::Create(m_core_data);
if (!expected_parser)
return Status(expected_parser.takeError());
m_minidump_parser = std::move(*expected_parser);
Status error;
// Do we support the minidump's architecture?
ArchSpec arch = GetArchitecture();
switch (arch.GetMachine()) {
case llvm::Triple::x86:
case llvm::Triple::x86_64:
case llvm::Triple::arm:
case llvm::Triple::aarch64:
// Any supported architectures must be listed here and also supported in
// ThreadMinidump::CreateRegisterContextForFrame().
break;
default:
error.SetErrorStringWithFormat("unsupported minidump architecture: %s",
arch.GetArchitectureName());
return error;
}
GetTarget().SetArchitecture(arch, true /*set_platform*/);
m_thread_list = m_minidump_parser->GetThreads();
m_active_exception = m_minidump_parser->GetExceptionStream();
SetUnixSignals(UnixSignals::Create(GetArchitecture()));
ReadModuleList();
llvm::Optional<lldb::pid_t> pid = m_minidump_parser->GetPid();
if (!pid) {
GetTarget().GetDebugger().GetAsyncErrorStream()->PutCString(
"Unable to retrieve process ID from minidump file, setting process ID "
"to 1.\n");
pid = 1;
}
SetID(pid.getValue());
return error;
}
Status ProcessMinidump::DoDestroy() { return Status(); }
void ProcessMinidump::RefreshStateAfterStop() {
if (!m_active_exception)
return;
constexpr uint32_t BreakpadDumpRequested = 0xFFFFFFFF;
if (m_active_exception->ExceptionRecord.ExceptionCode ==
BreakpadDumpRequested) {
// This "ExceptionCode" value is a sentinel that is sometimes used
// when generating a dump for a process that hasn't crashed.
// TODO: The definition and use of this "dump requested" constant
// in Breakpad are actually Linux-specific, and for similar use
// cases on Mac/Windows it defines different constants, referring
// to them as "simulated" exceptions; consider moving this check
// down to the OS-specific paths and checking each OS for its own
// constant.
return;
}
lldb::StopInfoSP stop_info;
lldb::ThreadSP stop_thread;
Process::m_thread_list.SetSelectedThreadByID(m_active_exception->ThreadId);
stop_thread = Process::m_thread_list.GetSelectedThread();
ArchSpec arch = GetArchitecture();
if (arch.GetTriple().getOS() == llvm::Triple::Linux) {
uint32_t signo = m_active_exception->ExceptionRecord.ExceptionCode;
if (signo == 0) {
// No stop.
return;
}
stop_info = StopInfo::CreateStopReasonWithSignal(
*stop_thread, signo);
} else if (arch.GetTriple().getVendor() == llvm::Triple::Apple) {
stop_info = StopInfoMachException::CreateStopReasonWithMachException(
*stop_thread, m_active_exception->ExceptionRecord.ExceptionCode, 2,
m_active_exception->ExceptionRecord.ExceptionFlags,
m_active_exception->ExceptionRecord.ExceptionAddress, 0);
} else {
std::string desc;
llvm::raw_string_ostream desc_stream(desc);
desc_stream << "Exception "
<< llvm::format_hex(
m_active_exception->ExceptionRecord.ExceptionCode, 8)
<< " encountered at address "
<< llvm::format_hex(
m_active_exception->ExceptionRecord.ExceptionAddress, 8);
stop_info = StopInfo::CreateStopReasonWithException(
*stop_thread, desc_stream.str().c_str());
}
stop_thread->SetStopInfo(stop_info);
}
bool ProcessMinidump::IsAlive() { return true; }
bool ProcessMinidump::WarnBeforeDetach() const { return false; }
size_t ProcessMinidump::ReadMemory(lldb::addr_t addr, void *buf, size_t size,
Status &error) {
// Don't allow the caching that lldb_private::Process::ReadMemory does since
// we have it all cached in our dump file anyway.
return DoReadMemory(addr, buf, size, error);
}
size_t ProcessMinidump::DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
Status &error) {
llvm::ArrayRef<uint8_t> mem = m_minidump_parser->GetMemory(addr, size);
if (mem.empty()) {
error.SetErrorString("could not parse memory info");
return 0;
}
std::memcpy(buf, mem.data(), mem.size());
return mem.size();
}
ArchSpec ProcessMinidump::GetArchitecture() {
if (!m_is_wow64) {
return m_minidump_parser->GetArchitecture();
}
llvm::Triple triple;
triple.setVendor(llvm::Triple::VendorType::UnknownVendor);
triple.setArch(llvm::Triple::ArchType::x86);
triple.setOS(llvm::Triple::OSType::Win32);
return ArchSpec(triple);
}
void ProcessMinidump::BuildMemoryRegions() {
if (m_memory_regions)
return;
m_memory_regions.emplace();
bool is_complete;
std::tie(*m_memory_regions, is_complete) =
m_minidump_parser->BuildMemoryRegions();
if (is_complete)
return;
MemoryRegionInfos to_add;
ModuleList &modules = GetTarget().GetImages();
SectionLoadList &load_list = GetTarget().GetSectionLoadList();
modules.ForEach([&](const ModuleSP &module_sp) {
SectionList *sections = module_sp->GetSectionList();
for (size_t i = 0; i < sections->GetSize(); ++i) {
SectionSP section_sp = sections->GetSectionAtIndex(i);
addr_t load_addr = load_list.GetSectionLoadAddress(section_sp);
if (load_addr == LLDB_INVALID_ADDRESS)
continue;
MemoryRegionInfo::RangeType section_range(load_addr,
section_sp->GetByteSize());
MemoryRegionInfo region =
MinidumpParser::GetMemoryRegionInfo(*m_memory_regions, load_addr);
if (region.GetMapped() != MemoryRegionInfo::eYes &&
region.GetRange().GetRangeBase() <= section_range.GetRangeBase() &&
section_range.GetRangeEnd() <= region.GetRange().GetRangeEnd()) {
to_add.emplace_back();
to_add.back().GetRange() = section_range;
to_add.back().SetLLDBPermissions(section_sp->GetPermissions());
to_add.back().SetMapped(MemoryRegionInfo::eYes);
to_add.back().SetName(module_sp->GetFileSpec().GetPath().c_str());
}
}
return true;
});
m_memory_regions->insert(m_memory_regions->end(), to_add.begin(),
to_add.end());
llvm::sort(*m_memory_regions);
}
Status ProcessMinidump::GetMemoryRegionInfo(lldb::addr_t load_addr,
MemoryRegionInfo &region) {
BuildMemoryRegions();
region = MinidumpParser::GetMemoryRegionInfo(*m_memory_regions, load_addr);
return Status();
}
Status ProcessMinidump::GetMemoryRegions(MemoryRegionInfos &region_list) {
BuildMemoryRegions();
region_list = *m_memory_regions;
return Status();
}
void ProcessMinidump::Clear() { Process::m_thread_list.Clear(); }
bool ProcessMinidump::DoUpdateThreadList(ThreadList &old_thread_list,
ThreadList &new_thread_list) {
for (const minidump::Thread &thread : m_thread_list) {
LocationDescriptor context_location = thread.Context;
// If the minidump contains an exception context, use it
if (m_active_exception != nullptr &&
m_active_exception->ThreadId == thread.ThreadId) {
context_location = m_active_exception->ThreadContext;
}
llvm::ArrayRef<uint8_t> context;
if (!m_is_wow64)
context = m_minidump_parser->GetThreadContext(context_location);
else
context = m_minidump_parser->GetThreadContextWow64(thread);
lldb::ThreadSP thread_sp(new ThreadMinidump(*this, thread, context));
new_thread_list.AddThread(thread_sp);
}
return new_thread_list.GetSize(false) > 0;
}
ModuleSP ProcessMinidump::GetOrCreateModule(UUID minidump_uuid,
llvm::StringRef name,
ModuleSpec module_spec) {
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER));
Status error;
ModuleSP module_sp =
GetTarget().GetOrCreateModule(module_spec, true /* notify */, &error);
if (!module_sp)
return module_sp;
// We consider the module to be a match if the minidump UUID is a
// prefix of the actual UUID, or if either of the UUIDs are empty.
const auto dmp_bytes = minidump_uuid.GetBytes();
const auto mod_bytes = module_sp->GetUUID().GetBytes();
const bool match = dmp_bytes.empty() || mod_bytes.empty() ||
mod_bytes.take_front(dmp_bytes.size()) == dmp_bytes;
if (match) {
LLDB_LOG(log, "Partial uuid match for {0}.", name);
return module_sp;
}
// Breakpad generates minindump files, and if there is no GNU build
// ID in the binary, it will calculate a UUID by hashing first 4096
// bytes of the .text section and using that as the UUID for a module
// in the minidump. Facebook uses a modified breakpad client that
// uses a slightly modified this hash to avoid collisions. Check for
// UUIDs from the minindump that match these cases and accept the
// module we find if they do match.
std::vector<uint8_t> breakpad_uuid;
std::vector<uint8_t> facebook_uuid;
HashElfTextSection(module_sp, breakpad_uuid, facebook_uuid);
if (dmp_bytes == llvm::ArrayRef<uint8_t>(breakpad_uuid)) {
LLDB_LOG(log, "Breakpad .text hash match for {0}.", name);
return module_sp;
}
if (dmp_bytes == llvm::ArrayRef<uint8_t>(facebook_uuid)) {
LLDB_LOG(log, "Facebook .text hash match for {0}.", name);
return module_sp;
}
// The UUID wasn't a partial match and didn't match the .text hash
// so remove the module from the target, we will need to create a
// placeholder object file.
GetTarget().GetImages().Remove(module_sp);
module_sp.reset();
return module_sp;
}
void ProcessMinidump::ReadModuleList() {
std::vector<const minidump::Module *> filtered_modules =
m_minidump_parser->GetFilteredModuleList();
Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_DYNAMIC_LOADER));
for (auto module : filtered_modules) {
std::string name = cantFail(m_minidump_parser->GetMinidumpFile().getString(
module->ModuleNameRVA));
const uint64_t load_addr = module->BaseOfImage;
const uint64_t load_size = module->SizeOfImage;
LLDB_LOG(log, "found module: name: {0} {1:x10}-{2:x10} size: {3}", name,
load_addr, load_addr + load_size, load_size);
// check if the process is wow64 - a 32 bit windows process running on a
// 64 bit windows
if (llvm::StringRef(name).endswith_insensitive("wow64.dll")) {
m_is_wow64 = true;
}
const auto uuid = m_minidump_parser->GetModuleUUID(module);
auto file_spec = FileSpec(name, GetArchitecture().GetTriple());
ModuleSpec module_spec(file_spec, uuid);
module_spec.GetArchitecture() = GetArchitecture();
Status error;
// Try and find a module with a full UUID that matches. This function will
// add the module to the target if it finds one.
lldb::ModuleSP module_sp = GetTarget().GetOrCreateModule(module_spec,
true /* notify */, &error);
if (module_sp) {
LLDB_LOG(log, "Full uuid match for {0}.", name);
} else {
// We couldn't find a module with an exactly-matching UUID. Sometimes
// a minidump UUID is only a partial match or is a hash. So try again
// without specifying the UUID, then again without specifying the
// directory if that fails. This will allow us to find modules with
// partial matches or hash UUIDs in user-provided sysroots or search
// directories (target.exec-search-paths).
ModuleSpec partial_module_spec = module_spec;
partial_module_spec.GetUUID().Clear();
module_sp = GetOrCreateModule(uuid, name, partial_module_spec);
if (!module_sp) {
partial_module_spec.GetFileSpec().GetDirectory().Clear();
module_sp = GetOrCreateModule(uuid, name, partial_module_spec);
}
}
if (module_sp) {
// Watch out for place holder modules that have different paths, but the
// same UUID. If the base address is different, create a new module. If
// we don't then we will end up setting the load address of a different
// PlaceholderObjectFile and an assertion will fire.
auto *objfile = module_sp->GetObjectFile();
if (objfile &&
objfile->GetPluginName() ==
PlaceholderObjectFile::GetStaticPluginName().GetStringRef()) {
if (((PlaceholderObjectFile *)objfile)->GetBaseImageAddress() !=
load_addr)
module_sp.reset();
}
}
if (!module_sp) {
// We failed to locate a matching local object file. Fortunately, the
// minidump format encodes enough information about each module's memory
// range to allow us to create placeholder modules.
//
// This enables most LLDB functionality involving address-to-module
// translations (ex. identifing the module for a stack frame PC) and
// modules/sections commands (ex. target modules list, ...)
LLDB_LOG(log,
"Unable to locate the matching object file, creating a "
"placeholder module for: {0}",
name);
module_sp = Module::CreateModuleFromObjectFile<PlaceholderObjectFile>(
module_spec, load_addr, load_size);
GetTarget().GetImages().Append(module_sp, true /* notify */);
}
bool load_addr_changed = false;
module_sp->SetLoadAddress(GetTarget(), load_addr, false,
load_addr_changed);
}
}
bool ProcessMinidump::GetProcessInfo(ProcessInstanceInfo &info) {
info.Clear();
info.SetProcessID(GetID());
info.SetArchitecture(GetArchitecture());
lldb::ModuleSP module_sp = GetTarget().GetExecutableModule();
if (module_sp) {
const bool add_exe_file_as_first_arg = false;
info.SetExecutableFile(GetTarget().GetExecutableModule()->GetFileSpec(),
add_exe_file_as_first_arg);
}
return true;
}
// For minidumps there's no runtime generated code so we don't need JITLoader(s)
// Avoiding them will also speed up minidump loading since JITLoaders normally
// try to set up symbolic breakpoints, which in turn may force loading more
// debug information than needed.
JITLoaderList &ProcessMinidump::GetJITLoaders() {
if (!m_jit_loaders_up) {
m_jit_loaders_up = std::make_unique<JITLoaderList>();
}
return *m_jit_loaders_up;
}
#define INIT_BOOL(VAR, LONG, SHORT, DESC) \
VAR(LLDB_OPT_SET_1, false, LONG, SHORT, DESC, false, true)
#define APPEND_OPT(VAR) \
m_option_group.Append(&VAR, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1)
class CommandObjectProcessMinidumpDump : public CommandObjectParsed {
private:
OptionGroupOptions m_option_group;
OptionGroupBoolean m_dump_all;
OptionGroupBoolean m_dump_directory;
OptionGroupBoolean m_dump_linux_cpuinfo;
OptionGroupBoolean m_dump_linux_proc_status;
OptionGroupBoolean m_dump_linux_lsb_release;
OptionGroupBoolean m_dump_linux_cmdline;
OptionGroupBoolean m_dump_linux_environ;
OptionGroupBoolean m_dump_linux_auxv;
OptionGroupBoolean m_dump_linux_maps;
OptionGroupBoolean m_dump_linux_proc_stat;
OptionGroupBoolean m_dump_linux_proc_uptime;
OptionGroupBoolean m_dump_linux_proc_fd;
OptionGroupBoolean m_dump_linux_all;
OptionGroupBoolean m_fb_app_data;
OptionGroupBoolean m_fb_build_id;
OptionGroupBoolean m_fb_version;
OptionGroupBoolean m_fb_java_stack;
OptionGroupBoolean m_fb_dalvik;
OptionGroupBoolean m_fb_unwind;
OptionGroupBoolean m_fb_error_log;
OptionGroupBoolean m_fb_app_state;
OptionGroupBoolean m_fb_abort;
OptionGroupBoolean m_fb_thread;
OptionGroupBoolean m_fb_logcat;
OptionGroupBoolean m_fb_all;
void SetDefaultOptionsIfNoneAreSet() {
if (m_dump_all.GetOptionValue().GetCurrentValue() ||
m_dump_linux_all.GetOptionValue().GetCurrentValue() ||
m_fb_all.GetOptionValue().GetCurrentValue() ||
m_dump_directory.GetOptionValue().GetCurrentValue() ||
m_dump_linux_cpuinfo.GetOptionValue().GetCurrentValue() ||
m_dump_linux_proc_status.GetOptionValue().GetCurrentValue() ||
m_dump_linux_lsb_release.GetOptionValue().GetCurrentValue() ||
m_dump_linux_cmdline.GetOptionValue().GetCurrentValue() ||
m_dump_linux_environ.GetOptionValue().GetCurrentValue() ||
m_dump_linux_auxv.GetOptionValue().GetCurrentValue() ||
m_dump_linux_maps.GetOptionValue().GetCurrentValue() ||
m_dump_linux_proc_stat.GetOptionValue().GetCurrentValue() ||
m_dump_linux_proc_uptime.GetOptionValue().GetCurrentValue() ||
m_dump_linux_proc_fd.GetOptionValue().GetCurrentValue() ||
m_fb_app_data.GetOptionValue().GetCurrentValue() ||
m_fb_build_id.GetOptionValue().GetCurrentValue() ||
m_fb_version.GetOptionValue().GetCurrentValue() ||
m_fb_java_stack.GetOptionValue().GetCurrentValue() ||
m_fb_dalvik.GetOptionValue().GetCurrentValue() ||
m_fb_unwind.GetOptionValue().GetCurrentValue() ||
m_fb_error_log.GetOptionValue().GetCurrentValue() ||
m_fb_app_state.GetOptionValue().GetCurrentValue() ||
m_fb_abort.GetOptionValue().GetCurrentValue() ||
m_fb_thread.GetOptionValue().GetCurrentValue() ||
m_fb_logcat.GetOptionValue().GetCurrentValue())
return;
// If no options were set, then dump everything
m_dump_all.GetOptionValue().SetCurrentValue(true);
}
bool DumpAll() const {
return m_dump_all.GetOptionValue().GetCurrentValue();
}
bool DumpDirectory() const {
return DumpAll() ||
m_dump_directory.GetOptionValue().GetCurrentValue();
}
bool DumpLinux() const {
return DumpAll() || m_dump_linux_all.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxCPUInfo() const {
return DumpLinux() ||
m_dump_linux_cpuinfo.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxProcStatus() const {
return DumpLinux() ||
m_dump_linux_proc_status.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxProcStat() const {
return DumpLinux() ||
m_dump_linux_proc_stat.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxLSBRelease() const {
return DumpLinux() ||
m_dump_linux_lsb_release.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxCMDLine() const {
return DumpLinux() ||
m_dump_linux_cmdline.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxEnviron() const {
return DumpLinux() ||
m_dump_linux_environ.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxAuxv() const {
return DumpLinux() ||
m_dump_linux_auxv.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxMaps() const {
return DumpLinux() ||
m_dump_linux_maps.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxProcUptime() const {
return DumpLinux() ||
m_dump_linux_proc_uptime.GetOptionValue().GetCurrentValue();
}
bool DumpLinuxProcFD() const {
return DumpLinux() ||
m_dump_linux_proc_fd.GetOptionValue().GetCurrentValue();
}
bool DumpFacebook() const {
return DumpAll() || m_fb_all.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookAppData() const {
return DumpFacebook() || m_fb_app_data.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookBuildID() const {
return DumpFacebook() || m_fb_build_id.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookVersionName() const {
return DumpFacebook() || m_fb_version.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookJavaStack() const {
return DumpFacebook() || m_fb_java_stack.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookDalvikInfo() const {
return DumpFacebook() || m_fb_dalvik.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookUnwindSymbols() const {
return DumpFacebook() || m_fb_unwind.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookErrorLog() const {
return DumpFacebook() || m_fb_error_log.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookAppStateLog() const {
return DumpFacebook() || m_fb_app_state.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookAbortReason() const {
return DumpFacebook() || m_fb_abort.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookThreadName() const {
return DumpFacebook() || m_fb_thread.GetOptionValue().GetCurrentValue();
}
bool DumpFacebookLogcat() const {
return DumpFacebook() || m_fb_logcat.GetOptionValue().GetCurrentValue();
}
public:
CommandObjectProcessMinidumpDump(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "process plugin dump",
"Dump information from the minidump file.", nullptr),
m_option_group(),
INIT_BOOL(m_dump_all, "all", 'a',
"Dump the everything in the minidump."),
INIT_BOOL(m_dump_directory, "directory", 'd',
"Dump the minidump directory map."),
INIT_BOOL(m_dump_linux_cpuinfo, "cpuinfo", 'C',
"Dump linux /proc/cpuinfo."),
INIT_BOOL(m_dump_linux_proc_status, "status", 's',
"Dump linux /proc/<pid>/status."),
INIT_BOOL(m_dump_linux_lsb_release, "lsb-release", 'r',
"Dump linux /etc/lsb-release."),
INIT_BOOL(m_dump_linux_cmdline, "cmdline", 'c',
"Dump linux /proc/<pid>/cmdline."),
INIT_BOOL(m_dump_linux_environ, "environ", 'e',
"Dump linux /proc/<pid>/environ."),
INIT_BOOL(m_dump_linux_auxv, "auxv", 'x',
"Dump linux /proc/<pid>/auxv."),
INIT_BOOL(m_dump_linux_maps, "maps", 'm',
"Dump linux /proc/<pid>/maps."),
INIT_BOOL(m_dump_linux_proc_stat, "stat", 'S',
"Dump linux /proc/<pid>/stat."),
INIT_BOOL(m_dump_linux_proc_uptime, "uptime", 'u',
"Dump linux process uptime."),
INIT_BOOL(m_dump_linux_proc_fd, "fd", 'f',
"Dump linux /proc/<pid>/fd."),
INIT_BOOL(m_dump_linux_all, "linux", 'l',
"Dump all linux streams."),
INIT_BOOL(m_fb_app_data, "fb-app-data", 1,
"Dump Facebook application custom data."),
INIT_BOOL(m_fb_build_id, "fb-build-id", 2,
"Dump the Facebook build ID."),
INIT_BOOL(m_fb_version, "fb-version", 3,
"Dump Facebook application version string."),
INIT_BOOL(m_fb_java_stack, "fb-java-stack", 4,
"Dump Facebook java stack."),
INIT_BOOL(m_fb_dalvik, "fb-dalvik-info", 5,
"Dump Facebook Dalvik info."),
INIT_BOOL(m_fb_unwind, "fb-unwind-symbols", 6,
"Dump Facebook unwind symbols."),
INIT_BOOL(m_fb_error_log, "fb-error-log", 7,
"Dump Facebook error log."),
INIT_BOOL(m_fb_app_state, "fb-app-state-log", 8,
"Dump Facebook java stack."),
INIT_BOOL(m_fb_abort, "fb-abort-reason", 9,
"Dump Facebook abort reason."),
INIT_BOOL(m_fb_thread, "fb-thread-name", 10,
"Dump Facebook thread name."),
INIT_BOOL(m_fb_logcat, "fb-logcat", 11,
"Dump Facebook logcat."),
INIT_BOOL(m_fb_all, "facebook", 12, "Dump all Facebook streams.") {
APPEND_OPT(m_dump_all);
APPEND_OPT(m_dump_directory);
APPEND_OPT(m_dump_linux_cpuinfo);
APPEND_OPT(m_dump_linux_proc_status);
APPEND_OPT(m_dump_linux_lsb_release);
APPEND_OPT(m_dump_linux_cmdline);
APPEND_OPT(m_dump_linux_environ);
APPEND_OPT(m_dump_linux_auxv);
APPEND_OPT(m_dump_linux_maps);
APPEND_OPT(m_dump_linux_proc_stat);
APPEND_OPT(m_dump_linux_proc_uptime);
APPEND_OPT(m_dump_linux_proc_fd);
APPEND_OPT(m_dump_linux_all);
APPEND_OPT(m_fb_app_data);
APPEND_OPT(m_fb_build_id);
APPEND_OPT(m_fb_version);
APPEND_OPT(m_fb_java_stack);
APPEND_OPT(m_fb_dalvik);
APPEND_OPT(m_fb_unwind);
APPEND_OPT(m_fb_error_log);
APPEND_OPT(m_fb_app_state);
APPEND_OPT(m_fb_abort);
APPEND_OPT(m_fb_thread);
APPEND_OPT(m_fb_logcat);
APPEND_OPT(m_fb_all);
m_option_group.Finalize();
}
~CommandObjectProcessMinidumpDump() override = default;
Options *GetOptions() override { return &m_option_group; }
bool DoExecute(Args &command, CommandReturnObject &result) override {
const size_t argc = command.GetArgumentCount();
if (argc > 0) {
result.AppendErrorWithFormat("'%s' take no arguments, only options",
m_cmd_name.c_str());
return false;
}
SetDefaultOptionsIfNoneAreSet();
ProcessMinidump *process = static_cast<ProcessMinidump *>(
m_interpreter.GetExecutionContext().GetProcessPtr());
result.SetStatus(eReturnStatusSuccessFinishResult);
Stream &s = result.GetOutputStream();
MinidumpParser &minidump = *process->m_minidump_parser;
if (DumpDirectory()) {
s.Printf("RVA SIZE TYPE StreamType\n");
s.Printf("---------- ---------- ---------- --------------------------\n");
for (const auto &stream_desc : minidump.GetMinidumpFile().streams())
s.Printf(
"0x%8.8x 0x%8.8x 0x%8.8x %s\n", (uint32_t)stream_desc.Location.RVA,
(uint32_t)stream_desc.Location.DataSize,
(unsigned)(StreamType)stream_desc.Type,
MinidumpParser::GetStreamTypeAsString(stream_desc.Type).data());
s.Printf("\n");
}
auto DumpTextStream = [&](StreamType stream_type,
llvm::StringRef label) -> void {
auto bytes = minidump.GetStream(stream_type);
if (!bytes.empty()) {
if (label.empty())
label = MinidumpParser::GetStreamTypeAsString(stream_type);
s.Printf("%s:\n%s\n\n", label.data(), bytes.data());
}
};
auto DumpBinaryStream = [&](StreamType stream_type,
llvm::StringRef label) -> void {
auto bytes = minidump.GetStream(stream_type);
if (!bytes.empty()) {
if (label.empty())
label = MinidumpParser::GetStreamTypeAsString(stream_type);
s.Printf("%s:\n", label.data());
DataExtractor data(bytes.data(), bytes.size(), eByteOrderLittle,
process->GetAddressByteSize());
DumpDataExtractor(data, &s, 0, lldb::eFormatBytesWithASCII, 1,
bytes.size(), 16, 0, 0, 0);
s.Printf("\n\n");
}
};
if (DumpLinuxCPUInfo())
DumpTextStream(StreamType::LinuxCPUInfo, "/proc/cpuinfo");
if (DumpLinuxProcStatus())
DumpTextStream(StreamType::LinuxProcStatus, "/proc/PID/status");
if (DumpLinuxLSBRelease())
DumpTextStream(StreamType::LinuxLSBRelease, "/etc/lsb-release");
if (DumpLinuxCMDLine())
DumpTextStream(StreamType::LinuxCMDLine, "/proc/PID/cmdline");
if (DumpLinuxEnviron())
DumpTextStream(StreamType::LinuxEnviron, "/proc/PID/environ");
if (DumpLinuxAuxv())
DumpBinaryStream(StreamType::LinuxAuxv, "/proc/PID/auxv");
if (DumpLinuxMaps())
DumpTextStream(StreamType::LinuxMaps, "/proc/PID/maps");
if (DumpLinuxProcStat())
DumpTextStream(StreamType::LinuxProcStat, "/proc/PID/stat");
if (DumpLinuxProcUptime())
DumpTextStream(StreamType::LinuxProcUptime, "uptime");
if (DumpLinuxProcFD())
DumpTextStream(StreamType::LinuxProcFD, "/proc/PID/fd");
if (DumpFacebookAppData())
DumpTextStream(StreamType::FacebookAppCustomData,
"Facebook App Data");
if (DumpFacebookBuildID()) {
auto bytes = minidump.GetStream(StreamType::FacebookBuildID);
if (bytes.size() >= 4) {
DataExtractor data(bytes.data(), bytes.size(), eByteOrderLittle,
process->GetAddressByteSize());
lldb::offset_t offset = 0;
uint32_t build_id = data.GetU32(&offset);
s.Printf("Facebook Build ID:\n");
s.Printf("%u\n", build_id);
s.Printf("\n");
}
}
if (DumpFacebookVersionName())
DumpTextStream(StreamType::FacebookAppVersionName,
"Facebook Version String");
if (DumpFacebookJavaStack())
DumpTextStream(StreamType::FacebookJavaStack,
"Facebook Java Stack");
if (DumpFacebookDalvikInfo())
DumpTextStream(StreamType::FacebookDalvikInfo,
"Facebook Dalvik Info");
if (DumpFacebookUnwindSymbols())
DumpBinaryStream(StreamType::FacebookUnwindSymbols,
"Facebook Unwind Symbols Bytes");
if (DumpFacebookErrorLog())
DumpTextStream(StreamType::FacebookDumpErrorLog,
"Facebook Error Log");
if (DumpFacebookAppStateLog())
DumpTextStream(StreamType::FacebookAppStateLog,
"Faceook Application State Log");
if (DumpFacebookAbortReason())
DumpTextStream(StreamType::FacebookAbortReason,
"Facebook Abort Reason");
if (DumpFacebookThreadName())
DumpTextStream(StreamType::FacebookThreadName,
"Facebook Thread Name");
if (DumpFacebookLogcat())
DumpTextStream(StreamType::FacebookLogcat,
"Facebook Logcat");
return true;
}
};
class CommandObjectMultiwordProcessMinidump : public CommandObjectMultiword {
public:
CommandObjectMultiwordProcessMinidump(CommandInterpreter &interpreter)
: CommandObjectMultiword(interpreter, "process plugin",
"Commands for operating on a ProcessMinidump process.",
"process plugin <subcommand> [<subcommand-options>]") {
LoadSubCommand("dump",
CommandObjectSP(new CommandObjectProcessMinidumpDump(interpreter)));
}
~CommandObjectMultiwordProcessMinidump() override = default;
};
CommandObject *ProcessMinidump::GetPluginCommandObject() {
if (!m_command_sp)
m_command_sp = std::make_shared<CommandObjectMultiwordProcessMinidump>(
GetTarget().GetDebugger().GetCommandInterpreter());
return m_command_sp.get();
}