
### Context Over a year ago, I landed support for 64b Memory ranges in Minidump (#95312). In this patch we added the Memory64 list stream, which is effectively a Linked List on disk. The layout is a sixteen byte header and then however many Memory descriptors. ### The Bug This is a classic off-by one error, where I added 8 bytes instead of 16 for the header. This caused the first region to start 8 bytes before the correct RVA, thus shifting all memory reads by 8 bytes. We are correctly writing all the regions to disk correctly, with no physical corruption but the RVA is defined wrong, meaning we were incorrectly reading memory  ### Why wasn't this caught? One problem we've had is forcing Minidump to actually use the 64b mode, it would be a massive waste of resources to have a test that actually wrote >4.2gb of IO to validate the 64b regions, and so almost all validation has been manual. As a weakness of manual testing, this issue is psuedo non-deterministic, as what regions end up in 64b or 32b is handled greedily and iterated in the order it's laid out in /proc/pid/maps. We often validated 64b was written correctly by hexdumping the Minidump itself, which was not corrupted (other than the BaseRVA)  ### Why is this showing up now? During internal usage, we had a bug report that the Minidump wasn't displaying values. I was unable to repro the issue, but during my investigation I saw the variables were in the 64b regions which resulted in me identifying the bug. ### How do we prevent future regressions? To prevent regressions, and honestly to save my sanity for figuring out where 8 bytes magically came from, I've added a new API to SBSaveCoreOptions. ```SBSaveCoreOptions::GetMemoryRegionsToSave()``` The ability to get the memory regions that we intend to include in the Coredump. I added this so we can compare what we intended to include versus what was actually included. Traditionally we've always had issues comparing regions because Minidump includes `/proc/pid/maps` and it can be difficult to know what memoryregion read failure was a genuine error or just a page that wasn't meant to be included. We are also leveraging this API to choose the memory regions to be generated, as well as for testing what regions should be bytewise 1:1. After much debate with @clayborg, I've moved all non-stack memory to the Memory64 List. This list doesn't incur us any meaningful overhead and Greg originally suggested doing this in the original 64b PR. This also means we're exercising the 64b path every single time we save a Minidump, preventing regressions on this feature from slipping through testing in the future. Snippet produced by [minidump.py](https://github.com/clayborg/scripts) ``` MINIDUMP_MEMORY_LIST: NumberOfMemoryRanges = 0x00000002 MemoryRanges[0] = [0x00007f61085ff9f0 - 0x00007f6108601000) @ 0x0003f655 MemoryRanges[1] = [0x00007ffe47e50910 - 0x00007ffe47e52000) @ 0x00040c65 MINIDUMP_MEMORY64_LIST: NumberOfMemoryRanges = 0x000000000000002e BaseRva = 0x0000000000042669 MemoryRanges[0] = [0x00005584162d8000 - 0x00005584162d9000) MemoryRanges[1] = [0x00005584162d9000 - 0x00005584162db000) MemoryRanges[2] = [0x00005584162db000 - 0x00005584162dd000) MemoryRanges[3] = [0x00005584162dd000 - 0x00005584162ff000) MemoryRanges[4] = [0x00007f6100000000 - 0x00007f6100021000) MemoryRanges[5] = [0x00007f6108800000 - 0x00007f6108828000) MemoryRanges[6] = [0x00007f6108828000 - 0x00007f610899d000) MemoryRanges[7] = [0x00007f610899d000 - 0x00007f61089f9000) MemoryRanges[8] = [0x00007f61089f9000 - 0x00007f6108a08000) MemoryRanges[9] = [0x00007f6108bf5000 - 0x00007f6108bf7000) ``` ### Misc As a part of this fix I had to look at LLDB logs a lot, you'll notice I added `0x` to many of the PRIx64 `LLDB_LOGF`. This is so the user (or I) can directly copy paste the address in the logs instead of adding the hex prefix themselves. Added some SBSaveCore tests for the new GetMemoryAPI, and Docstrings. CC: @DavidSpickett, @da-viper @labath because we've been working together on save-core plugins, review it optional and I didn't tag you but figured you'd want to know
159 lines
4.6 KiB
C++
159 lines
4.6 KiB
C++
//===-- SBSaveCoreOptions.cpp -----------------------------------*- 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "lldb/API/SBSaveCoreOptions.h"
|
|
#include "lldb/API/SBMemoryRegionInfo.h"
|
|
#include "lldb/Host/FileSystem.h"
|
|
#include "lldb/Symbol/SaveCoreOptions.h"
|
|
#include "lldb/Target/ThreadCollection.h"
|
|
#include "lldb/Utility/Instrumentation.h"
|
|
|
|
#include "Utils.h"
|
|
|
|
using namespace lldb;
|
|
|
|
SBSaveCoreOptions::SBSaveCoreOptions() {
|
|
LLDB_INSTRUMENT_VA(this)
|
|
|
|
m_opaque_up = std::make_unique<lldb_private::SaveCoreOptions>();
|
|
}
|
|
|
|
SBSaveCoreOptions::SBSaveCoreOptions(const SBSaveCoreOptions &rhs) {
|
|
LLDB_INSTRUMENT_VA(this, rhs);
|
|
|
|
m_opaque_up = clone(rhs.m_opaque_up);
|
|
}
|
|
|
|
SBSaveCoreOptions::~SBSaveCoreOptions() = default;
|
|
|
|
const SBSaveCoreOptions &
|
|
SBSaveCoreOptions::operator=(const SBSaveCoreOptions &rhs) {
|
|
LLDB_INSTRUMENT_VA(this, rhs);
|
|
|
|
if (this != &rhs)
|
|
m_opaque_up = clone(rhs.m_opaque_up);
|
|
return *this;
|
|
}
|
|
|
|
SBError SBSaveCoreOptions::SetPluginName(const char *name) {
|
|
LLDB_INSTRUMENT_VA(this, name);
|
|
return SBError(m_opaque_up->SetPluginName(name));
|
|
}
|
|
|
|
void SBSaveCoreOptions::SetStyle(lldb::SaveCoreStyle style) {
|
|
LLDB_INSTRUMENT_VA(this, style);
|
|
m_opaque_up->SetStyle(style);
|
|
}
|
|
|
|
void SBSaveCoreOptions::SetOutputFile(lldb::SBFileSpec file_spec) {
|
|
LLDB_INSTRUMENT_VA(this, file_spec);
|
|
m_opaque_up->SetOutputFile(file_spec.ref());
|
|
}
|
|
|
|
const char *SBSaveCoreOptions::GetPluginName() const {
|
|
LLDB_INSTRUMENT_VA(this);
|
|
const auto name = m_opaque_up->GetPluginName();
|
|
if (!name)
|
|
return nullptr;
|
|
return lldb_private::ConstString(name.value()).GetCString();
|
|
}
|
|
|
|
SBFileSpec SBSaveCoreOptions::GetOutputFile() const {
|
|
LLDB_INSTRUMENT_VA(this);
|
|
const auto file_spec = m_opaque_up->GetOutputFile();
|
|
if (file_spec)
|
|
return SBFileSpec(file_spec.value());
|
|
return SBFileSpec();
|
|
}
|
|
|
|
lldb::SaveCoreStyle SBSaveCoreOptions::GetStyle() const {
|
|
LLDB_INSTRUMENT_VA(this);
|
|
return m_opaque_up->GetStyle();
|
|
}
|
|
|
|
SBError SBSaveCoreOptions::SetProcess(lldb::SBProcess process) {
|
|
LLDB_INSTRUMENT_VA(this, process);
|
|
return m_opaque_up->SetProcess(process.GetSP());
|
|
}
|
|
|
|
SBProcess SBSaveCoreOptions::GetProcess() {
|
|
LLDB_INSTRUMENT_VA(this);
|
|
return SBProcess(m_opaque_up->GetProcess());
|
|
}
|
|
|
|
SBError SBSaveCoreOptions::AddThread(lldb::SBThread thread) {
|
|
LLDB_INSTRUMENT_VA(this, thread);
|
|
return m_opaque_up->AddThread(thread.GetSP());
|
|
}
|
|
|
|
bool SBSaveCoreOptions::RemoveThread(lldb::SBThread thread) {
|
|
LLDB_INSTRUMENT_VA(this, thread);
|
|
return m_opaque_up->RemoveThread(thread.GetSP());
|
|
}
|
|
|
|
lldb::SBError
|
|
SBSaveCoreOptions::AddMemoryRegionToSave(const SBMemoryRegionInfo ®ion) {
|
|
LLDB_INSTRUMENT_VA(this, region);
|
|
// Currently add memory region can't fail, so we always return a success
|
|
// SBerror, but because these API's live forever, this is the most future
|
|
// proof thing to do.
|
|
m_opaque_up->AddMemoryRegionToSave(region.ref());
|
|
return SBError();
|
|
}
|
|
|
|
lldb::SBThreadCollection SBSaveCoreOptions::GetThreadsToSave() const {
|
|
LLDB_INSTRUMENT_VA(this);
|
|
lldb::ThreadCollectionSP threadcollection_sp =
|
|
std::make_shared<lldb_private::ThreadCollection>(
|
|
m_opaque_up->GetThreadsToSave());
|
|
return SBThreadCollection(threadcollection_sp);
|
|
}
|
|
|
|
void SBSaveCoreOptions::Clear() {
|
|
LLDB_INSTRUMENT_VA(this);
|
|
m_opaque_up->Clear();
|
|
}
|
|
|
|
uint64_t SBSaveCoreOptions::GetCurrentSizeInBytes(SBError &error) {
|
|
LLDB_INSTRUMENT_VA(this, error);
|
|
llvm::Expected<uint64_t> expected_bytes =
|
|
m_opaque_up->GetCurrentSizeInBytes();
|
|
if (!expected_bytes) {
|
|
error =
|
|
SBError(lldb_private::Status::FromError(expected_bytes.takeError()));
|
|
return 0;
|
|
}
|
|
// Clear the error, so if the clearer uses it we set it to success.
|
|
error.Clear();
|
|
return *expected_bytes;
|
|
}
|
|
|
|
lldb::SBMemoryRegionInfoList SBSaveCoreOptions::GetMemoryRegionsToSave() {
|
|
LLDB_INSTRUMENT_VA(this);
|
|
llvm::Expected<lldb_private::CoreFileMemoryRanges> memory_ranges =
|
|
m_opaque_up->GetMemoryRegionsToSave();
|
|
if (!memory_ranges) {
|
|
llvm::consumeError(memory_ranges.takeError());
|
|
return SBMemoryRegionInfoList();
|
|
}
|
|
|
|
SBMemoryRegionInfoList memory_region_infos;
|
|
for (const auto &range : *memory_ranges) {
|
|
SBMemoryRegionInfo region_info(
|
|
nullptr, range.GetRangeBase(), range.GetRangeEnd(),
|
|
range.data.lldb_permissions, /*mapped=*/true);
|
|
memory_region_infos.Append(region_info);
|
|
}
|
|
|
|
return memory_region_infos;
|
|
}
|
|
|
|
lldb_private::SaveCoreOptions &SBSaveCoreOptions::ref() const {
|
|
return *m_opaque_up;
|
|
}
|