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

Conversation

Jlalond
Copy link
Contributor

@Jlalond Jlalond commented Jul 17, 2025

This is a follow up to #149206, where I tried to make it more apparent for the user when a memory range is absent or if an error occurred on reading. I was happy with the improvements, but because of /proc/pid/maps being used to identify the memory regions it was still frustrating that you'd see a memory range as rw- and then not be able to read it!

To make it easier for both me and users to debug. I've created a new command that will return if an address is present in the Minidump. If it isn't we try to get the closets prior and subsequent (I phrased as following to align with RangeMap.h) regions to make it easier to say what memory is available and which is not. This does open up the discussion on is the current memory region behavior a good user experience but I'll deem this outside the scope of this patch.

@Jlalond Jlalond requested a review from JDevlieghere as a code owner July 17, 2025 20:32
@llvmbot llvmbot added the lldb label Jul 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 17, 2025

@llvm/pr-subscribers-lldb

Author: Jacob Lalonde (Jlalond)

Changes

This is a follow up to #149206, where I tried to make it more apparent for the user when a memory range is absent or if an error occurred on reading. I was happy with the improvements, but because of /proc/pid/maps being used to identify the memory regions it was still frustrating that you'd see a memory range as rw- and then not be able to read it!

To make it easier for both me and users to debug. I've created a new command that will return if an address is present in the Minidump. If it isn't we try to get the closets prior and subsequent (I phrased as following to align with RangeMap.h) regions to make it easier to say what memory is available and which is not. This does open up the discussion on is the current memory region behavior a good user experience but I'll deem this outside the scope of this patch.


Full diff: https://github.com/llvm/llvm-project/pull/149401.diff

7 Files Affected:

  • (modified) lldb/include/lldb/Target/MemoryRegionInfo.h (+30)
  • (modified) lldb/include/lldb/Utility/RangeMap.h (+20)
  • (modified) lldb/source/Plugins/Process/minidump/MinidumpParser.cpp (+26)
  • (modified) lldb/source/Plugins/Process/minidump/MinidumpParser.h (+6)
  • (modified) lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp (+103-1)
  • (modified) lldb/source/Plugins/Process/minidump/ProcessMinidump.h (+5)
  • (added) lldb/test/Shell/Minidump/check-memory.yaml (+52)
diff --git a/lldb/include/lldb/Target/MemoryRegionInfo.h b/lldb/include/lldb/Target/MemoryRegionInfo.h
index dc37a7dbeda52..2957709692307 100644
--- a/lldb/include/lldb/Target/MemoryRegionInfo.h
+++ b/lldb/include/lldb/Target/MemoryRegionInfo.h
@@ -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 {
@@ -141,6 +142,35 @@ class MemoryRegionInfo {
     m_dirty_pages = std::move(pagelist);
   }
 
+  std::string Dump() const {
+    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;
diff --git a/lldb/include/lldb/Utility/RangeMap.h b/lldb/include/lldb/Utility/RangeMap.h
index e701ae1ba96c8..da17aee1fde1d 100644
--- a/lldb/include/lldb/Utility/RangeMap.h
+++ b/lldb/include/lldb/Utility/RangeMap.h
@@ -648,6 +648,26 @@ 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 pos = llvm::lower_bound(
+          m_entries, addr, [](const Entry &lhs, B rhs_base) -> bool {
+            return lhs.GetRangeEnd() <= rhs_base;
+          });
+
+      if (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());
diff --git a/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp b/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp
index 58ebb7be11994..16eeb99d91ad8 100644
--- a/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp
+++ b/lldb/source/Plugins/Process/minidump/MinidumpParser.cpp
@@ -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;
+}
diff --git a/lldb/source/Plugins/Process/minidump/MinidumpParser.h b/lldb/source/Plugins/Process/minidump/MinidumpParser.h
index 3b7d33daca717..ac8fc682925ee 100644
--- a/lldb/source/Plugins/Process/minidump/MinidumpParser.h
+++ b/lldb/source/Plugins/Process/minidump/MinidumpParser.h
@@ -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();
diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
index 17a421a722743..79ccafe7a561c 100644
--- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
+++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
@@ -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) {
@@ -961,6 +993,73 @@ class CommandObjectProcessMinidumpDump : public CommandObjectParsed {
   }
 };
 
+class CommandObjectProcessMinidumpCheckMemory : public CommandObjectParsed {
+public:
+  CommandObjectProcessMinidumpCheckMemory(CommandInterpreter &interpreter)
+      : CommandObjectParsed(interpreter, "process plugin check-memory",
+                            "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)
@@ -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;
diff --git a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h
index ad8d0ed7a4832..f5a3a87260521 100644
--- a/lldb/source/Plugins/Process/minidump/ProcessMinidump.h
+++ b/lldb/source/Plugins/Process/minidump/ProcessMinidump.h
@@ -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
diff --git a/lldb/test/Shell/Minidump/check-memory.yaml b/lldb/test/Shell/Minidump/check-memory.yaml
new file mode 100644
index 0000000000000..cce315fb687fe
--- /dev/null
+++ b/lldb/test/Shell/Minidump/check-memory.yaml
@@ -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 :        ''
+...

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

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

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 area to print MemoryRegionInfo? I must be reinventing the wheel but there isn't an existing method on MemoryRegion?

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!

@Jlalond Jlalond requested review from clayborg and qxy11 July 17, 2025 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants