]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-131565: Implement ctypes.util.dllist() in the _ctypes extension (GH-154255)
authorSerhiy Storchaka <storchaka@gmail.com>
Wed, 29 Jul 2026 14:47:52 +0000 (17:47 +0300)
committerGitHub <noreply@github.com>
Wed, 29 Jul 2026 14:47:52 +0000 (17:47 +0300)
On NetBSD dl_iterate_phdr() reports only the link-map group of the calling
object.  Called through ctypes, the caller is libffi's closure trampoline,
which belongs to no object, so only the main executable was reported.
Calling dl_iterate_phdr() from the _ctypes extension module makes _ctypes
the caller and reports all loaded shared libraries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lib/ctypes/util.py
Misc/NEWS.d/next/Library/2026-07-20-17-15-00.gh-issue-131565.dllist.rst [new file with mode: 0644]
Modules/_ctypes/callproc.c
configure
configure.ac
pyconfig.h.in

index c0a578c86549ec96a6fd7cc76fede628712d83ac..141d3fbe938247284c6b8666d37cd7d0b6b6e7cb 100644 (file)
@@ -412,53 +412,12 @@ elif os.name == "posix":
                    _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
 
 
-# Listing loaded libraries on other systems will try to use
-# functions common to Linux and a few other Unix-like systems.
-# See the following for several platforms' documentation of the same API:
-# https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
-# https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
-# https://man.openbsd.org/dl_iterate_phdr
-# https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
-if (os.name == "posix" and
-    sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
-    import ctypes
-    if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
-
-        class _dl_phdr_info(ctypes.Structure):
-            _fields_ = [
-                ("dlpi_addr", ctypes.c_void_p),
-                ("dlpi_name", ctypes.c_char_p),
-                ("dlpi_phdr", ctypes.c_void_p),
-                ("dlpi_phnum", ctypes.c_ushort),
-            ]
-
-        _dl_phdr_callback = ctypes.CFUNCTYPE(
-            ctypes.c_int,
-            ctypes.POINTER(_dl_phdr_info),
-            ctypes.c_size_t,
-            ctypes.POINTER(ctypes.py_object),
-        )
-
-        @_dl_phdr_callback
-        def _info_callback(info, _size, data):
-            libraries = data.contents.value
-            name = os.fsdecode(info.contents.dlpi_name)
-            libraries.append(name)
-            return 0
-
-        _dl_iterate_phdr = _libc["dl_iterate_phdr"]
-        _dl_iterate_phdr.argtypes = [
-            _dl_phdr_callback,
-            ctypes.POINTER(ctypes.py_object),
-        ]
-        _dl_iterate_phdr.restype = ctypes.c_int
-
-        def dllist():
-            """Return a list of loaded shared libraries in the current process."""
-            libraries = []
-            _dl_iterate_phdr(_info_callback,
-                             ctypes.byref(ctypes.py_object(libraries)))
-            return libraries
+# On platforms which provide dl_iterate_phdr(), dllist() is implemented
+# in _ctypes.
+try:
+    from _ctypes import dllist
+except ImportError:
+    pass
 
 
 @dataclass(slots=True, frozen=True)
diff --git a/Misc/NEWS.d/next/Library/2026-07-20-17-15-00.gh-issue-131565.dllist.rst b/Misc/NEWS.d/next/Library/2026-07-20-17-15-00.gh-issue-131565.dllist.rst
new file mode 100644 (file)
index 0000000..5fb2dd0
--- /dev/null
@@ -0,0 +1,4 @@
+:func:`ctypes.util.dllist` now works on NetBSD.  It is implemented in the
+:mod:`!_ctypes` extension module so that ``dl_iterate_phdr()`` reports all
+loaded shared libraries: on NetBSD it only reports the link-map group of
+the calling object, which excluded them when called through ctypes.
index ccc57e347b07acf41be2ba683de48360ad69d517..746034c8004c5c44b7a77a7204f8107f7e6fa6f0 100644 (file)
@@ -1660,6 +1660,42 @@ static PyObject *py_dl_sym(PyObject *self, PyObject *args)
     PyErr_Format(PyExc_OSError, "symbol '%s' not found", name);
     return NULL;
 }
