From 9380c15a4d297daad27972eabbb9f406384ecfa8 Mon Sep 17 00:00:00 2001 From: Charles Zablit Date: Thu, 9 Oct 2025 16:50:49 +0100 Subject: [PATCH 1/3] [lldb][windows] add support for out of PATH python.dll resolution (#162509) This patch adds the `LLDB_PYTHON_DLL_RELATIVE_PATH` Cmake variable which is the relative path to the directory containing `python.dll`. The path is relative to the directory containing `lldb.exe`. If this variable is set and the resolved path points to an existing directory, we call `SetDllDirectoryW` to add `python.dll` to the list of DLL search paths. This, combined with `liblldb.dll` being delay loaded, allows to package `python.dll` with the `llvm` installer. --- lldb/cmake/modules/AddLLDB.cmake | 6 ++++ lldb/tools/driver/CMakeLists.txt | 4 +++ lldb/tools/driver/Driver.cpp | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/lldb/cmake/modules/AddLLDB.cmake b/lldb/cmake/modules/AddLLDB.cmake index 154123bbadb86..8de289468b232 100644 --- a/lldb/cmake/modules/AddLLDB.cmake +++ b/lldb/cmake/modules/AddLLDB.cmake @@ -248,6 +248,12 @@ function(add_lldb_executable name) ) target_link_libraries(${name} PRIVATE ${ARG_LINK_LIBS}) + if(WIN32) + list(FIND ARG_LINK_LIBS liblldb LIBLLDB_INDEX) + if(NOT LIBLLDB_INDEX EQUAL -1) + target_link_options(${name} PRIVATE "/DELAYLOAD:$.dll") + endif() + endif() if(CLANG_LINK_CLANG_DYLIB) target_link_libraries(${name} PRIVATE clang-cpp) else() diff --git a/lldb/tools/driver/CMakeLists.txt b/lldb/tools/driver/CMakeLists.txt index 5c0cac484cc94..941ef0d83385b 100644 --- a/lldb/tools/driver/CMakeLists.txt +++ b/lldb/tools/driver/CMakeLists.txt @@ -34,6 +34,10 @@ add_dependencies(lldb ${tablegen_deps} ) +if(DEFINED LLDB_PYTHON_DLL_RELATIVE_PATH) + target_compile_definitions(lldb PRIVATE LLDB_PYTHON_DLL_RELATIVE_PATH="${LLDB_PYTHON_DLL_RELATIVE_PATH}") +endif() + if(LLDB_BUILD_FRAMEWORK) # In the build-tree, we know the exact path to the framework directory. # The installed framework can be in different locations. diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index 76288bd3019d4..0d8bba22880fd 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -22,7 +22,10 @@ #include "lldb/Host/MainLoop.h" #include "lldb/Host/MainLoopBase.h" #include "lldb/Utility/Status.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/ConvertUTF.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" @@ -30,6 +33,10 @@ #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" +#ifdef _WIN32 +#include "llvm/Support/Windows/WindowsSupport.h" +#endif + #include #include #include @@ -436,6 +443,47 @@ SBError Driver::ProcessArgs(const opt::InputArgList &args, bool &exiting) { return error; } +#ifdef _WIN32 +/// Returns the full path to the lldb.exe executable. +inline std::wstring GetPathToExecutableW() { + // Iterate until we reach the Windows API maximum path length (32,767). + std::vector buffer; + buffer.resize(MAX_PATH /*=260*/); + while (buffer.size() < 32767) { + if (GetModuleFileNameW(NULL, buffer.data(), buffer.size()) < buffer.size()) + return std::wstring(buffer.begin(), buffer.end()); + buffer.resize(buffer.size() * 2); + } + return L""; +} + +/// Resolve the full path of the directory defined by +/// LLDB_PYTHON_DLL_RELATIVE_PATH. If it exists, add it to the list of DLL +/// search directories. +void AddPythonDLLToSearchPath() { + std::wstring modulePath = GetPathToExecutableW(); + if (modulePath.empty()) { + llvm::errs() << "error: unable to find python.dll." << '\n'; + return; + } + + SmallVector utf8Path; + if (sys::windows::UTF16ToUTF8(modulePath.c_str(), modulePath.length(), + utf8Path)) + return; + sys::path::remove_filename(utf8Path); + sys::path::append(utf8Path, LLDB_PYTHON_DLL_RELATIVE_PATH); + sys::fs::make_absolute(utf8Path); + + SmallVector widePath; + if (sys::windows::widenPath(utf8Path.data(), widePath)) + return; + + if (sys::fs::exists(utf8Path)) + SetDllDirectoryW(widePath.data()); +} +#endif + std::string EscapeString(std::string arg) { std::string::size_type pos = 0; while ((pos = arg.find_first_of("\"\\", pos)) != std::string::npos) { @@ -738,6 +786,10 @@ int main(int argc, char const *argv[]) { "~/Library/Logs/DiagnosticReports/.\n"); #endif +#if defined(_WIN32) && defined(LLDB_PYTHON_DLL_RELATIVE_PATH) + AddPythonDLLToSearchPath(); +#endif + // Parse arguments. LLDBOptTable T; unsigned MissingArgIndex; From f8a6917f6aee0ff72d3c93d35d879236ac2c2026 Mon Sep 17 00:00:00 2001 From: Charles Zablit Date: Thu, 9 Oct 2025 18:28:17 +0100 Subject: [PATCH 2/3] [lldb][windows] fix undeclared identifier error --- lldb/tools/driver/Driver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index 0d8bba22880fd..b5fe66fef7129 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -443,7 +443,7 @@ SBError Driver::ProcessArgs(const opt::InputArgList &args, bool &exiting) { return error; } -#ifdef _WIN32 +#if defined(_WIN32) && defined(LLDB_PYTHON_DLL_RELATIVE_PATH) /// Returns the full path to the lldb.exe executable. inline std::wstring GetPathToExecutableW() { // Iterate until we reach the Windows API maximum path length (32,767). From 64aa88fddfa848ca71a1ed6cd8edf689b8725e32 Mon Sep 17 00:00:00 2001 From: Charles Zablit Date: Mon, 3 Nov 2025 20:00:16 +0000 Subject: [PATCH 3/3] [lldb][windows] print an error if python.dll is not in the DLL search path (#164893) This is a follow up to https://github.com/llvm/llvm-project/pull/162509. Using the `SearchPathW` API, we can ensure that the correct version of Python is installed before `liblldb` is loaded (and `python.dll` subsequently). If it's not, we try to add it to the search path with the methods introduced in https://github.com/llvm/llvm-project/pull/162509. If that fails or if that method is `#ifdef`'d out, we print an error which will appear before lldb crashes due to the missing dll. Before https://github.com/llvm/llvm-project/pull/162509, when invoked from Powershell, lldb would silently crash (no error message/crash report). After https://github.com/llvm/llvm-project/pull/162509, it crashes without any indications that the root cause is the missing python.dll. With this patch, we print the error before crashing. --- lldb/CMakeLists.txt | 6 +++ lldb/tools/driver/CMakeLists.txt | 3 ++ lldb/tools/driver/Driver.cpp | 68 ++++++++++++++++++++++++++------ 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/lldb/CMakeLists.txt b/lldb/CMakeLists.txt index 71298d76ed4d6..2d97218cd3f55 100644 --- a/lldb/CMakeLists.txt +++ b/lldb/CMakeLists.txt @@ -113,6 +113,12 @@ if (LLDB_ENABLE_PYTHON) set(LLDB_PYTHON_EXT_SUFFIX "_d${LLDB_PYTHON_EXT_SUFFIX}") endif() endif() + if(TARGET Python3::Python) + get_target_property(_Python3_LIB_PATH Python3::Python IMPORTED_LIBRARY_LOCATION) + if(_Python3_LIB_PATH) + get_filename_component(LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME "${_Python3_LIB_PATH}" NAME) + endif() + endif() endif () if (LLDB_ENABLE_LUA) diff --git a/lldb/tools/driver/CMakeLists.txt b/lldb/tools/driver/CMakeLists.txt index 941ef0d83385b..d02d0ab22d60c 100644 --- a/lldb/tools/driver/CMakeLists.txt +++ b/lldb/tools/driver/CMakeLists.txt @@ -37,6 +37,9 @@ add_dependencies(lldb if(DEFINED LLDB_PYTHON_DLL_RELATIVE_PATH) target_compile_definitions(lldb PRIVATE LLDB_PYTHON_DLL_RELATIVE_PATH="${LLDB_PYTHON_DLL_RELATIVE_PATH}") endif() +if(DEFINED LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME) + target_compile_definitions(lldb PRIVATE LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME="${LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME}") +endif() if(LLDB_BUILD_FRAMEWORK) # In the build-tree, we know the exact path to the framework directory. diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index b5fe66fef7129..1be3969538dd6 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -443,7 +443,8 @@ SBError Driver::ProcessArgs(const opt::InputArgList &args, bool &exiting) { return error; } -#if defined(_WIN32) && defined(LLDB_PYTHON_DLL_RELATIVE_PATH) +#ifdef _WIN32 +#ifdef LLDB_PYTHON_DLL_RELATIVE_PATH /// Returns the full path to the lldb.exe executable. inline std::wstring GetPathToExecutableW() { // Iterate until we reach the Windows API maximum path length (32,767). @@ -457,30 +458,73 @@ inline std::wstring GetPathToExecutableW() { return L""; } -/// Resolve the full path of the directory defined by +/// \brief Resolve the full path of the directory defined by /// LLDB_PYTHON_DLL_RELATIVE_PATH. If it exists, add it to the list of DLL /// search directories. -void AddPythonDLLToSearchPath() { +/// \return `true` if the library was added to the search path. +/// `false` otherwise. +bool AddPythonDLLToSearchPath() { std::wstring modulePath = GetPathToExecutableW(); - if (modulePath.empty()) { - llvm::errs() << "error: unable to find python.dll." << '\n'; - return; - } + if (modulePath.empty()) + return false; SmallVector utf8Path; if (sys::windows::UTF16ToUTF8(modulePath.c_str(), modulePath.length(), utf8Path)) - return; + return false; sys::path::remove_filename(utf8Path); sys::path::append(utf8Path, LLDB_PYTHON_DLL_RELATIVE_PATH); sys::fs::make_absolute(utf8Path); SmallVector widePath; if (sys::windows::widenPath(utf8Path.data(), widePath)) - return; + return false; if (sys::fs::exists(utf8Path)) - SetDllDirectoryW(widePath.data()); + return SetDllDirectoryW(widePath.data()); + return false; +} +#endif + +#ifdef LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME +/// Returns whether `python3x.dll` is in the DLL search path. +bool IsPythonDLLInPath() { +#define WIDEN2(x) L##x +#define WIDEN(x) WIDEN2(x) + WCHAR foundPath[MAX_PATH]; + DWORD result = + SearchPathW(nullptr, WIDEN(LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME), nullptr, + MAX_PATH, foundPath, nullptr); +#undef WIDEN2 +#undef WIDEN + + return result > 0; +} +#endif + +/// Try to setup the DLL search path for the Python Runtime Library +/// (python3xx.dll). +/// +/// If `LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME` is set, we first check if +/// python3xx.dll is in the search path. If it's not, we try to add it and +/// check for it a second time. +/// If only `LLDB_PYTHON_DLL_RELATIVE_PATH` is set, we try to add python3xx.dll +/// to the search path python.dll is already in the search path or not. +void SetupPythonRuntimeLibrary() { +#ifdef LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME + if (IsPythonDLLInPath()) + return; +#ifdef LLDB_PYTHON_DLL_RELATIVE_PATH + if (AddPythonDLLToSearchPath() && IsPythonDLLInPath()) + return; +#endif + llvm::errs() << "error: unable to find '" + << LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME << "'.\n"; + return; +#elif defined(LLDB_PYTHON_DLL_RELATIVE_PATH) + if (!AddPythonDLLToSearchPath()) + llvm::errs() << "error: unable to find the Python runtime library.\n"; +#endif } #endif @@ -786,8 +830,8 @@ int main(int argc, char const *argv[]) { "~/Library/Logs/DiagnosticReports/.\n"); #endif -#if defined(_WIN32) && defined(LLDB_PYTHON_DLL_RELATIVE_PATH) - AddPythonDLLToSearchPath(); +#ifdef _WIN32 + SetupPythonRuntimeLibrary(); #endif // Parse arguments.