Author: Jason Molenda
Date: 2026-06-30T18:51:33Z
New Revision: 31340a9222bbc20f35b892afde79f1beecce98a9

URL: 
https://github.com/llvm/llvm-project/commit/31340a9222bbc20f35b892afde79f1beecce98a9
DIFF: 
https://github.com/llvm/llvm-project/commit/31340a9222bbc20f35b892afde79f1beecce98a9.diff

LOG: [lldb] Add memory region info cache to Process (#206208)

Every time a memory region is queried in the inferior with a
ProcessGDBRemote connection, lldb needs to send a packet requesting the
information. When we are querying multiple addresses within the same
region, this can result in many redundant packets - e.g. to test for
whether an address is in stack memory during a backtrace.

This adds a MemoryRegionInfoCache wrapper around RangeDataVector to save
the memory regions that we query, and supply them to future queries. The
cache is flushed on any process resume in the same way that the memory
cache is; the valid pages may change as a program executes.

This PR started as a combination of this cache plus extensions to gdb
remote serial protocol packets so the debug stub can provide memory
region infos when we have a public stop (e.g. the info about each
thread's stack memory region) - including changes to both debugserver
and ProcessGDBRemote to implement that (
https://github.com/llvm/llvm-project/pull/202509 ). I'm separating out
the two pieces into separate patchsets. The test in this PR retains the
full code in the .c file that I will need to test the expedited memory
region infos still, even though it doesn't do antyhing but launch the
inferior process in the current tests.

rdar://179067896

---------

Co-authored-by: Jonas Devlieghere <[email protected]>

Added: 
    lldb/include/lldb/Target/MemoryRegionInfoCache.h
    lldb/source/Target/MemoryRegionInfoCache.cpp
    lldb/test/API/tools/lldb-server/cached-memory-region-info/Makefile
    
lldb/test/API/tools/lldb-server/cached-memory-region-info/TestCachedMemoryRegionInfo.py
    lldb/test/API/tools/lldb-server/cached-memory-region-info/main.cpp

Modified: 
    lldb/include/lldb/Target/Process.h
    lldb/source/Target/CMakeLists.txt
    lldb/source/Target/Process.cpp
    
lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/MemoryRegionInfoCache.h 
b/lldb/include/lldb/Target/MemoryRegionInfoCache.h
new file mode 100644
index 0000000000000..538d01f5fb81f
--- /dev/null
+++ b/lldb/include/lldb/Target/MemoryRegionInfoCache.h
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_MEMORYREGIONINFOCACHE_H
+#define LLDB_TARGET_MEMORYREGIONINFOCACHE_H
+
+#include "lldb/Target/MemoryRegionInfo.h"
+#include "lldb/Utility/RangeMap.h"
+
+#include <mutex>
+#include <optional>
+
+namespace lldb_private {
+class MemoryRegionInfoCache {
+public:
+  MemoryRegionInfoCache() : m_region_infos(), m_is_sorted(true), m_mutex() {}
+
+  /// Remove all cached entries.  Should be called whenever
+  /// Process resumes execution of the inferior.
+  void Clear();
+
+  /// Return a MemoryRegionInfo that covers \p load_addr,
+  /// returns empty optional if there is no entry.
+  std::optional<MemoryRegionInfo> GetMemoryRegion(lldb::addr_t load_addr);
+
+  /// Add a MemoryRegionInfo to the collection.
+  void AddRegion(const MemoryRegionInfo &region_info);
+
+  size_t GetSize();
+
+private:
+  typedef RangeDataVector<lldb::addr_t, size_t, lldb_private::MemoryRegionInfo>
+      InfoMap;
+  InfoMap m_region_infos;
+  bool m_is_sorted;
+  std::mutex m_mutex;
+};
+} // namespace lldb_private
+
+#endif // LLDB_TARGET_MEMORYREGIONINFOCACHE_H

diff  --git a/lldb/include/lldb/Target/Process.h 
b/lldb/include/lldb/Target/Process.h
index cdf0268ad6163..7b1424e6dfa59 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -40,6 +40,7 @@
 #include "lldb/Target/ExecutionContextScope.h"
 #include "lldb/Target/InstrumentationRuntime.h"
 #include "lldb/Target/Memory.h"
+#include "lldb/Target/MemoryRegionInfoCache.h"
 #include "lldb/Target/MemoryTagManager.h"
 #include "lldb/Target/QueueList.h"
 #include "lldb/Target/ThreadList.h"
@@ -3549,6 +3550,7 @@ void PruneThreadPlans();
   std::vector<std::string> m_profile_data;
   Predicate<uint32_t> m_iohandler_sync;
   MemoryCache m_memory_cache;
+  MemoryRegionInfoCache m_memory_region_infos_cache;
   AllocatedMemoryCache m_allocated_memory_cache;
   bool m_should_detach; /// Should we detach if the process object goes away
                         /// with an explicit call to Kill or Detach?

diff  --git a/lldb/source/Target/CMakeLists.txt 
b/lldb/source/Target/CMakeLists.txt
index 704de84e25169..8e603f9a4df82 100644
--- a/lldb/source/Target/CMakeLists.txt
+++ b/lldb/source/Target/CMakeLists.txt
@@ -26,6 +26,7 @@ add_lldb_library(lldbTarget
   Memory.cpp
   MemoryHistory.cpp
   MemoryRegionInfo.cpp
+  MemoryRegionInfoCache.cpp
   MemoryTagMap.cpp
   ModuleCache.cpp
   OperatingSystem.cpp

diff  --git a/lldb/source/Target/MemoryRegionInfoCache.cpp 
b/lldb/source/Target/MemoryRegionInfoCache.cpp
new file mode 100644
index 0000000000000..4adaf20227910
--- /dev/null
+++ b/lldb/source/Target/MemoryRegionInfoCache.cpp
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/Target/MemoryRegionInfoCache.h"
+#include "lldb/Target/MemoryRegionInfo.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+void MemoryRegionInfoCache::Clear() {
+  std::lock_guard<std::mutex> guard(m_mutex);
+  m_region_infos.Clear();
+  m_is_sorted = true;
+}
+
+size_t MemoryRegionInfoCache::GetSize() {
+  std::lock_guard<std::mutex> guard(m_mutex);
+  return m_region_infos.GetSize();
+}
+
+std::optional<MemoryRegionInfo>
+MemoryRegionInfoCache::GetMemoryRegion(addr_t load_addr) {
+  std::lock_guard<std::mutex> guard(m_mutex);
+  if (!m_is_sorted) {
+    m_region_infos.Sort();
+    m_is_sorted = true;
+  }
+  uint32_t index = m_region_infos.FindEntryIndexThatContains(load_addr);
+  if (index != UINT32_MAX)
+    return m_region_infos.GetEntryAtIndex(index)->data;
+
+  return std::nullopt;
+}
+
+void MemoryRegionInfoCache::AddRegion(const MemoryRegionInfo &ri) {
+  std::lock_guard<std::mutex> guard(m_mutex);
+  InfoMap::Entry new_entry(ri.GetRange().GetRangeBase(),
+                           ri.GetRange().GetByteSize(), ri);
+  m_region_infos.Append(new_entry);
+  m_is_sorted = false;
+}

diff  --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index ff7734a47fdca..afd4ef7d6b291 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -50,6 +50,7 @@
 #include "lldb/Target/LanguageRuntime.h"
 #include "lldb/Target/MemoryHistory.h"
 #include "lldb/Target/MemoryRegionInfo.h"
+#include "lldb/Target/MemoryRegionInfoCache.h"
 #include "lldb/Target/OperatingSystem.h"
 #include "lldb/Target/Platform.h"
 #include "lldb/Target/Process.h"
@@ -479,14 +480,15 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP 
listener_sp,
       m_stdio_communication("process.stdio"), m_stdio_communication_mutex(),
       m_stdin_forward(false), m_stdout_data(), m_stderr_data(),
       m_profile_data_comm_mutex(), m_profile_data(), m_iohandler_sync(0),
-      m_memory_cache(*this), m_allocated_memory_cache(*this),
-      m_should_detach(false), m_next_event_action_up(),
-      m_currently_handling_do_on_removals(false), m_resume_requested(false),
-      m_interrupt_tid(LLDB_INVALID_THREAD_ID), m_finalizing(false),
-      m_destructing(false), m_clear_thread_plans_on_stop(false),
-      m_force_next_event_delivery(false), 
m_last_broadcast_state(eStateInvalid),
-      m_destroy_in_process(false), m_can_interpret_function_calls(false),
-      m_run_thread_plan_lock(), m_can_jit(eCanJITDontKnow),
+      m_memory_cache(*this), m_memory_region_infos_cache(),
+      m_allocated_memory_cache(*this), m_should_detach(false),
+      m_next_event_action_up(), m_currently_handling_do_on_removals(false),
+      m_resume_requested(false), m_interrupt_tid(LLDB_INVALID_THREAD_ID),
+      m_finalizing(false), m_destructing(false),
+      m_clear_thread_plans_on_stop(false), m_force_next_event_delivery(false),
+      m_last_broadcast_state(eStateInvalid), m_destroy_in_process(false),
+      m_can_interpret_function_calls(false), m_run_thread_plan_lock(),
+      m_can_jit(eCanJITDontKnow),
       m_crash_info_dict_sp(new StructuredData::Dictionary()) {
   CheckInWithManager();
 
@@ -595,6 +597,7 @@ void Process::Finalize(bool destructing) {
   m_notifications.swap(empty_notifications);
   m_image_tokens.clear();
   m_memory_cache.Clear();
+  m_memory_region_infos_cache.Clear();
   m_allocated_memory_cache.Clear(/*deallocate_memory=*/true);
   {
     std::lock_guard<std::recursive_mutex> guard(m_language_runtimes_mutex);
@@ -1458,6 +1461,7 @@ void Process::SetPrivateState(StateType new_state) {
       if (!m_mod_id.IsLastResumeForUserExpression())
         m_mod_id.SetStopEventForLastNaturalStopID(event_sp);
       m_memory_cache.Clear();
+      m_memory_region_infos_cache.Clear();
       LLDB_LOGF(log, "(plugin = %s, state = %s, stop_id = %u",
                GetPluginName().data(), StateAsCString(new_state),
                m_mod_id.GetStopID());
@@ -2697,7 +2701,11 @@ addr_t Process::AllocateMemory(size_t size, uint32_t 
permissions,
     return LLDB_INVALID_ADDRESS;
   }
 
-  return m_allocated_memory_cache.AllocateMemory(size, permissions, error);
+  addr_t alloced_addr =
+      m_allocated_memory_cache.AllocateMemory(size, permissions, error);
+  m_memory_region_infos_cache.Clear();
+
+  return alloced_addr;
 }
 
 addr_t Process::CallocateMemory(size_t size, uint32_t permissions,
@@ -2750,6 +2758,7 @@ void Process::SetCanRunCode(bool can_run_code) {
 
 Status Process::DeallocateMemory(addr_t ptr) {
   Status error;
+  m_memory_region_infos_cache.Clear();
   if (!m_allocated_memory_cache.DeallocateMemory(ptr)) {
     error = Status::FromErrorStringWithFormat(
         "deallocation of memory at 0x%" PRIx64 " failed.", (uint64_t)ptr);
@@ -6269,6 +6278,7 @@ void Process::DidExec() {
   m_instrumentation_runtimes.clear();
   m_thread_list.DiscardThreadPlans();
   m_memory_cache.Clear(true);
+  m_memory_region_infos_cache.Clear();
   DoDidExec();
   CompleteAttach();
   // Flush the process (threads and all stack frames) after running
@@ -6489,10 +6499,22 @@ Status Process::GetMemoryRegionInfo(lldb::addr_t 
load_addr,
                                     MemoryRegionInfo &range_info) {
   if (const lldb::ABISP &abi = GetABI())
     load_addr = abi->FixAnyAddress(load_addr);
+
+  std::optional<MemoryRegionInfo> cached_region =
+      m_memory_region_infos_cache.GetMemoryRegion(load_addr);
+  if (cached_region) {
+    range_info = *cached_region;
+    return Status();
+  }
+
   Status error = DoGetMemoryRegionInfo(load_addr, range_info);
-  // Reject a region that does not contain the requested address.
-  if (error.Success() && !range_info.GetRange().Contains(load_addr))
-    error = Status::FromErrorString("Invalid memory region");
+  if (error.Success()) {
+    // Reject a region that does not contain the requested address.
+    if (!range_info.GetRange().Contains(load_addr))
+      error = Status::FromErrorString("Invalid memory region");
+    else
+      m_memory_region_infos_cache.AddRegion(range_info);
+  }
 
   return error;
 }

diff  --git 
a/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py 
b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
index 695faf896ef5d..f95f2cc257a10 100644
--- 
a/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
+++ 
b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
@@ -19,7 +19,9 @@ def as_packet(self):
                 + ",".join([format(a, "x") for a in self.dirty_pages])
                 + ";"
             )
-        return 
f"start:{self.start_addr:x};size:{self.size};permissions:r;{dirty_pages}"
+        return (
+            
f"start:{self.start_addr:x};size:{self.size:x};permissions:r;{dirty_pages}"
+        )
 
     def expected_command_output(self):
         if self.dirty_pages is None:

diff  --git 
a/lldb/test/API/tools/lldb-server/cached-memory-region-info/Makefile 
b/lldb/test/API/tools/lldb-server/cached-memory-region-info/Makefile
new file mode 100644
index 0000000000000..785b17c7fd698
--- /dev/null
+++ b/lldb/test/API/tools/lldb-server/cached-memory-region-info/Makefile
@@ -0,0 +1,4 @@
+CXX_SOURCES := main.cpp
+ENABLE_THREADS := YES
+include Makefile.rules
+

diff  --git 
a/lldb/test/API/tools/lldb-server/cached-memory-region-info/TestCachedMemoryRegionInfo.py
 
b/lldb/test/API/tools/lldb-server/cached-memory-region-info/TestCachedMemoryRegionInfo.py
new file mode 100644
index 0000000000000..30695b32a7b45
--- /dev/null
+++ 
b/lldb/test/API/tools/lldb-server/cached-memory-region-info/TestCachedMemoryRegionInfo.py
@@ -0,0 +1,47 @@
+"""
+Test that memory region info results are cached.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+@skipIfWindowsAndNoLLDBServer
+class MemoryRegionInfoPacketsCached(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_cached_packets(self):
+        """Test that qMemoryRegionInfo packets are cached."""
+        logfile = os.path.join(self.getBuildDir(), "log.txt")
+        self.runCmd(f"log enable -f {logfile} gdb-remote packets")
+        self.build()
+        main_source_spec = lldb.SBFileSpec("main.cpp")
+        (
+            target,
+            process,
+            _,
+            _,
+        ) = lldbutil.run_to_source_breakpoint(
+            self, "break", main_source_spec, only_one_thread=False
+        )
+
+        frame = process.GetSelectedThread().GetFrameAtIndex(0)
+        sp = frame.GetSP()
+        pc = frame.GetPC()
+        self.runCmd("memory region 0x%x" % sp)
+        self.runCmd("memory region 0x%x" % pc)
+
+        self.runCmd(f"proc plugin packet send AFTER_MRI_CMD", check=False)
+
+        # We've fetched the memory region info for $sp, now
+        # see that we don't re-fetch it.
+        self.runCmd("memory region 0x%x" % pc)
+        self.runCmd("memory region 0x%x" % (sp + 64))
+
+        self.assertTrue(os.path.exists(logfile))
+        log_text = open(logfile).read()
+
+        log_after_cmd = log_text.split("AFTER_MRI_CMD")[1]
+        self.assertNotIn("qMemoryRegionInfo", log_after_cmd)

diff  --git 
a/lldb/test/API/tools/lldb-server/cached-memory-region-info/main.cpp 
b/lldb/test/API/tools/lldb-server/cached-memory-region-info/main.cpp
new file mode 100644
index 0000000000000..dbbe365faec7c
--- /dev/null
+++ b/lldb/test/API/tools/lldb-server/cached-memory-region-info/main.cpp
@@ -0,0 +1,44 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <chrono>
+#include <thread>
+
+void sleep_for_a_tiny_bit() {
+  std::this_thread::sleep_for(std::chrono::seconds(1));
+}
+
+int var() {
+  // Make all the worker threads stop here, so we can do
+  // backtraces with a few stack frames on each thread.
+  std::this_thread::sleep_for(std::chrono::seconds(100));
+  return 0;
+}
+
+int baz() { return 10 + var(); }
+int bar() { return 15 + baz(); }
+int foo() { return 20 + bar(); }
+void work_primary_thread() {
+  // Allow all threads to get started, and get to their
+  // longer sleep()
+  sleep_for_a_tiny_bit();
+  foo(); // break here
+}
+void work() { foo(); }
+
+int main() {
+  std::thread thread_1(work_primary_thread);
+  std::thread thread_2(work);
+  std::thread thread_3(work);
+  std::thread thread_4(work);
+  std::thread thread_5(work);
+
+  std::this_thread::sleep_for(std::chrono::seconds(20));
+
+  thread_5.join();
+  thread_4.join();
+  thread_3.join();
+  thread_2.join();
+  thread_1.join();
+  return 0;
+}


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to