[Lldb-commits] [lldb] [lldb][Windows] Don't synthesise a fake thread name from the executable (PR #199983)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Wed May 27 09:06:46 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/2] [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/2] [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
More information about the lldb-commits
mailing list