[Lldb-commits] [lldb] [lldb] Add PythonRuntimeLoader for runtime libpython lookup (NFC) (PR #200524)
Christian Sigg via lldb-commits
lldb-commits at lists.llvm.org
Wed Jun 3 00:43:41 PDT 2026
================
@@ -0,0 +1,162 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Host/Config.h"
+#include "lldb/Host/ScriptInterpreterRuntimeLoader.h"
+
+#include "PythonRuntimeLoaderInternal.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "llvm/Support/DynamicLibrary.h"
+#include "llvm/Support/ErrorExtras.h"
+#include "llvm/Support/Threading.h"
+
+#include <cstdlib>
+#include <string>
+
+#if defined(_WIN32)
+#include "lldb/Host/windows/windows.h"
+#else
+#include <dlfcn.h>
+#endif
+
+namespace lldb_private {
+
+namespace {
+
+/// True if libpython is currently mapped into the process. A single-symbol
+/// probe can match an incompatible runtime that happens to export it, so
+/// pair an old symbol with one introduced in 3.8 to bound the version on
+/// both ends.
+bool IsPythonAlreadyLoaded() {
+#if defined(_WIN32)
+ HMODULE main = ::GetModuleHandleW(nullptr);
+ return ::GetProcAddress(main, "Py_IsInitialized") != nullptr &&
+ ::GetProcAddress(main, "Py_InitializeFromConfig") != nullptr;
+#else
+ return ::dlsym(RTLD_DEFAULT, "Py_IsInitialized") != nullptr &&
+ ::dlsym(RTLD_DEFAULT, "Py_InitializeFromConfig") != nullptr;
+#endif
+}
+
+llvm::Error TryLoad(const char *path) {
+ std::string err_msg;
+ llvm::sys::DynamicLibrary lib =
+ llvm::sys::DynamicLibrary::getPermanentLibrary(path, &err_msg);
+ if (!lib.isValid())
+ return llvm::createStringErrorV("could not load '{0}': {1}", path, err_msg);
+
+ // A successful load does not imply the library is actually libpython,
+ // so reject anything that doesn't expose a stable-ABI symbol.
+ if (!lib.getAddressOfSymbol("Py_IsInitialized"))
+ return llvm::createStringErrorV(
+ "'{0}' does not export Py_IsInitialized; not a Python runtime", path);
+
+ return llvm::Error::success();
+}
+
+llvm::Expected<std::string> LoadPythonRuntime() {
+ if (IsPythonAlreadyLoaded())
+ return std::string();
+
+ llvm::Error failures =
+ llvm::createStringError("could not locate the Python runtime library");
+ std::string loaded;
+
+ auto try_path = [&](const char *path) -> bool {
+ if (llvm::Error err = TryLoad(path)) {
+ failures = llvm::joinErrors(std::move(failures), std::move(err));
+ return false;
+ }
+ loaded = path;
+ return true;
+ };
+
+ if (const char *override_path = std::getenv("LLDB_PYTHON_LIBRARY");
+ override_path && *override_path) {
+ if (try_path(override_path)) {
+ consumeError(std::move(failures));
+ return loaded;
+ }
+ }
+
+#ifdef LLDB_PYTHON_RUNTIME_LIBRARY_BUILD_PATH
+ // The build-time Python is the closest stable-ABI match for what the
+ // plugin was compiled against, so prefer it over the platform fallbacks.
+ if (try_path(LLDB_PYTHON_RUNTIME_LIBRARY_BUILD_PATH)) {
+ consumeError(std::move(failures));
+ return loaded;
+ }
+#endif
+
+ bool found = false;
+ ForEachPythonRuntimeCandidate(
+ [&](const char *candidate) { return found = try_path(candidate); });
+
+ if (found) {
+ consumeError(std::move(failures));
+ return loaded;
+ }
+ return std::move(failures);
+}
+
+class PythonRuntimeLoader : public ScriptInterpreterRuntimeLoader {
+public:
+ llvm::Error Load() override {
+ EnsureLoaded();
+ if (m_error_message.empty())
+ return llvm::Error::success();
+ return llvm::createStringError(m_error_message);
+ }
+
+ llvm::Expected<llvm::StringRef> GetLoadedPath() override {
+ EnsureLoaded();
+ if (!m_error_message.empty())
+ return llvm::createStringError(m_error_message);
+ return llvm::StringRef(m_path);
+ }
+
+ bool IsLoaded() override { return IsPythonAlreadyLoaded(); }
+
+private:
+ void EnsureLoaded() {
+ llvm::call_once(m_once, [this] {
+ if (llvm::Expected<std::string> result = LoadPythonRuntime()) {
+ m_path = std::move(*result);
+ } else {
+ m_error_message = llvm::toString(result.takeError());
+ LLDB_LOG(GetLog(LLDBLog::Host), "Python runtime load failed: {0}",
+ m_error_message);
+ }
+ });
+ }
+
+ llvm::once_flag m_once;
+ std::string m_path;
+ std::string m_error_message;
+};
+
+} // namespace
+
+llvm::Expected<ScriptInterpreterRuntimeLoader &>
+ScriptInterpreterRuntimeLoader::Get(lldb::ScriptLanguage language) {
----------------
chsigg wrote:
Should this implementation be wrapped in `#if LLDB_ENABLE_PYTHON`?
https://github.com/llvm/llvm-project/pull/200524
More information about the lldb-commits
mailing list