[Lldb-commits] [lldb] [lldb][Darwin] debugserver expedite new binary info, lldb use (PR #192754)

Jason Molenda via lldb-commits lldb-commits at lists.llvm.org
Fri Apr 17 16:46:54 PDT 2026


https://github.com/jasonmolenda created https://github.com/llvm/llvm-project/pull/192754

When lldb stops at the "new binaries loaded" internal breakpoint, it must read the list of addresses of the new binaries out of process memory, then send a jGetLoadedDynamicLibrariesInfos packet to debugserver to get the filepath, uuid, and addresses of where all the segments are loaded in memory.  It is possible for debugserver to independently find the address of the "new binaries loaded" function in dyld in the inferior, and tell when it is stopped at that breakpoint.  Finding the number of binaries, and the address of the binaries, is a simple matter of looking at the argument registers.

This PR reduces the packet traffic for a new binary load notifications by

1. When debugserver sees a thread that has hit a breakpoint, and the pc matches the new-binaries-loaded function address, reads the list of binaries that have been newly added and includes them in the stop info packet (or the jThreadsInfo packet) in the `added-binaries` key. The value is a list (array) of binary addresses.

2. If the number of binaries is small (today: one), debugserver may collect the full information that jGetLoadedDynamicLibrariesInfos would send back about it, and also expedite that in the stop info packet (or jThreadsInfo) if the same conditions are met.  The stop info packet is a semicolon separated series of key-values, and the JSON string may contain a semicolon or one of the other gdb remote special characters, so it is included in asciihex format, just like the jstopinfo key that we already send in the stop packet.  In the jThreadsInfo packet, the JSON for the binary information is included in the response as-is.

3. If debugserver isn't given the newly-added-binaries, we will use the same process as before.  However, in
DynamicLoaderMacOS::NotifyBreakpointHit I was reading the load addresses out of memory individually, with each binary having a 24-byte entry.  lldb's memory cache meant we read 512 bytes per 8-byte read, but when 1000 binaries were being loaded at process launch time, that was 24,000 bytes of VM that we would read in 512 byte batches.  This patch changes that to read the entire VM range that we will be accessing in a large memory read (as large as the remote gdb RSP stub will support), dramatically reducing packet traffic in that case.

4. debugserver needs to read the "new binaries loaded" function pointer out of the "dyld_all_image_infos" structure in the inferior, and it is a signed function pointer on arm64e processes, so debugserver needs to strip off the signing bits before comparing the pc.  I hoisted the strip function out of DNBArchImplArm64.cpp into DNBFixAddress(), and the only complicated bit here is in DNBProcessAddrSize(), when an arm64e debugserver is debugging an arm64_32 process on a watch.  It's not a common combination (mostly we will have arm64e debugservers debugging arm64 processes, or arm64_32 debugservers debuggging arm64_32 processe) but it is supported.

5. A very minor enhancement, I have debugserver now include `sizeof_mh_and_loadcmds` in the full binary information that jGetLoadedDynamicLibrariesInfos returns.  When lldb needs to read a binary out of memory, it needs to start by reading the mach header & load commands, and it doesn't know the full size of that, so we end up doing one read of the mach header, then the header + load commands.  I'm not using this information in lldb yet, but I would like to, to improve that.

At an implementation detail level, ProcessGDBRemote collects these two new data from the stop packet / jThreadsInfo, and passes them to the method that creates a new ThreadGDBRemote.  I added two methods to the Thread base class to retrieve the information, if it has been set.  DynamicLoaderMacOS tries to read them from the thread that hit the "new binaries loaded" breakpoint, and if the number of entries matches the number expected by the register values, uses them.  Else it falls back to fetching them the traditional way.  On an old debugserver that doesn't support these new expedited fields, DynamicLoaderMacOS will get back a zero-length of binary addresses and a null StructuredData dictionary for the detailed image information, and behave as it always does.  I tested this patch with both the debugserver changes, and without.

Testing is clearly the big questionmark here - I added none.  While writing these patches, I had some bugs and the lldb testsuite on macOS was very good at finding them, simply with our normal process launching and dlopen'ing in our existing API tests.  I could imagine a test that would capture the packet log and try to ensure that the expedited information is being used by lldb and we are not re-fetching the information, though.

rdar://175033129

>From 4b6d9218f57020f07cc6d2308b2c9ea5735e2489 Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmolenda at apple.com>
Date: Fri, 17 Apr 2026 16:26:49 -0700
Subject: [PATCH] [lldb][Darwin] debugserver expedite new binary info, lldb use

When lldb stops at the "new binaries loaded" internal breakpoint,
it must read the list of addresses of the new binaries out of process
memory, then send a jGetLoadedDynamicLibrariesInfos packet to
debugserver to get the filepath, uuid, and addresses of where all
the segments are loaded in memory.  It is possible for debugserver
to independently find the address of the "new binaries loaded"
function in dyld in the inferior, and tell when it is stopped at
that breakpoint.  Finding the number of binaries, and the address
of the binaries, is a simple matter of looking at the argument
registers.

This PR reduces the packet traffic for a new binary load notifications
by

1. When debugserver sees a thread that has hit a breakpoint, and the
pc matches the new-binaries-loaded function address, reads the list of
binaries that have been newly added and includes them in the stop info
packet (or the jThreadsInfo packet) in the `added-binaries` key.
The value is a list (array) of binary addresses.

2. If the number of binaries is small (today: one), debugserver may
collect the full information that jGetLoadedDynamicLibrariesInfos would
send back about it, and also expedite that in the stop info packet (or
jThreadsInfo) if the same conditions are met.  The stop info packet is
a semicolon separated series of key-values, and the JSON string may
contain a semicolon or one of the other gdb remote special characters,
so it is included in asciihex format, just like the jstopinfo key
that we already send in the stop packet.  In the jThreadsInfo packet,
the JSON for the binary information is included in the response as-is.

3. If debugserver isn't given the newly-added-binaries, we will use
the same process as before.  However, in
DynamicLoaderMacOS::NotifyBreakpointHit I was reading the load
addresses out of memory individually, with each binary having a
24-byte entry.  lldb's memory cache meant we read 512 bytes per
8-byte read, but when 1000 binaries were being loaded at process
launch time, that was 24,000 bytes of VM that we would read in 512
byte batches.  This patch changes that to read the entire VM range
that we will be accessing in a large memory read (as large as the
remote gdb RSP stub will support), dramatically reducing packet
traffic in that case.

4. debugserver needs to read the "new binaries loaded" function
pointer out of the "dyld_all_image_infos" structure in the inferior,
and it is a signed function pointer on arm64e processes, so debugserver
needs to strip off the signing bits before comparing the pc.  I
hoisted the strip function out of DNBArchImplArm64.cpp into
DNBFixAddress(), and the only complicated bit here is in
DNBProcessAddrSize(), when an arm64e debugserver is debugging an
arm64_32 process on a watch.  It's not a common combination (mostly
we will have arm64e debugservers debugging arm64 processes, or
arm64_32 debugservers debuggging arm64_32 processe) but it is
supported.

