[Lldb-commits] [lldb] [lldb] Add a MemoryRegionInfo cache at a process stop (PR #202509)
Jason Molenda via lldb-commits
lldb-commits at lists.llvm.org
Tue Jun 9 18:50:07 PDT 2026
https://github.com/jasonmolenda updated https://github.com/llvm/llvm-project/pull/202509
>From 24424d39a3b258995bac0ff5a6366c1bcb067e3c Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmolenda at apple.com>
Date: Mon, 8 Jun 2026 22:13:52 -0700
Subject: [PATCH 1/7] [lldb] Add a MemoryRegionInfo cache at a process stop
This PR caches qMemoryRegionInfo packet results, so if we
probe the same memory region multiple times at a single stop,
we don't repeat the same requests to the debug stub.
lldb has a memory cache that increases the sizes of small memory
reads so a "cache line" of 512 bytes is read instead, and if lldb
then goes to read another bit of the memory within that cache line,
the data can be returned without communicating with the debug stub
any further. The memory cache is flushed when a process resumes/exits;
we do not try to track what memory might be valid across stops.
stubs can expedite memory that we know lldb will often try to read --
for instance, stack memory that may be needed to do a simple stack
walk when an ABI saves the frame pointer and pc to stack in prologues,
so lldb can do a basic stack walk without any memory reads.
Some code in lldb may query memory region infos when trying to
decide if an address is a valid stack or heap address, if a possible
pc value is actually in an allocated page of memory when doing a
stack walk, and so on. This can lead to issuing multiple
qMemoryRegionInfo packets for the same region when doing an operation.
Downstream swift lldb on macOS does this to determine if a frame
pointer is pointing to stack or heap (for swift async function
reasons), for instance, leading to many redundant calls.
This PR adds a very simple MemoryRegionInfoCache class, Process has
it as an ivar, flushes it on resumes, runs memory region info queries
through it and they are either sent to the Process subclass to execute,
or are fulfilled from the cache.
It adds a `memory-region-info` key to the `jThreadsInfo` packet sent
at public stop events, to get thread-specific information for all
threads in JSON. Each thread may specify one or more memory regions
that it wants to expedite to lldb, in anticipation of what lldb will
likely need. It also adds a `memory-region-info` key to the stop
info packet, and puts an asciihex representation of that same JSON
array there. I would have preferred to use key-value separated
values for it in the stop-info packet to keep with that style, but
I'd need to create a third representation of the information to make
that work. ((1) the key-value format for `qMemoryRegionInfo`'s reply,
(2) the JSON dictionary in `jThreadsInfo`, and (3) some kind of
key-value entry for the stop info packet that would include all of
the keys there or something.)
This doesn't solve any big perf issues with llvm.org main Darwin
debugging today, we don't have anything in mainline lldb querying
memory regions redundantly that I've seen, so I only added it to
the Darwin debugserver (not lldb-server) for now, primarily for
benefit of the downstream swift lldb. But if we find people wanting
to query memory regions in other parts of the debugger and looking
at the perf trade of doing that, the cache can be a helpful tool.
rdar://179067896
---
lldb/docs/resources/lldbgdbremote.md | 44 ++++++-
.../lldb/Target/MemoryRegionInfoCache.h | 42 +++++++
lldb/include/lldb/Target/Process.h | 4 +
.../Python/lldbsuite/test/lldbreverse.py | 1 +
.../GDBRemoteCommunicationClient.cpp | 6 +
.../gdb-remote/GDBRemoteCommunicationClient.h | 2 +
.../Process/gdb-remote/ProcessGDBRemote.cpp | 116 +++++++++++++++++-
lldb/source/Target/CMakeLists.txt | 1 +
lldb/source/Target/MemoryRegionInfoCache.cpp | 54 ++++++++
lldb/source/Target/Process.cpp | 33 ++---
.../TestMemoryRegionDirtyPages.py | 2 +-
.../cached-memory-region-info/Makefile | 4 +
.../TestCachedMemoryRegionInfo.py | 78 ++++++++++++
.../cached-memory-region-info/main.cpp | 29 +++++
lldb/tools/debugserver/source/DNB.cpp | 39 ++++++
lldb/tools/debugserver/source/DNB.h | 2 +
lldb/tools/debugserver/source/RNBRemote.cpp | 47 ++++++-
17 files changed, 482 insertions(+), 22 deletions(-)
create mode 100644 lldb/include/lldb/Target/MemoryRegionInfoCache.h
create mode 100644 lldb/source/Target/MemoryRegionInfoCache.cpp
create mode 100644 lldb/test/API/tools/lldb-server/cached-memory-region-info/Makefile
create mode 100644 lldb/test/API/tools/lldb-server/cached-memory-region-info/TestCachedMemoryRegionInfo.py
create mode 100644 lldb/test/API/tools/lldb-server/cached-memory-region-info/main.cpp
diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index ef42ebd6ae41c..f5be47439ab85 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -787,6 +787,22 @@ JSON and uses JSON arrays where applicable. The JSON output looks like:
{"address":140734799804592,"bytes":"c8f8bf5fff7f0000c9a59e8cff7f0000"},
{"address":140734799804616,"bytes":"00000000000000000100000000000000"}
]
+ [
+ {
+ "memory-region-info": {
+ "start": 6096306176,
+ "size": 557056,
+ "permissions": "rw",
+ "flags": [],
+ "dirty_pages": [
+ 6096830464
+ ],
+ "types": [
+ "stack"
+ ]
+ }
+ }
+ ]
}
]
```
@@ -819,6 +835,22 @@ which indicates that the register cannot be read at this current
stop point, and lldb should not try to read the register value with
a separate `p` read-register request, it will not succeed.
+`jThreadsInfo` is an array of thread-specific information, but some
+things we expedite are usually not thread-specific; this packet
+could have been structured as a Dictionary with a `threads` array
+and separate keys for non-thread specific things like this.
+
+`memory-region-info` is an array of memory region information; a
+thread may want to provide region information about both the program
+counter and the stack pointer. Note that the keys in `memory-region-info`
+like `dirty_pages` and `types` are significant in their presence
+or absence. If a stub can correctly identify which pages of memory
+have been modified (are dirty), it can include `"dirty_pages":[]`
+to indicate that no pages in this VM region are dirty. This is
+different than the absence of the `dirty_pages` key, which means
+that this stub cannot provide information about pages of memory
+being modified or not.
+
**Priority To Implement:** Low
This is a performance optimization, which speeds up debugging by avoiding
@@ -1510,9 +1542,9 @@ tuples to return are:
the file while for anonymous regions it have to be the name
associated to the region if that is available.
* `flags:<flags-string>;` - where `<flags-string>` is a space separated string
- of flag names. Currently the only supported flag
- is `mt` for AArch64 memory tagging. lldb will
- ignore any other flags in this field.
+ of flag names. Currently supported flags are
+ `mt` for AArch64 memory tagging, and
+ `ss` for shadow stack.
* `type:[<type>][,<type>];` - memory types that apply to this region, e.g.
`stack` for stack memory.
* `error:<ascii-byte-error-string>;` - where `<ascii-byte-error-string>` is
@@ -2357,6 +2389,12 @@ following keys and values:
being added at this stop are provided. The value is asciihex
encoded JSON. It must be asciihex encoded in case a filename
includes one of the gdb RSP packet metacharacters or a semicolon.
+* `memory-region-info`: An array of memory region informations for
+ this thread's stack region may be expedited. This will be an
+ asciihex encoded JSON reply with the same key-values that appear
+ in the jThreadsInfo `memory-region-info` entry. A stub may want to
+ expedite the memory region of the stack and the pc, so this is
+ an array of memory regions.
### Best Practices
diff --git a/lldb/include/lldb/Target/MemoryRegionInfoCache.h b/lldb/include/lldb/Target/MemoryRegionInfoCache.h
new file mode 100644
index 0000000000000..73eb2bd960c98
--- /dev/null
+++ b/lldb/include/lldb/Target/MemoryRegionInfoCache.h
@@ -0,0 +1,42 @@
+//===-- MemoryRegionInfoCache.h ---------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TARGET_MEMORYREGIONINFOCACHE_H
+#define LLDB_TARGET_MEMORYREGIONINFOCACHE_H
+
+#include "lldb/Target/MemoryRegionInfo.h"
+#include "lldb/Utility/RangeMap.h"
+
+namespace lldb_private {
+class MemoryRegionInfoCache {
+public:
+ MemoryRegionInfoCache(Process &process)
+ : m_region_infos(), m_process(process) {}
+
+ /// Remove all cached entries.
+ void Clear();
+
+ /// Remove cached information about region containing \a addr, if any.
+ void Flush(lldb::addr_t addr, lldb::addr_t size);
+
+ /// Locate the memory region that contains load_addr.
+ Status GetMemoryRegionInfo(lldb::addr_t load_addr,
+ MemoryRegionInfo ®ion_info);
+
+ void AddRegion(const MemoryRegionInfo ®ion_info);
+
+private:
+ typedef RangeDataVector<lldb::addr_t, lldb::addr_t,
+ lldb_private::MemoryRegionInfo>
+ InfoMap;
+ InfoMap m_region_infos;
+ Process &m_process;
+};
+} // 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 8432c326d3281..4fa11a8d60655 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"
@@ -363,6 +364,7 @@ class Process : public std::enable_shared_from_this<Process>,
friend class Target;
friend class ThreadList;
friend class MemoryCache;
+ friend class MemoryRegionInfoCache;
public:
/// Broadcaster event bits definitions.
@@ -3539,6 +3541,8 @@ void PruneThreadPlans();
Predicate<uint32_t> m_iohandler_sync;
MemoryCache m_memory_cache;
AllocatedMemoryCache m_allocated_memory_cache;
+ MemoryRegionInfoCache m_memory_region_info_cache;
+
bool m_should_detach; /// Should we detach if the process object goes away
/// with an explicit call to Kill or Detach?
LanguageRuntimeCollection m_language_runtimes;
diff --git a/lldb/packages/Python/lldbsuite/test/lldbreverse.py b/lldb/packages/Python/lldbsuite/test/lldbreverse.py
index 50b97b5597f3a..e27202693fb5a 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbreverse.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbreverse.py
@@ -252,6 +252,7 @@ def continue_with_recording(self, packet):
"mecount",
"medata",
"memory",
+ "memory-region-info",
]:
continue
raise ValueError(f"Unknown stop key '{key}' in {reply}")
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 8f7bf296e0d95..f75dfede6af5d 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -201,6 +201,12 @@ uint64_t GDBRemoteCommunicationClient::GetRemoteMaxPacketSize() {
return m_max_packet_size;
}
+uint32_t GDBRemoteCommunicationClient::GetRemotePageSize() {
+ if (m_target_vm_page_size == 0)
+ GetRemoteQSupported();
+ return m_target_vm_page_size;
+}
+
bool GDBRemoteCommunicationClient::GetReverseContinueSupported() {
if (m_supports_reverse_continue == eLazyBoolCalculate)
GetRemoteQSupported();
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 5fa7057be2625..4416620a14ec9 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -332,6 +332,8 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
uint64_t GetRemoteMaxPacketSize();
+ uint32_t GetRemotePageSize();
+
bool GetEchoSupported();
bool GetQPassSignalsSupported();
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 16284f0052f5e..b794090019c16 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -394,6 +394,103 @@ bool ProcessGDBRemote::ParsePythonTargetDefinition(
return false;
}
+static bool ParseMemoryRegionInfoJSON(StructuredData::Object *json,
+ MemoryRegionInfo &ri,
+ uint32_t page_size) {
+ ri.Clear();
+ if (!json || !json->GetAsArray())
+ return false;
+ bool returnval = true;
+ json->GetAsArray()->ForEach(
+ [&ri, page_size, &returnval](StructuredData::Object *obj) -> bool {
+ StructuredData::Dictionary *dict = obj->GetAsDictionary();
+ if (!dict)
+ returnval = false;
+
+ if (!dict->HasKey("start") || !dict->HasKey("size") ||
+ !dict->HasKey("permissions"))
+ returnval = false;
+
+ ri.GetRange().SetRangeBase(
+ dict->GetValueForKey("start")->GetUnsignedIntegerValue());
+ ri.GetRange().SetByteSize(
+ dict->GetValueForKey("size")->GetUnsignedIntegerValue());
+ llvm::StringRef permissions =
+ dict->GetValueForKey("permissions")->GetStringValue();
+ if (permissions.contains('r'))
+ ri.SetReadable(eLazyBoolYes);
+ else
+ ri.SetReadable(eLazyBoolNo);
+
+ if (permissions.contains('w'))
+ ri.SetWritable(eLazyBoolYes);
+ else
+ ri.SetWritable(eLazyBoolNo);
+
+ if (permissions.contains('x'))
+ ri.SetExecutable(eLazyBoolYes);
+ else
+ ri.SetExecutable(eLazyBoolNo);
+
+ if (!permissions.empty())
+ ri.SetMapped(eLazyBoolYes);
+ else
+ ri.SetMapped(eLazyBoolNo);
+
+ if (dict->HasKey("flags")) {
+ dict->GetValueForKey("flags")->GetAsArray()->ForEach(
+ [&ri](StructuredData::Object *obj) -> bool {
+ if (!obj->GetAsString())
+ return true;
+ if (obj->GetAsString()->GetValue() == "mt")
+ ri.SetMemoryTagged(eLazyBoolYes);
+ if (obj->GetAsString()->GetValue() == "ss")
+ ri.SetIsShadowStack(eLazyBoolYes);
+ return true;
+ });
+ }
+
+ if (dict->HasKey("name") && dict->GetValueForKey("name")->GetAsString())
+ ri.SetName(dict->GetValueForKey("name")
+ ->GetAsString()
+ ->GetValue()
+ .str()
+ .c_str());
+
+ if (dict->HasKey("dirty_pages") &&
+ dict->GetValueForKey("dirty_pages")->GetAsArray()) {
+ std::vector<addr_t> dirty_pages;
+ dict->GetValueForKey("dirty_pages")
+ ->GetAsArray()
+ ->ForEach([&dirty_pages](StructuredData::Object *obj) -> bool {
+ if (!obj->GetAsUnsignedInteger())
+ return true;
+ dirty_pages.push_back(
+ obj->GetAsUnsignedInteger()->GetUnsignedIntegerValue());
+ return true;
+ });
+ }
+
+ if (dict->HasKey("types")) {
+ dict->GetValueForKey("types")->GetAsArray()->ForEach(
+ [&ri](StructuredData::Object *obj) -> bool {
+ if (!obj->GetAsString())
+ return true;
+ if (obj->GetAsString()->GetValue() == "stack")
+ ri.SetIsStackMemory(eLazyBoolYes);
+ return true;
+ });
+ }
+
+ if (page_size != 0)
+ ri.SetPageSize(page_size);
+
+ return true;
+ });
+
+ return true;
+}
+
static size_t SplitCommaSeparatedRegisterNumberString(
const llvm::StringRef &comma_separated_register_numbers,
std::vector<uint32_t> ®nums, int base) {
@@ -2172,6 +2269,8 @@ ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
static constexpr llvm::StringLiteral g_key_queue_serial_number("qserialnum");
static constexpr llvm::StringLiteral g_key_registers("registers");
static constexpr llvm::StringLiteral g_key_memory("memory");
+ static constexpr llvm::StringLiteral g_key_memory_region_info(
+ "memory-region-info");
static constexpr llvm::StringLiteral g_key_description("description");
static constexpr llvm::StringLiteral g_key_signal("signal");
static constexpr llvm::StringLiteral g_key_added_binaries("added-binaries");
@@ -2304,6 +2403,10 @@ ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
return true; // Keep iterating through all array items
});
}
+ } else if (key == g_key_memory_region_info) {
+ MemoryRegionInfo ri;
+ if (ParseMemoryRegionInfoJSON(object, ri, m_gdb_comm.GetRemotePageSize()))
+ m_memory_region_info_cache.AddRegion(ri);
} else if (key == g_key_signal)
signo = object->GetUnsignedIntegerValue(LLDB_INVALID_SIGNAL_NUMBER);
else if (key == g_key_added_binaries) {
@@ -2489,6 +2592,17 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
m_memory_cache.AddL1CacheData(mem_cache_addr, data_buffer_sp);
}
}
+ } else if (key.compare("memory-region-info") == 0) {
+ StringExtractor json_extractor(value);
+ std::string json;
+ // Now convert the HEX bytes into a string value.
+ json_extractor.GetHexByteString(json);
+ MemoryRegionInfo ri;
+ StructuredData::ObjectSP json_sp = StructuredData::ParseJSON(json);
+ if (ParseMemoryRegionInfoJSON(json_sp.get(), ri,
+ m_gdb_comm.GetRemotePageSize())) {
+ m_memory_region_info_cache.AddRegion(ri);
+ }
} else if (key.compare("watch") == 0 || key.compare("rwatch") == 0 ||
key.compare("awatch") == 0) {
// Support standard GDB remote stop reply packet 'TAAwatch:addr'
@@ -2561,7 +2675,7 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
// Now convert the HEX bytes into a string value.
json_extractor.GetHexByteString(json);
- // This JSON contains detailed information about binares.
+ // This JSON contains detailed information about binaries.
detailed_binaries_info = StructuredData::ParseJSON(json);
} else if (key.size() == 2 && ::isxdigit(key[0]) && ::isxdigit(key[1])) {
uint32_t reg = UINT32_MAX;
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..68df8a8c7b837
--- /dev/null
+++ b/lldb/source/Target/MemoryRegionInfoCache.cpp
@@ -0,0 +1,54 @@
+//===-- MemoryRegionInfoCache.cpp -----------------------------------------===//
+//
+// 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/Core/AddressRange.h"
+#include "lldb/Target/MemoryRegionInfo.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Utility/Status.h"
+
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+void MemoryRegionInfoCache::Clear() { m_region_infos.Clear(); }
+
+void MemoryRegionInfoCache::Flush(addr_t load_addr, addr_t size) {
+ m_region_infos.Erase(load_addr, size);
+ m_region_infos.Sort();
+}
+
+Status
+MemoryRegionInfoCache::GetMemoryRegionInfo(lldb::addr_t load_addr,
+ MemoryRegionInfo ®ion_info) {
+ uint32_t index = m_region_infos.FindEntryIndexThatContains(load_addr);
+ if (index != UINT32_MAX) {
+ region_info = m_region_infos.GetEntryAtIndex(index)->data;
+ return Status();
+ }
+
+ load_addr = m_process.FixAnyAddress(load_addr);
+ Status error = m_process.DoGetMemoryRegionInfo(load_addr, region_info);
+ // Reject a region that does not contain the requested address.
+ if (error.Success() && !region_info.GetRange().Contains(load_addr))
+ error = Status::FromErrorString("Invalid memory region");
+
+ if (error.Success())
+ AddRegion(region_info);
+
+ return error;
+}
+
+void MemoryRegionInfoCache::AddRegion(const MemoryRegionInfo &ri) {
+ InfoMap::Entry new_entry(ri.GetRange().GetRangeBase(),
+ ri.GetRange().GetByteSize(), ri);
+ m_region_infos.Append(new_entry);
+ m_region_infos.Sort();
+}
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 6e20703f65a45..551b35b8c444f 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -480,13 +480,14 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp,
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_region_info_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 +596,7 @@ void Process::Finalize(bool destructing) {
m_notifications.swap(empty_notifications);
m_image_tokens.clear();
m_memory_cache.Clear();
+ m_memory_region_info_cache.Clear();
m_allocated_memory_cache.Clear(/*deallocate_memory=*/true);
{
std::lock_guard<std::recursive_mutex> guard(m_language_runtimes_mutex);
@@ -1458,6 +1460,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_info_cache.Clear();
LLDB_LOGF(log, "(plugin = %s, state = %s, stop_id = %u",
GetPluginName().data(), StateAsCString(new_state),
m_mod_id.GetStopID());
@@ -2696,8 +2699,11 @@ addr_t Process::AllocateMemory(size_t size, uint32_t permissions,
"cannot allocate memory while process is running");
return LLDB_INVALID_ADDRESS;
}
+ addr_t alloced_addr =
+ m_allocated_memory_cache.AllocateMemory(size, permissions, error);
+ m_memory_region_info_cache.Flush(alloced_addr, size);
- return m_allocated_memory_cache.AllocateMemory(size, permissions, error);
+ return alloced_addr;
}
addr_t Process::CallocateMemory(size_t size, uint32_t permissions,
@@ -2754,6 +2760,7 @@ Status Process::DeallocateMemory(addr_t ptr) {
error = Status::FromErrorStringWithFormat(
"deallocation of memory at 0x%" PRIx64 " failed.", (uint64_t)ptr);
}
+ m_memory_region_info_cache.Flush(ptr, ptr + 1);
return error;
}
@@ -6269,6 +6276,7 @@ void Process::DidExec() {
m_instrumentation_runtimes.clear();
m_thread_list.DiscardThreadPlans();
m_memory_cache.Clear(true);
+ m_memory_region_info_cache.Clear();
DoDidExec();
CompleteAttach();
// Flush the process (threads and all stack frames) after running
@@ -6487,14 +6495,7 @@ Process::AdvanceAddressToNextBranchInstruction(Address default_stop_addr,
Status Process::GetMemoryRegionInfo(lldb::addr_t load_addr,
MemoryRegionInfo &range_info) {
- if (const lldb::ABISP &abi = GetABI())
- load_addr = abi->FixAnyAddress(load_addr);
- 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");
-
- return error;
+ return m_memory_region_info_cache.GetMemoryRegionInfo(load_addr, range_info);
}
Status Process::GetMemoryRegions(lldb_private::MemoryRegionInfos ®ion_list) {
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
index 695faf896ef5d..512d6c88249ee 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
@@ -19,7 +19,7 @@ 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..ffd2109c59a25
--- /dev/null
+++ b/lldb/test/API/tools/lldb-server/cached-memory-region-info/TestCachedMemoryRegionInfo.py
@@ -0,0 +1,78 @@
+"""
+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
+
+
+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)
+
+ @skipIfOutOfTreeDebugserver
+ @skipUnlessDarwin
+ def test_expedited_stack_memory_region(self):
+ """Test that the stack memory region is expedited from stub so we don't need to fetch it."""
+ 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
+ )
+
+ self.runCmd(f"proc plugin packet send AFTER_STOP", check=False)
+
+ # Force lldb to walk all the stacks of all the threads.
+ # Should not see any qMemoryRegionInfo packets.
+ for th in process.threads:
+ th.GetNumFrames()
+ for f in th.frames:
+ self.runCmd("memory region 0x%x" % f.GetSP())
+
+ self.assertTrue(os.path.exists(logfile))
+ log_text = open(logfile).read()
+
+ log_after_cmd = log_text.split("AFTER_STOP")[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..43fc9dc5b1f79
--- /dev/null
+++ b/lldb/test/API/tools/lldb-server/cached-memory-region-info/main.cpp
@@ -0,0 +1,29 @@
+#include <thread>
+#include <unistd.h>
+
+int var() {
+ sleep(100); // break here
+}
+int baz() { return 10 + var(); }
+int bar() { return 15 + baz(); }
+int foo() { return 20 + bar(); }
+void work () {
+ foo();
+}
+
+int main()
+{
+ std::thread thread_1(work);
+ std::thread thread_2(work);
+ std::thread thread_3(work);
+ std::thread thread_4(work);
+ std::thread thread_5(work);
+
+ sleep (20);
+
+ thread_5.join();
+ thread_4.join();
+ thread_3.join();
+ thread_2.join();
+ thread_1.join();
+}
diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp
index 1f13e70a12546..55f567293cd49 100644
--- a/lldb/tools/debugserver/source/DNB.cpp
+++ b/lldb/tools/debugserver/source/DNB.cpp
@@ -1389,6 +1389,45 @@ int DNBProcessMemoryRegionInfo(nub_process_t pid, nub_addr_t addr,
return -1;
}
+JSONGenerator::ObjectSP DNBProcessMemoryRegionInfo(nub_process_t pid,
+ nub_addr_t addr) {
+ JSONGenerator::DictionarySP result_sp;
+ DNBRegionInfo ri;
+ if (DNBProcessMemoryRegionInfo(pid, addr, &ri) == -1)
+ return result_sp;
+
+ result_sp = std::make_shared<JSONGenerator::Dictionary>();
+ result_sp->AddIntegerItem("start", ri.addr);
+ result_sp->AddIntegerItem("size", ri.size);
+ std::string permissions;
+ if (ri.permissions & eMemoryPermissionsReadable)
+ permissions += 'r';
+ if (ri.permissions & eMemoryPermissionsWritable)
+ permissions += 'w';
+ if (ri.permissions & eMemoryPermissionsExecutable)
+ permissions += 'x';
+ result_sp->AddStringItem("permissions", permissions);
+
+ JSONGenerator::ArraySP flags_sp = std::make_shared<JSONGenerator::Array>();
+ for (std::string flag : ri.flags)
+ flags_sp->AddItem(std::make_shared<JSONGenerator::String>(flag));
+ result_sp->AddItem("flags", flags_sp);
+
+ JSONGenerator::ArraySP dirty_pages_sp =
+ std::make_shared<JSONGenerator::Array>();
+ for (nub_addr_t dirty_page : ri.dirty_pages)
+ dirty_pages_sp->AddItem(
+ std::make_shared<JSONGenerator::Integer>(dirty_page));
+ result_sp->AddItem("dirty_pages", dirty_pages_sp);
+
+ JSONGenerator::ArraySP vm_types_sp = std::make_shared<JSONGenerator::Array>();
+ for (std::string vmtype : ri.vm_types)
+ vm_types_sp->AddItem(std::make_shared<JSONGenerator::String>(vmtype));
+ result_sp->AddItem("types", vm_types_sp);
+
+ return result_sp;
+}
+
nub_bool_t DNBProcessGetMemoryTags(nub_process_t pid, nub_addr_t addr,
nub_size_t size,
std::vector<uint8_t> &tags) {
diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h
index ccdc5fe1750e9..ad09322f1f3bb 100644
--- a/lldb/tools/debugserver/source/DNB.h
+++ b/lldb/tools/debugserver/source/DNB.h
@@ -105,6 +105,8 @@ nub_bool_t DNBProcessMemoryDeallocate(nub_process_t pid,
nub_addr_t addr) DNB_EXPORT;
int DNBProcessMemoryRegionInfo(nub_process_t pid, nub_addr_t addr,
DNBRegionInfo *region_info) DNB_EXPORT;
+JSONGenerator::ObjectSP DNBProcessMemoryRegionInfo(nub_process_t pid,
+ nub_addr_t addr) DNB_EXPORT;
nub_bool_t DNBProcessGetMemoryTags(nub_process_t pid, nub_addr_t addr,
nub_size_t size,
std::vector<uint8_t> &tags) DNB_EXPORT;
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 1c7c3eb8e41d7..61df11dcdcca7 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -3024,6 +3024,31 @@ rnb_err_t RNBRemote::SendStopReplyPacketForThread(nub_thread_t tid) {
}
}
+ DNBRegisterValue sp_regval;
+ if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC,
+ GENERIC_REGNUM_SP, &sp_regval)) {
+ uint64_t sp = INVALID_NUB_ADDRESS;
+ if (sp_regval.value.uint64 != INVALID_NUB_ADDRESS) {
+ if (sp_regval.info.size == 4)
+ sp = sp_regval.value.uint32;
+ else if (sp_regval.info.size == 8)
+ sp = sp_regval.value.uint64;
+ if (sp != INVALID_NUB_ADDRESS) {
+ if (JSONGenerator::ObjectSP obj_sp =
+ DNBProcessMemoryRegionInfo(pid, sp)) {
+ JSONGenerator::ArraySP containing_arr_sp =
+ std::make_shared<JSONGenerator::Array>();
+ containing_arr_sp->AddItem(obj_sp);
+ ostrm << "memory-region-info:";
+ std::ostringstream json_strm;
+ containing_arr_sp->Dump(json_strm);
+ append_hexified_string(ostrm, json_strm.str());
+ ostrm << ';';
+ }
+ }
+ }
+ }
+
return SendPacket(ostrm.str());
}
return SendErrorPacket("E51");
@@ -5894,8 +5919,28 @@ RNBRemote::GetJSONThreadsInfo(bool threads_with_valid_stop_info_only) {
thread_dict_sp->AddItem("detailed-binaries-info",
detailed_binary_infos);
}
- }
+ DNBRegisterValue sp_regval;
+ if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC,
+ GENERIC_REGNUM_SP, &sp_regval)) {
+ nub_addr_t sp = INVALID_NUB_ADDRESS;
+ if (sp_regval.value.uint64 != INVALID_NUB_ADDRESS) {
+ if (sp_regval.info.size == 4)
+ sp = sp_regval.value.uint32;
+ else if (sp_regval.info.size == 8)
+ sp = sp_regval.value.uint64;
+ }
+ if (sp != INVALID_NUB_ADDRESS) {
+ if (JSONGenerator::ObjectSP obj_sp =
+ DNBProcessMemoryRegionInfo(pid, sp)) {
+ JSONGenerator::ArraySP containing_arr_sp =
+ std::make_shared<JSONGenerator::Array>();
+ containing_arr_sp->AddItem(obj_sp);
+ thread_dict_sp->AddItem("memory-region-info", containing_arr_sp);
+ }
+ }
+ }
+ }
threads_array_sp->AddItem(thread_dict_sp);
}
}
>From 215aa744a8c82ab9e8e2465aeca435e161fe48cd Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmolenda at apple.com>
Date: Mon, 8 Jun 2026 22:55:42 -0700
Subject: [PATCH 2/7] ws
---
.../lldb-server/cached-memory-region-info/main.cpp | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
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
index 43fc9dc5b1f79..badf0b0fa94fd 100644
--- 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
@@ -1,25 +1,22 @@
#include <thread>
#include <unistd.h>
-int var() {
+int var() {
sleep(100); // break here
}
int baz() { return 10 + var(); }
int bar() { return 15 + baz(); }
int foo() { return 20 + bar(); }
-void work () {
- foo();
-}
+void work() { foo(); }
-int main()
-{
+int main() {
std::thread thread_1(work);
std::thread thread_2(work);
std::thread thread_3(work);
std::thread thread_4(work);
std::thread thread_5(work);
- sleep (20);
+ sleep(20);
thread_5.join();
thread_4.join();
>From 723eb2ea1271a9a84871ad067f915944a80fd792 Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmolenda at apple.com>
Date: Mon, 8 Jun 2026 23:39:01 -0700
Subject: [PATCH 3/7] ws fix, windows fix
---
.../gdb_remote_client/TestMemoryRegionDirtyPages.py | 4 +++-
.../API/tools/lldb-server/cached-memory-region-info/main.cpp | 4 +++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py b/lldb/test/API/functionalities/gdb_remote_client/TestMemoryRegionDirtyPages.py
index 512d6c88249ee..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:x};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/main.cpp b/lldb/test/API/tools/lldb-server/cached-memory-region-info/main.cpp
index badf0b0fa94fd..7c49d2da59d8b 100644
--- 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
@@ -1,5 +1,7 @@
+#include <stdio.h>
+#include <stdlib.h>
+
#include <thread>
-#include <unistd.h>
int var() {
sleep(100); // break here
>From 7dbcc48e6ed8d18d3b3b7288655d3b5ac075aa97 Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmolenda at apple.com>
Date: Tue, 9 Jun 2026 00:19:46 -0700
Subject: [PATCH 4/7] give up on sleep, cross platform that one is a mess
---
.../API/tools/lldb-server/cached-memory-region-info/main.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
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
index 7c49d2da59d8b..9cff5dfa27062 100644
--- 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
@@ -1,10 +1,11 @@
#include <stdio.h>
#include <stdlib.h>
+#include <chrono>
#include <thread>
int var() {
- sleep(100); // break here
+ std::this_thread::sleep_for(std::chrono::seconds(100)); // break here
}
int baz() { return 10 + var(); }
int bar() { return 15 + baz(); }
@@ -18,7 +19,7 @@ int main() {
std::thread thread_4(work);
std::thread thread_5(work);
- sleep(20);
+ std::this_thread::sleep_for(std::chrono::seconds(20));
thread_5.join();
thread_4.join();
>From 509d84f212345801d7591875290bf77f6a7ef4cc Mon Sep 17 00:00:00 2001
From: Jason Molenda <github-mail at molenda.com>
Date: Tue, 9 Jun 2026 10:08:09 -0700
Subject: [PATCH 5/7] Update
lldb/test/API/tools/lldb-server/cached-memory-region-info/TestCachedMemoryRegionInfo.py
Co-authored-by: Charles Zablit <c_zablit at apple.com>
---
.../cached-memory-region-info/TestCachedMemoryRegionInfo.py | 1 +
1 file changed, 1 insertion(+)
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
index ffd2109c59a25..5725aff932dac 100644
--- 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
@@ -8,6 +8,7 @@
from lldbsuite.test import lldbutil
+ at skipIfWindowsAndNoLLDBServer
class MemoryRegionInfoPacketsCached(TestBase):
NO_DEBUG_INFO_TESTCASE = True
>From 04c503086ac9c99917eb8714773dd45b984571cb Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmolenda at apple.com>
Date: Tue, 9 Jun 2026 18:03:07 -0700
Subject: [PATCH 6/7] fix JSON example in docs. Change `memory-region-info` to
`memory-region-infos` everywhere, to reflect that this is an array.
---
lldb/docs/resources/lldbgdbremote.md | 32 +++++++++----------
.../Python/lldbsuite/test/lldbreverse.py | 2 +-
.../Process/gdb-remote/ProcessGDBRemote.cpp | 4 +--
lldb/tools/debugserver/source/RNBRemote.cpp | 4 +--
4 files changed, 20 insertions(+), 22 deletions(-)
diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index efcfbc903073e..cb87bb6455087 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -787,20 +787,18 @@ JSON and uses JSON arrays where applicable. The JSON output looks like:
{"address":140734799804592,"bytes":"c8f8bf5fff7f0000c9a59e8cff7f0000"},
{"address":140734799804616,"bytes":"00000000000000000100000000000000"}
]
- [
+ "memory-region-infos": [
{
- "memory-region-info": {
- "start": 6096306176,
- "size": 557056,
- "permissions": "rw",
- "flags": [],
- "dirty_pages": [
- 6096830464
- ],
- "types": [
- "stack"
- ]
- }
+ "start": 6096306176,
+ "size": 557056,
+ "permissions": "rw",
+ "flags": [],
+ "dirty_pages": [
+ 6096830464
+ ],
+ "types": [
+ "stack"
+ ]
}
]
}
@@ -840,9 +838,9 @@ things we expedite are usually not thread-specific; this packet
could have been structured as a Dictionary with a `threads` array
and separate keys for non-thread specific things like this.
-`memory-region-info` is an array of memory region information; a
+`memory-region-infos` is an array of memory region information; a
thread may want to provide region information about both the program
-counter and the stack pointer. Note that the keys in `memory-region-info`
+counter and the stack pointer. Note that the keys in `memory-region-infos`
like `dirty_pages` and `types` are significant in their presence
or absence. If a stub can correctly identify which pages of memory
have been modified (are dirty), it can include `"dirty_pages":[]`
@@ -2419,10 +2417,10 @@ following keys and values:
being added at this stop are provided. The value is asciihex
encoded JSON. It must be asciihex encoded in case a filename
includes one of the gdb RSP packet metacharacters or a semicolon.
-* `memory-region-info`: An array of memory region informations for
+* `memory-region-infos`: An array of memory region informations for
this thread's stack region may be expedited. This will be an
asciihex encoded JSON reply with the same key-values that appear
- in the jThreadsInfo `memory-region-info` entry. A stub may want to
+ in the jThreadsInfo `memory-region-infos` entry. A stub may want to
expedite the memory region of the stack and the pc, so this is
an array of memory regions.
diff --git a/lldb/packages/Python/lldbsuite/test/lldbreverse.py b/lldb/packages/Python/lldbsuite/test/lldbreverse.py
index e27202693fb5a..e2897e6b16764 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbreverse.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbreverse.py
@@ -252,7 +252,7 @@ def continue_with_recording(self, packet):
"mecount",
"medata",
"memory",
- "memory-region-info",
+ "memory-region-infos",
]:
continue
raise ValueError(f"Unknown stop key '{key}' in {reply}")
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index c5aef6c30920e..01954d8d2fe2b 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -2296,7 +2296,7 @@ ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
static constexpr llvm::StringLiteral g_key_registers("registers");
static constexpr llvm::StringLiteral g_key_memory("memory");
static constexpr llvm::StringLiteral g_key_memory_region_info(
- "memory-region-info");
+ "memory-region-infos");
static constexpr llvm::StringLiteral g_key_description("description");
static constexpr llvm::StringLiteral g_key_signal("signal");
static constexpr llvm::StringLiteral g_key_added_binaries("added-binaries");
@@ -2618,7 +2618,7 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
m_memory_cache.AddL1CacheData(mem_cache_addr, data_buffer_sp);
}
}
- } else if (key.compare("memory-region-info") == 0) {
+ } else if (key.compare("memory-region-infos") == 0) {
StringExtractor json_extractor(value);
std::string json;
// Now convert the HEX bytes into a string value.
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 61df11dcdcca7..3ae793d077a0d 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -3039,7 +3039,7 @@ rnb_err_t RNBRemote::SendStopReplyPacketForThread(nub_thread_t tid) {
JSONGenerator::ArraySP containing_arr_sp =
std::make_shared<JSONGenerator::Array>();
containing_arr_sp->AddItem(obj_sp);
- ostrm << "memory-region-info:";
+ ostrm << "memory-region-infos:";
std::ostringstream json_strm;
containing_arr_sp->Dump(json_strm);
append_hexified_string(ostrm, json_strm.str());
@@ -5936,7 +5936,7 @@ RNBRemote::GetJSONThreadsInfo(bool threads_with_valid_stop_info_only) {
JSONGenerator::ArraySP containing_arr_sp =
std::make_shared<JSONGenerator::Array>();
containing_arr_sp->AddItem(obj_sp);
- thread_dict_sp->AddItem("memory-region-info", containing_arr_sp);
+ thread_dict_sp->AddItem("memory-region-infos", containing_arr_sp);
}
}
}
>From aec40e0e444bf3f8dfc8b27fdc55c7f423e2ddcf Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmolenda at apple.com>
Date: Tue, 9 Jun 2026 18:49:29 -0700
Subject: [PATCH 7/7] In the `memory-region-infos` documentation, more
explicitly link to the `qMemoryRegionInfo` documentation.
---
lldb/docs/resources/lldbgdbremote.md | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index cb87bb6455087..c8fa2feaa8734 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -840,14 +840,17 @@ and separate keys for non-thread specific things like this.
`memory-region-infos` is an array of memory region information; a
thread may want to provide region information about both the program
-counter and the stack pointer. Note that the keys in `memory-region-infos`
-like `dirty_pages` and `types` are significant in their presence
-or absence. If a stub can correctly identify which pages of memory
-have been modified (are dirty), it can include `"dirty_pages":[]`
-to indicate that no pages in this VM region are dirty. This is
-different than the absence of the `dirty_pages` key, which means
-that this stub cannot provide information about pages of memory
-being modified or not.
+counter and the stack pointer. The keys and values are the same
+as the `qMemoryRegionInfo`, except expressed in JSON key-value
+format.
+
+Note that the keys in a `memory-region-infos` entry like `dirty_pages`
+and `types` are significant in their presence or absence. If a
+stub can correctly identify which pages of memory have been modified
+(are dirty), it can include `"dirty_pages":[]` to indicate that no
+pages in this VM region are dirty. This is different than the
+absence of the `dirty_pages` key, which means that this stub cannot
+provide information about pages of memory being modified or not.
**Priority To Implement:** Low
More information about the lldb-commits
mailing list