Skip to content

Commit 49202fa

Browse files
serhiy-storchakaclaude
authored andcommitted
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. (cherry picked from commit bfc16a7) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent f9f44dd commit 49202fa

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
@@ -441,53 +441,12 @@ def find_library(name):
441441
_get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
442442

443443

444-
# Listing loaded libraries on other systems will try to use
445-
# functions common to Linux and a few other Unix-like systems.
446-
# See the following for several platforms' documentation of the same API:
447-
# https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
448-
# https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
449-
# https://man.openbsd.org/dl_iterate_phdr
450-
# https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
451-
if (os.name == "posix" and
452-
sys.platform not in {"darwin", "ios", "tvos", "watchos"}):
453-
import ctypes
454-
if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"):
455-
456-
class _dl_phdr_info(ctypes.Structure):
457-
_fields_ = [
458-
("dlpi_addr", ctypes.c_void_p),
459-
("dlpi_name", ctypes.c_char_p),
460-
("dlpi_phdr", ctypes.c_void_p),
461-
("dlpi_phnum", ctypes.c_ushort),
462-
]
463-
464-
_dl_phdr_callback = ctypes.CFUNCTYPE(
465-
ctypes.c_int,
466-
ctypes.POINTER(_dl_phdr_info),
467-
ctypes.c_size_t,
468-
ctypes.POINTER(ctypes.py_object),
469-
)
470-
471-
@_dl_phdr_callback
472-
def _info_callback(info, _size, data):
473-
libraries = data.contents.value
474-
name = os.fsdecode(info.contents.dlpi_name)
475-
libraries.append(name)
476-
return 0
477-
478-
_dl_iterate_phdr = _libc["dl_iterate_phdr"]
479-
_dl_iterate_phdr.argtypes = [
480-
_dl_phdr_callback,
481-
ctypes.POINTER(ctypes.py_object),
482-
]
483-
_dl_iterate_phdr.restype = ctypes.c_int
484-
485-
def dllist():
486-
"""Return a list of loaded shared libraries in the current process."""
487-
libraries = []
488-
_dl_iterate_phdr(_info_callback,
489-
ctypes.byref(ctypes.py_object(libraries)))
490-
return libraries
444+
# On platforms which provide dl_iterate_phdr(), dllist() is implemented
445+
# in _ctypes.
446+
try:
447+
from _ctypes import dllist
448+
except ImportError:
449+
pass
491450

492451
################################################################
493452
# test code
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
@@ -1667,6 +1667,42 @@ static PyObject *py_dl_sym(PyObject *self, PyObject *args)
16671667
PyErr_Format(PyExc_OSError, "symbol '%s' not found", name);
16681668
return NULL;
16691669
}
1670+
1671+
// Apple platforms use the dyld API in ctypes.util instead.
1672+
#if defined(HAVE_DL_ITERATE_PHDR) && !defined(__APPLE__)
1673+
#include <link.h>
1674+
1675+
static int
1676+
_dllist_callback(struct dl_phdr_info *info, size_t size, void *data)
1677+
{
1678+
PyObject *list = (PyObject *)data;
1679+
PyObject *name = PyUnicode_DecodeFSDefault(info->dlpi_name);
1680+
if (name == NULL) {
1681+
return -1;
1682+
}
1683+
int res = PyList_Append(list, name);
1684+
Py_DECREF(name);
1685+
return res;
1686+
}
1687+
1688+
static PyObject *
1689+
dllist(PyObject *self, PyObject *Py_UNUSED(ignored))
1690+
{
1691+
// On NetBSD dl_iterate_phdr() only reports the link-map group of the
1692+
// caller, so it cannot be called via a libffi trampoline.
1693+
PyObject *list = PyList_New(0);
1694+
if (list == NULL) {
1695+
return NULL;
1696+
}
1697+
// The return value only echoes the callback result.
1698+
dl_iterate_phdr(_dllist_callback, list);
1699+
if (PyErr_Occurred()) {
1700+
Py_DECREF(list);
1701+
return NULL;
1702+
}
1703+
return list;
1704+
}
1705+
#endif
16701706
#endif
16711707

16721708
/*
@@ -2018,6 +2054,10 @@ PyMethodDef _ctypes_module_methods[] = {
20182054
"dlopen(name, flag={RTLD_GLOBAL|RTLD_LOCAL}) open a shared library"},
20192055
{"dlclose", py_dl_close, METH_VARARGS, "dlclose a library"},
20202056
{"dlsym", py_dl_sym, METH_VARARGS, "find symbol in shared library"},
2057+
#if defined(HAVE_DL_ITERATE_PHDR) && !defined(__APPLE__)
2058+
{"dllist", dllist, METH_NOARGS,
2059+
"dllist() return a list of loaded shared libraries"},
2060+
#endif
20212061
#endif
20222062
#ifdef __APPLE__
20232063
{"_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
@@ -5249,6 +5249,9 @@ DLINCLDIR=.
52495249
# platforms have dlopen(), but don't want to use it.
52505250
AC_CHECK_FUNCS([dlopen])
52515251

5252+
# Used by ctypes.util.dllist().
5253+
AC_CHECK_FUNCS([dl_iterate_phdr])
5254+
52525255
# DYNLOADFILE specifies which dynload_*.o file we will use for dynamic
52535256
# loading of modules.
52545257
AC_SUBST([DYNLOADFILE])

pyconfig.h.in

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

317+
/* Define to 1 if you have the 'dl_iterate_phdr' function. */
318+
#undef HAVE_DL_ITERATE_PHDR
319+
317320
/* Define to 1 if you have the 'dup' function. */
318321
#undef HAVE_DUP
319322

0 commit comments

Comments
 (0)