@@ -67,6 +67,65 @@ def find_library(name):
67
67
return fname
68
68
return None
69
69
70
+ # Listing loaded DLLs on Windows relies on the following APIs:
71
+ # https://learn.microsoft.com/windows/win32/api/psapi/nf-psapi-enumprocessmodules
72
+ # https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew
73
+ import ctypes
74
+ from ctypes import wintypes
75
+
76
+ _kernel32 = ctypes .WinDLL ('kernel32' , use_last_error = True )
77
+ _get_current_process = _kernel32 ["GetCurrentProcess" ]
78
+ _get_current_process .restype = wintypes .HANDLE
79
+
80
+ _k32_get_module_file_name = _kernel32 ["GetModuleFileNameW" ]
81
+ _k32_get_module_file_name .restype = wintypes .DWORD
82
+ _k32_get_module_file_name .argtypes = (
83
+ wintypes .HMODULE ,
84
+ wintypes .LPWSTR ,
85
+ wintypes .DWORD ,
86
+ )
87
+
88
+ _psapi = ctypes .WinDLL ('psapi' , use_last_error = True )
89
+ _enum_process_modules = _psapi ["EnumProcessModules" ]
90
+ _enum_process_modules .restype = wintypes .BOOL
91
+ _enum_process_modules .argtypes = (
92
+ wintypes .HANDLE ,
93
+ ctypes .POINTER (wintypes .HMODULE ),
94
+ wintypes .DWORD ,
95
+ wintypes .LPDWORD ,
96
+ )
97
+
98
+ def _get_module_filename (module : wintypes .HMODULE ):
99
+ name = (wintypes .WCHAR * 32767 )() # UNICODE_STRING_MAX_CHARS
100
+ if _k32_get_module_file_name (module , name , len (name )):
101
+ return name .value
102
+ return None
103
+
104
+
105
+ def _get_module_handles ():
106
+ process = _get_current_process ()
107
+ space_needed = wintypes .DWORD ()
108
+ n = 1024
109
+ while True :
110
+ modules = (wintypes .HMODULE * n )()
111
+ if not _enum_process_modules (process ,
112
+ modules ,
113
+ ctypes .sizeof (modules ),
114
+ ctypes .byref (space_needed )):
115
+ err = ctypes .get_last_error ()
116
+ msg = ctypes .FormatError (err ).strip ()
117
+ raise ctypes .WinError (err , f"EnumProcessModules failed: { msg } " )
118
+ n = space_needed .value // ctypes .sizeof (wintypes .HMODULE )
119
+ if n <= len (modules ):
120
+ return modules [:n ]
121
+
122
+ def dllist ():
123
+ """Return a list of loaded shared libraries in the current process."""
124
+ modules = _get_module_handles ()
125
+ libraries = [name for h in modules
126
+ if (name := _get_module_filename (h )) is not None ]
127
+ return libraries
128
+
70
129
elif os .name == "posix" and sys .platform in {"darwin" , "ios" , "tvos" , "watchos" }:
71
130
from ctypes .macholib .dyld import dyld_find as _dyld_find
72
131
def find_library (name ):
@@ -80,6 +139,22 @@ def find_library(name):
80
139
continue
81
140
return None
82
141
142
+ # Listing loaded libraries on Apple systems relies on the following API:
143
+ # https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html
144
+ import ctypes
145
+
146
+ _libc = ctypes .CDLL (find_library ("c" ))
147
+ _dyld_get_image_name = _libc ["_dyld_get_image_name" ]
148
+ _dyld_get_image_name .restype = ctypes .c_char_p
149
+
150
+ def dllist ():
151
+ """Return a list of loaded shared libraries in the current process."""
152
+ num_images = _libc ._dyld_image_count ()
153
+ libraries = [os .fsdecode (name ) for i in range (num_images )
154
+ if (name := _dyld_get_image_name (i )) is not None ]
155
+
156
+ return libraries
157
+
83
158
elif sys .platform .startswith ("aix" ):
84
159
# AIX has two styles of storing shared libraries
85
160
# GNU auto_tools refer to these as svr4 and aix
@@ -341,6 +416,55 @@ def find_library(name):
341
416
return _findSoname_ldconfig (name ) or \
342
417
_get_soname (_findLib_gcc (name )) or _get_soname (_findLib_ld (name ))
343
418
419
+
420
+ # Listing loaded libraries on other systems will try to use
421
+ # functions common to Linux and a few other Unix-like systems.
422
+ # See the following for several platforms' documentation of the same API:
423
+ # https://man7.org/linux/man-pages/man3/dl_iterate_phdr.3.html
424
+ # https://man.freebsd.org/cgi/man.cgi?query=dl_iterate_phdr
425
+ # https://man.openbsd.org/dl_iterate_phdr
426
+ # https://docs.oracle.com/cd/E88353_01/html/E37843/dl-iterate-phdr-3c.html
427
+ if (os .name == "posix" and
428
+ sys .platform not in {"darwin" , "ios" , "tvos" , "watchos" }):
429
+ import ctypes
430
+ if hasattr ((_libc := ctypes .CDLL (None )), "dl_iterate_phdr" ):
431
+
432
+ class _dl_phdr_info (ctypes .Structure ):
433
+ _fields_ = [
434
+ ("dlpi_addr" , ctypes .c_void_p ),
435
+ ("dlpi_name" , ctypes .c_char_p ),
436
+ ("dlpi_phdr" , ctypes .c_void_p ),
437
+ ("dlpi_phnum" , ctypes .c_ushort ),
438
+ ]
439
+
440
+ _dl_phdr_callback = ctypes .CFUNCTYPE (
441
+ ctypes .c_int ,
442
+ ctypes .POINTER (_dl_phdr_info ),
443
+ ctypes .c_size_t ,
444
+ ctypes .POINTER (ctypes .py_object ),
445
+ )
446
+
447
+ @_dl_phdr_callback
448
+ def _info_callback (info , _size , data ):
449
+ libraries = data .contents .value
450
+ name = os .fsdecode (info .contents .dlpi_name )
451
+ libraries .append (name )
452
+ return 0
453
+
454
+ _dl_iterate_phdr = _libc ["dl_iterate_phdr" ]
455
+ _dl_iterate_phdr .argtypes = [
456
+ _dl_phdr_callback ,
457
+ ctypes .POINTER (ctypes .py_object ),
458
+ ]
459
+ _dl_iterate_phdr .restype = ctypes .c_int
460
+
461
+ def dllist ():
462
+ """Return a list of loaded shared libraries in the current process."""
463
+ libraries = []
464
+ _dl_iterate_phdr (_info_callback ,
465
+ ctypes .byref (ctypes .py_object (libraries )))
466
+ return libraries
467
+
344
468
################################################################
345
469
# test code
346
470
@@ -384,5 +508,12 @@ def test():
384
508
print (cdll .LoadLibrary ("libcrypt.so" ))
385
509
print (find_library ("crypt" ))
386
510
511
+ try :
512
+ dllist
513
+ except NameError :
514
+ print ('dllist() not available' )
515
+ else :
516
+ print (dllist ())
517
+
387
518
if __name__ == "__main__" :
388
519
test ()
0 commit comments