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 <[email protected]> Date: Mon, 8 Jun 2026 22:13:52 -0700 Subject: [PATCH 1/4] [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 <[email protected]> Date: Mon, 8 Jun 2026 22:55:42 -0700 Subject: [PATCH 2/4] 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 <[email protected]> Date: Mon, 8 Jun 2026 23:39:01 -0700 Subject: [PATCH 3/4] 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 <[email protected]> Date: Tue, 9 Jun 2026 00:19:46 -0700 Subject: [PATCH 4/4] 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(); _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