5. A very minor enhancement, I have debugserver now include
`sizeof_mh_and_loadcmds` in the full binary information that
jGetLoadedDynamicLibrariesInfos returns.  When lldb needs to read
a binary out of memory, it needs to start by reading the mach header
& load commands, and it doesn't know the full size of that, so we
end up doing one read of the mach header, then the header + load
commands.  I'm not using this information in lldb yet, but I would
like to, to improve that.

Testing is clearly the big questionmark here - I added none.  While
writing these patches, I had some bugs and the lldb testsuite on
macOS was very good at finding them, simply with our normal process
launching and dlopen'ing in our existing API tests.  I could imagine
a test that would capture the packet log and try to ensure that the
expedited information is being used by lldb and we are not re-fetching
the information, though.

rdar://175033129
---
 lldb/docs/resources/lldbgdbremote.md          |  50 +++++-
 lldb/include/lldb/Target/Thread.h             |  15 ++
 .../MacOSX-DYLD/DynamicLoaderMacOS.cpp        |  93 +++++++---
 .../MacOSX-DYLD/DynamicLoaderMacOS.h          |   4 +-
 .../Process/gdb-remote/ProcessGDBRemote.cpp   |  77 ++++++++-
 .../Process/gdb-remote/ProcessGDBRemote.h     |   4 +-
 .../Process/gdb-remote/ThreadGDBRemote.cpp    |  18 ++
 .../Process/gdb-remote/ThreadGDBRemote.h      |   9 +
 lldb/tools/debugserver/source/DNB.cpp         | 162 ++++++++++++++++++
 lldb/tools/debugserver/source/DNB.h           |   7 +
 lldb/tools/debugserver/source/JSONGenerator.h |  13 ++
 .../debugserver/source/MacOSX/MachProcess.mm  |   4 +
 .../source/MacOSX/arm64/DNBArchImplARM64.cpp  |  51 ++----
 lldb/tools/debugserver/source/RNBRemote.cpp   |  57 +++++-
 14 files changed, 482 insertions(+), 82 deletions(-)

diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index 9aa7ad2259a6a..4254a2aba6633 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -147,12 +147,28 @@ One requests information on all shared libraries:
 ```
 jGetLoadedDynamicLibrariesInfos:{"fetch_all_solibs":true}
 ```
-with an optional `"report_load_commands":false` which can be added, asking
-that only the dyld SPI information (load addresses, filenames) be returned.
-The default behavior is that debugserver scans the mach-o header and load
-commands of each binary, and returns it in the JSON reply.
 
