-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
gh-119349: Add ctypes.util function to list loaded shared libraries #122946
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 25 commits
d9c92c3
89a3c71
c29b348
97d2eb1
2e12e22
6e18c36
2ce1cb9
de2c048
8728393
dd97ba0
297c71e
a059573
586f37c
efb7c1c
4aba7d6
fc54910
ab739d2
1626a25
a23b9c1
58edf0f
c91849e
9c351d1
eedf35e
3918675
5ab1b02
ca0a8c6
94b1157
5dc0ecd
43ff300
80be2ab
e1dae92
cb22479
1c3594e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,64 @@ def find_library(name): | |
| return fname | ||
| return None | ||
|
|
||
| import ctypes | ||
|
||
| from ctypes import wintypes | ||
|
|
||
| # https://learn.microsoft.com/windows/win32/api/psapi/nf-psapi-enumprocessmodules | ||
WardBrian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew | ||
|
|
||
| _kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) | ||
WardBrian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _get_current_process = _kernel32["GetCurrentProcess"] | ||
| _get_current_process.restype = wintypes.HANDLE | ||
|
|
||
| _k32_get_module_file_name = _kernel32["GetModuleFileNameW"] | ||
| _k32_get_module_file_name.restype = wintypes.DWORD | ||
| _k32_get_module_file_name.argtypes = ( | ||
| wintypes.HMODULE, | ||
| wintypes.LPWSTR, | ||
| wintypes.DWORD, | ||
| ) | ||
|
|
||
| _psapi = ctypes.WinDLL('psapi', use_last_error=True) | ||
| _enum_process_modules = _psapi["EnumProcessModules"] | ||
| _enum_process_modules.restype = wintypes.BOOL | ||
| _enum_process_modules.argtypes = ( | ||
| wintypes.HANDLE, | ||
| ctypes.POINTER(wintypes.HMODULE), | ||
| wintypes.DWORD, | ||
| wintypes.LPDWORD, | ||
| ) | ||
|
|
||
| def _get_module_filename(module: wintypes.HMODULE): | ||
| name = (wintypes.WCHAR * 32767)() # UNICODE_STRING_MAX_CHARS | ||
| if _k32_get_module_file_name(module, name, len(name)): | ||
| return name.value | ||
| return None | ||
|
|
||
|
|
||
| def _get_module_handles(): | ||
| process = _get_current_process() | ||
| space_needed = wintypes.DWORD() | ||
| n = 1024 | ||
| while True: | ||
| modules = (wintypes.HMODULE * n)() | ||
| if not _enum_process_modules(process, | ||
| modules, | ||
| ctypes.sizeof(modules), | ||
| ctypes.byref(space_needed)): | ||
| err = ctypes.get_last_error() | ||
| msg = ctypes.FormatError(err).strip() | ||
| raise ctypes.WinError(err, f"EnumProcessModules failed: {msg}") | ||
| n = space_needed.value // ctypes.sizeof(wintypes.HMODULE) | ||
| if n <= len(modules): | ||
| return modules[:n] | ||
|
|
||
| def dllist(): | ||
WardBrian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| modules = _get_module_handles() | ||
| libraries = [name for h in modules | ||
| if (name := _get_module_filename(h)) is not None] | ||
| return libraries | ||
|
|
||
| elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}: | ||
| from ctypes.macholib.dyld import dyld_find as _dyld_find | ||
| def find_library(name): | ||
|
|
@@ -80,6 +138,20 @@ def find_library(name): | |
| continue | ||
| return None | ||
|
|
||
| import ctypes | ||
|
|
||
| # https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html | ||
| _libc = ctypes.CDLL(find_library("c")) | ||
| _dyld_get_image_name = _libc["_dyld_get_image_name"] | ||
| _dyld_get_image_name.restype = ctypes.c_char_p | ||
|
|
||
| def dllist(): | ||
| num_images = _libc._dyld_image_count() | ||
| libraries = [os.fsdecode(name) for i in range(num_images) | ||
| if (name := _dyld_get_image_name(i)) is not None] | ||
|
|
||
| return libraries | ||
|
|
||
| elif sys.platform.startswith("aix"): | ||
| # AIX has two styles of storing shared libraries | ||
| # GNU auto_tools refer to these as svr4 and aix | ||
|
|
@@ -341,6 +413,52 @@ def find_library(name): | |
| return _findSoname_ldconfig(name) or \ | ||
| _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name)) | ||
|
|
||
|
|
||
| # other systems use functions common to Linux and a few other Unix-like systems | ||
| # 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"}): | ||
WardBrian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import ctypes | ||
| if hasattr((_libc := ctypes.CDLL(None)), "dl_iterate_phdr"): | ||
|
|
||
WardBrian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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(): | ||
| libraries = [] | ||
| _dl_iterate_phdr(_info_callback, | ||
| ctypes.byref(ctypes.py_object(libraries))) | ||
| return libraries | ||
|
|
||
| ################################################################ | ||
| # test code | ||
|
|
||
|
|
@@ -350,6 +468,7 @@ def test(): | |
| print(cdll.msvcrt) | ||
| print(cdll.load("msvcrt")) | ||
| print(find_library("msvcrt")) | ||
| print(dllist()) | ||
WardBrian marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if os.name == "posix": | ||
| # find and load_version | ||
|
|
@@ -363,6 +482,7 @@ def test(): | |
| print(cdll.LoadLibrary("libcrypto.dylib")) | ||
| print(cdll.LoadLibrary("libSystem.dylib")) | ||
| print(cdll.LoadLibrary("System.framework/System")) | ||
| print(dllist()) | ||
| # issue-26439 - fix broken test call for AIX | ||
| elif sys.platform.startswith("aix"): | ||
| from ctypes import CDLL | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import os | ||
| import sys | ||
| import test.support | ||
| import unittest | ||
| from ctypes import CDLL | ||
| from ctypes.util import dllist | ||
WardBrian marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| from test.support import import_helper | ||
|
|
||
|
|
||
| WINDOWS = os.name == 'nt' | ||
| APPLE = sys.platform in {"darwin", "ios", "tvos", "watchos"} | ||
WardBrian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if WINDOWS: | ||
| SYSTEM_LIBRARY = 'KERNEL32.DLL' | ||
| elif APPLE: | ||
| SYSTEM_LIBRARY = 'libSystem.B.dylib' | ||
| else: | ||
| SYSTEM_LIBRARY = 'libc.so' | ||
|
|
||
| class ListSharedLibraries(unittest.TestCase): | ||
|
|
||
| def test_lists_system(self): | ||
| dlls = dllist() | ||
|
|
||
| self.assertIsNotNone(dlls) | ||
| self.assertGreater(len(dlls), 0, f'loaded={dlls}') | ||
| self.assertTrue(any(SYSTEM_LIBRARY in dll for dll in dlls), f'loaded={dlls}') | ||
|
|
||
|
|
||
| def test_lists_updates(self): | ||
| dlls = dllist() | ||
|
|
||
| if dlls is not None: | ||
WardBrian marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if any("_ctypes_test" in dll for dll in dlls): | ||
| self.skipTest("Test library is already loaded") | ||
WardBrian marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| _ctypes_test = import_helper.import_module("_ctypes_test") | ||
| test_module = CDLL(_ctypes_test.__file__) | ||
| dlls2 = dllist() | ||
| self.assertIsNotNone(dlls2) | ||
|
|
||
| dlls1 = set(dlls) | ||
| dlls2 = set(dlls2) | ||
| if test.support.verbose: | ||
| print("Newly loaded shared libraries:") | ||
WardBrian marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for dll in (dlls2 - dlls1): | ||
| print("\t", dll) | ||
|
|
||
| self.assertGreater(dlls2, dlls1) | ||
| self.assertTrue(any("_ctypes_test" in dll for dll in dlls2)) | ||
|
|
||
WardBrian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1990,6 +1990,7 @@ Edward C Wang | |
| Jiahua Wang | ||
| Ke Wang | ||
| Liang-Bo Wang | ||
| Brian Ward | ||
| Greg Ward | ||
| Tom Wardill | ||
| Zachary Ware | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Added the :func:`ctypes.util.dllist` function to list the loaded shared | ||
WardBrian marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
WardBrian marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| libraries for the current process. | ||
Uh oh!
There was an error while loading. Please reload this page.