[Lldb-commits] [lldb] 27abffa - [lldb] New expedited register specfication for unavailable regs (#193894)

via lldb-commits lldb-commits at lists.llvm.org
Tue May 26 14:52:28 PDT 2026


Author: Jason Molenda
Date: 2026-05-26T14:52:24-07:00
New Revision: 27abffa3efe272d725a6c4eceeebfe8c17e0f8e8

URL: https://github.com/llvm/llvm-project/commit/27abffa3efe272d725a6c4eceeebfe8c17e0f8e8
DIFF: https://github.com/llvm/llvm-project/commit/27abffa3efe272d725a6c4eceeebfe8c17e0f8e8.diff

LOG: [lldb] New expedited register specfication for unavailable regs (#193894)

When lldb-server/debugserver send a stop packet, they expedite the
vaLues of many of the general purpose registers in the stop packet, so
lldb doesn't need to fetch them separately.

On Darwin systems using an AArch64 M4 or newer SOC with SME, we need to
fetch the streaming vector length (svl) register when in Streaming SVE
Mode to correctly size the registers in lldb. On Darwin systems, when we
are not in SSVE mode, svl is undefined -- it is not included in the
expedited registers. However, lldb will still try to fetch the value, so
we get a register-read packet at every stop on M4 and newer systems,
trying to fetch the value.

This patch adds a new format for the expedited registers. They are
normally a `;` separated series of `{regnum}:{native endian bytes}`.
This allows for `{regnum}:` alone, indicating that the register value
for regnum cannot be fetched at this stop.

It updates debugserver to always add svcr, tpidr2, and svl to the
expedited registers, whether we can fetch them or not.

Most of the changes happen in GDBRemoteRegisterContext. This class
maintains a buffer sized for the register context bytes, and an array of
bool to indicate whether we have fetched the value for a given register
yet or not. If we have fetched the value, we can supply the bytes from
the register context byte array. If it is not yet fetched, we read it.

This PR changes this fetched-or-not bool array to a LazyBool with

eLazyBoolYes meaning we have the bytes for the register value in the
lldb buffer.
eLazyBoolCalculate means we have not yet tried to read the register
value.
eLazyBoolNo means that the register is currently readable.

The meaning of these 3 LazyBool states could be a little confusing as
you read GDBRemoteRegisterContext, so all getting/setting of these is
done through the methods

SetAllRegistersValid()
SetAllRegistersUnfetched()

{Set,Get}RegisterIsValid()
{Set,Get}RegisterIsUnavailable()
{Set,Get}RegisterIsUnfetched()

Whcih I think makes it a little easer to understand. The changes to
GDBRemoteRegisterContext were the trickiest part of this PR, all the
other changes are simple.

Linux et al mostly work on AArch64 systems with SVE + SME, in which case
we have either a vector length or a streaming vector length, at all
times. But the M4 & newer SOCs from Apple don't have any SVE registers
when not in streaming SVE mode, so we have this issue of an extra
register read at all stops, unless in SSVE mode.

rdar://161581129

---------

Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>

Added: 
    lldb/test/API/functionalities/gdb_remote_client/TestUnavailableRegisters.py

Modified: 
    lldb/docs/resources/lldbgdbremote.md
    lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.cpp
    lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h
    lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
    lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp
    lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h
    lldb/tools/debugserver/source/RNBRemote.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md
index e93a5ea120d17..6e54f8eccf89d 100644
--- a/lldb/docs/resources/lldbgdbremote.md
+++ b/lldb/docs/resources/lldbgdbremote.md
@@ -814,6 +814,11 @@ On macOS with debugserver, we expedite the frame pointer backchain for a thread
 the previous FP and PC), and follow the backchain. Most backtraces on macOS and
 iOS now don't require us to read any memory.
 
+An expedited register may have an empty string as its value (`"21":""`)
+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.
+
 **Priority To Implement:** Low
 
 This is a performance optimization, which speeds up debugging by avoiding
@@ -2174,6 +2179,9 @@ following forms:
   followed by a series of key/value pairs:
     * If key is a hex number, it is a register number and value is
       the hex value of the register in debuggee endian byte order.
+      An empty value indicates that the register cannot be fetched
+      at this stop point; lldb will not succeed if it sends a separate
+      read-register packet.
     * If key == "thread", then the value is the big endian hex
       thread-id of the stopped thread.
     * If key == "core", then value is a hex number of the core on

diff  --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.cpp
index a513f53e57b7d..c97aabea9fe36 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.cpp
@@ -42,6 +42,7 @@ GDBRemoteRegisterContext::GDBRemoteRegisterContext(
   // use these boolean values to know when a register value is valid in
   // m_reg_data.
   m_reg_valid.resize(m_reg_info_sp->GetNumRegisters());
+  SetAllRegistersValidState(eLazyBoolCalculate);
 
   // Make a heap based buffer that is big enough to store all registers
   DataBufferSP reg_data_sp(
@@ -53,15 +54,30 @@ GDBRemoteRegisterContext::GDBRemoteRegisterContext(
 // Destructor
 GDBRemoteRegisterContext::~GDBRemoteRegisterContext() = default;
 
-void GDBRemoteRegisterContext::InvalidateAllRegisters() {
-  SetAllRegisterValid(false);
+void GDBRemoteRegisterContext::SetAllRegistersValidState(LazyBool valid) {
+  m_gpacket_cached = (valid == eLazyBoolYes);
+  for (auto &reg : m_reg_valid)
+    reg = valid;
 }
 
-void GDBRemoteRegisterContext::SetAllRegisterValid(bool b) {
-  m_gpacket_cached = b;
-  std::vector<bool>::iterator pos, end = m_reg_valid.end();
-  for (pos = m_reg_valid.begin(); pos != end; ++pos)
-    *pos = b;
+LazyBool GDBRemoteRegisterContext::GetRegisterValidState(uint32_t reg) const {
+  assert(reg < m_reg_valid.size());
+  if (reg < m_reg_valid.size())
+    return m_reg_valid[reg];
+  return eLazyBoolNo;
+}
+
+void GDBRemoteRegisterContext::SetRegisterIsValidState(
+    const RegisterInfo *reg_info, LazyBool valid) {
+  if (reg_info)
+    SetRegisterIsValidState(reg_info->kinds[lldb::eRegisterKindLLDB], valid);
+}
+
+void GDBRemoteRegisterContext::SetRegisterIsValidState(uint32_t reg,
+                                                       LazyBool valid) {
+  assert(reg < m_reg_valid.size());
+  if (reg < m_reg_valid.size())
+    m_reg_valid[reg] = valid;
 }
 
 size_t GDBRemoteRegisterContext::GetRegisterCount() {
@@ -86,7 +102,7 @@ bool GDBRemoteRegisterContext::ReadRegister(const RegisterInfo *reg_info,
   // Read the register
   if (ReadRegisterBytes(reg_info)) {
     const uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
-    if (m_reg_valid[reg] == false)
+    if (GetRegisterIsUnavailable(reg))
       return false;
     if (reg_info->value_regs &&
         reg_info->value_regs[0] != LLDB_INVALID_REGNUM &&
@@ -135,11 +151,11 @@ bool GDBRemoteRegisterContext::PrivateSetRegisterValue(
          data.data(), std::min(data.size(), reg_byte_size));
   bool success = data.size() >= reg_byte_size;
   if (success) {
-    SetRegisterIsValid(reg, true);
+    SetRegisterIsValid(reg);
   } else if (data.size() > 0) {
-    // Only set register is valid to false if we copied some bytes, else leave
+    // Only set register to Unfetched if we copied some bytes, else leave
     // it as it was.
-    SetRegisterIsValid(reg, false);
+    SetRegisterIsUnfetched(reg);
   }
   return success;
 }
@@ -184,7 +200,7 @@ bool GDBRemoteRegisterContext::PrivateSetRegisterValue(uint32_t reg,
           reg_info->byte_size,        // dst length
           m_reg_data.GetByteOrder())) // dst byte order
   {
-    SetRegisterIsValid(reg, true);
+    SetRegisterIsValid(reg);
     return true;
   }
   return false;
@@ -219,7 +235,7 @@ bool GDBRemoteRegisterContext::ReadRegisterBytes(const RegisterInfo *reg_info) {
 
   const uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
 
-  if (!GetRegisterIsValid(reg)) {
+  if (GetRegisterIsUnfetched(reg)) {
     if (m_read_all_at_once && !m_gpacket_cached) {
       if (DataBufferSP buffer_sp =
               gdb_comm.ReadAllRegisters(m_thread.GetProtocolID())) {
@@ -227,16 +243,18 @@ bool GDBRemoteRegisterContext::ReadRegisterBytes(const RegisterInfo *reg_info) {
                buffer_sp->GetBytes(),
                std::min(buffer_sp->GetByteSize(), m_reg_data.GetByteSize()));
         if (buffer_sp->GetByteSize() >= m_reg_data.GetByteSize()) {
-          SetAllRegisterValid(true);
+          SetAllRegistersValid();
           return true;
         } else if (buffer_sp->GetByteSize() > 0) {
           for (auto x : llvm::enumerate(
                    m_reg_info_sp->registers<
                        DynamicRegisterInfo::reg_collection_const_range>())) {
             const struct RegisterInfo &reginfo = x.value();
-            m_reg_valid[x.index()] =
-                (reginfo.byte_offset + reginfo.byte_size <=
-                 buffer_sp->GetByteSize());
+            if (reginfo.byte_offset + reginfo.byte_size <=
+                buffer_sp->GetByteSize())
+              SetRegisterIsValid(x.index());
+            else
+              SetRegisterIsUnfetched(x.index());
           }
 
           m_gpacket_cached = true;
@@ -264,6 +282,8 @@ bool GDBRemoteRegisterContext::ReadRegisterBytes(const RegisterInfo *reg_info) {
       bool success = true;
       for (uint32_t idx = 0; success; ++idx) {
         const uint32_t prim_reg = reg_info->value_regs[idx];
+        // We've fetched all primordial registers that provide
+        // data for this reg.
         if (prim_reg == LLDB_INVALID_REGNUM)
           break;
         // We have a valid primordial register as our constituent. Grab the
@@ -274,23 +294,31 @@ bool GDBRemoteRegisterContext::ReadRegisterBytes(const RegisterInfo *reg_info) {
           success = false;
         else {
           // Read the containing register if it hasn't already been read
-          if (!GetRegisterIsValid(prim_reg))
+          if (GetRegisterIsUnfetched(prim_reg))
             success = GetPrimordialRegister(prim_reg_info, gdb_comm);
+          // Containing register cannot be read
+          else if (GetRegisterIsUnavailable(prim_reg))
+            success = false;
         }
       }
 
       if (success) {
         // If we reach this point, all primordial register requests have
         // succeeded. Validate this composite register.
-        SetRegisterIsValid(reg_info, true);
+        SetRegisterIsValid(reg_info);
+      } else {
+        SetRegisterIsUnfetched(reg_info);
       }
     } else {
       // Get each register individually
-      GetPrimordialRegister(reg_info, gdb_comm);
+      if (GetPrimordialRegister(reg_info, gdb_comm))
+        SetRegisterIsValid(reg_info);
+      else
+        SetRegisterIsUnfetched(reg_info);
     }
 
     // Make sure we got a valid register value after reading it
-    if (!GetRegisterIsValid(reg))
+    if (GetRegisterIsUnfetched(reg) || GetRegisterIsUnavailable(reg))
       return false;
   }
 
@@ -342,7 +370,7 @@ bool GDBRemoteRegisterContext::SetPrimordialRegister(
   StringExtractorGDBRemote response;
   const uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
   // Invalidate just this register
-  SetRegisterIsValid(reg, false);
+  SetRegisterIsUnfetched(reg);
 
   return gdb_comm.WriteRegister(
       m_thread.GetProtocolID(), reg_info->kinds[eRegisterKindProcessPlugin],
@@ -441,9 +469,8 @@ bool GDBRemoteRegisterContext::WriteRegisterBytes(const RegisterInfo *reg_info,
           for (uint32_t idx = 0, reg = reg_info->invalidate_regs[0];
                reg != LLDB_INVALID_REGNUM;
                reg = reg_info->invalidate_regs[++idx])
-            SetRegisterIsValid(ConvertRegisterKindToRegisterNumber(
-                                   eRegisterKindLLDB, reg),
-                               false);
+            SetRegisterIsUnfetched(
+                ConvertRegisterKindToRegisterNumber(eRegisterKindLLDB, reg));
         }
 
         if (success && should_reconfigure_registers &&
@@ -687,7 +714,7 @@ bool GDBRemoteRegisterContext::WriteAllRegisterValues(
         const uint8_t *restore_src =
             restore_data.PeekData(register_offset, reg_byte_size);
         if (restore_src) {
-          SetRegisterIsValid(reg, false);
+          SetRegisterIsUnfetched(reg);
           if (gdb_comm.WriteRegister(
                   m_thread.GetProtocolID(),
                   reg_info->kinds[eRegisterKindProcessPlugin],
@@ -727,7 +754,7 @@ bool GDBRemoteRegisterContext::WriteAllRegisterValues(
           continue;
         }
 
-        SetRegisterIsValid(reg_info, false);
+        SetRegisterIsUnfetched(reg_info);
         if (gdb_comm.WriteRegister(m_thread.GetProtocolID(),
                                    reg_info->kinds[eRegisterKindProcessPlugin],
                                    {data_sp->GetBytes() + reg_info->byte_offset,

diff  --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h
index 6a90f911353fe..f198d6c3ee445 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteRegisterContext.h
@@ -50,7 +50,7 @@ class GDBRemoteRegisterContext : public RegisterContext {
 
   ~GDBRemoteRegisterContext() override;
 
-  void InvalidateAllRegisters() override;
+  void InvalidateAllRegisters() override { SetAllRegistersUnfetched(); }
 
   size_t GetRegisterCount() override;
 
@@ -94,35 +94,58 @@ class GDBRemoteRegisterContext : public RegisterContext {
 
   bool PrivateSetRegisterValue(uint32_t reg, uint64_t val);
 
-  void SetAllRegisterValid(bool b);
+  void SetAllRegistersValid() { SetAllRegistersValidState(eLazyBoolYes); }
+  void SetAllRegistersUnfetched() {
+    SetAllRegistersValidState(eLazyBoolCalculate);
+  }
 
   bool GetRegisterIsValid(uint32_t reg) const {
-    assert(reg < m_reg_valid.size());
-    if (reg < m_reg_valid.size())
-      return m_reg_valid[reg];
-    return false;
+    return GetRegisterValidState(reg) == eLazyBoolYes;
   }
-
-  void SetRegisterIsValid(const RegisterInfo *reg_info, bool valid) {
-    if (reg_info)
-      return SetRegisterIsValid(reg_info->kinds[lldb::eRegisterKindLLDB],
-                                valid);
+  bool GetRegisterIsUnavailable(uint32_t reg) const {
+    return GetRegisterValidState(reg) == eLazyBoolNo;
+  }
+  bool GetRegisterIsUnfetched(uint32_t reg) const {
+    return GetRegisterValidState(reg) == eLazyBoolCalculate;
   }
 
-  void SetRegisterIsValid(uint32_t reg, bool valid) {
-    assert(reg < m_reg_valid.size());
-    if (reg < m_reg_valid.size())
-      m_reg_valid[reg] = valid;
+  void SetRegisterIsValid(const RegisterInfo *reg_info) {
+    SetRegisterIsValidState(reg_info, eLazyBoolYes);
+  }
+  void SetRegisterIsUnavailable(const RegisterInfo *reg_info) {
+    SetRegisterIsValidState(reg_info, eLazyBoolNo);
+  }
+  void SetRegisterIsUnfetched(const RegisterInfo *reg_info) {
+    SetRegisterIsValidState(reg_info, eLazyBoolCalculate);
+  }
+  void SetRegisterIsValid(uint32_t reg) {
+    SetRegisterIsValidState(reg, eLazyBoolYes);
+  }
+  void SetRegisterIsUnavailable(uint32_t reg) {
+    SetRegisterIsValidState(reg, eLazyBoolNo);
+  }
+  void SetRegisterIsUnfetched(uint32_t reg) {
+    SetRegisterIsValidState(reg, eLazyBoolCalculate);
   }
 
   GDBRemoteDynamicRegisterInfoSP m_reg_info_sp;
-  std::vector<bool> m_reg_valid;
+
+  /// eLazyBoolYes - we have the bytes for this register locally.
+  /// eLazyBoolCalculate - we have not yet tried to get the bytes.
+  /// eLazyBoolNo - the bytes are unreadable at this stop.
+  std::vector<LazyBool> m_reg_valid;
+
   DataExtractor m_reg_data;
   bool m_read_all_at_once;
   bool m_write_all_at_once;
   bool m_gpacket_cached;
 
 private:
+  LazyBool GetRegisterValidState(uint32_t reg) const;
+  void SetAllRegistersValidState(LazyBool valid);
+  void SetRegisterIsValidState(const RegisterInfo *reg_info, LazyBool valid);
+  void SetRegisterIsValidState(uint32_t reg, LazyBool valid);
+
   // Helper function for ReadRegisterBytes().
   bool GetPrimordialRegister(const RegisterInfo *reg_info,
                              GDBRemoteCommunicationClient &gdb_comm);

diff  --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index d1d445e4ced06..109cdd8f8dede 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1763,13 +1763,19 @@ void ProcessGDBRemote::ParseExpeditedRegisters(
   RegisterContextSP gdb_reg_ctx_sp(gdb_thread->GetRegisterContext());
 
   for (const auto &pair : expedited_register_map) {
-    StringExtractor reg_value_extractor(pair.second);
-    WritableDataBufferSP buffer_sp(
-        new DataBufferHeap(reg_value_extractor.GetStringRef().size() / 2, 0));
-    reg_value_extractor.GetHexBytes(buffer_sp->GetData(), '\xcc');
     uint32_t lldb_regnum = gdb_reg_ctx_sp->ConvertRegisterKindToRegisterNumber(
         eRegisterKindProcessPlugin, pair.first);
-    gdb_thread->PrivateSetRegisterValue(lldb_regnum, buffer_sp->GetData());
+    if (lldb_regnum != LLDB_INVALID_REGNUM) {
+      StringExtractor reg_value_extractor(pair.second);
+      if (reg_value_extractor.GetStringRef().empty()) {
+        gdb_thread->PrivateSetRegisterUnavailable(lldb_regnum);
+        continue;
+      }
+      WritableDataBufferSP buffer_sp(
+          new DataBufferHeap(reg_value_extractor.GetStringRef().size() / 2, 0));
+      reg_value_extractor.GetHexBytes(buffer_sp->GetData(), '\xcc');
+      gdb_thread->PrivateSetRegisterValue(lldb_regnum, buffer_sp->GetData());
+    }
   }
 }
 

diff  --git a/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp
index ab9d139f1d3e7..c0171734343a6 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.cpp
@@ -339,6 +339,13 @@ bool ThreadGDBRemote::PrivateSetRegisterValue(uint32_t reg, uint64_t regval) {
   return gdb_reg_ctx->PrivateSetRegisterValue(reg, regval);
 }
 
+void ThreadGDBRemote::PrivateSetRegisterUnavailable(uint32_t reg) {
+  GDBRemoteRegisterContext *gdb_reg_ctx =
+      static_cast<GDBRemoteRegisterContext *>(GetRegisterContext().get());
+  assert(gdb_reg_ctx);
+  gdb_reg_ctx->SetRegisterIsUnavailable(reg);
+}
+
 bool ThreadGDBRemote::CalculateStopInfo() {
   ProcessSP process_sp(GetProcess());
   if (process_sp)

diff  --git a/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h
index b2edab7f36978..84f73214bbb09 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ThreadGDBRemote.h
@@ -118,6 +118,8 @@ class ThreadGDBRemote : public Thread {
 
   bool PrivateSetRegisterValue(uint32_t reg, uint64_t regval);
 
+  void PrivateSetRegisterUnavailable(uint32_t reg);
+
   bool CachedQueueInfoIsValid() const {
     return m_queue_kind != lldb::eQueueKindUnknown;
   }

diff  --git a/lldb/test/API/functionalities/gdb_remote_client/TestUnavailableRegisters.py b/lldb/test/API/functionalities/gdb_remote_client/TestUnavailableRegisters.py
new file mode 100644
index 0000000000000..0a1f7d17a95e7
--- /dev/null
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestUnavailableRegisters.py
@@ -0,0 +1,110 @@
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test.gdbclientutils import *
+from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
+
+# Test that lldb will cache when a register is unavailable,
+# and not request the value multiple times at a stop event.
+#
+# Also test that a stop reply packet which indicates that
+# the register cannot be read currently will keep lldb from
+# trying to read it again.
+
+
+class TestUnavailableRegisters(GDBRemoteTestBase):
+    @skipIfXmlSupportMissing
+    @skipIfLLVMTargetMissing("AArch64")
+    def test(self):
+        class MyResponder(MockGDBServerResponder):
+            def __init__(self):
+                super().__init__()
+                self.initial_x2_read = True
+
+            def haltReason(self):
+                # Register 1 cannot be fetched.
+                return (
+                    "T02thread:1ff0d;threads:1ff0d;thread-pcs:000000010001bc00;00:0100000000000000;"
+                    "01:;"
+                    "03:0500000000000000;04:00bc010001000000;"
+                )
+
+            def threadStopInfo(self, threadnum):
+                return self.haltReason()
+
+            def writeRegisters(self):
+                return "E02"
+
+            def qHostInfo(self):
+                return "cputype:16777228;cpusubtype:2;endian:little;ptrsize:8;"
+
+            def readRegisters(self):
+                return "E01"
+
+            def readRegister(self, regnum):
+                # Register 1 was listed in the stop reply packet's expedited
+                # registers as unavailable.  lldb should not send a "p1" to try
+                # to read it.  Send back a value so we can detect if this happened.
+                if regnum == 1:
+                    return "5555555555555555"
+
+                # Register 2 was not included in the stop reply packet.  Return an
+                # error when we try to read it the first time, return a value the
+                # second time we try to read it.  We should never see the value.
+                if regnum == 2:
+                    if self.initial_x2_read:
+                        return "E20"
+                        self.initial_x2_read = False
+                    else:
+                        return "8888888888888888"
+
+                return "E03"
+
+            def qXferRead(self, obj, annex, offset, length):
+                if annex == "target.xml":
+                    return (
+                        """<?xml version="1.0"?>
+                        <target version="1.0">
+                          <architecture>aarch64</architecture>
+                          <feature name="org.gnu.gdb.aarch64.core">
+                            <reg name="x0" regnum="0" bitsize="64"/>
+                            <reg name="x1" bitsize="64"/>
+                            <reg name="x2" bitsize="64"/>
+                            <reg name="x3" bitsize="64"/>
+                            <reg name="x4" bitsize="64"/>
+                            <reg name="pc" bitsize="64"/>
+                          </feature>
+                        </target>""",
+                        False,
+                    )
+                else:
+                    return None, False
+
+        self.server.responder = MyResponder()
+        target = self.dbg.CreateTarget("")
+        if self.TraceOn():
+            self.runCmd("log enable gdb-remote packets")
+            self.addTearDownHook(lambda: self.runCmd("log disable gdb-remote packets"))
+        process = self.connect(target)
+
+        thread = process.GetThreadAtIndex(0)
+        frame = thread.GetFrameAtIndex(0)
+
+        self.assertEqual(frame.FindRegister("pc").GetValueAsUnsigned(), 0x10001BC00)
+
+        # value 1 from the stop reply packet's expedited registers
+        self.assertEqual(frame.FindRegister("x0").GetValueAsUnsigned(), 1)
+
+        # Register is marked unavailable in the stop reply packet's
+        # expedited register list.  Should not get a value by sending
+        # 'p1'.
+        self.assertFailure(frame.FindRegister("x1").GetError())
+
+        # x2 is _not_ in the expedited registers, and will reply with
+        # an error the first time it is requested.
+        self.assertFailure(frame.FindRegister("x2").GetError())
+
+        # x2 will reply with a value the second time it is requested.
+        # lldb should cache the error from the first attempt and not
+        # try again.
+        self.assertFailure(frame.FindRegister("x2").GetError())

diff  --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp
index b6349dc60392a..1c7c3eb8e41d7 100644
--- a/lldb/tools/debugserver/source/RNBRemote.cpp
+++ b/lldb/tools/debugserver/source/RNBRemote.cpp
@@ -2911,11 +2911,12 @@ rnb_err_t RNBRemote::SendStopReplyPacketForThread(nub_thread_t tid) {
         if (!DNBThreadGetRegisterValueByID(pid, tid, regset,
                                            g_reg_entries[reg].nub_info.reg,
                                            reg_value.get()))
-          continue;
-
-        debugserver_regnum_with_fixed_width_hex_register_value(
-            ostrm, pid, tid, &g_reg_entries[reg], reg_value.get(),
-            std::nullopt);
+          // base16(regnum):<no value>; means a register that cannot be fetched.
+          ostrm << RAWHEX8(g_reg_entries[reg].debugserver_regnum) << ":;";
+        else
+          debugserver_regnum_with_fixed_width_hex_register_value(
+              ostrm, pid, tid, &g_reg_entries[reg], reg_value.get(),
+              std::nullopt);
       }
     }
 
@@ -5818,15 +5819,30 @@ RNBRemote::GetJSONThreadsInfo(bool threads_with_valid_stop_info_only) {
               new JSONGenerator::Dictionary());
 
           for (uint32_t reg = 0; reg < g_num_reg_entries; reg++) {
+            bool include_reg = false;
             // Expedite all registers in the first register set that aren't
-            // contained in other registers
+            // contained in other registers.
             if (g_reg_entries[reg].nub_info.set == 1 &&
-                g_reg_entries[reg].nub_info.value_regs == NULL) {
+                g_reg_entries[reg].nub_info.value_regs == NULL)
+              include_reg = true;
+            // Include the SME state register values, whether we can
+            // fetch the value or not.
+            if (strcmp("svcr", g_reg_entries[reg].nub_info.name) == 0 ||
+                strcmp("tpidr2", g_reg_entries[reg].nub_info.name) == 0 ||
+                strcmp("svl", g_reg_entries[reg].nub_info.name) == 0)
+              include_reg = true;
+
+            if (include_reg) {
               if (!DNBThreadGetRegisterValueByID(
                       pid, tid, g_reg_entries[reg].nub_info.set,
-                      g_reg_entries[reg].nub_info.reg, reg_value.get()))
+                      g_reg_entries[reg].nub_info.reg, reg_value.get())) {
+                // Indicate unavailable registers as having an empty value
+                // string.
+                std::ostringstream reg_num;
+                reg_num << std::dec << g_reg_entries[reg].debugserver_regnum;
+                registers_dict_sp->AddStringItem(reg_num.str(), "");
                 continue;
-
+              }
               std::ostringstream reg_num;
               reg_num << std::dec << g_reg_entries[reg].debugserver_regnum;
               // Encode native byte ordered bytes as hex ascii


        


More information about the lldb-commits mailing list