[llvm-branch-commits] [lldb] 28a01b0 - [lldb][Windows] Translate file descriptors between CRT instances (#217126)

Tobias Hieta via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Sep 25 01:23:50 PDT 2026


Author: Nerixyz
Date: 2026-09-25T10:23:22+02:00
New Revision: 28a01b03e11ffeb3012677f12a8e18d55ba1847d

URL: https://github.com/llvm/llvm-project/commit/28a01b03e11ffeb3012677f12a8e18d55ba1847d
DIFF: https://github.com/llvm/llvm-project/commit/28a01b03e11ffeb3012677f12a8e18d55ba1847d.diff

LOG: [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)

Added: 
    

Modified: 
    lldb/include/lldb/API/SBFile.h
    lldb/source/API/SBFile.cpp
    lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp
    lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h
    lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
    lldb/tools/lldb-dap/DAP.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/API/SBFile.h b/lldb/include/lldb/API/SBFile.h
index 8cf4fe1b405fa7..835c70f480595c 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 a93f25389d2e0a..c2f0ff89367b55 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 fba98abf9e83be..7c9dbbd4852c8b 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 
diff erent 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 
diff erent 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 3f2b869bcfb0ad..be97d1c7e0ca79 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 7b1bd9d8411c59..6316787fdefc79 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 efd10698b9fb8b..81492ebc1ed9c7 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 
diff erent 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 
diff erent CRT states and thus 
diff erent 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


        


More information about the llvm-branch-commits mailing list