+
+// Apple platforms use the dyld API in ctypes.util instead.
+#if defined(HAVE_DL_ITERATE_PHDR) && !defined(__APPLE__)
+#include <link.h>
+
+static int
+_dllist_callback(struct dl_phdr_info *info, size_t size, void *data)
+{
+    PyObject *list = (PyObject *)data;
+    PyObject *name = PyUnicode_DecodeFSDefault(info->dlpi_name);
+    if (name == NULL) {
+        return -1;
+    }
+    int res = PyList_Append(list, name);
+    Py_DECREF(name);
+    return res;
+}
+
+static PyObject *
+dllist(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    // On NetBSD dl_iterate_phdr() only reports the link-map group of the
+    // caller, so it cannot be called via a libffi trampoline.
+    PyObject *list = PyList_New(0);
+    if (list == NULL) {
+        return NULL;
+    }
+    // The return value only echoes the callback result.
+    dl_iterate_phdr(_dllist_callback, list);
+    if (PyErr_Occurred()) {
+        Py_DECREF(list);
+        return NULL;
+    }
+    return list;
+}
+#endif
 #endif
 
 /*
@@ -2036,6 +2072,10 @@ PyMethodDef _ctypes_module_methods[] = {
      "dlopen(name, flag={RTLD_GLOBAL|RTLD_LOCAL}) open a shared library"},
     {"dlclose", py_dl_close, METH_VARARGS, "dlclose a library"},
     {"dlsym", py_dl_sym, METH_VARARGS, "find symbol in shared library"},
+#if defined(HAVE_DL_ITERATE_PHDR) && !defined(__APPLE__)
+    {"dllist", dllist, METH_NOARGS,
+     "dllist() return a list of loaded shared libraries"},
+#endif
 #endif
 #ifdef __APPLE__
      {"_dyld_shared_cache_contains_path", py_dyld_shared_cache_contains_path, METH_VARARGS, "check if path is in the shared cache"},
index 8e7accb4bc793da9747b8cc7fe75d95603d49ba9..2c82da923f68d4a1dd99a248a1a186ef01d610ef 100755 (executable)
--- a/configure
+++ b/configure
@@ -20229,6 +20229,15 @@ then :
 fi
 
 
+# Used by ctypes.util.dllist().
+ac_fn_c_check_func "$LINENO" "dl_iterate_phdr" "ac_cv_func_dl_iterate_phdr"
+if test "x$ac_cv_func_dl_iterate_phdr" = xyes
+then :
+  printf "%s\n" "#define HAVE_DL_ITERATE_PHDR 1" >>confdefs.h
+
+fi
+
+
 # DYNLOADFILE specifies which dynload_*.o file we will use for dynamic
 # loading of modules.
 
index ce146cfa1cbc4cd8b88b52366173daae33be8d95..771260c76b7f09491e1b423f1738b2c768c679e2 100644 (file)
@@ -5421,6 +5421,9 @@ DLINCLDIR=.
 # platforms have dlopen(), but don't want to use it.
 AC_CHECK_FUNCS([dlopen])
 
+# Used by ctypes.util.dllist().
+AC_CHECK_FUNCS([dl_iterate_phdr])
+
 # DYNLOADFILE specifies which dynload_*.o file we will use for dynamic
 # loading of modules.
 AC_SUBST([DYNLOADFILE])
index 2658fe8116781dbee980a4fae53b7e3b382d143a..691c6c0d9feb6d030d761e8eea47ec8660938a7c 100644 (file)
 /* Define to 1 if you have the 'dlopen' function. */
 #undef HAVE_DLOPEN
 
+/* Define to 1 if you have the 'dl_iterate_phdr' function. */
+#undef HAVE_DL_ITERATE_PHDR
+
 /* Define to 1 if you have the 'dup' function. */
 #undef HAVE_DUP