diff --git a/lldb/source/Plugins/ObjectFile/ELF/CMakeLists.txt b/lldb/source/Plugins/ObjectFile/ELF/CMakeLists.txt --- a/lldb/source/Plugins/ObjectFile/ELF/CMakeLists.txt +++ b/lldb/source/Plugins/ObjectFile/ELF/CMakeLists.txt @@ -1,6 +1,7 @@ add_lldb_library(lldbPluginObjectFileELF PLUGIN ELFHeader.cpp ObjectFileELF.cpp + MinidumpFileBuilder.cpp LINK_LIBS lldbCore diff --git a/lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.h b/lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.h @@ -0,0 +1,91 @@ +//===-- MinidumpFileBuilder.h ---------------------------------- -*- C++ +//-*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +/// \file +/// Structure holding data neccessary for minidump file creation. +/// +/// The class MinidumpFileWriter is used to hold the data that will eventually +/// be dumped to the file. +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MinidumpFileBuilder_h_ +#define liblldb_MinidumpFileBuilder_h_ + +#include + +#include "lldb/Target/Target.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/Status.h" + +#include "llvm/Object/Minidump.h" + +// Write std::string to minidump in the UTF16 format(with null termination char) +// with the size(without null termination char) preceding the UTF16 string. +// Empty strings are also printed with zero length and just null termination +// char. +lldb_private::Status WriteString(const std::string &to_write, + lldb_private::DataBufferHeap *buffer); + +/// \class MinidumpFileBuilder +/// Minidump writer for Linux +/// +/// This class provides a Minidump writer that is able to +/// snapshot the current process state. For the whole time, it stores all +/// the data on heap. +class MinidumpFileBuilder { +public: + MinidumpFileBuilder() = default; + + MinidumpFileBuilder(const MinidumpFileBuilder &) = delete; + MinidumpFileBuilder &operator=(const MinidumpFileBuilder &) = delete; + + MinidumpFileBuilder(MinidumpFileBuilder &&other) = default; + MinidumpFileBuilder &operator=(MinidumpFileBuilder &&other) = default; + + ~MinidumpFileBuilder() = default; + + // Add SystemInfo stream, used for storing the most basic information + // about the system, platform etc... + lldb_private::Status AddSystemInfo(const llvm::Triple &target_triple); + // Add ModuleList stream, containing information about all loaded modules + // at the time of saving minidump. + lldb_private::Status AddModuleList(lldb_private::Target &target); + // Add ThreadList stream, containing information about all threads running + // at the moment of core saving. Contains information about thread + // contexts. + lldb_private::Status AddThreadList(const lldb::ProcessSP &process_sp); + // Add Exception stream, this contains information about the exception + // that stopped the process. In case no thread made exception it return + // failed status. + lldb_private::Status AddException(const lldb::ProcessSP &process_sp); + // Add MemoryList stream, containing dumps of important memory segments + lldb_private::Status AddMemoryList(const lldb::ProcessSP &process_sp); + // Add MiscInfo stream, mainly providing ProcessId + void AddMiscInfo(const lldb::ProcessSP &process_sp); + // Dump the prepared data into file. In case of the failure data are + // intact. + lldb_private::Status Dump(lldb::FileUP &core_file) const; + // Returns the current number of directories(streams) that have been so far + // created. This number of directories will be dumped when calling Dump() + size_t GetDirectoriesNum() const; + +private: + // Add directory of StreamType pointing to the current end of the prepared + // file with the specified size. + void AddDirectory(llvm::minidump::StreamType type, size_t stream_size); + size_t GetCurrentDataEndOffset() const; + + // Stores directories to later put them at the end of minidump file + std::vector m_directories; + // Main data buffer consisting of data without the minidump header and + // directories + lldb_private::DataBufferHeap m_data; +}; + +#endif // liblldb_MinidumpFileBuilder_h_ \ No newline at end of file diff --git a/lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.cpp b/lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ObjectFile/ELF/MinidumpFileBuilder.cpp @@ -0,0 +1,672 @@ +//===-- 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/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" + +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(stream_size); + // Stream will begin at the current end of data section + loc.RVA = static_cast(GetCurrentDataEndOffset()); + + Directory dir; + dir.Type = static_cast>(type); + dir.Location = loc; + + m_directories.push_back(dir); +} + +Status MinidumpFileBuilder::AddSystemInfo(const llvm::Triple &target_triple) { + AddDirectory(StreamType::SystemInfo, sizeof(llvm::minidump::SystemInfo)); + + llvm::minidump::ProcessorArchitecture arch; + switch (target_triple.getArch()) { + case llvm::Triple::ArchType::x86: + arch = ProcessorArchitecture::X86; + 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; + case llvm::Triple::ArchType::arm: + arch = ProcessorArchitecture::ARM; + break; + default: + arch = ProcessorArchitecture::AMD64; + break; + }; + + llvm::minidump::SystemInfo sys_info; + sys_info.ProcessorArch = + static_cast>(arch); + // Global offset to beginning of a csd_string in a data section + sys_info.CSDVersionRVA = static_cast( + GetCurrentDataEndOffset() + sizeof(llvm::minidump::SystemInfo)); + sys_info.PlatformId = OSPlatform::Linux; + m_data.AppendData(&sys_info, sizeof(llvm::minidump::SystemInfo)); + + std::string csd_string = ""; + + Status error = WriteString(csd_string, &m_data); + if (error.Fail()) { + error.SetErrorString("Unable to convert the csd string to UTF16."); + return error; + } + + return Status(); +} + +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 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; +} + +// 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(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(); + + llvm::support::ulittle32_t signature = + static_cast( + static_cast(minidump::CvSignature::ElfBuildId)); + auto uuid = mod->GetUUID().GetBytes(); + + VSFixedFileInfo info; + info.Signature = static_cast(0u); + info.StructVersion = static_cast(0u); + info.FileVersionHigh = static_cast(0u); + info.FileVersionLow = static_cast(0u); + info.ProductVersionHigh = static_cast(0u); + info.ProductVersionLow = static_cast(0u); + info.FileFlagsMask = static_cast(0u); + info.FileFlags = static_cast(0u); + info.FileOS = static_cast(0u); + info.FileType = static_cast(0u); + info.FileSubtype = static_cast(0u); + info.FileDateHigh = static_cast(0u); + info.FileDateLow = static_cast(0u); + + LocationDescriptor ld; + ld.DataSize = static_cast(0u); + ld.RVA = static_cast(0u); + + // Setting up LocationDescriptor for uuid string. The global offset into + // minidump file is calculated. + LocationDescriptor ld_cv; + ld_cv.DataSize = static_cast( + sizeof(llvm::support::ulittle32_t) + uuid.size()); + ld_cv.RVA = static_cast( + 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( + mod->GetObjectFile()->GetBaseAddress().GetLoadAddress(&target)); + m.SizeOfImage = static_cast( + mod->GetObjectFile()->GetByteSize()); + m.Checksum = static_cast(0); + m.TimeDateStamp = static_cast(std::time(0)); + m.ModuleNameRVA = static_cast( + 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( + 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( + 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( + 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.context_flags = static_cast( + 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> +findStackHelper(const lldb::ProcessSP &process_sp, uint64_t rsp) { + MemoryRegionInfo range_info; + Status range_error = process_sp->GetMemoryRegionInfo(0, range_info); + + if (range_error.Fail()) { + return llvm::createStringError( + std::errc::not_supported, + "process doesn't support getting memory region info"); + } + + while (range_info.GetRange().GetRangeBase() != LLDB_INVALID_ADDRESS) { + const addr_t addr = range_info.GetRange().GetRangeBase(); + const addr_t size = range_info.GetRange().GetByteSize(); + + if (rsp >= addr && rsp <= addr + size) { + Status error; + return std::make_pair(addr, size); + } + + if (size == 0) + break; + + range_error = process_sp->GetMemoryRegionInfo( + range_info.GetRange().GetRangeEnd(), range_info); + if (range_error.Fail()) + break; + } + + return llvm::createStringError( + std::errc::not_supported, + "process doesn't support getting memory region info"); +} + +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(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 range = std::move(*expected_address_range); + uint64_t addr = range.first; + uint64_t size = range.second; + + auto data_up = std::make_unique(size, 0); + process_sp->ReadMemory(addr, data_up->GetBytes(), size, error); + + if (error.Fail()) { + return error; + } + + LocationDescriptor stack_memory; + stack_memory.DataSize = static_cast(size); + stack_memory.RVA = static_cast( + size_before + thread_stream_size + helper_data.GetByteSize()); + + MemoryDescriptor stack; + stack.StartOfMemoryRange = static_cast(addr); + stack.Memory = stack_memory; + + helper_data.AppendData(data_up->GetBytes(), size); + + LocationDescriptor thread_context_memory_locator; + thread_context_memory_locator.DataSize = + static_cast(sizeof(thread_context)); + thread_context_memory_locator.RVA = static_cast( + 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(thread_sp->GetID()); + t.SuspendCount = static_cast( + (thread_sp->GetState() == StateType::eStateSuspended) ? 1 : 0); + t.PriorityClass = static_cast(0); + t.Priority = static_cast(0); + t.EnvironmentBlock = static_cast(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) { + 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) { + Status error; + 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(sizeof(thread_context)); + thread_context_memory_locator.RVA = static_cast( + 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(stop_info_sp->GetValue()); + exp_record.ExceptionFlags = static_cast(0); + exp_record.ExceptionRecord = static_cast(0); + exp_record.ExceptionAddress = read_register_u64(reg_ctx, "rip"); + exp_record.NumberParameters = static_cast(0); + exp_record.UnusedAlignment = static_cast(0); + // exp_record.ExceptionInformation; + + ExceptionStream exp_stream; + exp_stream.ThreadId = + static_cast(thread_sp->GetID()); + exp_stream.UnusedAlignment = static_cast(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 Status(); +} + +lldb_private::Status +MinidumpFileBuilder::AddMemoryList(const lldb::ProcessSP &process_sp) { + Status error; + MemoryRegionInfo range_info; + error = process_sp->GetMemoryRegionInfo(0, range_info); + + if (error.Fail()) { + error.SetErrorString("Process doesn't support getting memory region info."); + return error; + } + + // Get interesting addresses + std::vector 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 mem_descriptors; + + while (range_info.GetRange().GetRangeBase() != LLDB_INVALID_ADDRESS) { + const addr_t addr = range_info.GetRange().GetRangeBase(); + const addr_t size = range_info.GetRange().GetByteSize(); + + bool is_interesting = false; + for (size_t interesting_address : interesting_addresses) + if (interesting_address >= addr && interesting_address < addr + size) { + is_interesting = true; + break; + } + + if (is_interesting) { + LocationDescriptor memory_dump; + memory_dump.DataSize = static_cast(size); + memory_dump.RVA = + static_cast(GetCurrentDataEndOffset()); + + MemoryDescriptor memory_desc; + memory_desc.StartOfMemoryRange = + static_cast(addr); + memory_desc.Memory = memory_dump; + mem_descriptors.push_back(memory_desc); + + auto data_up = std::make_unique(size, 0); + process_sp->ReadMemory(addr, data_up->GetBytes(), size, error); + + m_data.AppendData(data_up->GetBytes(), size); + } + + if (size == 0) + break; + + error = process_sp->GetMemoryRegionInfo(range_info.GetRange().GetRangeEnd(), + range_info); + if (error.Fail()) { + error = Status(); + break; + } + } + + 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( + 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(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(static_cast( + lldb_private::minidump::MinidumpMiscInfoFlags::ProcessID)); + misc_info.process_id = + static_cast(process_info.GetProcessID()); + } + + m_data.AppendData(&misc_info, + sizeof(lldb_private::minidump::MinidumpMiscInfo)); +} + +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::minidump::Header::MagicSignature); + header.Version = static_cast( + llvm::minidump::Header::MagicVersion); + header.NumberOfStreams = + static_cast(GetDirectoriesNum()); + header.StreamDirectoryRVA = + static_cast(GetCurrentDataEndOffset()); + header.Checksum = static_cast( + 0u), // not used in most of the writers + header.TimeDateStamp = + static_cast(std::time(0)); + header.Flags = + static_cast(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 %ld/%ld).", 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 %ld/%ld).", 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 %ld/%ld).", 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(); +} \ No newline at end of file diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h @@ -74,6 +74,12 @@ const lldb::ModuleSP &module_sp, lldb::DataBufferSP &data_sp, const lldb::ProcessSP &process_sp, lldb::addr_t header_addr); + // Saves dump in Minidump file format + static bool SaveCore(const lldb::ProcessSP &process_sp, + const lldb_private::FileSpec &outfile, + lldb::SaveCoreStyle &core_style, + lldb_private::Status &error); + static size_t GetModuleSpecifications(const lldb_private::FileSpec &file, lldb::DataBufferSP &data_sp, lldb::offset_t data_offset, @@ -155,7 +161,6 @@ void RelocateSection(lldb_private::Section *section) override; protected: - std::vector GetLoadableData(lldb_private::Target &target) override; diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -8,6 +8,8 @@ #include "ObjectFileELF.h" +#include "MinidumpFileBuilder.h" + #include #include #include @@ -22,6 +24,7 @@ #include "lldb/Host/LZMA.h" #include "lldb/Symbol/DWARFCallFrameInfo.h" #include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/Process.h" #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" #include "lldb/Utility/ArchSpec.h" @@ -38,6 +41,7 @@ #include "llvm/Object/Decompressor.h" #include "llvm/Support/ARMBuildAttributes.h" #include "llvm/Support/CRC.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/MemoryBuffer.h" @@ -326,9 +330,9 @@ // Static methods. void ObjectFileELF::Initialize() { - PluginManager::RegisterPlugin(GetPluginNameStatic(), - GetPluginDescriptionStatic(), CreateInstance, - CreateMemoryInstance, GetModuleSpecifications); + PluginManager::RegisterPlugin( + GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, + CreateMemoryInstance, GetModuleSpecifications, SaveCore); } void ObjectFileELF::Terminate() { @@ -707,6 +711,61 @@ return false; } +bool ObjectFileELF::SaveCore(const lldb::ProcessSP &process_sp, + const FileSpec &outfile, + lldb::SaveCoreStyle &core_style, Status &error) { + if (!process_sp) + return false; + + MinidumpFileBuilder builder; + + Target &target = process_sp->GetTarget(); + + error = builder.AddSystemInfo(target.GetArchitecture().GetTriple()); + if (error.Fail()) { + return false; + } + + error = builder.AddModuleList(target); + if (error.Fail()) { + return false; + } + + builder.AddMiscInfo(process_sp); + + if (target.GetArchitecture().GetMachine() == llvm::Triple::ArchType::x86_64) { + error = builder.AddThreadList(process_sp); + if (error.Fail()) { + return false; + } + + error = builder.AddException(process_sp); + if (error.Fail()) { + return false; + } + + error = builder.AddMemoryList(process_sp); + if (error.Fail()) { + return false; + } + } + + llvm::Expected maybe_core_file = FileSystem::Instance().Open( + outfile, File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate); + if (!maybe_core_file) { + error = maybe_core_file.takeError(); + return false; + } + lldb::FileUP core_file = std::move(maybe_core_file.get()); + + error = builder.Dump(core_file); + if (error.Fail()) { + return false; + } + + return true; +} + ByteOrder ObjectFileELF::GetByteOrder() const { if (m_header.e_ident[EI_DATA] == ELFDATA2MSB) return eByteOrderBig; diff --git a/lldb/test/API/functionalities/process_save_core_minidump/Makefile b/lldb/test/API/functionalities/process_save_core_minidump/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/process_save_core_minidump/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main.cpp + +CFLAGS_EXTRAS := -lpthread + +include Makefile.rules + diff --git a/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py b/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/process_save_core_minidump/TestProcessSaveCoreMinidump.py @@ -0,0 +1,78 @@ +""" +Test saving a mini dump. +""" + + +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class ProcessSaveCoreMinidumpTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipUnlessPlatform(['linux']) + def test_save_linux_mini_dump(self): + """Test that we can save a Linux mini dump.""" + self.build() + exe = self.getBuildArtifact("a.out") + core = self.getBuildArtifact("core.dmp") + try: + target = self.dbg.CreateTarget(exe) + process = target.LaunchSimple( + None, None, self.get_process_working_directory()) + self.assertEqual(process.GetState(), lldb.eStateStopped) + + # get neccessary data for the verification phase + process_info = process.GetProcessInfo() + expected_pid = process_info.GetProcessID() if process_info.IsValid() else -1 + expected_number_of_modules = target.GetNumModules() + expected_modules = target.modules + expected_number_of_threads = process.GetNumThreads() + expected_threads = [] + + for thread_idx in range(process.GetNumThreads()): + thread = process.GetThreadAtIndex(thread_idx) + thread_id = thread.GetThreadID() + expected_threads.append(thread_id) + + # save core and, kill process and verify corefile existence + self.assertTrue(process.SaveCore(core)) + self.assertTrue(os.path.isfile(core)) + self.assertTrue(process.Kill().Success()) + + # To verify, we'll launch with the mini dump + target = self.dbg.CreateTarget(None) + process = target.LoadCore(core) + + # check if the core is in desired state + self.assertTrue(process, PROCESS_IS_VALID) + self.assertTrue(process.GetProcessInfo().IsValid()) + self.assertEqual(process.GetProcessInfo().GetProcessID(), expected_pid) + self.assertTrue(target.GetTriple().find("linux") != -1) + self.assertTrue(target.GetNumModules(), expected_number_of_modules) + self.assertEqual(process.GetNumThreads(), expected_number_of_threads) + + for module, expected in zip(target.modules, expected_modules): + self.assertTrue(module.IsValid()) + module_file_name = module.GetFileSpec().GetFilename() + expected_file_name = expected.GetFileSpec().GetFilename() + # skip kernel virtual dynamic shared objects + if "vdso" in expected_file_name: + continue + self.assertEqual(module_file_name, expected_file_name) + self.assertEqual(module.GetUUIDString(), expected.GetUUIDString()) + + for thread_idx in range(process.GetNumThreads()): + thread = process.GetThreadAtIndex(thread_idx) + self.assertTrue(thread.IsValid()) + thread_id = thread.GetThreadID() + self.assertTrue(thread_id in expected_threads) + finally: + # Clean up the mini dump file. + self.assertTrue(self.dbg.DeleteTarget(target)) + if (os.path.isfile(core)): + os.unlink(core) diff --git a/lldb/test/API/functionalities/process_save_core_minidump/main.cpp b/lldb/test/API/functionalities/process_save_core_minidump/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/process_save_core_minidump/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +using namespace std; + +void g() { assert(false); } + +void f() { g(); } + +size_t h() { + size_t sum = 0; + for (size_t i = 0; i < 1000000; ++i) + for (size_t j = 0; j < 1000000; ++j) + if ((i * j) % 2 == 0) { + sum += 1; + } + return sum; +} + +int main() { + thread t1(f); + + size_t x = h(); + + t1.join(); + + cout << "X is " << x << "\n"; + return 0; +} \ No newline at end of file