https://github.com/python/cpython/commit/bfc16a71cf18015c47c1b9cae9196e279d8f60d7
commit: bfc16a71cf18015c47c1b9cae9196e279d8f60d7
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-07-29T17:47:52+03:00
summary:

gh-131565: Implement ctypes.util.dllist() in the _ctypes extension (GH-154255)

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 <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-20-17-15-00.gh-issue-131565.dllist.rst
M Lib/ctypes/util.py
M Modules/_ctypes/callproc.c
M configure
M configure.ac
M pyconfig.h.in

diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
index c0a578c86549ec9..141d3fbe9382472 100644
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -412,53 +412,12 @@ def find_library(name):
                    _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
index 000000000000000..5fb2dd099bd419f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-20-17-15-00.gh-issue-131565.dllist.rst
@@ -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.
diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
index ccc57e347b07acf..746034c8004c5c4 100644
--- a/Modules/_ctypes/callproc.c
+++ b/Modules/_ctypes/callproc.c
@@ -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"},
diff --git a/configure b/configure
index 8e7accb4bc793da..2c82da923f68d4a 100755
--- 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.
 
diff --git a/configure.ac b/configure.ac
index ce146cfa1cbc4cd..771260c76b7f094 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
diff --git a/pyconfig.h.in b/pyconfig.h.in
index 2658fe8116781db..691c6c0d9feb6d0 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -398,6 +398,9 @@
 /* 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
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to