[llvm-branch-commits] [lldb] release/23.x: [lldb][Windows] Remove use of unstable ABI and fix SB API visibility (#222665) (PR #223004)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Sep 11 11:08:44 PDT 2026
https://github.com/llvmbot created https://github.com/llvm/llvm-project/pull/223004
Backport 1ca6698be7708121f987d0acf896c7dc4cf63a51 ba1687b72476eb368641b9a9f565a38cf41b7393
Requested by: @Nerixyz
>From 5ceea691d6009231a06f54544bfe396f5c8865cc Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Tue, 8 Sep 2026 19:35:44 +0200
Subject: [PATCH 1/2] [lldb][Windows] Translate file descriptors between CRT
instances (#217126)
The official LLVM and lldb builds use `LLVM_ENABLE_RPMALLOC`. This
changes the default CRT allocator to rpmalloc for performance reasons.
In doing so, it also changes the CRT linkage from dynamic (`/MD`) to
static (`/MT`).
Since the CRT is the one managing file descriptors on Windows, its
universe is now scoped to a module. For lldb, this means we have three
fd-spaces: the driver (lldb/lldb-dap), liblldb, and Python.
We usually pass files around as file descriptors. This breaks when we do
it between modules.
Fortunately, the C runtime provides us with helpers to translate between
the process scoped HANDLEs and file descriptors. This way, we can
allocate an fd for a handle passed across modules. For Python, there's
the [`msvcrt`](https://docs.python.org/3/library/msvcrt.html) library we
can use to translate.
Fixes #216679.
(cherry picked from commit 1ca6698be7708121f987d0acf896c7dc4cf63a51)
---
lldb/include/lldb/API/SBFile.h | 4 +
lldb/source/API/SBFile.cpp | 6 ++
.../Python/PythonDataObjects.cpp | 77 ++++++++++++++++++-
.../Python/PythonDataObjects.h | 3 +
.../Python/ScriptInterpreterPython.cpp | 6 +-
lldb/tools/lldb-dap/DAP.cpp | 15 +++-
6 files changed, 105 insertions(+), 6 deletions(-)
diff --git a/lldb/include/lldb/API/SBFile.h b/lldb/include/lldb/API/SBFile.h
index 8cf4fe1b405fa..835c70f480595 100644
--- a/lldb/include/lldb/API/SBFile.h
+++ b/lldb/include/lldb/API/SBFile.h
@@ -35,6 +35,10 @@ class LLDB_API SBFile {
SBFile(int fd, const char *mode, bool transfer_ownership);
~SBFile();
+#if defined(_WIN32) && !defined(SWIG)
+ static int OpenFdFromHandle(intptr_t handle, int flags);
+#endif
+
SBFile &operator=(const SBFile &rhs);
SBError Read(uint8_t *buf, size_t num_bytes, size_t *OUTPUT);
diff --git a/lldb/source/API/SBFile.cpp b/lldb/source/API/SBFile.cpp
index a93f25389d2e0..c2f0ff89367b5 100644
--- a/lldb/source/API/SBFile.cpp
+++ b/lldb/source/API/SBFile.cpp
@@ -69,6 +69,12 @@ SBFile::SBFile(int fd, const char *mode, bool transfer_ownership) {
std::make_shared<NativeFile>(fd, options.get(), transfer_ownership);
}
+#ifdef _WIN32
+int SBFile::OpenFdFromHandle(intptr_t handle, int flags) {
+ return _open_osfhandle(handle, flags);
+}
+#endif
+
SBError SBFile::Read(uint8_t *buf, size_t num_bytes, size_t *bytes_read) {
LLDB_INSTRUMENT_VA(this, buf, num_bytes, bytes_read);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
index fba98abf9e83b..7c9dbbd4852c8 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
@@ -21,6 +21,10 @@
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Errno.h"
+#ifdef _WIN32
+#include "lldb/Host/windows/windows.h"
+#endif
+
#include <cstdio>
#include <variant>
@@ -906,6 +910,63 @@ bool PythonFile::Check(PyObject *py_obj) {
return !!r;
}
+#if defined(_WIN32) && !defined(_DLL)
+// When LLVM is built with a different CRT allocator, it's built against the
+// static C runtime. The official Python builds link to the dynamic C runtime.
+// Since the file descriptors are managed per CRT instance, liblldb and Python
+// have different fd mappings. This translates between the two using the msvcrt
+// module.
+int PythonFile::TranslateFdToPython(int our_fd) {
+ intptr_t handle = _get_osfhandle(our_fd);
+ if (handle == 0 || (HANDLE)handle == INVALID_HANDLE_VALUE)
+ return -1;
+
+ PyObject *msvcrt = PyImport_ImportModule("msvcrt");
+ if (!msvcrt)
+ return -1;
+ PyObject *open_osf = PyObject_GetAttrString(msvcrt, "open_osfhandle");
+ Py_XDECREF(msvcrt);
+ if (!open_osf)
+ return -1;
+ PyObject *fd_obj =
+ PyObject_CallFunction(open_osf, "Li", (long long)handle, 0);
+ Py_XDECREF(open_osf);
+ if (!fd_obj)
+ return -1;
+ if (!PyLong_Check(fd_obj)) {
+ Py_XDECREF(fd_obj);
+ return -1;
+ }
+ long theirs = PyLong_AsLong(fd_obj);
+ Py_XDECREF(fd_obj);
+ return (int)theirs;
+}
+
+int PythonFile::TranslateFdFromPython(int their_fd) {
+ PyObject *msvcrt = PyImport_ImportModule("msvcrt");
+ if (!msvcrt)
+ return -1;
+ PyObject *get_handle = PyObject_GetAttrString(msvcrt, "get_osfhandle");
+ Py_XDECREF(msvcrt);
+ if (!get_handle)
+ return -1;
+ PyObject *handle_obj = PyObject_CallFunction(get_handle, "i", their_fd);
+ Py_XDECREF(get_handle);
+ if (!handle_obj)
+ return -1;
+ if (!PyLong_Check(handle_obj)) {
+ Py_XDECREF(handle_obj);
+ return -1;
+ }
+ size_t handle = PyLong_AsSize_t(handle_obj);
+ Py_XDECREF(handle_obj);
+ return _open_osfhandle((intptr_t)handle, 0);
+}
+#else
+int PythonFile::TranslateFdToPython(int our_fd) { return our_fd; }
+int PythonFile::TranslateFdFromPython(int their_fd) { return their_fd; }
+#endif
+
const char *PythonException::toCString() const {
if (!m_repr_bytes)
return "unknown exception";
@@ -1274,6 +1335,12 @@ llvm::Expected<FileSP> PythonFile::ConvertToFile(bool borrowed) {
PyErr_Clear();
return ConvertToFileForcingUseOfScriptingIOMethods(borrowed);
}
+ fd = TranslateFdFromPython(fd);
+ if (fd < 0) {
+ PyErr_Clear();
+ return llvm::createStringError("failed to translate Python fd to our fd");
+ }
+
auto options = GetOptionsForPyObject(*this);
if (!options)
return options.takeError();
@@ -1318,6 +1385,12 @@ PythonFile::ConvertToFileForcingUseOfScriptingIOMethods(bool borrowed) {
if (fd < 0) {
PyErr_Clear();
fd = File::kInvalidDescriptor;
+ } else {
+ fd = TranslateFdFromPython(fd);
+ if (fd < 0) {
+ PyErr_Clear();
+ return llvm::createStringError("failed to translate Python fd to our fd");
+ }
}
auto io_module = PythonModule::Import("io");
@@ -1383,8 +1456,8 @@ Expected<PythonFile> PythonFile::FromFile(File &file, const char *mode) {
}
PyObject *file_obj;
- file_obj = PyFile_FromFd(file.GetDescriptor(), nullptr, mode, -1, nullptr,
- "ignore", nullptr, /*closefd=*/0);
+ file_obj = PyFile_FromFd(TranslateFdToPython(file.GetDescriptor()), nullptr,
+ mode, -1, nullptr, "ignore", nullptr, /*closefd=*/0);
if (!file_obj)
return exception();
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h
index 3f2b869bcfb0a..be97d1c7e0ca7 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h
@@ -635,6 +635,9 @@ class PythonFile : public TypedPythonObject<PythonFile> {
static bool Check(PyObject *py_obj);
+ static int TranslateFdToPython(int our_fd);
+ static int TranslateFdFromPython(int their_fd);
+
static llvm::Expected<PythonFile> FromFile(File &file,
const char *mode = nullptr);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 7b1bd9d8411c5..6316787fdefc7 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -1088,8 +1088,10 @@ bool ScriptInterpreterPythonImpl::RedirectTerminalHandleThroughLock(
// terminal) promptly: the pipe descriptor is not a tty, so the default
// buffering would hold output back until the buffer filled.
PyObject *pipe_file = PyFile_FromFd(
- redirect->GetWriteDescriptor(), nullptr, mode, /*buffering=*/1,
- /*encoding=*/nullptr, /*errors=*/"ignore", /*newline=*/nullptr,
+ PythonFile::TranslateFdToPython(redirect->GetWriteDescriptor()), nullptr,
+ mode, /*buffering=*/1,
+ /*encoding=*/nullptr, /*errors=*/"ignore",
+ /*newline=*/nullptr,
/*closefd=*/0);
if (!pipe_file) {
// Fall back to the raw descriptor. That reopens the statusline race, so
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index efd10698b9fb8..81492ebc1ed9c 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -1319,11 +1319,22 @@ llvm::Error DAP::InitializeDebugger() {
llvm::Expected<int> out_fd = out.GetWriteFileDescriptor();
if (!out_fd)
return out_fd.takeError();
- debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));
-
llvm::Expected<int> err_fd = err.GetWriteFileDescriptor();
if (!err_fd)
return err_fd.takeError();
+
+#if defined(_WIN32) && !defined(_DLL)
+ // When LLVM is built with a different CRT allocator, it's built against the
+ // static C runtime. Since the C runtime is the one managing the file
+ // descriptors, its state is now local to each module. Here, lldb-dap and
+ // liblldb have two different CRT states and thus different sets of file
+ // descriptors. This translates an lldb-dap fd into a liblldb fd by creating a
+ // mapping of fd -> HANDLE inside liblldb.
+ *out_fd = lldb::SBFile::OpenFdFromHandle(_get_osfhandle(*out_fd), 0);
+ *err_fd = lldb::SBFile::OpenFdFromHandle(_get_osfhandle(*err_fd), 0);
+#endif
+
+ debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));
debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));
// The sourceInitFile option is not part of the DAP specification. It is an
>From c84f3f8d997b6237e11bab6f82d93de61eba68b5 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Fri, 11 Sep 2026 19:50:45 +0200
Subject: [PATCH 2/2] [lldb][Windows] Remove use of unstable ABI and fix SB API
visibility (#222665)
Addresses the post-commit review comments from the original PR
(https://github.com/llvm/llvm-project/pull/217126#pullrequestreview-5162589053).
- `SBFile::OpenFdFromHandle` is now visible on all platforms (just
`#ifndef SWIG`) and returns -1 by default.
- Removed non-stable `PyLong_Check` in favor of `PyErr_Occurred` and
`PyErr_Clear`.
(cherry picked from commit ba1687b72476eb368641b9a9f565a38cf41b7393)
---
lldb/include/lldb/API/SBFile.h | 8 +++++++-
lldb/source/API/SBFile.cpp | 8 ++++++--
.../Python/PythonDataObjects.cpp | 19 +++++++++++--------
3 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/lldb/include/lldb/API/SBFile.h b/lldb/include/lldb/API/SBFile.h
index 835c70f480595..73b7dc461b443 100644
--- a/lldb/include/lldb/API/SBFile.h
+++ b/lldb/include/lldb/API/SBFile.h
@@ -35,7 +35,13 @@ class LLDB_API SBFile {
SBFile(int fd, const char *mode, bool transfer_ownership);
~SBFile();
-#if defined(_WIN32) && !defined(SWIG)
+#ifndef SWIG
+ /// Open a file descriptor in liblldb from a Windows HANDLE.
+ ///
+ /// This is useful for builds that statically link to the C runtime (`/MT`),
+ /// because the fd -> HANDLE mapping is local to liblldb's CRT instance.
+ ///
+ /// On other platforms, this always returns -1.
static int OpenFdFromHandle(intptr_t handle, int flags);
#endif
diff --git a/lldb/source/API/SBFile.cpp b/lldb/source/API/SBFile.cpp
index c2f0ff89367b5..ab8e6e152d39e 100644
--- a/lldb/source/API/SBFile.cpp
+++ b/lldb/source/API/SBFile.cpp
@@ -69,11 +69,15 @@ SBFile::SBFile(int fd, const char *mode, bool transfer_ownership) {
std::make_shared<NativeFile>(fd, options.get(), transfer_ownership);
}
-#ifdef _WIN32
int SBFile::OpenFdFromHandle(intptr_t handle, int flags) {
+#if _WIN32
return _open_osfhandle(handle, flags);
-}
+#else
+ (void)handle;
+ (void)flags;
+ return -1;
#endif
+}
SBError SBFile::Read(uint8_t *buf, size_t num_bytes, size_t *bytes_read) {
LLDB_INSTRUMENT_VA(this, buf, num_bytes, bytes_read);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
index 7c9dbbd4852c8..8b2d66a168c73 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
@@ -933,12 +933,13 @@ int PythonFile::TranslateFdToPython(int our_fd) {
Py_XDECREF(open_osf);
if (!fd_obj)
return -1;
- if (!PyLong_Check(fd_obj)) {
- Py_XDECREF(fd_obj);
- return -1;
- }
+
long theirs = PyLong_AsLong(fd_obj);
Py_XDECREF(fd_obj);
+ if (PyErr_Occurred()) {
+ PyErr_Clear();
+ return -1;
+ }
return (int)theirs;
}
@@ -954,12 +955,14 @@ int PythonFile::TranslateFdFromPython(int their_fd) {
Py_XDECREF(get_handle);
if (!handle_obj)
return -1;
- if (!PyLong_Check(handle_obj)) {
- Py_XDECREF(handle_obj);
- return -1;
- }
+
size_t handle = PyLong_AsSize_t(handle_obj);
Py_XDECREF(handle_obj);
+ if (PyErr_Occurred()) {
+ PyErr_Clear();
+ return -1;
+ }
+
return _open_osfhandle((intptr_t)handle, 0);
}
#else
More information about the llvm-branch-commits
mailing list