Skip to content

[LLDB] Process minidump is in memory check command #149401

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions lldb/include/lldb/Target/MemoryRegionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/RangeMap.h"
#include "lldb/Utility/StreamString.h"
#include "llvm/Support/FormatProviders.h"

namespace lldb_private {
Expand Down Expand Up @@ -141,6 +142,35 @@ class MemoryRegionInfo {
m_dirty_pages = std::move(pagelist);
}

std::string Dump() const {
Copy link
Contributor Author

@Jlalond Jlalond Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @JDevlieghere is there a better way to print MemoryRegionInfo? I must be reinventing the wheel but there isn't an existing method on MemoryRegion?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, stream this into an ostream:

  std::string buffer;
  llvm::raw_string_ostream output(buffer);
  output << *this;
  return buffer;

lldb_private::StreamString stream;
stream << "[";
stream.PutHex64(GetRange().GetRangeBase());
stream << "-";
stream.PutHex64(GetRange().GetRangeEnd());
stream << ") ";
if (m_read == eYes)
stream << "r";
else if (m_read == eNo)
stream << "-";
else
stream << "?";
if (m_write == eYes)
stream << "w";
else if (m_write == eNo)
stream << "-";
else
stream << "?";
if (m_execute == eYes)
stream << "x";
else if (m_execute == eNo)
stream << "-";
else
stream << "?";

return stream.GetString().str();
}

protected:
RangeType m_range;
OptionalBool m_read = eDontKnow;
Expand Down
21 changes: 21 additions & 0 deletions lldb/include/lldb/Utility/RangeMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,27 @@ class RangeDataVector {
return nullptr;
}

const Entry *FindEntryThatContainsOrPrior(B addr) const {
#ifdef ASSERT_RANGEMAP_ARE_SORTED
assert(IsSorted());
#endif
if (!m_entries.empty()) {
typename Collection::const_iterator begin = m_entries.begin();
typename Collection::const_iterator end = m_entries.end();
typename Collection::const_iterator pos = llvm::lower_bound(
m_entries, addr, [](const Entry &lhs, B rhs_base) -> bool {
return lhs.GetRangeEnd() <= rhs_base;
});

if (pos != end && pos->Contains(addr))
return &(*pos);

if (pos != begin)
return &(*std::prev(pos));
}
return nullptr;
}

uint32_t FindEntryIndexThatContainsOrFollows(B addr) const {
#ifdef ASSERT_RANGEMAP_ARE_SORTED
assert(IsSorted());
Expand Down
26 changes: 26 additions & 0 deletions lldb/source/Plugins/Process/minidump/MinidumpParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,3 +711,29 @@ MinidumpParser::GetMemoryRegionInfo(const MemoryRegionInfos &regions,
region.SetMapped(MemoryRegionInfo::eNo);
return region;
}

std::optional<minidump::Range>
MinidumpParser::GetClosestPriorRegion(lldb::addr_t load_addr) {
if (m_memory_ranges.IsEmpty())
PopulateMemoryRanges();

const MemoryRangeVector::Entry *entry =
m_memory_ranges.FindEntryThatContainsOrPrior(load_addr);
if (!entry)
return std::nullopt;

return entry->data;
}

std::optional<minidump::Range>
MinidumpParser::GetClosestFollowingRegion(lldb::addr_t load_addr) {
if (m_memory_ranges.IsEmpty())
PopulateMemoryRanges();

const MemoryRangeVector::Entry *entry =
m_memory_ranges.FindEntryThatContainsOrFollows(load_addr);
if (!entry)
return std::nullopt;

return entry->data;
}
6 changes: 6 additions & 0 deletions lldb/source/Plugins/Process/minidump/MinidumpParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ class MinidumpParser {
llvm::Expected<llvm::ArrayRef<uint8_t>> GetMemory(lldb::addr_t addr,
size_t size);

// Pair of functions to get relative memory regions from the minidump file.
// These aren't for evaluating the data, but checking the ranges stored in the
// minidump and their permissions.
std::optional<Range> GetClosestPriorRegion(lldb::addr_t addr);
std::optional<Range> GetClosestFollowingRegion(lldb::addr_t addr);

/// Returns a list of memory regions and a flag indicating whether the list is
/// complete (includes all regions mapped into the process memory).
std::pair<MemoryRegionInfos, bool> BuildMemoryRegions();
Expand Down
104 changes: 103 additions & 1 deletion lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -601,9 +601,41 @@ bool ProcessMinidump::GetProcessInfo(ProcessInstanceInfo &info) {
return true;
}

std::optional<lldb_private::MemoryRegionInfo>
ProcessMinidump::TryGetMemoryRegionInCore(
lldb::addr_t addr,
std::optional<lldb_private::MemoryRegionInfo> &closest_prior_region,
std::optional<lldb_private::MemoryRegionInfo> &closest_following_region) {
// Ensure memory regions are built!
BuildMemoryRegions();

// First try to find the region that contains the address (if any
std::optional<minidump::Range> addr_region_maybe =
m_minidump_parser->FindMemoryRange(addr);
if (addr_region_maybe) {
MemoryRegionInfo region = MinidumpParser::GetMemoryRegionInfo(
*m_memory_regions, addr_region_maybe->start);
return region;
}

// If we didn't find a region, try to find the closest prior and following
std::optional<minidump::Range> closest_prior_range_maybe =
m_minidump_parser->GetClosestPriorRegion(addr);
if (closest_prior_range_maybe)
closest_prior_region = MinidumpParser::GetMemoryRegionInfo(
*m_memory_regions, closest_prior_range_maybe->start);

std::optional<minidump::Range> closest_following_range_maybe =
m_minidump_parser->GetClosestFollowingRegion(addr);
if (closest_following_range_maybe)
closest_following_region = MinidumpParser::GetMemoryRegionInfo(
*m_memory_regions, closest_following_range_maybe->start);

return std::nullopt;
}

// 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) {
Expand Down Expand Up @@ -961,6 +993,73 @@ class CommandObjectProcessMinidumpDump : public CommandObjectParsed {
}
};

class CommandObjectProcessMinidumpCheckMemory : public CommandObjectParsed {
public:
CommandObjectProcessMinidumpCheckMemory(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "process plugin check-memory",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bulbazord Hey Alex, do you have any idea on a better name than check-memory? Thanks!

"Check if a memory address is stored in the "
"Minidump, or the closest ranges.",
nullptr) {
CommandArgumentEntry arg1;
CommandArgumentData addr_arg;
addr_arg.arg_type = eArgTypeAddressOrExpression;

arg1.push_back(addr_arg);
m_arguments.push_back(arg1);
}

void DoExecute(Args &command, CommandReturnObject &result) override {
const size_t argc = command.GetArgumentCount();
if (argc == 0) {
result.AppendErrorWithFormat("'%s' Requires a memory address",
m_cmd_name.c_str());
return;
}

Status error;
lldb::addr_t address = OptionArgParser::ToAddress(
&m_exe_ctx, command[0].ref(), LLDB_INVALID_ADDRESS, &error);

if (error.Fail() || address == LLDB_INVALID_ADDRESS) {
result.AppendErrorWithFormat("'%s' Is an invalid address.",
command[0].c_str());
return;
}

ProcessMinidump *process = static_cast<ProcessMinidump *>(
m_interpreter.GetExecutionContext().GetProcessPtr());

result.SetStatus(eReturnStatusSuccessFinishResult);
std::optional<lldb_private::MemoryRegionInfo> closest_prior_region;
std::optional<lldb_private::MemoryRegionInfo> closest_following_region;
std::optional<lldb_private::MemoryRegionInfo> memory_region_maybe =
process->TryGetMemoryRegionInCore(address, closest_prior_region,
closest_following_region);

if (memory_region_maybe) {
result.AppendMessageWithFormat(
"Address found in Minidump. Address located in range: %s",
memory_region_maybe->Dump().c_str());
return;
}

lldb_private::StreamString result_stream;
result_stream << "Address not found in Minidump" << "\n";
if (closest_prior_region)
result_stream << "Closest prior range: "
<< closest_prior_region->Dump().c_str() << "\n";
else
result_stream << "No prior range found" << "\n";
if (closest_following_region)
result_stream << "Closest following range: "
<< closest_following_region->Dump().c_str() << "\n";
else
result_stream << "No following range found" << "\n";

result.AppendMessage(result_stream.GetString());
}
};

class CommandObjectMultiwordProcessMinidump : public CommandObjectMultiword {
public:
CommandObjectMultiwordProcessMinidump(CommandInterpreter &interpreter)
Expand All @@ -969,6 +1068,9 @@ class CommandObjectMultiwordProcessMinidump : public CommandObjectMultiword {
"process plugin <subcommand> [<subcommand-options>]") {
LoadSubCommand("dump",
CommandObjectSP(new CommandObjectProcessMinidumpDump(interpreter)));
LoadSubCommand("check-memory",
CommandObjectSP(new CommandObjectProcessMinidumpCheckMemory(
interpreter)));
}

~CommandObjectMultiwordProcessMinidump() override = default;
Expand Down
5 changes: 5 additions & 0 deletions lldb/source/Plugins/Process/minidump/ProcessMinidump.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ class ProcessMinidump : public PostMortemProcess {
size_t DoReadMemory(lldb::addr_t addr, void *buf, size_t size,
Status &error) override;

std::optional<lldb_private::MemoryRegionInfo> TryGetMemoryRegionInCore(
lldb::addr_t addr,
std::optional<lldb_private::MemoryRegionInfo> &closest_prior_region,
std::optional<lldb_private::MemoryRegionInfo> &closest_following_region);

ArchSpec GetArchitecture();

Status
Expand Down
52 changes: 52 additions & 0 deletions lldb/test/Shell/Minidump/check-memory.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Check that looking up a memory region not present in the Minidump fails
# even if it's in the /proc/<pid>/maps file.

# RUN: yaml2obj %s -o %t
# RUN: %lldb -c %t -o "process plugin check-memory 0x1000" \
# RUN: -o "process plugin check-memory 0x800" \
# RUN: -o "process plugin check-memory 0x1500" \
# RUN: -o "process plugin check-memory 0x2400" | FileCheck %s

# CHECK-LABEL: (lldb) process plugin check-memory 0x1000
# CHECK-NEXT: Address found in Minidump. Address located in range: [0000000000001000-0000000000001100) r??
# CHECK-LABEL: (lldb) process plugin check-memory 0x800
# CHECK-NEXT: Address not found in Minidump
# CHECK-NEXT: No prior range found
# CHECK-NEXT: Closest following range: [0000000000001000-0000000000001100) r??
# CHECK-LABEL: (lldb) process plugin check-memory 0x1500
# CHECK-NEXT: Address not found in Minidump
# CHECK-NEXT: Closest prior range: [0000000000001000-0000000000001100) r??
# CHECK-NEXT: Closest following range: [0000000000002000-0000000000002200) r??
# CHECK-LABEL: (lldb) process plugin check-memory 0x2400
# CHECK-NEXT: Address not found in Minidump
# CHECK-NEXT: Closest prior range: [0000000000002000-0000000000002200) r??
# CHECK-NEXT: No following range found

--- !minidump
Streams:
- Type: SystemInfo
Processor Arch: AMD64
Processor Level: 6
Processor Revision: 15876
Number of Processors: 40
Platform ID: Linux
CSD Version: 'Linux 3.13.0-91-generic #138-Ubuntu SMP Fri Jun 24 17:00:34 UTC 2016 x86_64'
CPU:
Vendor ID: GenuineIntel
Version Info: 0x00000000
Feature Info: 0x00000000
- Type: LinuxProcStatus
Text: |
Name: test-yaml
Umask: 0002
State: t (tracing stop)
Pid: 8567
- Type: Memory64List
Memory Ranges:
- Start of Memory Range: 0x1000
Data Size: 0x100
Content : ''
- Start of Memory Range: 0x2000
Data Size: 0x200
Content : ''
...
Loading