diff --git a/lldb/include/lldb/Host/windows/LazyImport.h b/lldb/include/lldb/Host/windows/LazyImport.h new file mode 100644 index 0000000000000..231500da0fe82 --- /dev/null +++ b/lldb/include/lldb/Host/windows/LazyImport.h @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// 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 class LazyImport { +public: + 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. + FnPtr get() const { return m_resolved; } + + 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) { + HMODULE module = ::LoadLibraryW(dll); + if (!module) + return nullptr; + return reinterpret_cast( + reinterpret_cast(::GetProcAddress(module, symbol))); + } + + FnPtr m_resolved; +}; + +} // namespace lldb_private + +#endif // LLDB_HOST_WINDOWS_LAZYIMPORT_H diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp index 442af86af40d2..da0fe0d891d6f 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 using namespace lldb; using namespace lldb_private; @@ -99,17 +101,25 @@ Status NativeThreadWindows::DoResume(lldb::StateType resume_state) { } std::string NativeThreadWindows::GetName() { - if (!m_name.empty()) + Log *log = GetLog(LLDBLog::Thread); + static LazyImport + s_get_thread_description{L"Kernel32.dll", "GetThreadDescription"}; + if (!s_get_thread_description) 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; + auto GetThreadDescription = *s_get_thread_description; + + 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(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..b2131a5ee7b13 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( - (void *)::GetProcAddress(hModule, "GetThreadDescription")) - : nullptr; - }(); - LLDB_LOGF(log, "GetProcAddress: %p", - reinterpret_cast(GetThreadDescription)); - if (!GetThreadDescription) + static LazyImport + s_get_thread_description{L"Kernel32.dll", "GetThreadDescription"}; + if (!s_get_thread_description) return m_name.c_str(); + auto GetThreadDescription = *s_get_thread_description; + 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..590b936191a22 --- /dev/null +++ b/lldb/test/API/windows/thread/TestThreadName.py @@ -0,0 +1,39 @@ +""" +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") + + _, process, thread, breakpoint = lldbutil.run_to_source_breakpoint( + self, "// break here", source + ) + self.assertEqual( + breakpoint.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") diff --git a/lldb/test/API/windows/thread/main.c b/lldb/test/API/windows/thread/main.c new file mode 100644 index 0000000000000..bc8e822e54fbd --- /dev/null +++ b/lldb/test/API/windows/thread/main.c @@ -0,0 +1,16 @@ +#include +#include +#include + +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; +}