[Lldb-commits] [lldb] [lldb-server][Windows] add support for thread name (PR #199983)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Thu May 28 07:48:18 PDT 2026
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/199983
>From a0aed4b7998ad8717e80d417c13dc33149f7f147 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 27 May 2026 13:52:22 +0100
Subject: [PATCH 1/6] [lldb][Windows] Don't synthesise a fake thread name from
the executable
---
.../Process/Windows/Common/NativeThreadWindows.cpp | 13 ++-----------
1 file changed, 2 insertions(+), 11 deletions(-)
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
index 442af86af40d2..598bbdf6c9eed 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
@@ -99,17 +99,8 @@ Status NativeThreadWindows::DoResume(lldb::StateType resume_state) {
}
std::string NativeThreadWindows::GetName() {
- if (!m_name.empty())
- return m_name;
-
- // Name is not a property of the Windows thread. Create one with the
- // process's.
- NativeProcessProtocol &process = GetProcess();
- ProcessInstanceInfo process_info;
- if (Host::GetProcessInfo(process.GetID(), process_info)) {
- std::string process_name(process_info.GetName());
- m_name = process_name;
- }
+ // Windows threads only have a name when the inferior calls
+ // SetThreadDescription explicitly.
return m_name;
}
>From 38f0ce3d5bdce2066b0567a78d2757db72460665 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 27 May 2026 17:06:31 +0100
Subject: [PATCH 2/6] [lldb-server][Windows] add support for thread name
---
lldb/include/lldb/Host/windows/LazyImport.h | 46 +++++++++++++++++++
.../Windows/Common/NativeThreadWindows.cpp | 23 +++++++++-
.../Windows/Common/TargetThreadWindows.cpp | 19 +++-----
lldb/test/API/windows/thread/Makefile | 3 ++
.../test/API/windows/thread/TestThreadName.py | 38 +++++++++++++++
lldb/test/API/windows/thread/main.c | 16 +++++++
6 files changed, 130 insertions(+), 15 deletions(-)
create mode 100644 lldb/include/lldb/Host/windows/LazyImport.h
create mode 100644 lldb/test/API/windows/thread/Makefile
create mode 100644 lldb/test/API/windows/thread/TestThreadName.py
create mode 100644 lldb/test/API/windows/thread/main.c
diff --git a/lldb/include/lldb/Host/windows/LazyImport.h b/lldb/include/lldb/Host/windows/LazyImport.h
new file mode 100644
index 0000000000000..9be0fb73da4ca
--- /dev/null
+++ b/lldb/include/lldb/Host/windows/LazyImport.h
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_HOST_WINDOWS_LAZYIMPORT_H
+#define LLDB_HOST_WINDOWS_LAZYIMPORT_H
+
+#include "lldb/Host/windows/windows.h"
+
+namespace lldb_private {
+
+template <typename FnPtr> class LazyImport {
+public:
+ constexpr LazyImport(const wchar_t *dll, const char *symbol)
+ : m_dll(dll), m_symbol(symbol) {}
+
+ /// Returns the resolved function pointer, or nullptr if the DLL or symbol
+ /// is unavailable on this system. Resolution happens once.
+ FnPtr get() const {
+ static FnPtr resolved = Resolve(m_dll, m_symbol);
+ return resolved;
+ }
+
+ explicit operator bool() const { return get() != nullptr; }
+ FnPtr operator*() const { return get(); }
+
+private:
+ static FnPtr Resolve(const wchar_t *dll, const char *symbol) {
+ HMODULE module = ::LoadLibraryW(dll);
+ if (!module)
+ return nullptr;
+ return reinterpret_cast<FnPtr>(
+ reinterpret_cast<void *>(::GetProcAddress(module, symbol)));
+ }
+
+ const wchar_t *m_dll;
+ const char *m_symbol;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_HOST_WINDOWS_LAZYIMPORT_H
\ No newline at end of file
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
index 598bbdf6c9eed..302b1c347b5ca 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
@@ -11,6 +11,7 @@
#include "lldb/Host/HostThread.h"
#include "lldb/Host/windows/HostThreadWindows.h"
+#include "lldb/Host/windows/LazyImport.h"
#include "lldb/Host/windows/windows.h"
#include "lldb/Target/Process.h"
#include "lldb/Utility/LLDBLog.h"
@@ -18,6 +19,7 @@
#include "lldb/Utility/State.h"
#include "lldb/lldb-forward.h"
+#include <llvm/Support/ConvertUTF.h>
using namespace lldb;
using namespace lldb_private;
@@ -99,8 +101,25 @@ Status NativeThreadWindows::DoResume(lldb::StateType resume_state) {
}
std::string NativeThreadWindows::GetName() {
- // Windows threads only have a name when the inferior calls
- // SetThreadDescription explicitly.
+ Log *log = GetLog(LLDBLog::Thread);
+ static LazyImport<HRESULT(WINAPI *)(HANDLE, PWSTR *)> kGetThreadDescription{
+ L"Kernel32.dll", "GetThreadDescription"};
+ if (!kGetThreadDescription)
+ return m_name;
+ auto GetThreadDescription = *kGetThreadDescription;
+
+ PWSTR pszThreadName;
+ if (SUCCEEDED(GetThreadDescription(
+ m_host_thread.GetNativeThread().GetSystemHandle(), &pszThreadName))) {
+ LLDB_LOGF(log, "GetThreadDescription: %ls", pszThreadName);
+ m_name.clear();
+ llvm::convertUTF16ToUTF8String(
+ llvm::ArrayRef(reinterpret_cast<char *>(pszThreadName),
+ wcslen(pszThreadName) * sizeof(wchar_t)),
+ m_name);
+ ::LocalFree(pszThreadName);
+ }
+
return m_name;
}
diff --git a/lldb/source/Plugins/Process/Windows/Common/TargetThreadWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/TargetThreadWindows.cpp
index bd6db55446cd6..f04b0a7eb217b 100644
--- a/lldb/source/Plugins/Process/Windows/Common/TargetThreadWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/TargetThreadWindows.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "lldb/Host/HostInfo.h"
+#include "lldb/Host/windows/LazyImport.h"
#include "lldb/Target/Unwind.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
@@ -29,9 +30,6 @@
using namespace lldb;
using namespace lldb_private;
-using GetThreadDescriptionFunctionPtr =
- HRESULT(WINAPI *)(HANDLE hThread, PWSTR *ppszThreadDescription);
-
TargetThreadWindows::TargetThreadWindows(ProcessWindows &process,
const HostThread &thread)
: Thread(process, thread.GetNativeThread().GetThreadId()),
@@ -177,17 +175,12 @@ Status TargetThreadWindows::DoResume() {
const char *TargetThreadWindows::GetName() {
Log *log = GetLog(LLDBLog::Thread);
- static GetThreadDescriptionFunctionPtr GetThreadDescription = []() {
- HMODULE hModule = ::LoadLibraryW(L"Kernel32.dll");
- return hModule
- ? reinterpret_cast<GetThreadDescriptionFunctionPtr>(
- (void *)::GetProcAddress(hModule, "GetThreadDescription"))
- : nullptr;
- }();
- LLDB_LOGF(log, "GetProcAddress: %p",
- reinterpret_cast<void *>(GetThreadDescription));
- if (!GetThreadDescription)
+ static LazyImport<HRESULT(WINAPI *)(HANDLE, PWSTR *)> kGetThreadDescription{
+ L"Kernel32.dll", "GetThreadDescription"};
+ if (!kGetThreadDescription)
return m_name.c_str();
+ auto GetThreadDescription = *kGetThreadDescription;
+
PWSTR pszThreadName;
if (SUCCEEDED(GetThreadDescription(
m_host_thread.GetNativeThread().GetSystemHandle(), &pszThreadName))) {
diff --git a/lldb/test/API/windows/thread/Makefile b/lldb/test/API/windows/thread/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/windows/thread/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/windows/thread/TestThreadName.py b/lldb/test/API/windows/thread/TestThreadName.py
new file mode 100644
index 0000000000000..b8169ab0bdd33
--- /dev/null
+++ b/lldb/test/API/windows/thread/TestThreadName.py
@@ -0,0 +1,38 @@
+"""
+Test that lldb reports the Windows thread description set via SetThreadDescription.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestThreadName(TestBase):
+ @skipUnlessWindows
+ @skipIfWindows(windows_version=["<", "10.0.14393"])
+ def test_with_thread_description(self):
+ """SBThread.GetName() reflects SetThreadDescription on Windows."""
+ self.build()
+ source = lldb.SBFileSpec("main.c")
+
+ target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+ self, "// break here", source
+ )
+ self.assertEqual(
+ bkpt.GetNumLocations(), 2,
+ "expected breakpoints at both '// break here' markers",
+ )
+
+ # No thread name yet.
+ self.assertFalse(
+ thread.GetName(),
+ "thread should have no name before SetThreadDescription",
+ )
+
+ process.Continue()
+ self.assertEqual(process.GetState(), lldb.eStateStopped)
+
+ thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
+ self.assertTrue(thread.IsValid(), "no thread stopped at breakpoint")
+ self.assertEqual(thread.GetName(), "ThreadName")
\ No newline at end of file
diff --git a/lldb/test/API/windows/thread/main.c b/lldb/test/API/windows/thread/main.c
new file mode 100644
index 0000000000000..e9384c4bcdddf
--- /dev/null
+++ b/lldb/test/API/windows/thread/main.c
@@ -0,0 +1,16 @@
+#include <processthreadsapi.h>
+#include <stdio.h>
+#include <windows.h>
+
+int main() {
+ // break here
+ HANDLE thread = GetCurrentThread();
+ HRESULT hr = SetThreadDescription(thread, L"ThreadName");
+ if (FAILED(hr)) {
+ fprintf(stderr, "SetThreadDescription failed: 0x%08lx\n", hr);
+ return 1;
+ }
+
+ printf("Thread name set successfully.\n"); // break here
+ return 0;
+}
\ No newline at end of file
>From 7cde91b206a125504062bd2052e2dac0475dfdbe Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 27 May 2026 17:10:53 +0100
Subject: [PATCH 3/6] fixup! [lldb-server][Windows] add support for thread name
---
lldb/test/API/windows/thread/TestThreadName.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lldb/test/API/windows/thread/TestThreadName.py b/lldb/test/API/windows/thread/TestThreadName.py
index b8169ab0bdd33..590b936191a22 100644
--- a/lldb/test/API/windows/thread/TestThreadName.py
+++ b/lldb/test/API/windows/thread/TestThreadName.py
@@ -16,11 +16,12 @@ def test_with_thread_description(self):
self.build()
source = lldb.SBFileSpec("main.c")
- target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+ _, process, thread, breakpoint = lldbutil.run_to_source_breakpoint(
self, "// break here", source
)
self.assertEqual(
- bkpt.GetNumLocations(), 2,
+ breakpoint.GetNumLocations(),
+ 2,
"expected breakpoints at both '// break here' markers",
)
@@ -35,4 +36,4 @@ def test_with_thread_description(self):
thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
self.assertTrue(thread.IsValid(), "no thread stopped at breakpoint")
- self.assertEqual(thread.GetName(), "ThreadName")
\ No newline at end of file
+ self.assertEqual(thread.GetName(), "ThreadName")
>From 4f5164d5e6d566f7f8611fc5893fc17c2d392578 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 27 May 2026 17:11:50 +0100
Subject: [PATCH 4/6] add empty line
---
lldb/include/lldb/Host/windows/LazyImport.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/include/lldb/Host/windows/LazyImport.h b/lldb/include/lldb/Host/windows/LazyImport.h
index 9be0fb73da4ca..cd3b4512a241e 100644
--- a/lldb/include/lldb/Host/windows/LazyImport.h
+++ b/lldb/include/lldb/Host/windows/LazyImport.h
@@ -43,4 +43,4 @@ template <typename FnPtr> class LazyImport {
} // namespace lldb_private
-#endif // LLDB_HOST_WINDOWS_LAZYIMPORT_H
\ No newline at end of file
+#endif // LLDB_HOST_WINDOWS_LAZYIMPORT_H
>From 6b26a719ae7e6e978b5259a128120796d0952c8e Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Thu, 28 May 2026 15:22:24 +0100
Subject: [PATCH 5/6] fixup! add empty line
---
lldb/test/API/windows/thread/main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/test/API/windows/thread/main.c b/lldb/test/API/windows/thread/main.c
index e9384c4bcdddf..bc8e822e54fbd 100644
--- a/lldb/test/API/windows/thread/main.c
+++ b/lldb/test/API/windows/thread/main.c
@@ -13,4 +13,4 @@ int main() {
printf("Thread name set successfully.\n"); // break here
return 0;
-}
\ No newline at end of file
+}
>From 6d972f2b25c02f93b6f711c63fb1a3ee26e04999 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Thu, 28 May 2026 15:47:58 +0100
Subject: [PATCH 6/6] remove constexpr
---
lldb/include/lldb/Host/windows/LazyImport.h | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/lldb/include/lldb/Host/windows/LazyImport.h b/lldb/include/lldb/Host/windows/LazyImport.h
index cd3b4512a241e..231500da0fe82 100644
--- a/lldb/include/lldb/Host/windows/LazyImport.h
+++ b/lldb/include/lldb/Host/windows/LazyImport.h
@@ -15,18 +15,15 @@ namespace lldb_private {
template <typename FnPtr> class LazyImport {
public:
- constexpr LazyImport(const wchar_t *dll, const char *symbol)
- : m_dll(dll), m_symbol(symbol) {}
+ LazyImport(const wchar_t *dll, const char *symbol)
+ : m_resolved(Resolve(dll, symbol)) {}
/// Returns the resolved function pointer, or nullptr if the DLL or symbol
- /// is unavailable on this system. Resolution happens once.
- FnPtr get() const {
- static FnPtr resolved = Resolve(m_dll, m_symbol);
- return resolved;
- }
+ /// is unavailable on this system.
+ FnPtr get() const { return m_resolved; }
- explicit operator bool() const { return get() != nullptr; }
- FnPtr operator*() const { return get(); }
+ explicit operator bool() const { return m_resolved != nullptr; }
+ FnPtr operator*() const { return m_resolved; }
private:
static FnPtr Resolve(const wchar_t *dll, const char *symbol) {
@@ -37,8 +34,7 @@ template <typename FnPtr> class LazyImport {
reinterpret_cast<void *>(::GetProcAddress(module, symbol)));
}
- const wchar_t *m_dll;
- const char *m_symbol;
+ FnPtr m_resolved;
};
} // namespace lldb_private
More information about the lldb-commits
mailing list