[Lldb-commits] [lldb] [lldb-server] Handle jThreadExtendedInfo (PR #227472)

via lldb-commits lldb-commits at lists.llvm.org
Sun Oct 4 11:30:38 PDT 2026


https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/227472

>From 058a6017b74f8d8d896090c147c3d8cab1444e6c Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Tue, 29 Sep 2026 22:59:04 +0200
Subject: [PATCH] [lldb-server] Handle jThreadExtendedInfo

---
 .../lldb/Host/common/NativeThreadProtocol.h   |  3 ++
 .../lldb/Utility/StringExtractorGDBRemote.h   |  1 +
 .../Python/lldbsuite/test/gdbclientutils.py   | 37 +++++++++++++-
 .../Windows/Common/NativeThreadWindows.cpp    |  4 ++
 .../Windows/Common/NativeThreadWindows.h      |  2 +
 .../GDBRemoteCommunicationServerLLGS.cpp      | 49 ++++++++++++++++++
 .../GDBRemoteCommunicationServerLLGS.h        |  2 +
 .../Utility/StringExtractorGDBRemote.cpp      |  2 +
 .../TestGdbRemote_jThreadExtendedInfo.py      | 51 +++++++++++++++++++
 .../API/windows/thread-extended-info/Makefile |  3 ++
 .../TestThreadExtendedInfo.py                 | 42 +++++++++++++++
 .../API/windows/thread-extended-info/main.c   |  3 ++
 12 files changed, 198 insertions(+), 1 deletion(-)
 create mode 100644 lldb/test/API/tools/lldb-server/TestGdbRemote_jThreadExtendedInfo.py
 create mode 100644 lldb/test/API/windows/thread-extended-info/Makefile
 create mode 100644 lldb/test/API/windows/thread-extended-info/TestThreadExtendedInfo.py
 create mode 100644 lldb/test/API/windows/thread-extended-info/main.c

diff --git a/lldb/include/lldb/Host/common/NativeThreadProtocol.h b/lldb/include/lldb/Host/common/NativeThreadProtocol.h
index 1bd16cfa26c1e81..cb5079424974714 100644
--- a/lldb/include/lldb/Host/common/NativeThreadProtocol.h
+++ b/lldb/include/lldb/Host/common/NativeThreadProtocol.h
@@ -13,6 +13,7 @@
 #include <string>
 
 #include "lldb/Host/Debug.h"
+#include "lldb/Utility/StructuredData.h"
 #include "lldb/Utility/UnimplementedError.h"
 #include "lldb/lldb-private-forward.h"
 #include "lldb/lldb-types.h"
@@ -57,6 +58,8 @@ class NativeThreadProtocol {
     return llvm::make_error<UnimplementedError>();
   }
 
+  virtual StructuredData::ObjectSP GetExtendedInfo() const { return nullptr; }
+
 protected:
   void ClearStopInfo() {
     m_stop_info = ThreadStopInfo();
diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
index fec3c09f225d774..b70372db4b6107d 100644
--- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
+++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h
@@ -178,6 +178,7 @@ class StringExtractorGDBRemote : public StringExtractor {
     eServerPacketType_jAcceleratorPluginInitialize,
     eServerPacketType_jAcceleratorPluginBreakpointHit,
     eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo,
+    eServerPacketType_jThreadExtendedInfo,
 
     eServerPacketType_qMemTags, // read memory tags
     eServerPacketType_QMemTags, // write memory tags
diff --git a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
index 4cd204e6d7b558b..efe23d78c76c497 100644
--- a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
+++ b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py
@@ -56,6 +56,36 @@ def escape_binary(message):
     return out
 
 
+def unescape_binary(message: str) -> Optional[str]:
+    """
+    Unescape the binary message using the process described in the GDB server
+    protocol documentation.
+    """
+    out = ""
+    next_is_xor = False
+    repeat_char = None
+    for c in message:
+        d = ord(c)
+        if repeat_char is not None:
+            rep_count = d + 3 - 0x20
+            out += repeat_char * rep_count
+            rep_count = None
+        elif d == 0x2A:  # ASCII '*'
+            if len(out) == 0:
+                return None  # no char to repeat
+            repeat_char = out[-1]
+        elif next_is_xor:
+            next_is_xor = False
+            out += chr(d ^ 0x20)
+        elif d == 0x7D:  # ASCII '}'
+            next_is_xor = True
+        else:
+            out += c
+    if next_is_xor or repeat_char is not None:
+        return None  # incomplete escape
+    return out
+
+
 def hex_encode_bytes(message):
     """
     Encode the binary message by converting each byte into a two-character
@@ -224,7 +254,7 @@ def respond(self, packet: str) -> Sequence[Response]:
             self.packetLog.add_sent(part)
         return response
 
-    def _respond_impl(self, packet) -> Union[Response, List[Response]]:
+    def _respond_impl(self, packet: str) -> Union[Response, List[Response]]:
         if packet is MockGDBServer.PACKET_INTERRUPT:
             return self.interrupt()
         if packet == "c":
@@ -341,6 +371,8 @@ def _respond_impl(self, packet) -> Union[Response, List[Response]]:
         if packet.startswith("qRegisterInfo"):
             regnum = int(packet[len("qRegisterInfo") :], 16)
             return self.qRegisterInfo(regnum)
+        if packet.startswith("jThreadExtendedInfo:"):
+            return self.jThreadExtendedInfo(packet.split(":", 1)[1])
         if packet == "k":
             return self.k()
 
@@ -494,6 +526,9 @@ def QEnvironmentHexEncoded(self, packet) -> str:
     def qRegisterInfo(self, num) -> str:
         return ""
 
+    def jThreadExtendedInfo(self, value: str) -> str:
+        return ""
+
     def k(self):
         return ["W01", self.RESPONSE_DISCONNECT]
 
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
index 0842968f25d6037..b6363616cf31a08 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
@@ -194,3 +194,7 @@ Status NativeThreadWindows::SetHardwareBreakpoint(lldb::addr_t addr,
 Status NativeThreadWindows::RemoveHardwareBreakpoint(lldb::addr_t addr) {
   return Status::FromErrorString("unimplemented.");
 }
+
+StructuredData::ObjectSP NativeThreadWindows::GetExtendedInfo() const {
+  return m_host_thread.GetNativeThread().GetExtendedInfo();
+}
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h
index 392a5b9a2a7d4af..b867489a2fd2887 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.h
@@ -48,6 +48,8 @@ class NativeThreadWindows : public NativeThreadProtocol {
 
   Status RemoveHardwareBreakpoint(lldb::addr_t addr) override;
 
+  StructuredData::ObjectSP GetExtendedInfo() const override;
+
   void SetStopReason(ThreadStopInfo stop_info, std::string description);
 
   const HostThread &GetHostThread() { return m_host_thread; }
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
index 516f88aaf607683..a35dcece5953d20 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -228,6 +228,9 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() {
   RegisterMemberFunctionHandler(
       StringExtractorGDBRemote::eServerPacketType_jMultiBreakpoint,
       &GDBRemoteCommunicationServerLLGS::Handle_jMultiBreakpoint);
+  RegisterMemberFunctionHandler(
+      StringExtractorGDBRemote::eServerPacketType_jThreadExtendedInfo,
+      &GDBRemoteCommunicationServerLLGS::Handle_jThreadExtendedInfo);
   RegisterMemberFunctionHandler(
       StringExtractorGDBRemote::eServerPacketType_jAcceleratorPluginInitialize,
       &GDBRemoteCommunicationServerLLGS::Handle_jAcceleratorPluginInitialize);
@@ -4732,3 +4735,49 @@ GDBRemoteCommunication::PacketResult GDBRemoteCommunicationServerLLGS::
   return SendErrorResponse(
       Status::FromErrorString("unknown accelerator plugin name"));
 }
+
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_jThreadExtendedInfo(
+    StringExtractorGDBRemote &packet) {
+  llvm::StringRef packet_str = packet.GetStringRef();
+  if (!packet_str.consume_front("jThreadExtendedInfo:"))
+    return SendIllFormedResponse(packet,
+                                 "Invalid jThreadExtendedInfo packet prefix");
+  // Empty packet is sent to check if we support jThreadExtendedInfo.
+  if (packet_str.empty())
+    return SendOKResponse();
+
+  llvm::Expected<llvm::json::Value> parsed = llvm::json::parse(packet_str);
+  if (!parsed) {
+    llvm::consumeError(parsed.takeError());
+    return SendIllFormedResponse(
+        packet, "jThreadExtendedInfo did not contain valid JSON");
+  }
+  llvm::json::Object *request_dict = parsed->getAsObject();
+  if (!request_dict)
+    return SendIllFormedResponse(
+        packet, "jThreadExtendedInfo did not contain a JSON dictionary");
+
+  std::optional<int64_t> thread_id = request_dict->getInteger("thread");
+  if (!thread_id)
+    return SendIllFormedResponse(packet, "jThreadExtendedInfo did not contain "
+                                         "a valid 'thread' field");
+
+  if (!m_current_process)
+    return SendIllFormedResponse(packet, "no current process");
+
+  NativeThreadProtocol *thread = m_current_process->GetThreadByID(*thread_id);
+  if (!thread)
+    return SendIllFormedResponse(packet, "no thread with specified ID");
+
+  StructuredData::ObjectSP ext_info = thread->GetExtendedInfo();
+
+  StreamString stream;
+  if (ext_info)
+    ext_info->Dump(stream, false);
+
+  StringRef response_str = stream.GetString();
+  StreamGDBRemote response;
+  response.PutEscapedBytes(response_str.data(), response_str.size());
+  return SendPacketNoLock(response.GetString());
+}
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
index 873f24d10a62770..ac24c69dee0fae7 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
@@ -313,6 +313,8 @@ class GDBRemoteCommunicationServerLLGS
   PacketResult Handle_jAcceleratorPluginGetDynamicLoaderLibraryInfo(
       StringExtractorGDBRemote &packet);
 
+  PacketResult Handle_jThreadExtendedInfo(StringExtractorGDBRemote &packet);
+
   void SetCurrentThreadID(lldb::tid_t tid);
 
   lldb::tid_t GetCurrentThreadID() const;
diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp
index a015e7c3c5c8b46..ed15926d621bf08 100644
--- a/lldb/source/Utility/StringExtractorGDBRemote.cpp
+++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp
@@ -342,6 +342,8 @@ StringExtractorGDBRemote::GetServerPacketType() const {
       return eServerPacketType_jAcceleratorPluginBreakpointHit;
     if (PACKET_STARTS_WITH("jAcceleratorPluginGetDynamicLoaderLibraryInfo:"))
       return eServerPacketType_jAcceleratorPluginGetDynamicLoaderLibraryInfo;
+    if (PACKET_STARTS_WITH("jThreadExtendedInfo:"))
+      return eServerPacketType_jThreadExtendedInfo;
     break;
 
   case 'v':
diff --git a/lldb/test/API/tools/lldb-server/TestGdbRemote_jThreadExtendedInfo.py b/lldb/test/API/tools/lldb-server/TestGdbRemote_jThreadExtendedInfo.py
new file mode 100644
index 000000000000000..d84cb85f1214574
--- /dev/null
+++ b/lldb/test/API/tools/lldb-server/TestGdbRemote_jThreadExtendedInfo.py
@@ -0,0 +1,51 @@
+import gdbremote_testcase
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import re
+import json
+
+
+ at add_test_categories(["llgs"])
+class TestGdbRemote_jThreadExtendedInfo(gdbremote_testcase.GdbRemoteTestCaseBase):
+    def test(self):
+        self.build()
+        self.prep_debug_monitor_and_inferior()
+
+        self.add_threadinfo_collection_packets()
+        self.test_sequence.add_log_lines(
+            [
+                "read packet: $jThreadExtendedInfo:#00",
+                "send packet: $OK#00",
+            ],
+            True,
+        )
+        context = self.expect_gdbremote_sequence()
+        threads = self.parse_threadinfo_packets(context)
+        self.assertEqual(len(threads), 1)
+        print(threads)
+        self.test_sequence.add_log_lines(
+            [
+                f'read packet: $jThreadExtendedInfo:{{"thread":{threads[0]}}}]#00',
+                {
+                    "direction": "send",
+                    "regex": re.compile(
+                        r"^\$(.*)#[0-9a-fA-F]{2}$", re.MULTILINE | re.DOTALL
+                    ),
+                    "capture": {1: "content_raw"},
+                },
+            ],
+            True,
+        )
+        context = self.expect_gdbremote_sequence()
+        content_raw = context.get("content_raw")
+        self.assertIsNotNone(content_raw)
+        content = self.decode_gdbremote_binary(content_raw)
+        triple = self.dbg.GetSelectedPlatform().GetTriple()
+        # Only Windows has extended info.
+        if re.match(".*-.*-windows", triple):
+            dec = json.loads(content)
+            self.assertIsInstance(dec, dict)
+            self.assertIn("teb_address", dec)
+            self.assertNotEqual(dec["teb_address"], 0)
+        else:
+            self.assertEqual(content, "")
diff --git a/lldb/test/API/windows/thread-extended-info/Makefile b/lldb/test/API/windows/thread-extended-info/Makefile
new file mode 100644
index 000000000000000..10495940055b63d
--- /dev/null
+++ b/lldb/test/API/windows/thread-extended-info/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/windows/thread-extended-info/TestThreadExtendedInfo.py b/lldb/test/API/windows/thread-extended-info/TestThreadExtendedInfo.py
new file mode 100644
index 000000000000000..d530979d1e2c226
--- /dev/null
+++ b/lldb/test/API/windows/thread-extended-info/TestThreadExtendedInfo.py
@@ -0,0 +1,42 @@
+"""
+Test that the extended info of a thread contains the TEB address.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+ at requireWindows
+class TestThreadExtendedInfo(TestBase):
+    def tearDown(self):
+        self.runCmd("settings clear thread-format")
+        return super().tearDown()
+
+    @no_debug_info_test
+    def test(self):
+        self.build()
+
+        target, process, thread, _ = lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.c")
+        )
+
+        err = lldb.SBError()
+        fmt = lldb.SBFormat("${thread.id%%zu};${thread.info.teb_address%%zu}", err)
+        self.assertSuccess(err)
+        stream = lldb.SBStream()
+        self.assertSuccess(thread.GetDescriptionWithFormat(fmt, stream))
+        data = (stream.GetData() or "").split(";")
+        self.assertEqual(len(data), 2)
+        tid = int(data[0])
+        teb_addr = int(data[1])
+        self.assertNotEqual(tid, 0)
+        self.assertNotEqual(teb_addr, 0)
+
+        # Check that the address we got is correct by reading the thread ID out
+        # of the block.
+        tid_off = 0x48 if target.GetAddressByteSize() == 8 else 0x24
+        tid_from_teb = process.ReadUnsignedFromMemory(teb_addr + tid_off, 4, err)
+        self.assertSuccess(err)
+        self.assertEqual(tid, tid_from_teb)
diff --git a/lldb/test/API/windows/thread-extended-info/main.c b/lldb/test/API/windows/thread-extended-info/main.c
new file mode 100644
index 000000000000000..ba45ee316cd42b6
--- /dev/null
+++ b/lldb/test/API/windows/thread-extended-info/main.c
@@ -0,0 +1,3 @@
+int main() {
+  return 0; // break here
+}



More information about the lldb-commits mailing list