diff --git a/lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.cpp b/lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.cpp index 7231433619ffb..7a09c6104d08c 100644 --- a/lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.cpp +++ b/lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.cpp @@ -20,25 +20,100 @@ #include "lldb/Target/RegisterContext.h" #include "lldb/Target/StopInfo.h" #include "lldb/Target/ThreadList.h" +#include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/DataExtractor.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" +#include "lldb/Utility/RangeMap.h" #include "lldb/Utility/RegisterValue.h" #include "llvm/ADT/StringRef.h" #include "llvm/BinaryFormat/Minidump.h" #include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/Endian.h" #include "llvm/Support/Error.h" +#include "llvm/TargetParser/Triple.h" #include "Plugins/Process/minidump/MinidumpTypes.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-forward.h" +#include "lldb/lldb-types.h" +#include #include +#include +#include +#include +#include +#include +#include +#include +#include using namespace lldb; using namespace lldb_private; using namespace llvm::minidump; -void MinidumpFileBuilder::AddDirectory(StreamType type, size_t stream_size) { +Status MinidumpFileBuilder::AddHeaderAndCalculateDirectories() { + // First set the offset on the file, and on the bytes saved + m_saved_data_size = HEADER_SIZE; + // We know we will have at least Misc, SystemInfo, Modules, and ThreadList + // (corresponding memory list for stacks) And an additional memory list for + // non-stacks. + lldb_private::Target &target = m_process_sp->GetTarget(); + m_expected_directories = 6; + // Check if OS is linux and reserve directory space for all linux specific + // breakpad extension directories. + if (target.GetArchitecture().GetTriple().getOS() == + llvm::Triple::OSType::Linux) + m_expected_directories += 9; + + // Go through all of the threads and check for exceptions. + lldb_private::ThreadList thread_list = m_process_sp->GetThreadList(); + 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)); + StopInfoSP stop_info_sp = thread_sp->GetStopInfo(); + if (stop_info_sp) { + const StopReason &stop_reason = stop_info_sp->GetStopReason(); + if (stop_reason == StopReason::eStopReasonException || + stop_reason == StopReason::eStopReasonSignal) + m_expected_directories++; + } + } + + m_saved_data_size += + m_expected_directories * sizeof(llvm::minidump::Directory); + Status error; + offset_t new_offset = m_core_file->SeekFromStart(m_saved_data_size); + if (new_offset != m_saved_data_size) + error.SetErrorStringWithFormat("Failed to fill in header and directory " + "sections. Written / Expected (%" PRIx64 + " / %" PRIx64 ")", + new_offset, m_saved_data_size); + + return error; +} + +Status MinidumpFileBuilder::AddDirectory(StreamType type, + uint64_t stream_size) { + // We explicitly cast type, an 32b enum, to uint32_t to avoid warnings. + Status error; + if (GetCurrentDataEndOffset() > UINT32_MAX) { + error.SetErrorStringWithFormat("Unable to add directory for stream type " + "%x, offset is greater then 32 bit limit.", + (uint32_t)type); + return error; + } + + if (m_directories.size() + 1 > m_expected_directories) { + error.SetErrorStringWithFormat( + "Unable to add directory for stream type %x, exceeded expected number " + "of directories %d.", + (uint32_t)type, m_expected_directories); + return error; + } + LocationDescriptor loc; loc.DataSize = static_cast(stream_size); // Stream will begin at the current end of data section @@ -49,11 +124,17 @@ void MinidumpFileBuilder::AddDirectory(StreamType type, size_t stream_size) { dir.Location = loc; m_directories.push_back(dir); + return error; } -Status MinidumpFileBuilder::AddSystemInfo(const llvm::Triple &target_triple) { +Status MinidumpFileBuilder::AddSystemInfo() { Status error; - AddDirectory(StreamType::SystemInfo, sizeof(llvm::minidump::SystemInfo)); + const llvm::Triple &target_triple = + m_process_sp->GetTarget().GetArchitecture().GetTriple(); + error = + AddDirectory(StreamType::SystemInfo, sizeof(llvm::minidump::SystemInfo)); + if (error.Fail()) + return error; llvm::minidump::ProcessorArchitecture arch; switch (target_triple.getArch()) { @@ -165,7 +246,6 @@ llvm::Expected getModuleFileSize(Target &target, } return SizeOfImage; } - SectionSP sect_sp = mod->GetObjectFile()->GetBaseAddress().GetSection(); if (!sect_sp) { @@ -203,10 +283,11 @@ llvm::Expected getModuleFileSize(Target &target, // 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) { +Status MinidumpFileBuilder::AddModuleList() { constexpr size_t minidump_module_size = sizeof(llvm::minidump::Module); Status error; + lldb_private::Target &target = m_process_sp->GetTarget(); const ModuleList &modules = target.GetImages(); llvm::support::ulittle32_t modules_count = static_cast(modules.GetSize()); @@ -223,7 +304,9 @@ Status MinidumpFileBuilder::AddModuleList(Target &target) { sizeof(llvm::support::ulittle32_t) + modules_count * minidump_module_size; // Adding directory describing this stream. - AddDirectory(StreamType::ModuleList, module_stream_size); + error = AddDirectory(StreamType::ModuleList, module_stream_size); + if (error.Fail()) + return error; m_data.AppendData(&modules_count, sizeof(llvm::support::ulittle32_t)); @@ -480,42 +563,32 @@ class ArchThreadContexts { } }; -// 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 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"); - - // This is a duplicate of the logic in - // Process::SaveOffRegionsWithStackPointers but ultimately, we need to only - // save up from the start of the stack down to the stack pointer - const addr_t range_end = range_info.GetRange().GetRangeEnd(); - const addr_t red_zone = process_sp->GetABI()->GetRedZoneSize(); - const addr_t stack_head = rsp - red_zone; - if (stack_head > range_info.GetRange().GetRangeEnd()) { - range_info.GetRange().SetRangeBase(stack_head); - range_info.GetRange().SetByteSize(range_end - stack_head); - } - - 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::FixThreadStacks() { + Status error; + // If we have anything in the heap flush it. + FlushBufferToDisk(); + m_core_file->SeekFromStart(m_thread_list_start); + for (auto &pair : m_thread_by_range_end) { + // The thread objects will get a new memory descriptor added + // When we are emitting the memory list and then we write it here + const llvm::minidump::Thread &thread = pair.second; + size_t bytes_to_write = sizeof(llvm::minidump::Thread); + size_t bytes_written = bytes_to_write; + error = m_core_file->Write(&thread, bytes_written); + if (error.Fail() || bytes_to_write != bytes_written) { + error.SetErrorStringWithFormat( + "Wrote incorrect number of bytes to minidump file. (written %zd/%zd)", + bytes_written, bytes_to_write); + return error; + } + } + + return error; } -Status MinidumpFileBuilder::AddThreadList(const lldb::ProcessSP &process_sp) { +Status MinidumpFileBuilder::AddThreadList() { constexpr size_t minidump_thread_size = sizeof(llvm::minidump::Thread); - lldb_private::ThreadList thread_list = process_sp->GetThreadList(); + lldb_private::ThreadList thread_list = m_process_sp->GetThreadList(); // size of the entire thread stream consists of: // number of threads and threads array @@ -523,28 +596,31 @@ Status MinidumpFileBuilder::AddThreadList(const lldb::ProcessSP &process_sp) { 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); + Status error; + error = AddDirectory(StreamType::ThreadList, thread_stream_size); + if (error.Fail()) + return error; llvm::support::ulittle32_t thread_count = static_cast(thread_list.GetSize()); m_data.AppendData(&thread_count, sizeof(llvm::support::ulittle32_t)); + // Take the offset after the thread count. + m_thread_list_start = GetCurrentDataEndOffset(); DataBufferHeap helper_data; const uint32_t num_threads = thread_list.GetSize(); - + Log *log = GetLog(LLDBLog::Object); 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(); - Target &target = process_sp->GetTarget(); + Target &target = m_process_sp->GetTarget(); const ArchSpec &arch = target.GetArchitecture(); ArchThreadContexts thread_context(arch.GetMachine()); if (!thread_context.prepareRegisterContext(reg_ctx)) { @@ -553,38 +629,18 @@ Status MinidumpFileBuilder::AddThreadList(const lldb::ProcessSP &process_sp) { arch.GetTriple().getArchName().str().c_str()); return error; } - uint64_t sp = reg_ctx->GetSP(); - auto expected_address_range = findStackHelper(process_sp, sp); - if (!expected_address_range) { - consumeError(expected_address_range.takeError()); - 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); - 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(stack_bytes_read); - stack_memory.RVA = static_cast( - size_before + thread_stream_size + helper_data.GetByteSize()); + uint64_t sp = reg_ctx->GetSP(); + MemoryRegionInfo sp_region; + m_process_sp->GetMemoryRegionInfo(sp, sp_region); + // Emit a blank descriptor MemoryDescriptor stack; - stack.StartOfMemoryRange = static_cast(addr); - stack.Memory = stack_memory; - - helper_data.AppendData(data_up->GetBytes(), stack_bytes_read); - + LocationDescriptor empty_label; + empty_label.DataSize = 0; + empty_label.RVA = 0; + stack.Memory = empty_label; + stack.StartOfMemoryRange = 0; LocationDescriptor thread_context_memory_locator; thread_context_memory_locator.DataSize = static_cast(thread_context.size()); @@ -593,6 +649,8 @@ Status MinidumpFileBuilder::AddThreadList(const lldb::ProcessSP &process_sp) { // Cache thie thread context memory so we can reuse for exceptions. m_tid_to_reg_ctx[thread_sp->GetID()] = thread_context_memory_locator; + LLDB_LOGF(log, "AddThreadList for thread %d: thread_context %zu bytes", + thread_idx, thread_context.size()); helper_data.AppendData(thread_context.data(), thread_context.size()); llvm::minidump::Thread t; @@ -604,16 +662,20 @@ Status MinidumpFileBuilder::AddThreadList(const lldb::ProcessSP &process_sp) { t.EnvironmentBlock = static_cast(0); t.Stack = stack, t.Context = thread_context_memory_locator; + // We save off the stack object so we can circle back and clean it up. + m_thread_by_range_end[sp_region.GetRange().GetRangeEnd()] = t; m_data.AppendData(&t, sizeof(llvm::minidump::Thread)); } + LLDB_LOGF(log, "AddThreadList(): total helper_data %" PRIx64 " bytes", + helper_data.GetByteSize()); m_data.AppendData(helper_data.GetBytes(), helper_data.GetByteSize()); return Status(); } -void MinidumpFileBuilder::AddExceptions(const lldb::ProcessSP &process_sp) { - lldb_private::ThreadList thread_list = process_sp->GetThreadList(); - +Status MinidumpFileBuilder::AddExceptions() { + lldb_private::ThreadList thread_list = m_process_sp->GetThreadList(); + Status error; 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)); @@ -632,7 +694,10 @@ void MinidumpFileBuilder::AddExceptions(const lldb::ProcessSP &process_sp) { if (add_exception) { constexpr size_t minidump_exception_size = sizeof(llvm::minidump::ExceptionStream); - AddDirectory(StreamType::Exception, minidump_exception_size); + error = AddDirectory(StreamType::Exception, minidump_exception_size); + if (error.Fail()) + return error; + StopInfoSP stop_info_sp = thread_sp->GetStopInfo(); RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext()); Exception exp_record = {}; @@ -660,69 +725,16 @@ void MinidumpFileBuilder::AddExceptions(const lldb::ProcessSP &process_sp) { m_data.AppendData(&exp_stream, minidump_exception_size); } } -} - -lldb_private::Status -MinidumpFileBuilder::AddMemoryList(const lldb::ProcessSP &process_sp, - lldb::SaveCoreStyle core_style) { - Status error; - Process::CoreFileMemoryRanges core_ranges; - error = process_sp->CalculateCoreFileSaveRanges(core_style, core_ranges); - if (error.Fail()) { - error.SetErrorString("Process doesn't support getting memory region info."); - return error; - } - - DataBufferHeap helper_data; - std::vector mem_descriptors; - for (const auto &core_range : core_ranges) { - // Skip empty memory regions. - if (core_range.range.empty()) - continue; - const addr_t addr = core_range.range.start(); - const addr_t size = core_range.range.size(); - auto data_up = std::make_unique(size, 0); - const size_t bytes_read = - process_sp->ReadMemory(addr, data_up->GetBytes(), size, error); - if (error.Fail()) { - Log *log = GetLog(LLDBLog::Object); - LLDB_LOGF(log, "Failed to read memory region. Bytes read: %zu, error: %s", - bytes_read, error.AsCString()); - error.Clear(); - } - if (bytes_read == 0) - continue; - // We have a good memory region with valid bytes to store. - LocationDescriptor memory_dump; - memory_dump.DataSize = static_cast(bytes_read); - 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); - 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::Status MinidumpFileBuilder::AddMiscInfo() { + Status error; + error = AddDirectory(StreamType::MiscInfo, + sizeof(lldb_private::minidump::MinidumpMiscInfo)); + if (error.Fail()) + return error; lldb_private::minidump::MinidumpMiscInfo misc_info; misc_info.size = static_cast( @@ -732,7 +744,7 @@ void MinidumpFileBuilder::AddMiscInfo(const lldb::ProcessSP &process_sp) { misc_info.flags1 = static_cast(0); lldb_private::ProcessInstanceInfo process_info; - process_sp->GetProcessInfo(process_info); + m_process_sp->GetProcessInfo(process_info); if (process_info.ProcessIDIsValid()) { // Set flags1 to reflect that PID is filled in misc_info.flags1 = @@ -744,6 +756,7 @@ void MinidumpFileBuilder::AddMiscInfo(const lldb::ProcessSP &process_sp) { m_data.AppendData(&misc_info, sizeof(lldb_private::minidump::MinidumpMiscInfo)); + return error; } std::unique_ptr @@ -754,15 +767,20 @@ getFileStreamHelper(const std::string &path) { return std::move(maybe_stream.get()); } -void MinidumpFileBuilder::AddLinuxFileStreams( - const lldb::ProcessSP &process_sp) { +Status MinidumpFileBuilder::AddLinuxFileStreams() { + Status error; + // No-op if we are not on linux. + if (m_process_sp->GetTarget().GetArchitecture().GetTriple().getOS() != + llvm::Triple::Linux) + return error; + std::vector> files_with_stream_types = { {StreamType::LinuxCPUInfo, "/proc/cpuinfo"}, {StreamType::LinuxLSBRelease, "/etc/lsb-release"}, }; lldb_private::ProcessInstanceInfo process_info; - process_sp->GetProcessInfo(process_info); + m_process_sp->GetProcessInfo(process_info); if (process_info.ProcessIDIsValid()) { lldb::pid_t pid = process_info.GetProcessID(); std::string pid_str = std::to_string(pid); @@ -791,16 +809,94 @@ void MinidumpFileBuilder::AddLinuxFileStreams( size_t size = memory_buffer->getBufferSize(); if (size == 0) continue; - AddDirectory(stream, size); + error = AddDirectory(stream, size); + if (error.Fail()) + return error; m_data.AppendData(memory_buffer->getBufferStart(), size); } } + + return error; } -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); +Status MinidumpFileBuilder::AddMemoryList(SaveCoreStyle core_style) { + Status error; + + // We first save the thread stacks to ensure they fit in the first UINT32_MAX + // bytes of the core file. Thread structures in minidump files can only use + // 32 bit memory descriptiors, so we emit them first to ensure the memory is + // in accessible with a 32 bit offset. + Process::CoreFileMemoryRanges ranges_32; + Process::CoreFileMemoryRanges ranges_64; + error = m_process_sp->CalculateCoreFileSaveRanges( + SaveCoreStyle::eSaveCoreStackOnly, ranges_32); + if (error.Fail()) + return error; + + // Calculate totalsize including the current offset. + uint64_t total_size = GetCurrentDataEndOffset(); + total_size += ranges_32.size() * sizeof(llvm::minidump::MemoryDescriptor); + std::unordered_set stack_start_addresses; + for (const auto &core_range : ranges_32) { + stack_start_addresses.insert(core_range.range.start()); + total_size += core_range.range.size(); + } + + if (total_size >= UINT32_MAX) { + error.SetErrorStringWithFormat("Unable to write minidump. Stack memory " + "exceeds 32b limit. (Num Stacks %zu)", + ranges_32.size()); + return error; + } + + Process::CoreFileMemoryRanges all_core_memory_ranges; + if (core_style != SaveCoreStyle::eSaveCoreStackOnly) { + error = m_process_sp->CalculateCoreFileSaveRanges(core_style, + all_core_memory_ranges); + if (error.Fail()) + return error; + } + + // After saving the stacks, we start packing as much as we can into 32b. + // We apply a generous padding here so that the Directory, MemoryList and + // Memory64List sections all begin in 32b addressable space. + // Then anything overflow extends into 64b addressable space. + // All core memeroy ranges will either container nothing on stacks only + // or all the memory ranges including stacks + if (!all_core_memory_ranges.empty()) + total_size += + 256 + (all_core_memory_ranges.size() - stack_start_addresses.size()) * + sizeof(llvm::minidump::MemoryDescriptor_64); + + for (const auto &core_range : all_core_memory_ranges) { + const addr_t range_size = core_range.range.size(); + if (stack_start_addresses.count(core_range.range.start()) > 0) + // Don't double save stacks. + continue; + + if (total_size + range_size < UINT32_MAX) { + ranges_32.push_back(core_range); + total_size += range_size; + } else { + ranges_64.push_back(core_range); + } + } + error = AddMemoryList_32(ranges_32); + if (error.Fail()) + return error; + + // Add the remaining memory as a 64b range. + if (!ranges_64.empty()) { + error = AddMemoryList_64(ranges_64); + if (error.Fail()) + return error; + } + + return FixThreadStacks(); +} + +Status MinidumpFileBuilder::DumpHeader() const { // write header llvm::minidump::Header header; header.Signature = static_cast( @@ -808,9 +904,10 @@ Status MinidumpFileBuilder::Dump(lldb::FileUP &core_file) const { header.Version = static_cast( llvm::minidump::Header::MagicVersion); header.NumberOfStreams = - static_cast(GetDirectoriesNum()); + static_cast(m_directories.size()); + // We write the directories right after the header. header.StreamDirectoryRVA = - static_cast(GetCurrentDataEndOffset()); + static_cast(HEADER_SIZE); header.Checksum = static_cast( 0u), // not used in most of the writers header.TimeDateStamp = @@ -821,36 +918,35 @@ Status MinidumpFileBuilder::Dump(lldb::FileUP &core_file) const { 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) + m_core_file->SeekFromStart(0); + bytes_written = HEADER_SIZE; + error = m_core_file->Write(&header, bytes_written); + if (error.Fail() || bytes_written != HEADER_SIZE) { + if (bytes_written != HEADER_SIZE) error.SetErrorStringWithFormat( - "unable to write the header (written %zd/%zd)", bytes_written, - header_size); + "Unable to write the minidump header (written %zd/%zd)", + bytes_written, HEADER_SIZE); return error; } + 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; - } +offset_t MinidumpFileBuilder::GetCurrentDataEndOffset() const { + return m_data.GetByteSize() + m_saved_data_size; +} - // write directories +Status MinidumpFileBuilder::DumpDirectories() const { + Status error; + size_t bytes_written; + m_core_file->SeekFromStart(HEADER_SIZE); for (const Directory &dir : m_directories) { - bytes_written = directory_size; - error = core_file->Write(&dir, bytes_written); - if (error.Fail() || bytes_written != directory_size) { - if (bytes_written != directory_size) + bytes_written = DIRECTORY_SIZE; + error = m_core_file->Write(&dir, bytes_written); + if (error.Fail() || bytes_written != DIRECTORY_SIZE) { + if (bytes_written != DIRECTORY_SIZE) error.SetErrorStringWithFormat( "unable to write the directory (written %zd/%zd)", bytes_written, - directory_size); + DIRECTORY_SIZE); return error; } } @@ -858,10 +954,254 @@ Status MinidumpFileBuilder::Dump(lldb::FileUP &core_file) const { return error; } -size_t MinidumpFileBuilder::GetDirectoriesNum() const { - return m_directories.size(); +static uint64_t +GetLargestRangeSize(const Process::CoreFileMemoryRanges &ranges) { + uint64_t max_size = 0; + for (const auto &core_range : ranges) + max_size = std::max(max_size, core_range.range.size()); + return max_size; +} + +Status +MinidumpFileBuilder::AddMemoryList_32(Process::CoreFileMemoryRanges &ranges) { + std::vector descriptors; + Status error; + if (ranges.size() == 0) + return error; + + Log *log = GetLog(LLDBLog::Object); + size_t region_index = 0; + auto data_up = + std::make_unique(GetLargestRangeSize(ranges), 0); + for (const auto &core_range : ranges) { + // Take the offset before we write. + const offset_t offset_for_data = GetCurrentDataEndOffset(); + const addr_t addr = core_range.range.start(); + const addr_t size = core_range.range.size(); + const addr_t end = core_range.range.end(); + + LLDB_LOGF(log, + "AddMemoryList %zu/%zu reading memory for region " + "(%" PRIx64 " bytes) [%" PRIx64 ", %" PRIx64 ")", + region_index, ranges.size(), size, addr, addr + size); + ++region_index; + + const size_t bytes_read = + m_process_sp->ReadMemory(addr, data_up->GetBytes(), size, error); + if (error.Fail() || bytes_read == 0) { + LLDB_LOGF(log, "Failed to read memory region. Bytes read: %zu, error: %s", + bytes_read, error.AsCString()); + // Just skip sections with errors or zero bytes in 32b mode + continue; + } else if (bytes_read != size) { + LLDB_LOGF( + log, "Memory region at: %" PRIx64 " failed to read %" PRIx64 " bytes", + addr, size); + } + + MemoryDescriptor descriptor; + descriptor.StartOfMemoryRange = + static_cast(addr); + descriptor.Memory.DataSize = + static_cast(bytes_read); + descriptor.Memory.RVA = + static_cast(offset_for_data); + descriptors.push_back(descriptor); + if (m_thread_by_range_end.count(end) > 0) + m_thread_by_range_end[end].Stack = descriptor; + + // Add the data to the buffer, flush as needed. + error = AddData(data_up->GetBytes(), bytes_read); + if (error.Fail()) + return error; + } + + // Add a directory that references this list + // With a size of the number of ranges as a 32 bit num + // And then the size of all the ranges + error = AddDirectory(StreamType::MemoryList, + sizeof(llvm::support::ulittle32_t) + + descriptors.size() * + sizeof(llvm::minidump::MemoryDescriptor)); + if (error.Fail()) + return error; + + llvm::support::ulittle32_t memory_ranges_num = + static_cast(descriptors.size()); + m_data.AppendData(&memory_ranges_num, sizeof(llvm::support::ulittle32_t)); + // For 32b we can get away with writing off the descriptors after the data. + // This means no cleanup loop needed. + m_data.AppendData(descriptors.data(), + descriptors.size() * sizeof(MemoryDescriptor)); + + return error; } -size_t MinidumpFileBuilder::GetCurrentDataEndOffset() const { - return sizeof(llvm::minidump::Header) + m_data.GetByteSize(); +Status +MinidumpFileBuilder::AddMemoryList_64(Process::CoreFileMemoryRanges &ranges) { + Status error; + if (ranges.empty()) + return error; + + error = AddDirectory(StreamType::Memory64List, + (sizeof(llvm::support::ulittle64_t) * 2) + + ranges.size() * + sizeof(llvm::minidump::MemoryDescriptor_64)); + if (error.Fail()) + return error; + + llvm::support::ulittle64_t memory_ranges_num = + static_cast(ranges.size()); + m_data.AppendData(&memory_ranges_num, sizeof(llvm::support::ulittle64_t)); + // Capture the starting offset for all the descriptors so we can clean them up + // if needed. + offset_t starting_offset = + GetCurrentDataEndOffset() + sizeof(llvm::support::ulittle64_t); + // The base_rva needs to start after the directories, which is right after + // this 8 byte variable. + offset_t base_rva = + starting_offset + + (ranges.size() * sizeof(llvm::minidump::MemoryDescriptor_64)); + llvm::support::ulittle64_t memory_ranges_base_rva = + static_cast(base_rva); + m_data.AppendData(&memory_ranges_base_rva, + sizeof(llvm::support::ulittle64_t)); + + bool cleanup_required = false; + std::vector descriptors; + // Enumerate the ranges and create the memory descriptors so we can append + // them first + for (const auto core_range : ranges) { + // Add the space required to store the memory descriptor + MemoryDescriptor_64 memory_desc; + memory_desc.StartOfMemoryRange = + static_cast(core_range.range.start()); + memory_desc.DataSize = + static_cast(core_range.range.size()); + descriptors.push_back(memory_desc); + // Now write this memory descriptor to the buffer. + m_data.AppendData(&memory_desc, sizeof(MemoryDescriptor_64)); + } + + Log *log = GetLog(LLDBLog::Object); + size_t region_index = 0; + auto data_up = + std::make_unique(GetLargestRangeSize(ranges), 0); + for (const auto &core_range : ranges) { + const addr_t addr = core_range.range.start(); + const addr_t size = core_range.range.size(); + + LLDB_LOGF(log, + "AddMemoryList_64 %zu/%zu reading memory for region " + "(%" PRIx64 "bytes) " + "[%" PRIx64 ", %" PRIx64 ")", + region_index, ranges.size(), size, addr, addr + size); + ++region_index; + + const size_t bytes_read = + m_process_sp->ReadMemory(addr, data_up->GetBytes(), size, error); + if (error.Fail()) { + LLDB_LOGF(log, "Failed to read memory region. Bytes read: %zu, error: %s", + bytes_read, error.AsCString()); + error.Clear(); + cleanup_required = true; + descriptors[region_index].DataSize = 0; + } + if (bytes_read != size) { + LLDB_LOGF( + log, "Memory region at: %" PRIx64 " failed to read %" PRIx64 " bytes", + addr, size); + cleanup_required = true; + descriptors[region_index].DataSize = bytes_read; + } + + // Add the data to the buffer, flush as needed. + error = AddData(data_up->GetBytes(), bytes_read); + if (error.Fail()) + return error; + } + + // Early return if there is no cleanup needed. + if (!cleanup_required) { + return error; + } else { + // Flush to disk we can make the fixes in place. + FlushBufferToDisk(); + // Fixup the descriptors that were not read correctly. + m_core_file->SeekFromStart(starting_offset); + size_t bytes_written = sizeof(MemoryDescriptor_64) * descriptors.size(); + error = m_core_file->Write(descriptors.data(), bytes_written); + if (error.Fail() || + bytes_written != sizeof(MemoryDescriptor_64) * descriptors.size()) { + error.SetErrorStringWithFormat( + "unable to write the memory descriptors (written %zd/%zd)", + bytes_written, sizeof(MemoryDescriptor_64) * descriptors.size()); + } + + return error; + } +} + +Status MinidumpFileBuilder::AddData(const void *data, uint64_t size) { + // This should also get chunked, because worst case we copy over a big + // object / memory range, say 5gb. In that case, we'd have to allocate 10gb + // 5 gb for the buffer we're copying from, and then 5gb for the buffer we're + // copying to. Which will be short lived and immedaitely go to disk, the goal + // here is to limit the number of bytes we need to host in memory at any given + // time. + m_data.AppendData(data, size); + if (m_data.GetByteSize() > MAX_WRITE_CHUNK_SIZE) + return FlushBufferToDisk(); + + return Status(); +} + +Status MinidumpFileBuilder::FlushBufferToDisk() { + Status error; + // Set the stream to it's end. + m_core_file->SeekFromStart(m_saved_data_size); + addr_t starting_size = m_data.GetByteSize(); + addr_t remaining_bytes = starting_size; + offset_t offset = 0; + + while (remaining_bytes > 0) { + size_t bytes_written = remaining_bytes; + // We don't care how many bytes we wrote unless we got an error + // so just decrement the remaining bytes. + error = m_core_file->Write(m_data.GetBytes() + offset, bytes_written); + if (error.Fail()) { + error.SetErrorStringWithFormat( + "Wrote incorrect number of bytes to minidump file. (written %" PRIx64 + "/%" PRIx64 ")", + starting_size - remaining_bytes, starting_size); + return error; + } + + offset += bytes_written; + remaining_bytes -= bytes_written; + } + + m_saved_data_size += starting_size; + m_data.Clear(); + return error; +} + +Status MinidumpFileBuilder::DumpFile() { + Status error; + // If anything is left unsaved, dump it. + error = FlushBufferToDisk(); + if (error.Fail()) + return error; + + // Overwrite the header which we filled in earlier. + error = DumpHeader(); + if (error.Fail()) + return error; + + // Overwrite the space saved for directories + error = DumpDirectories(); + if (error.Fail()) + return error; + + return error; } diff --git a/lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.h b/lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.h index b2e984191983f..b606f925f9912 100644 --- a/lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.h +++ b/lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.h @@ -17,12 +17,20 @@ #define LLDB_SOURCE_PLUGINS_OBJECTFILE_MINIDUMP_MINIDUMPFILEBUILDER_H #include +#include #include +#include +#include +#include +#include "lldb/Target/Process.h" #include "lldb/Target/Target.h" #include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/Status.h" +#include "lldb/lldb-forward.h" +#include "lldb/lldb-types.h" +#include "llvm/BinaryFormat/Minidump.h" #include "llvm/Object/Minidump.h" // Write std::string to minidump in the UTF16 format(with null termination char) @@ -36,11 +44,39 @@ lldb_private::Status WriteString(const std::string &to_write, /// 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. +/// snapshot the current process state. +/// +/// Minidumps are a Microsoft format for dumping process state. +/// This class constructs the minidump on disk starting with +/// Headers and Directories are written at the top of the file, +/// with the amount of bytes being precalculates before any writing takes place +/// Then the smaller data sections are written +/// SystemInfo, ModuleList, Misc Info. +/// Then Threads are emitted, threads are the first section that needs to be +/// 'fixed up' this happens when later we emit the memory stream, we identify if +/// that stream is the expected stack, and if so we update the stack with the +/// current RVA. Lastly the Memory lists are added. For Memory List, this will +/// contain everything that can fit within 4.2gb. MemoryList has it's +/// descriptors written at the end so it cannot be allowed to overflow. +/// +/// Memory64List is a special case where it has to be begin before 4.2gb but can +/// expand forever The difference in Memory64List is there are no RVA's and all +/// the addresses are figured out by starting at the base RVA, and adding the +/// antecedent memory sections. +/// +/// Because Memory64List can be arbitrarily large, this class has to write +/// chunks to disk this means we have to precalculate the descriptors and write +/// them first, and if we encounter any error, or are unable to read the same +/// number of bytes we have to go back and update them on disk. +/// +/// And as the last step, after all the directories have been added, we go back +/// to the top of the file to fill in the header and the redirectory sections +/// that we preallocated. class MinidumpFileBuilder { public: - MinidumpFileBuilder() = default; + MinidumpFileBuilder(lldb::FileUP &&core_file, + const lldb::ProcessSP &process_sp) + : m_process_sp(process_sp), m_core_file(std::move(core_file)){}; MinidumpFileBuilder(const MinidumpFileBuilder &) = delete; MinidumpFileBuilder &operator=(const MinidumpFileBuilder &) = delete; @@ -50,48 +86,84 @@ class MinidumpFileBuilder { ~MinidumpFileBuilder() = default; + // This method only calculates the amount of bytes the header and directories + // will take up. It does not write the directories or headers. This function + // must be called with a followup to fill in the data. + lldb_private::Status AddHeaderAndCalculateDirectories(); // Add SystemInfo stream, used for storing the most basic information // about the system, platform etc... - lldb_private::Status AddSystemInfo(const llvm::Triple &target_triple); + lldb_private::Status AddSystemInfo(); // Add ModuleList stream, containing information about all loaded modules // at the time of saving minidump. - lldb_private::Status AddModuleList(lldb_private::Target &target); + lldb_private::Status AddModuleList(); // 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); + lldb_private::Status AddThreadList(); // Add Exception streams for any threads that stopped with exceptions. - void AddExceptions(const lldb::ProcessSP &process_sp); + lldb_private::Status AddExceptions(); // Add MemoryList stream, containing dumps of important memory segments - lldb_private::Status AddMemoryList(const lldb::ProcessSP &process_sp, - lldb::SaveCoreStyle core_style); + lldb_private::Status AddMemoryList(lldb::SaveCoreStyle core_style); // Add MiscInfo stream, mainly providing ProcessId - void AddMiscInfo(const lldb::ProcessSP &process_sp); + lldb_private::Status AddMiscInfo(); // Add informative files about a Linux process - void AddLinuxFileStreams(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; + lldb_private::Status AddLinuxFileStreams(); + + // Run cleanup and write all remaining bytes to file + lldb_private::Status DumpFile(); private: + // Add data to the end of the buffer, if the buffer exceeds the flush level, + // trigger a flush. + lldb_private::Status AddData(const void *data, uint64_t size); + // Add MemoryList stream, containing dumps of important memory segments + lldb_private::Status + AddMemoryList_64(lldb_private::Process::CoreFileMemoryRanges &ranges); + lldb_private::Status + AddMemoryList_32(lldb_private::Process::CoreFileMemoryRanges &ranges); + // Update the thread list on disk with the newly emitted stack RVAs. + lldb_private::Status FixThreadStacks(); + lldb_private::Status FlushBufferToDisk(); + + lldb_private::Status DumpHeader() const; + lldb_private::Status DumpDirectories() const; // 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 + lldb_private::Status AddDirectory(llvm::minidump::StreamType type, + uint64_t stream_size); + lldb::offset_t GetCurrentDataEndOffset() const; + // Stores directories to fill in later std::vector m_directories; + // When we write off the threads for the first time, we need to clean them up + // and give them the correct RVA once we write the stack memory list. + // We save by the end because we only take from the stack pointer up + // So the saved off range base can differ from the memory region the stack + // pointer is in. + std::unordered_map + m_thread_by_range_end; // Main data buffer consisting of data without the minidump header and // directories lldb_private::DataBufferHeap m_data; + lldb::ProcessSP m_process_sp; + + uint m_expected_directories = 0; + uint64_t m_saved_data_size = 0; + lldb::offset_t m_thread_list_start = 0; + // We set the max write amount to 128 mb, this is arbitrary + // but we want to try to keep the size of m_data small + // and we will only exceed a 128 mb buffer if we get a memory region + // that is larger than 128 mb. + static constexpr size_t MAX_WRITE_CHUNK_SIZE = (1024 * 1024 * 128); + + static constexpr size_t HEADER_SIZE = sizeof(llvm::minidump::Header); + static constexpr size_t DIRECTORY_SIZE = sizeof(llvm::minidump::Directory); // More that one place can mention the register thread context locations, // so when we emit the thread contents, remember where it is so we don't have // to duplicate it in the exception data. - std::map m_tid_to_reg_ctx; + std::unordered_map + m_tid_to_reg_ctx; + lldb::FileUP m_core_file; }; #endif // LLDB_SOURCE_PLUGINS_OBJECTFILE_MINIDUMP_MINIDUMPFILEBUILDER_H diff --git a/lldb/source/Plugins/ObjectFile/Minidump/ObjectFileMinidump.cpp b/lldb/source/Plugins/ObjectFile/Minidump/ObjectFileMinidump.cpp index 1af5d99f0b160..3668c37c5191d 100644 --- a/lldb/source/Plugins/ObjectFile/Minidump/ObjectFileMinidump.cpp +++ b/lldb/source/Plugins/ObjectFile/Minidump/ObjectFileMinidump.cpp @@ -15,8 +15,10 @@ #include "lldb/Core/Section.h" #include "lldb/Target/Process.h" #include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" #include "llvm/Support/FileSystem.h" +#include using namespace lldb; using namespace lldb_private; @@ -65,56 +67,70 @@ bool ObjectFileMinidump::SaveCore(const lldb::ProcessSP &process_sp, if (!process_sp) return false; - MinidumpFileBuilder builder; - - Target &target = process_sp->GetTarget(); + llvm::Expected maybe_core_file = FileSystem::Instance().Open( + outfile, File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate); + if (!maybe_core_file) { + error = maybe_core_file.takeError(); + return false; + } + MinidumpFileBuilder builder(std::move(maybe_core_file.get()), process_sp); Log *log = GetLog(LLDBLog::Object); - error = builder.AddSystemInfo(target.GetArchitecture().GetTriple()); + error = builder.AddHeaderAndCalculateDirectories(); if (error.Fail()) { - LLDB_LOG(log, "AddSystemInfo failed: %s", error.AsCString()); + LLDB_LOGF(log, "AddHeaderAndCalculateDirectories failed: %s", + error.AsCString()); + return false; + }; + error = builder.AddSystemInfo(); + if (error.Fail()) { + LLDB_LOGF(log, "AddSystemInfo failed: %s", error.AsCString()); return false; } - error = builder.AddModuleList(target); + error = builder.AddModuleList(); if (error.Fail()) { - LLDB_LOG(log, "AddModuleList failed: %s", error.AsCString()); + LLDB_LOGF(log, "AddModuleList failed: %s", error.AsCString()); return false; } - - builder.AddMiscInfo(process_sp); - - error = builder.AddThreadList(process_sp); + error = builder.AddMiscInfo(); if (error.Fail()) { - LLDB_LOG(log, "AddThreadList failed: %s", error.AsCString()); + LLDB_LOGF(log, "AddMiscInfo failed: %s", error.AsCString()); return false; } - // Add any exceptions but only if there are any in any threads. - builder.AddExceptions(process_sp); + error = builder.AddThreadList(); + if (error.Fail()) { + LLDB_LOGF(log, "AddThreadList failed: %s", error.AsCString()); + return false; + } - error = builder.AddMemoryList(process_sp, core_style); + error = builder.AddLinuxFileStreams(); if (error.Fail()) { - LLDB_LOG(log, "AddMemoryList failed: %s", error.AsCString()); + LLDB_LOGF(log, "AddLinuxFileStreams failed: %s", error.AsCString()); return false; } - if (target.GetArchitecture().GetTriple().getOS() == - llvm::Triple::OSType::Linux) { - builder.AddLinuxFileStreams(process_sp); + // Add any exceptions but only if there are any in any threads. + error = builder.AddExceptions(); + if (error.Fail()) { + LLDB_LOGF(log, "AddExceptions failed: %s", error.AsCString()); + return false; } - llvm::Expected maybe_core_file = FileSystem::Instance().Open( - outfile, File::eOpenOptionWriteOnly | File::eOpenOptionCanCreate); - if (!maybe_core_file) { - error = maybe_core_file.takeError(); + // Note: add memory HAS to be the last thing we do. It can overflow into 64b + // land and many RVA's only support 32b + error = builder.AddMemoryList(core_style); + if (error.Fail()) { + LLDB_LOGF(log, "AddMemoryList failed: %s", error.AsCString()); return false; } - lldb::FileUP core_file = std::move(maybe_core_file.get()); - error = builder.Dump(core_file); - if (error.Fail()) + error = builder.DumpFile(); + if (error.Fail()) { + LLDB_LOGF(log, "DumpFile failed: %s", error.AsCString()); return false; + } return true; } diff --git a/llvm/include/llvm/BinaryFormat/Minidump.h b/llvm/include/llvm/BinaryFormat/Minidump.h index bc303929498d0..9669303252615 100644 --- a/llvm/include/llvm/BinaryFormat/Minidump.h +++ b/llvm/include/llvm/BinaryFormat/Minidump.h @@ -70,6 +70,11 @@ struct MemoryDescriptor { }; static_assert(sizeof(MemoryDescriptor) == 16); +struct MemoryDescriptor_64 { + support::ulittle64_t StartOfMemoryRange; + support::ulittle64_t DataSize; +}; + struct MemoryInfoListHeader { support::ulittle32_t SizeOfHeader; support::ulittle32_t SizeOfEntry;