From 6e7c8ce12ab53869dd14eb7f9119e842d54e3386 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 20 Jul 2026 12:55:25 +0300 Subject: [PATCH 1/3] gh-131565: Implement ctypes.util.dllist() in the _ctypes extension 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 --- Lib/ctypes/util.py | 54 +++---------------- ...-07-20-17-15-00.gh-issue-131565.dllist.rst | 4 ++ Modules/_ctypes/callproc.c | 42 +++++++++++++++ configure | 9 ++++ configure.ac | 3 ++ pyconfig.h.in | 3 ++ 6 files changed, 68 insertions(+), 47 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-20-17-15-00.gh-issue-131565.dllist.rst diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py index a73422598b2cd96..287340539fa0772 100644 --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py @@ -450,53 +450,13 @@ 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 it, dllist() is implemented in _ctypes using +# dl_iterate_phdr(). It is not defined here otherwise (e.g. on Windows and +# Apple, which define it above). +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..9bbe45ae19fea76 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -1660,6 +1660,44 @@ static PyObject *py_dl_sym(PyObject *self, PyObject *args) PyErr_Format(PyExc_OSError, "symbol '%s' not found", name); return NULL; } + +// Apple platforms have dl_iterate_phdr() but ctypes.util.dllist() uses the +// dyld API there, so do not provide it from here. +#if defined(HAVE_DL_ITERATE_PHDR) && !defined(__APPLE__) +#include + +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)) +{ + // Call dl_iterate_phdr() from _ctypes, not through ctypes: on NetBSD it + // reports only the calling object's link-map group, and a libffi + // trampoline belongs to none (gh-131565). + PyObject *list = PyList_New(0); + if (list == NULL) { + return NULL; + } + // Its return value only echoes the callback; rely on the exception. + dl_iterate_phdr(_dllist_callback, list); + if (PyErr_Occurred()) { + Py_DECREF(list); + return NULL; + } + return list; +} +#endif #endif /* @@ -2036,6 +2074,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 71fa0b435d16505..baca56ac7b72312 100755 --- a/configure +++ b/configure @@ -20065,6 +20065,15 @@ then : fi +# Used by ctypes.util.dllist() to enumerate loaded shared libraries. +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 5fcf406087cbe5e..67eef2cf5ea8db2 100644 --- a/configure.ac +++ b/configure.ac @@ -5387,6 +5387,9 @@ DLINCLDIR=. # platforms have dlopen(), but don't want to use it. AC_CHECK_FUNCS([dlopen]) +# Used by ctypes.util.dllist() to enumerate loaded shared libraries. +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 25c4842d25e8a67..b4c8267f8abfc7f 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 From f8dbf9ca32c230cee800d104426f751b37371bf6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 21 Jul 2026 17:43:43 +0300 Subject: [PATCH 2/3] Simplify comments Co-Authored-By: Claude Opus 4.8 --- Lib/ctypes/util.py | 5 ++--- Modules/_ctypes/callproc.c | 10 ++++------ configure.ac | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py index 287340539fa0772..c92fad2aaa47731 100644 --- a/Lib/ctypes/util.py +++ b/Lib/ctypes/util.py @@ -450,9 +450,8 @@ def find_library(name): _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name)) -# On platforms which provide it, dllist() is implemented in _ctypes using -# dl_iterate_phdr(). It is not defined here otherwise (e.g. on Windows and -# Apple, which define it above). +# On platforms which provide dl_iterate_phdr(), dllist() is implemented +# in _ctypes. try: from _ctypes import dllist except ImportError: diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index 9bbe45ae19fea76..746034c8004c5c4 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -1661,8 +1661,7 @@ static PyObject *py_dl_sym(PyObject *self, PyObject *args) return NULL; } -// Apple platforms have dl_iterate_phdr() but ctypes.util.dllist() uses the -// dyld API there, so do not provide it from here. +// Apple platforms use the dyld API in ctypes.util instead. #if defined(HAVE_DL_ITERATE_PHDR) && !defined(__APPLE__) #include @@ -1682,14 +1681,13 @@ _dllist_callback(struct dl_phdr_info *info, size_t size, void *data) static PyObject * dllist(PyObject *self, PyObject *Py_UNUSED(ignored)) { - // Call dl_iterate_phdr() from _ctypes, not through ctypes: on NetBSD it - // reports only the calling object's link-map group, and a libffi - // trampoline belongs to none (gh-131565). + // 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; } - // Its return value only echoes the callback; rely on the exception. + // The return value only echoes the callback result. dl_iterate_phdr(_dllist_callback, list); if (PyErr_Occurred()) { Py_DECREF(list); diff --git a/configure.ac b/configure.ac index 67eef2cf5ea8db2..270d2bb2a4c894c 100644 --- a/configure.ac +++ b/configure.ac @@ -5387,7 +5387,7 @@ DLINCLDIR=. # platforms have dlopen(), but don't want to use it. AC_CHECK_FUNCS([dlopen]) -# Used by ctypes.util.dllist() to enumerate loaded shared libraries. +# Used by ctypes.util.dllist(). AC_CHECK_FUNCS([dl_iterate_phdr]) # DYNLOADFILE specifies which dynload_*.o file we will use for dynamic From b6c20e56a9ac18b054c53293de5c5a03b9e2a3e8 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 22 Jul 2026 08:34:34 +0300 Subject: [PATCH 3/3] Regenerate configure Co-Authored-By: Claude Opus 4.8 --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 98cd1ffdf1691b3..26239547fceade3 100755 --- a/configure +++ b/configure @@ -20182,7 +20182,7 @@ then : fi -# Used by ctypes.util.dllist() to enumerate loaded shared libraries. +# 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 :