[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