Skip to content

Commit bfc16a7

Browse files
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 <noreply@anthropic.com>
1 parent ae18dae commit bfc16a7

6 files changed

Lines changed: 65 additions & 47 deletions

File tree

Lib/ctypes/util.py

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -412,53 +412,12 @@ def find_library(name):
412412
_get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
413413

414414

415-
# Listing loaded libraries on other systems will try to use
416-
# functions common to Linux and a few other Unix-like systems.
417-
# See the following for several platforms' documentation of the same API:
418-
# https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
419-
# https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
420-
# https://man.openbsd.org/dl_iterate_phdr
421-
# https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
422-
if (os.name == "posix" and
423-
sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
424-
import ctypes
425-
if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
426-
427-
class _dl_phdr_info(ctypes.Structure):
428-
_fields_ = [
429-
("dlpi_addr", ctypes.c_void_p),
430-
("dlpi_name", ctypes.c_char_p),
431-
("dlpi_phdr", ctypes.c_void_p),
432-
("dlpi_phnum", ctypes.c_ushort),
433-
]
434-
435-
_dl_phdr_callback = ctypes.CFUNCTYPE(
436-
ctypes.c_int,
437-
ctypes.POINTER(_dl_phdr_info),
438-
ctypes.c_size_t,
439-
ctypes.POINTER(ctypes.py_object),
440-
)
441-
442-
@_dl_phdr_callback
443-
def _info_callback(info, _size, data):
444-
libraries = data.contents.value
445-
name = os.fsdecode(info.contents.dlpi_name)
446-
libraries.append(name)
447-
return 0
448-
449-
_dl_iterate_phdr = _libc["dl_iterate_phdr"]
450-
_dl_iterate_phdr.argtypes = [
451-
_dl_phdr_callback,
452-
ctypes.POINTER(ctypes.py_object),
453-
]
454-
_dl_iterate_phdr.restype = ctypes.c_int
455-
456-
def dllist():
457-
"""Return a list of loaded shared libraries in the current process."""
458-
libraries = []
459-
_dl_iterate_phdr(_info_callback,
460-
ctypes.byref(ctypes.py_object(libraries)))
461-
return libraries
415+
# On platforms which provide dl_iterate_phdr(), dllist() is implemented
416+
# in _ctypes.
417+
try:
418+
from _ctypes import dllist
419+
except ImportError:
420+
pass
462421

463422

464423
@dataclass(slots=True, frozen=True)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:func:`ctypes.util.dllist` now works on NetBSD. It is implemented in the
2+
:mod:`!_ctypes` extension module so that ``dl_iterate_phdr()`` reports all
3+
loaded shared libraries: on NetBSD it only reports the link-map group of
4+
the calling object, which excluded them when called through ctypes.

Modules/_ctypes/callproc.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,42 @@ static PyObject *py_dl_sym(PyObject *self, PyObject *args)
16601660
PyErr_Format(PyExc_OSError, "symbol '%s' not found", name);
16611661
return NULL;
16621662
}
1663+
1664+
// Apple platforms use the dyld API in ctypes.util instead.
1665+
#if defined(HAVE_DL_ITERATE_PHDR) && !defined(__APPLE__)
1666+
#include <link.h>
1667+
1668+
static int
1669+
_dllist_callback(struct dl_phdr_info *info, size_t size, void *data)
1670+
{
1671+
PyObject *list = (PyObject *)data;
1672+
PyObject *name = PyUnicode_DecodeFSDefault(info->dlpi_name);
1673+
if (name == NULL) {
1674+
return -1;
1675+
}
1676+
int res = PyList_Append(list, name);
1677+
Py_DECREF(name);
1678+
return res;
1679+
}
1680+
1681+
static PyObject *
1682+
dllist(PyObject *self, PyObject *Py_UNUSED(ignored))
1683+
{
1684+
// On NetBSD dl_iterate_phdr() only reports the link-map group of the
1685+
// caller, so it cannot be called via a libffi trampoline.
1686+
PyObject *list = PyList_New(0);
1687+
if (list == NULL) {
1688+
return NULL;
1689+
}
1690+
// The return value only echoes the callback result.
1691+
dl_iterate_phdr(_dllist_callback, list);
1692+
if (PyErr_Occurred()) {
1693+
Py_DECREF(list);
1694+
return NULL;
1695+
}
1696+
return list;
1697+
}
1698+
#endif
16631699
#endif
16641700

16651701
/*
@@ -2036,6 +2072,10 @@ PyMethodDef _ctypes_module_methods[] = {
20362072
"dlopen(name, flag={RTLD_GLOBAL|RTLD_LOCAL}) open a shared library"},
20372073
{"dlclose", py_dl_close, METH_VARARGS, "dlclose a library"},
20382074
{"dlsym", py_dl_sym, METH_VARARGS, "find symbol in shared library"},
2075+
#if defined(HAVE_DL_ITERATE_PHDR) && !defined(__APPLE__)
2076+
{"dllist", dllist, METH_NOARGS,
2077+
"dllist() return a list of loaded shared libraries"},
2078+
#endif
20392079
#endif
20402080
#ifdef __APPLE__
20412081
{"_dyld_shared_cache_contains_path", py_dyld_shared_cache_contains_path, METH_VARARGS, "check if path is in the shared cache"},

configure

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5421,6 +5421,9 @@ DLINCLDIR=.
54215421
# platforms have dlopen(), but don't want to use it.
54225422
AC_CHECK_FUNCS([dlopen])
54235423

5424+
# Used by ctypes.util.dllist().
5425+
AC_CHECK_FUNCS([dl_iterate_phdr])
5426+
54245427
# DYNLOADFILE specifies which dynload_*.o file we will use for dynamic
54255428
# loading of modules.
54265429
AC_SUBST([DYNLOADFILE])

pyconfig.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@
398398
/* Define to 1 if you have the 'dlopen' function. */
399399
#undef HAVE_DLOPEN
400400

401+
/* Define to 1 if you have the 'dl_iterate_phdr' function. */
402+
#undef HAVE_DL_ITERATE_PHDR
403+
401404
/* Define to 1 if you have the 'dup' function. */
402405
#undef HAVE_DUP
403406

0 commit comments

Comments
 (0)