-And the second requests information about a list of shared libraries, given their load addresses:
+There are two additional keys that can be specified: the older
+`"report_load_commands":false` which specifies that the detailed
+information about the binary should not be included (the Mach-O
+header and load commands), and the newer key that supplants
+`report_load_commands`, `information-level` which takes a string
+argument that is one of `address-only`, `address-name`,
+`address-name-uuid`, `full`.
+
+`"report_load_commands":false` is equivalent to
+`"information-level":"address-only" or so.
+
+`information-level` allows the caller to limit the amount of data
+being returned to one of (address, address+name, address+name+uuid,
+full).  When we first attach to a process, we may want to fetch the
+binary addresses for all binaries loaded in the process, and then
+fetch detailed information in batches, to keep the size of the
+packets from becoming too large.
+
+
+And the second form of jGetLoadedDynamicLibrariesInfos 
+requests information about a list of binaries, given their load addresses:
 ```
 jGetLoadedDynamicLibrariesInfos:{"solib_addresses":[8382824135,3258302053,830202858503]}
 ```
@@ -727,10 +743,19 @@ server to expedite memory that the client is likely to use (e.g., areas around t
 stack pointer, which are needed for computing backtraces) and it reduces the packet
 count.
 
+When a thread has hit the binaries-loaded lldb internal breakpoint
+(if it can detect that), it may expedite information about the
+binaries that have been loaded, to reduce packet traffic that would
+immediately follow.  The key `added-binaries` will have a value of
+an array of binary addresses.  The key `detailed-binaries-info`
+will have a value of a JSON dictionary which is the reply that
+`jGetLoadedDynamicLibrariesInfos` would return for these binaries,
+so lldb doesn't need to request it.
+
 On macOS with debugserver, we expedite the frame pointer backchain for a thread
 (up to 256 entries) by reading 2 pointers worth of bytes at the frame pointer (for
 the previous FP and PC), and follow the backchain. Most backtraces on macOS and
-iOS now don't require us to read any memory!
+iOS now don't require us to read any memory.
 
 **Priority To Implement:** Low
 
@@ -2243,6 +2268,19 @@ following keys and values:
   Specifies how many bits in addresses in high memory are significant for
   addressing, base 10.  AArch64 can have different page table setups for low and
   high memory, and therefore a different number of bits used for addressing.
+* `added-binaries` when the remote stub knows that a thread has stopped
+  at a binaries-loaded breakpoint notification, and it can retrieve the list
+  of binaries that have just been loaded, it may send the list of base16
+  addresses (no 0x prefix) for all of the binaries, to save lldb the need
+  to read it from memory.
+* `detailed-binaries-info` when the remote stub knows that a thread
+  has stopped at a binaries-loaded breakpoint notification, it may
+  be able to gather detailed information about the newly loaded
+  binaries, the information jGetLoadedDynamicLibrariesInfos would
+  return.  If this key is present, the information for all binaries
+  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.
 
 ### Best Practices
 
diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h
index 4353725ca47f6..7ca68227fdc66 100644
--- a/lldb/include/lldb/Target/Thread.h
+++ b/lldb/include/lldb/Target/Thread.h
@@ -1185,6 +1185,21 @@ class Thread : public std::enable_shared_from_this<Thread>,
                              uint32_t num_frames, bool show_frame_info,
                              uint32_t num_frames_with_source, bool show_hidden);
 
+  /// If this thread stopped on a binary-loaded breakpoint, the
+  /// addresses of the newly added binaries may have already been
+  /// provided by the gdb stub in the stop-packet.
+  virtual std::vector<lldb::addr_t> FetchNewlyAddedBinaries() { return {}; }
+
+  /// If this thread stopped on a binary-loaded breakpoint, the
+  /// detailed information about the new binaries may be provided.
+  /// If any detailed information about binaries is provided, it must
+  /// be provided for all binaries that have been loaded at this stop.
+  /// Detailed information is likely to only be provided when the number
+  /// of new binaries is small.
+  virtual lldb_private::StructuredData::ObjectSP FetchDetailedBinariesInfo() {
+    return {};
+  }
+
   // We need a way to verify that even though we have a thread in a shared
   // pointer that the object itself is still valid. Currently this won't be the
   // case if DestroyThread() was called. DestroyThread is called when a thread
diff --git a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.cpp b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.cpp
index ad97485373351..73ae06300d135 100644
--- a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.cpp
+++ b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.cpp
@@ -245,7 +245,7 @@ void DynamicLoaderMacOS::DoInitialImageFetch() {
             load_addresses.push_back(val);
         }
       }
-      AddBinaries(load_addresses);
+      AddBinaries(load_addresses, /*expedited_binary_infos=*/{});
     }
   }
 
@@ -326,7 +326,8 @@ bool DynamicLoaderMacOS::NotifyBreakpointHit(void *baton,
     argument_values.PushValue(count_value);
     argument_values.PushValue(headers_value);
 
-    if (abi->GetArgumentValues(exe_ctx.GetThreadRef(), argument_values)) {
+    Thread &thread = exe_ctx.GetThreadRef();
+    if (abi->GetArgumentValues(thread, argument_values)) {
       uint32_t dyld_mode =
           argument_values.GetValueAtIndex(0)->GetScalar().UInt(-1);
       if (dyld_mode != static_cast<uint32_t>(-1)) {
@@ -349,21 +350,39 @@ bool DynamicLoaderMacOS::NotifyBreakpointHit(void *baton,
             //
             // and we only need the imageLoadAddress fields.
 
-            const int addrsize =
-                process->GetTarget().GetArchitecture().GetAddressByteSize();
-            for (uint64_t i = 0; i < image_infos_count; i++) {
-              Status error;
-              addr_t dyld_image_info = header_array + (addrsize * 3 * i);
-              addr_t addr =
-                  process->ReadPointerFromMemory(dyld_image_info, error);
-              if (error.Success()) {
-                image_load_addresses.push_back(addr);
-              } else {
+            // The remote stub may have provided the addresses in the
+            // stop packet already.
+            image_load_addresses = thread.FetchNewlyAddedBinaries();
+            // Or, read them from memory.
+            if (image_load_addresses.size() != image_infos_count) {
+              image_load_addresses.clear();
+              ArchSpec target_arch = process->GetTarget().GetArchitecture();
+              const int addrsize = target_arch.GetAddressByteSize();
+              // Read the entire block of memory that we'll need to
+              // iterate over in one large read, to minimize packets sent.
+              WritableDataBufferSP buffer_sp = std::make_shared<DataBufferHeap>(
+                  addrsize * 3 * image_infos_count, 0);
+              Status read_error;
+              if (process->ReadMemory(header_array, buffer_sp->GetBytes(),
+                                      buffer_sp->GetByteSize(),
+                                      read_error) == buffer_sp->GetByteSize() &&
+                  read_error.Success()) {
+                DataExtractor added_binaries(
+                    buffer_sp, target_arch.GetByteOrder(), addrsize);
+
+                offset_t offset = 0;
+                for (uint64_t i = 0; i < image_infos_count; i++) {
+                  Status error;
+                  addr_t addr = added_binaries.GetAddress(&offset);
+                  image_load_addresses.push_back(addr);
+                  offset += 2 * addrsize;
+                }
+              }
+              if (!read_error.Success())
                 Debugger::ReportWarning(
                     "DynamicLoaderMacOS::NotifyBreakpointHit unable "
                     "to read binary mach-o load address at 0x%" PRIx64,
-                    addr);
-              }
+                    header_array);
             }
             if (dyld_mode == 0) {
               // dyld_notify_adding
@@ -381,7 +400,8 @@ bool DynamicLoaderMacOS::NotifyBreakpointHit(void *baton,
                 dyld_instance->DoInitialImageFetch();
                 dyld_instance->SetNotificationBreakpoint();
               } else {
-                dyld_instance->AddBinaries(image_load_addresses);
+                dyld_instance->AddBinaries(image_load_addresses,
+                                           thread.FetchDetailedBinariesInfo());
               }
             } else if (dyld_mode == 1) {
               // dyld_notify_removing
@@ -435,10 +455,39 @@ bool DynamicLoaderMacOS::NotifyBreakpointHit(void *baton,
   return dyld_instance->GetStopWhenImagesChange();
 }
 
+static size_t library_infos_count(StructuredData::ObjectSP binaries_info_sp) {
+  if (binaries_info_sp && binaries_info_sp->GetAsDictionary() &&
+      binaries_info_sp->GetAsDictionary()->HasKey("images") &&
+      binaries_info_sp->GetAsDictionary()
+          ->GetValueForKey("images")
+          ->GetAsArray())
+    return binaries_info_sp->GetAsDictionary()
+        ->GetValueForKey("images")
+        ->GetAsArray()
+        ->GetSize();
+
+  return 0;
+}
+
 void DynamicLoaderMacOS::AddBinaries(
-    const std::vector<lldb::addr_t> &load_addresses) {
+    const std::vector<lldb::addr_t> &load_addresses,
+    StructuredData::ObjectSP expedited_binary_infos) {
   Log *log = GetLog(LLDBLog::DynamicLoader);
   ImageInfo::collection image_infos;
+  if (load_addresses.size() == 0)
+    return;
+
+  // If the expedited detailed binaries information covers
+  // all of the newly added binaries, use that info and
+  // return.
+  if (library_infos_count(expedited_binary_infos) == load_addresses.size() &&
+      JSONImageInformationIntoImageInfo(expedited_binary_infos, image_infos)) {
+    auto new_images = PreloadModulesFromImageInfos(image_infos);
+    UpdateSpecialBinariesFromPreloadedModules(new_images);
+    AddModulesUsingPreloadedModules(new_images);
+    m_dyld_image_infos_stop_id = m_process->GetStopID();
+    return;
+  }
 
   // For now, hardcode a limit of fetching 600 binaries at once.
   // Fetching the full binary information for a large number of
@@ -460,16 +509,8 @@ void DynamicLoaderMacOS::AddBinaries(
     StructuredData::ObjectSP binaries_info_sp =
         m_process->GetLoadedDynamicLibrariesInfos(eBinaryInformationLevelFull,
                                                   fetch_binaries);
-    if (binaries_info_sp && binaries_info_sp->GetAsDictionary() &&
-        binaries_info_sp->GetAsDictionary()->HasKey("images") &&
-        binaries_info_sp->GetAsDictionary()
-            ->GetValueForKey("images")
-            ->GetAsArray()) {
-      StructuredData::Array *images = binaries_info_sp->GetAsDictionary()
-                                          ->GetValueForKey("images")
-                                          ->GetAsArray();
-      if (images->GetSize() == fetch_binaries.size() &&
-          JSONImageInformationIntoImageInfo(binaries_info_sp, image_infos)) {
+    if (library_infos_count(binaries_info_sp) == fetch_binaries.size()) {
+      if (JSONImageInformationIntoImageInfo(binaries_info_sp, image_infos)) {
         auto new_images = PreloadModulesFromImageInfos(image_infos);
         UpdateSpecialBinariesFromPreloadedModules(new_images);
         AddModulesUsingPreloadedModules(new_images);
diff --git a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.h b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.h
index d40ca3f23dbc6..ea456b8ff3b1d 100644
--- a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.h
+++ b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOS.h
@@ -77,7 +77,9 @@ class DynamicLoaderMacOS : public lldb_private::DynamicLoaderDarwin {
 
   void ClearDYLDHandoverBreakpoint();
 
-  void AddBinaries(const std::vector<lldb::addr_t> &load_addresses);
+  void
+  AddBinaries(const std::vector<lldb::addr_t> &load_addresses,
+              lldb_private::StructuredData::ObjectSP expedited_binary_infos);
 
   void DoClear() override;
 
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 07ef71917f771..3cbc8a31ac788 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1770,7 +1770,9 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
     bool queue_vars_valid, // Set to true if queue_name, queue_kind and
                            // queue_serial are valid
     LazyBool associated_with_dispatch_queue, addr_t dispatch_queue_t,
-    std::string &queue_name, QueueKind queue_kind, uint64_t queue_serial) {
+    std::string &queue_name, QueueKind queue_kind, uint64_t queue_serial,
+    std::vector<lldb::addr_t> &added_binaries,
+    StructuredData::ObjectSP &detailed_binaries_info) {
 
   if (tid == LLDB_INVALID_THREAD_ID)
     return nullptr;
@@ -1828,6 +1830,9 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
   if (dispatch_queue_t != LLDB_INVALID_ADDRESS)
     gdb_thread->SetQueueLibdispatchQueueAddress(dispatch_queue_t);
 
+  gdb_thread->SetNewlyAddedBinaries(added_binaries);
+  gdb_thread->SetDetailedBinariesInfo(detailed_binaries_info);
+
   // Make sure we update our thread stop reason just once, but don't overwrite
   // the stop info for threads that haven't moved:
   StopInfoSP current_stop_info_sp = thread_sp->GetPrivateStopInfo(false);
@@ -2120,6 +2125,9 @@ ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
   static constexpr llvm::StringLiteral g_key_memory("memory");
   static constexpr llvm::StringLiteral g_key_description("description");
   static constexpr llvm::StringLiteral g_key_signal("signal");
+  static constexpr llvm::StringLiteral g_added_binaries("added-binaries");
+  static constexpr llvm::StringLiteral g_detailed_binaries_info(
+      "detailed-binaries-info");
 
   // Stop with signal and thread info
   lldb::tid_t tid = LLDB_INVALID_THREAD_ID;
@@ -2137,6 +2145,8 @@ ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
   std::string queue_name;
   QueueKind queue_kind = eQueueKindUnknown;
   uint64_t queue_serial_number = 0;
+  std::vector<addr_t> added_binaries;
+  StructuredData::ObjectSP detailed_binaries_info;
   // Iterate through all of the thread dictionary key/value pairs from the
   // structured data dictionary
 
@@ -2145,7 +2155,8 @@ ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
                         &signo, &reason, &description, &exc_type, &exc_data,
                         &thread_dispatch_qaddr, &queue_vars_valid,
                         &associated_with_dispatch_queue, &dispatch_queue_t,
-                        &queue_name, &queue_kind, &queue_serial_number](
+                        &queue_name, &queue_kind, &queue_serial_number,
+                        &added_binaries, &detailed_binaries_info](
                            llvm::StringRef key,
                            StructuredData::Object *object) -> bool {
     if (key == g_key_tid) {
@@ -2244,17 +2255,43 @@ ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
           return true; // Keep iterating through all array items
         });
       }
-
     } else if (key == g_key_signal)
       signo = object->GetUnsignedIntegerValue(LLDB_INVALID_SIGNAL_NUMBER);
+    else if (key == g_added_binaries) {
+      StructuredData::Array *array = object->GetAsArray();
+      if (array) {
+        array->ForEach([&added_binaries](
+                           StructuredData::Object *object) -> bool {
+          StructuredData::UnsignedInteger *addr =
+              object->GetAsUnsignedInteger();
+          if (addr) {
+            addr_t value = addr->GetUnsignedIntegerValue(LLDB_INVALID_ADDRESS);
+            if (value != LLDB_INVALID_ADDRESS)
+              added_binaries.push_back(value);
+          }
+          return true; // Keep iterating through all array items
+        });
+      }
+    } else if (key == g_detailed_binaries_info) {
+      // Get a string representation and then parse it into
+      // StructuredData to get a separate copy of this part of
+      // the response.  We only have an Object* here, not the
+      // original shared pointer, to increase the ref count.
+      if (object->GetAsDictionary()) {
+        StreamString json_str;
+        object->Dump(json_str);
+        detailed_binaries_info =
+            StructuredData::ParseJSON(json_str.GetString());
+      }
+    }
     return true; // Keep iterating through all dictionary key/value pairs
   });
 
-  return SetThreadStopInfo(tid, expedited_register_map, signo, thread_name,
-                           reason, description, exc_type, exc_data,
-                           thread_dispatch_qaddr, queue_vars_valid,
-                           associated_with_dispatch_queue, dispatch_queue_t,
-                           queue_name, queue_kind, queue_serial_number);
+  return SetThreadStopInfo(
+      tid, expedited_register_map, signo, thread_name, reason, description,
+      exc_type, exc_data, thread_dispatch_qaddr, queue_vars_valid,
+      associated_with_dispatch_queue, dispatch_queue_t, queue_name, queue_kind,
+      queue_serial_number, added_binaries, detailed_binaries_info);
 }
 
 StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
@@ -2286,6 +2323,8 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
     std::string thread_name;
     std::string reason;
     std::string description;
+    std::vector<addr_t> added_binaries;
+    StructuredData::ObjectSP detailed_binaries_info;
     uint32_t exc_type = 0;
     std::vector<addr_t> exc_data;
     addr_t thread_dispatch_qaddr = LLDB_INVALID_ADDRESS;
@@ -2456,6 +2495,25 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
         if (!value.getAsInteger(0, addressing_bits)) {
           addressable_bits.SetHighmemAddressableBits(addressing_bits);
         }
+      } else if (key == "added-binaries") {
+        // A comma separated list of all threads in the current
+        // process that includes the thread for this stop reply packet
+        lldb::addr_t pc;
+        while (!value.empty()) {
+          llvm::StringRef pc_str;
+          std::tie(pc_str, value) = value.split(',');
+          if (pc_str.getAsInteger(16, pc))
+            pc = LLDB_INVALID_ADDRESS;
+          added_binaries.push_back(pc);
+        }
+      } else if (key == "detailed-binaries-info") {
+        StringExtractor json_extractor(value);
+        std::string json;
+        // Now convert the HEX bytes into a string value
+        json_extractor.GetHexByteString(json);
+
+        // This JSON contains detailed information about binares.
+        detailed_binaries_info = StructuredData::ParseJSON(json);
       } else if (key.size() == 2 && ::isxdigit(key[0]) && ::isxdigit(key[1])) {
         uint32_t reg = UINT32_MAX;
         if (!key.getAsInteger(16, reg))
@@ -2492,7 +2550,8 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
         tid, expedited_register_map, signo, thread_name, reason, description,
         exc_type, exc_data, thread_dispatch_qaddr, queue_vars_valid,
         associated_with_dispatch_queue, dispatch_queue_t, queue_name,
-        queue_kind, queue_serial_number);
+        queue_kind, queue_serial_number, added_binaries,
+        detailed_binaries_info);
 
     return eStateStopped;
   } break;
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index fc3b0c0f513d9..b696aad96b08d 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -394,7 +394,9 @@ class ProcessGDBRemote : public Process,
                     lldb::addr_t thread_dispatch_qaddr, bool queue_vars_valid,
                     lldb_private::LazyBool associated_with_libdispatch_queue,
                     lldb::addr_t dispatch_queue_t, std::string &queue_name,
-                    lldb::QueueKind queue_kind, uint64_t queue_serial);
+                    lldb::QueueKind queue_kind, uint64_t queue_serial,
+                    std::vector<lldb::addr_t> &added_binaries,
+                    StructuredData::ObjectSP &detailed_binaries_info);
 
   void ClearThreadIDList();
 
diff --git a/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp
index 3d23c074c1be3..66bb88c91cff9 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp
@@ -366,3 +366,21 @@ ThreadGDBRemote::GetSiginfo(size_t max_size) const {
 
   return llvm::MemoryBuffer::getMemBufferCopy(response.get());
 }
+
+std::vector<lldb::addr_t> ThreadGDBRemote::FetchNewlyAddedBinaries() {
+  return m_added_binaries;
+}
+
+StructuredData::ObjectSP ThreadGDBRemote::FetchDetailedBinariesInfo() {
+  return m_detailed_binaries_info;
+}
+
+void ThreadGDBRemote::SetNewlyAddedBinaries(
+    std::vector<lldb::addr_t> added_binaries) {
+  m_added_binaries = added_binaries;
+}
+
+void ThreadGDBRemote::SetDetailedBinariesInfo(
+    StructuredData::ObjectSP detailed_info) {
+  m_detailed_binaries_info = detailed_info;
+}
diff --git a/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h
index 5bc90a3dedceb..f889ff73db2cb 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h
@@ -90,6 +90,12 @@ class ThreadGDBRemote : public Thread {
 
   StructuredData::ObjectSP FetchThreadExtendedInfo() override;
 
+  std::vector<lldb::addr_t> FetchNewlyAddedBinaries() override;
+  StructuredData::ObjectSP FetchDetailedBinariesInfo() override;
+
+  void SetNewlyAddedBinaries(std::vector<lldb::addr_t> added_binaries);
+  void SetDetailedBinariesInfo(StructuredData::ObjectSP detailed_info);
+
 protected:
   friend class ProcessGDBRemote;
 
@@ -105,6 +111,9 @@ class ThreadGDBRemote : public Thread {
 
   GDBRemoteDynamicRegisterInfoSP m_reg_info_sp;
 
+  std::vector<lldb::addr_t> m_added_binaries;
+  lldb_private::StructuredData::ObjectSP m_detailed_binaries_info;
+
   bool PrivateSetRegisterValue(uint32_t reg, llvm::ArrayRef<uint8_t> data);
 
   bool PrivateSetRegisterValue(uint32_t reg, uint64_t regval);
diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp
index 86d7fa0a81647..004c8055552cd 100644
--- a/lldb/tools/debugserver/source/DNB.cpp
+++ b/lldb/tools/debugserver/source/DNB.cpp
@@ -1761,6 +1761,16 @@ uint32_t DNBProcessGetCPUType(nub_process_t pid) {
   return 0;
 }
 
+int DNBProcessAddrSize(nub_process_t pid) {
+  uint32_t cputype = DNBProcessGetCPUType(pid);
+  // unable to get process cpu type
+  if (cputype == 0)
+    return 0;
+  if (cputype == CPU_TYPE_ARM64_32)
+    return 4;
+  return 8;
+}
+
 nub_bool_t DNBResolveExecutablePath(const char *path, char *resolved_path,
                                     size_t resolved_path_size) {
   if (path == NULL || path[0] == '\0')
@@ -1918,6 +1928,30 @@ bool DNBGetAddressingBits(uint32_t &addressing_bits) {
   return addressing_bits > 0;
 }
 
+nub_addr_t DNBFixAddress(nub_addr_t addr, nub_process_t pid) {
+  uint32_t addressing_bits = 0;
+  if (!DNBGetAddressingBits(addressing_bits))
+    return addr;
+
+    // On arm64_32, no ptrauth bits to clear
+#if !defined(__LP64__)
+  return addr;
+#endif
+  if (pid != INVALID_NUB_PROCESS) {
+    cpu_type_t cputype = DNBProcessGetCPUType(pid);
+    if (cputype == CPU_TYPE_ARM64_32)
+      return addr;
+  }
+
+  uint64_t mask = ((1ULL << addressing_bits) - 1);
+
+  // Normally PAC bit clearing needs to check b55 and either set the
+  // non-addressing bits, or clear them, but debugserver only
+  // debugs userland processes in low memory.
+
+  return addr & mask; // high bits cleared to 0
+}
+
 nub_process_t DNBGetParentProcessID(nub_process_t child_pid) {
   return MachProcess::GetParentProcessID(child_pid);
 }
@@ -1925,3 +1959,131 @@ nub_process_t DNBGetParentProcessID(nub_process_t child_pid) {
 bool DNBProcessIsBeingDebugged(nub_process_t pid) {
   return MachProcess::ProcessIsBeingDebugged(pid);
 }
+
+bool DNBGetBinariesLoadedInfo(nub_process_t pid, nub_thread_t tid,
+                              std::vector<uint64_t> &added_binaries,
+                              JSONGenerator::ObjectSP &detailed_binary_infos) {
+
+  std::optional<nub_addr_t> arg1, arg2, arg3, pc;
+  DNBRegisterValue regval;
+  if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC,
+                                    GENERIC_REGNUM_PC, &regval))
+    pc = regval.value.uint64;
+  if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC,
+                                    GENERIC_REGNUM_ARG1, &regval))
+    arg1 = regval.value.uint64;
+  if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC,
+                                    GENERIC_REGNUM_ARG2, &regval))
+    arg2 = regval.value.uint64;
+  if (DNBThreadGetRegisterValueByID(pid, tid, REGISTER_SET_GENERIC,
+                                    GENERIC_REGNUM_ARG3, &regval))
+    arg3 = regval.value.uint64;
+
+  if (!arg1 || !arg2 || !arg3 || !pc)
+    return false;
+
+  // With a full process launch, the first few stops we are working
+  // with a dyld outside the shared cache.  It will transition to
+  // the shared cache dyld, and at that point we can cache the
+  // g_notifier_breakpoint_addr in the shared cache.  Use the
+  // shared cache VM range to detect when we are working with the
+  // final shared cache dyld -- don't return any information until that
+  // happens.
+  // We could use the outside-the-shared-cache launch dyld's notification
+  // address for this one-time use and not cache the address, but
+  // that's more complication than is probably worth it to save a couple
+  // of packets in the initial startup seqeuence.
+  static nub_size_t g_addr_size = 0;
+  static nub_addr_t g_notifier_breakpoint_addr = 0;
+
+  if (g_notifier_breakpoint_addr == 0) {
+    // Get the shared cache VM address start and size.
+    JSONGenerator::ObjectSP sc_info = DNBGetSharedCacheInfo(pid);
+    if (!sc_info->GetAsDictionary())
+      return false;
+    JSONGenerator::ObjectSP value =
+        sc_info->GetAsDictionary()->GetValueForKey("shared_cache_base_address");
+    if (!value || !value->GetAsInteger())
+      return false;
+    // shared cache not yet set up; we're very early in process launch.
+    if (value->GetAsInteger()->GetValue() == 0)
+      return false;
+    nub_addr_t sc_base_addr = value->GetAsInteger()->GetValue();
+
+    if (!sc_info->GetAsDictionary()->GetValueForKey("shared_cache_size"))
+      return false;
+    value = sc_info->GetAsDictionary()->GetValueForKey("shared_cache_size");
+    if (!value || !value->GetAsInteger() ||
+        value->GetAsInteger()->GetValue() == 0)
+      return false;
+    nub_addr_t sc_size = value->GetAsInteger()->GetValue();
+
+    // Get the pointer size for the inferior process.
+    if (g_addr_size == 0) {
+#if defined(__LP64__)
+      // Inferior may be arm64_32 if debugserver is running arm64.
+      g_addr_size = DNBProcessAddrSize(pid);
+#else
+      g_addr_size = 4;
+#endif
+    }
+    if (g_addr_size == 0)
+      return false;
+
+    // Early return if the dyld_all_image_infos is outside
+    // the shared cache VM region.
+    nub_addr_t dyld_all_image_infos =
+        DNBProcessGetSharedLibraryInfoAddress(pid);
+    if (dyld_all_image_infos == INVALID_NUB_ADDRESS ||
+        dyld_all_image_infos == 0)
+      return false;
+    if (dyld_all_image_infos < sc_base_addr ||
+        dyld_all_image_infos > sc_base_addr + sc_size)
+      return false;
+
+    nub_addr_t notifier_fptr_addr = dyld_all_image_infos + 4 + // version
+                                    4 +                        // infoArrayCount
+                                    g_addr_size;               // infoArray
+    nub_addr_t notifier_fptr = DNBProcessMemoryReadInteger(
+        pid, notifier_fptr_addr, g_addr_size, INVALID_NUB_ADDRESS);
+    if (notifier_fptr == INVALID_NUB_ADDRESS)
+      return false;
+
+    g_notifier_breakpoint_addr = DNBFixAddress(notifier_fptr, pid);
+  }
+  if (g_notifier_breakpoint_addr == 0 ||
+      g_notifier_breakpoint_addr == INVALID_NUB_ADDRESS)
+    return false;
+
+  if (*pc != g_notifier_breakpoint_addr)
+    return false;
+  if (*arg1 != /*dyld_notify_adding=*/0)
+    return false;
+  uint64_t count = *arg2;
+  if (count == 0)
+    return false;
+  nub_addr_t header_array = *arg3;
+
+  // header_array points to an array of image_infos_count elements,
+  // each is
+  // struct dyld_image_info {
+  //   const struct mach_header* imageLoadAddress;
+  //   const char*               imageFilePath;
+  //   uintptr_t                 imageFileModDate;
+  // };
+  //
+  // and we only need the imageLoadAddress fields.
+  for (uint64_t i = 0; i < count; i++) {
+    nub_addr_t dyld_image_info = header_array + (g_addr_size * 3 * i);
+    nub_addr_t load_addr = DNBProcessMemoryReadInteger(
+        pid, dyld_image_info, g_addr_size, INVALID_NUB_ADDRESS);
+    if (load_addr != INVALID_NUB_ADDRESS)
+      added_binaries.push_back(load_addr);
+  }
+
+  if (added_binaries.size() == 1)
+    detailed_binary_infos = DNBGetLibrariesInfoForAddresses(
+        pid, DNBBinaryInformationLevel::eBinaryInformationLevelFull,
+        added_binaries);
+  return true;
+}
diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h
index 007d8d812e115..78b5c8fbcd31c 100644
--- a/lldb/tools/debugserver/source/DNB.h
+++ b/lldb/tools/debugserver/source/DNB.h
@@ -159,6 +159,7 @@ nub_size_t DNBProcessGetAvailableProfileData(nub_process_t pid, char *buf,
                                              nub_size_t buf_size) DNB_EXPORT;
 nub_size_t DNBProcessGetStopCount(nub_process_t pid) DNB_EXPORT;
 uint32_t DNBProcessGetCPUType(nub_process_t pid) DNB_EXPORT;
+int DNBProcessAddrSize(nub_process_t pid) DNB_EXPORT;
 size_t DNBGetAllInfos(std::vector<struct kinfo_proc> &proc_infos);
 JSONGenerator::ObjectSP DNBGetDyldProcessState(nub_process_t pid);
 
@@ -253,9 +254,15 @@ std::string DNBGetMacCatalystVersionString();
 bool DNBDebugserverIsTranslated();
 
 bool DNBGetAddressingBits(uint32_t &addressing_bits);
+nub_addr_t DNBFixAddress(nub_addr_t addr,
+                         nub_process_t pid = INVALID_NUB_PROCESS);
 
 nub_process_t DNBGetParentProcessID(nub_process_t child_pid);
 
 bool DNBProcessIsBeingDebugged(nub_process_t pid);
 
+bool DNBGetBinariesLoadedInfo(nub_process_t pid, nub_thread_t tid,
+                              std::vector<uint64_t> &added_binaries,
+                              JSONGenerator::ObjectSP &detailed_binary_infos);
+
 #endif
diff --git a/lldb/tools/debugserver/source/JSONGenerator.h b/lldb/tools/debugserver/source/JSONGenerator.h
index 1818250df5281..bfd602e7fcf38 100644
--- a/lldb/tools/debugserver/source/JSONGenerator.h
+++ b/lldb/tools/debugserver/source/JSONGenerator.h
@@ -127,6 +127,10 @@ class JSONGenerator {
 
     void AddItem(ObjectSP item) { m_items.push_back(item); }
 
+    void AddIntegerItem(uint64_t value) {
+      AddItem(ObjectSP(new Integer(value)));
+    }
+
     void Dump(std::ostream &s) const override {
       s << "[";
       const size_t arrsize = m_items.size();
@@ -162,6 +166,8 @@ class JSONGenerator {
 
     void SetValue(uint64_t value) { m_value = value; }
 
+    uint64_t GetValue() { return m_value; }
+
     void Dump(std::ostream &s) const override { s << m_value; }
 
     void DumpBinaryEscaped(std::ostream &s) const override { Dump(s); }
@@ -290,6 +296,13 @@ class JSONGenerator {
       AddItem(key, ObjectSP(new Boolean(value)));
     }
 
+    ObjectSP GetValueForKey(std::string key) {
+      for (const auto &kv : m_dict)
+        if (kv.first == key)
+          return kv.second;
+      return {};
+    }
+
     void Dump(std::ostream &s) const override {
       bool have_printed_one_elem = false;
       s << "{";
diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm
index b2be3636bcce8..e9d27fe389327 100644
--- a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm
+++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm
@@ -1011,6 +1011,10 @@ static bool mach_header_validity_test(uint32_t magic, uint32_t cputype) {
         "filetype", image_infos[i].macho_info.mach_header.filetype);
     mach_header_dict_sp->AddIntegerItem ("flags", 
                          image_infos[i].macho_info.mach_header.flags);
+    mach_header_dict_sp->AddIntegerItem(
+        "sizeof_mh_and_loadcmds",
+        sizeof(mach_header_64) +
+            image_infos[i].macho_info.mach_header.sizeofcmds);
 
     //          DynamicLoaderMacOSX doesn't currently need these fields, so
     //          don't send them.
diff --git a/lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp b/lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
index e30e02a911529..b5897499b896f 100644
--- a/lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
+++ b/lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp
@@ -132,34 +132,11 @@ unsigned int DNBArchMachARM64::GetSMEMaxSVL() {
   return g_sme_max_svl;
 }
 
-static uint64_t clear_pac_bits(uint64_t value) {
-  uint32_t addressing_bits = 0;
-  if (!DNBGetAddressingBits(addressing_bits))
-    return value;
-
-    // On arm64_32, no ptrauth bits to clear
-#if !defined(__LP64__)
-  return value;
-#endif
-
-  uint64_t mask = ((1ULL << addressing_bits) - 1);
-
-  // Normally PAC bit clearing needs to check b55 and either set the
-  // non-addressing bits, or clear them.  But the register values we
-  // get from thread_get_state on an arm64e process don't follow this
-  // convention?, at least when there's been a PAC auth failure in
-  // the inferior.
-  // Userland processes are always in low memory, so this
-  // hardcoding b55 == 0 PAC stripping behavior here.
-
-  return value & mask; // high bits cleared to 0
-}
-
 uint64_t DNBArchMachARM64::GetPC(uint64_t failValue) {
   // Get program counter
   if (GetGPRState(false) == KERN_SUCCESS)
 #if defined(DEBUGSERVER_IS_ARM64E)
-    return clear_pac_bits(
+    return DNBFixAddress(
         reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc));
 #else
     return m_state.context.gpr.__pc;
@@ -191,7 +168,7 @@ uint64_t DNBArchMachARM64::GetSP(uint64_t failValue) {
   // Get stack pointer
   if (GetGPRState(false) == KERN_SUCCESS)
 #if defined(DEBUGSERVER_IS_ARM64E)
-    return clear_pac_bits(
+    return DNBFixAddress(
         reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_sp));
 #else
     return m_state.context.gpr.__sp;
@@ -252,13 +229,13 @@ kern_return_t DNBArchMachARM64::GetGPRState(bool force) {
 
   if (DNBLogEnabledForAny(LOG_THREAD)) {
 #if defined(DEBUGSERVER_IS_ARM64E)
-    uint64_t log_fp = clear_pac_bits(
+    uint64_t log_fp = DNBFixAddress(
         reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_fp));
-    uint64_t log_lr = clear_pac_bits(
+    uint64_t log_lr = DNBFixAddress(
         reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_lr));
-    uint64_t log_sp = clear_pac_bits(
+    uint64_t log_sp = DNBFixAddress(
         reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_sp));
-    uint64_t log_pc = clear_pac_bits(
+    uint64_t log_pc = DNBFixAddress(
         reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc));
 #else
     uint64_t log_fp = m_state.context.gpr.__fp;
@@ -931,7 +908,7 @@ kern_return_t DNBArchMachARM64::EnableHardwareSingleStep(bool enable) {
   }
 
 #if defined(DEBUGSERVER_IS_ARM64E)
-  uint64_t pc = clear_pac_bits(
+  uint64_t pc = DNBFixAddress(
       reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc));
 #else
   uint64_t pc = m_state.context.gpr.__pc;
@@ -2669,32 +2646,32 @@ bool DNBArchMachARM64::GetRegisterValue(uint32_t set, uint32_t reg,
         switch (reg) {
 #if defined(DEBUGSERVER_IS_ARM64E)
         case gpr_pc:
-          value->value.uint64 = clear_pac_bits(
+          value->value.uint64 = DNBFixAddress(
               reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc));
           break;
         case gpr_lr:
           value->value.uint64 = arm_thread_state64_get_lr(m_state.context.gpr);
           break;
         case gpr_sp:
-          value->value.uint64 = clear_pac_bits(
+          value->value.uint64 = DNBFixAddress(
               reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_sp));
           break;
         case gpr_fp:
-          value->value.uint64 = clear_pac_bits(
+          value->value.uint64 = DNBFixAddress(
               reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_fp));
           break;
 #else
         case gpr_pc:
-          value->value.uint64 = clear_pac_bits(m_state.context.gpr.__pc);
+          value->value.uint64 = DNBFixAddress(m_state.context.gpr.__pc);
           break;
         case gpr_lr:
-          value->value.uint64 = clear_pac_bits(m_state.context.gpr.__lr);
+          value->value.uint64 = DNBFixAddress(m_state.context.gpr.__lr);
           break;
         case gpr_sp:
-          value->value.uint64 = clear_pac_bits(m_state.context.gpr.__sp);
+          value->value.uint64 = DNBFixAddress(m_state.context.gpr.__sp);
           break;
         case gpr_fp:
-          value->value.uint64 = clear_pac_bits(m_state.context.gpr.__fp);
+          value->value.uint64 = DNBFixAddress(m_state.context.gpr.__fp);
           break;
 #endif
         default:
diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index 57ffbc08d2590..59a3bc6c63943 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -2830,9 +2830,8 @@ rnb_err_t RNBRemote::SendStopReplyPacketForThread(nub_thread_t tid) {
               } else if (pc_regval.info.size == 8) {
                 pc = pc_regval.value.uint64;
               }
-              if (pc != INVALID_NUB_ADDRESS) {
+              if (pc != INVALID_NUB_ADDRESS)
                 pc_values.push_back(pc);
-              }
             }
           }
         }
@@ -2982,6 +2981,38 @@ rnb_err_t RNBRemote::SendStopReplyPacketForThread(nub_thread_t tid) {
       }
     }
 
+    std::vector<uint64_t> added_binaries;
+    JSONGenerator::ObjectSP detailed_binary_infos;
+
+    // If we've stopped with a breakpoint exception on this
+    // thread, and we're stopped at the dyld notification
+    // function address, collect information about libraries
+    // that have been loaded, expedite that information in
+    // the stop packet.
+    if (tid_stop_info.details.exception.type == EXC_BREAKPOINT &&
+        DNBGetBinariesLoadedInfo(pid, tid, added_binaries,
+                                 detailed_binary_infos)) {
+      ostrm << std::hex << "added-binaries:";
+      bool first = true;
+      for (nub_addr_t addr : added_binaries) {
+        if (first)
+          first = false;
+        else
+          ostrm << ",";
+        ostrm << addr;
+      }
+      ostrm << ";";
+
+      if (detailed_binary_infos) {
+        ostrm << std::hex << "detailed-binaries-info:";
+        std::ostringstream json_strm;
+        detailed_binary_infos->Dump(json_strm);
+        detailed_binary_infos->Clear();
+        append_hexified_string(ostrm, json_strm.str());
+        ostrm << ';';
+      }
+    }
+
     return SendPacket(ostrm.str());
   }
   return SendErrorPacket("E51");
@@ -5743,6 +5774,28 @@ RNBRemote::GetJSONThreadsInfo(bool threads_with_valid_stop_info_only) {
         }
       }
 
+      std::vector<uint64_t> added_binaries;
+      JSONGenerator::ObjectSP detailed_binary_infos;
+
+      // If we've stopped with a breakpoint exception on this
+      // thread, and we're stopped at the dyld notification
+      // function address, collect information about libraries
+      // that have been loaded, expedite that information in
+      // the stop packet.
+      if (tid_stop_info.details.exception.type == EXC_BREAKPOINT &&
+          DNBGetBinariesLoadedInfo(pid, tid, added_binaries,
+                                   detailed_binary_infos)) {
+        JSONGenerator::ArraySP load_addresses;
+        load_addresses = std::make_shared<JSONGenerator::Array>();
+        for (nub_addr_t addr : added_binaries)
+          load_addresses->AddIntegerItem(addr);
+        thread_dict_sp->AddItem("added-binaries", load_addresses);
+
+        if (detailed_binary_infos)
+          thread_dict_sp->AddItem("detailed-binaries-info",
+                                  detailed_binary_infos);
+      }
+
       threads_array_sp->AddItem(thread_dict_sp);
     }
   }



More information about the lldb-commits mailing list