-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
test_dllist
fails on NetBSD: dl_iterate_phdr doesn't report shared libraries
#131565
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
Comments
CC: @WardBrian |
Interesting! I don't have access to a NetBSD machine to test on, but the man page for dl_iterate_phdr on netbsd.org is almost identical to Linux's or FreeBSD's, so the behavior you're describing is surprising, to say the least The most immediate "fix" is probably to update the availability to only |
Yes, |
Yes, this is a feasible solution. However, I think it is more important to find the underlying problem. |
I suppose the right place to start is probably checking the basic operation of the c function. If I compile and run this locally: #define _GNU_SOURCE // presumably not necessary on netbsd, but harmless?
#include <link.h>
#include <stdio.h>
int callback(struct dl_phdr_info *info, size_t size, void *data) {
printf("name=%s, base=%p, size=%d\n", info->dlpi_name, (void *)info->dlpi_addr, info->dlpi_phnum);
return 0;
}
int main(void) {
dl_iterate_phdr(callback, NULL);
return 0;
} I get
which matches
Could you run a similar experiment? |
╰─$ cat main.c
#define _GNU_SOURCE // presumably not necessary on netbsd, but harmless?
#include <link.h>
#include <stdio.h>
int callback(struct dl_phdr_info *info, size_t size, void *data) {
printf("name=%s, base=%p, size=%d\n", info->dlpi_name, (void *)info->dlpi_addr, info->dlpi_phnum);
return 0;
}
int main(void) {
dl_iterate_phdr(callback, NULL);
return 0;
} ╰─$ gcc main.c -o main
╰─$ ./main
name=./main, base=0x0, size=7
name=/usr/lib/libc.so.12, base=0x7ebdaac00000, size=8
name=/usr/libexec/ld.elf_so, base=0x7f7fa6e00000, size=7
╰─$ ldd ./main
./main:
-lc.12 => /usr/lib/libc.so.12
╭─blue@home ~
╰─$ uname -a
NetBSD home.localhost 10.0 NetBSD 10.0 (GENERIC) #0: Thu Mar 28 08:33:33 UTC 2024 mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64 |
Ok, interesting! Mind trying the next level up? file main.c: #define _GNU_SOURCE
#include <link.h>
#include <stdio.h>
int callback(struct dl_phdr_info *info, size_t size, void *data) {
printf("name=%s, base=%p, size=%d\n", info->dlpi_name,
(void *)info->dlpi_addr, info->dlpi_phnum);
return 0;
}
__attribute__((visibility("default"))) void call_me(void) {
dl_iterate_phdr(callback, NULL);
} gcc -shared -fpic -o libdllist_test.so main.c
python -c "import ctypes; ctypes.CDLL('./libdllist_test.so').call_me()" In particular, the output should contain something like
as well as the system libraries. |
╰─$ cat main.c #define _GNU_SOURCE
#include <link.h>
#include <stdio.h>
int callback(struct dl_phdr_info *info, size_t size, void *data) {
printf("name=%s, base=%p, size=%d\n", info->dlpi_name,
(void *)info->dlpi_addr, info->dlpi_phnum);
return 0;
}
__attribute__((visibility("default"))) void call_me(void) {
dl_iterate_phdr(callback, NULL);
} ╰─$ gcc -shared -fpic -o libdllist_test.so main.c ╰─$ python3.12 -c "import ctypes; ctypes.CDLL('./libdllist_test.so').call_me()"
name=python3.12, base=0x1c7e00000, size=8
name=/usr/pkg/lib/libpython3.12.so.1.0, base=0x79c970600000, size=8
name=/usr/lib/libintl.so.1, base=0x79c970200000, size=7
name=/usr/lib/libpthread.so.1, base=0x79c96fe00000, size=7
name=/usr/lib/libcrypt.so.1, base=0x79c96fa00000, size=7
name=/usr/lib/libutil.so.7, base=0x79c96f600000, size=7
name=/usr/lib/libm.so.0, base=0x79c96f200000, size=7
name=/usr/lib/libc.so.12, base=0x79c96ec00000, size=8
name=/usr/lib/libgcc_s.so.1, base=0x79c96e800000, size=7
name=/usr/lib/i18n/libUTF8.so.5.0, base=0x79c96de00000, size=7
name=/usr/pkg/lib/python3.12/lib-dynload/_ctypes.so, base=0x79c96da00000, size=7
name=/usr/pkg/lib/libffi.so.8, base=0x79c96d600000, size=7
name=/usr/pkg/lib/python3.12/lib-dynload/_struct.so, base=0x79c96d200000, size=7
name=./libdllist_test.so, base=0x79c96ce00000, size=6
name=/usr/libexec/ld.elf_so, base=0x7f7fa7400000, size=7
╭─blue@home ~
╰─$ ./cpython/python -c "import ctypes; ctypes.CDLL('./libdllist_test.so').call_me()"
name=./cpython/python, base=0x0, size=8
name=/usr/lib/libintl.so.1, base=0x72cc62400000, size=7
name=/usr/lib/libpthread.so.1, base=0x72cc62000000, size=7
name=/usr/lib/libutil.so.7, base=0x72cc61c00000, size=7
name=/usr/lib/libm.so.0, base=0x72cc61800000, size=7
name=/usr/lib/libgcc_s.so.1, base=0x72cc61400000, size=7
name=/usr/lib/libc.so.12, base=0x72cc60e00000, size=8
name=/usr/lib/i18n/libUTF8.so.5.0, base=0x72cc60400000, size=7
name=/home/blue/cpython/build/lib.netbsd-10.0-amd64-3.14/_ctypes.cpython-314d.so, base=0x72cc60000000, size=6
name=/usr/pkg/lib/libffi.so.8, base=0x72cc5fc00000, size=7
name=/home/blue/cpython/build/lib.netbsd-10.0-amd64-3.14/_struct.cpython-314d.so, base=0x72cc5f800000, size=6
name=./libdllist_test.so, base=0x72cc5f400000, size=6
name=/usr/libexec/ld.elf_so, base=0x7f7f48a00000, size=7
╭─blue@home ~
╰─$ |
Okay, fascinating To recap:
It may be beyond my debugging-over-email abilities to determine the cause here. If someone more familiar with either the internals of how ctypes calls the system loader or with NetBSD, that would be appreciated! Another guess is it could be related to the fact that loading it with #define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
struct dl_phdr_info {
void *dlpi_addr;
const char *dlpi_name;
const void *dlpi_phdr;
unsigned short dlpi_phnum;
};
int callback(struct dl_phdr_info *info, size_t size, void *data) {
printf("name=%s, base=%p, size=%d\n", info->dlpi_name, info->dlpi_addr,
info->dlpi_phnum);
return 0;
}
typedef int (*dl_iterate_phdr_t)(int (*callback)(struct dl_phdr_info *info,
size_t size, void *data),
void *data);
int main(void) {
void* handle = dlopen(NULL, RTLD_NOW);
if (!handle) {
fprintf(stderr, "dlopen failed: %s\n", dlerror());
return 1;
}
dl_iterate_phdr_t dl_iterate_phdr = dlsym(handle, "dl_iterate_phdr");
if (!dl_iterate_phdr) {
fprintf(stderr, "dlsym failed: %s\n", dlerror());
dlclose(handle);
return 1;
}
dl_iterate_phdr(callback, NULL);
} This hypothesis could also be tested by replacing the |
Inspired by your examples, I followed an approach using a library and got more meaningful results. gcc -shared -fpic -o libdllist_helper.so dllist_helper.c ╰─cat dllist_helper.c
#define _GNU_SOURCE
#include <link.h>
#include <stdlib.h>
#include <string.h>
// Function to count how many libraries are loaded
int count_libraries(struct dl_phdr_info *info, size_t size, void *data) {
int *count = (int*)data;
(*count)++;
return 0;
}
// Function to fill the library names
int get_library_names(struct dl_phdr_info *info, size_t size, void *data) {
char **names = (char**)data;
static int index = 0;
// Copy the library name
if (info->dlpi_name && info->dlpi_name[0] != '\0') {
names[index] = strdup(info->dlpi_name);
}
else {
// For the main executable with empty name
names[index] = strdup("main_executable");
}
index++;
return 0;
}
// Function to get the library count
__attribute__((visibility("default")))
int get_library_count(void) {
int count = 0;
dl_iterate_phdr(count_libraries, &count);
return count;
}
// Function to get the library names
__attribute__((visibility("default")))
char** get_library_names_array(void) {
int count = get_library_count();
// Allocate memory for the array of strings
char **names = (char**)malloc(count * sizeof(char*));
if (!names)
return NULL;
// Fill the array with library names
dl_iterate_phdr(get_library_names, names);
return names;
}
// Function to free the memory
__attribute__((visibility("default")))
void free_library_names(char **names, int count) {
if (!names)
return;
for (int i = 0; i < count; i++) {
if (names[i]) free(names[i]);
}
free(names);
} ╰─$ cat test_libdllist_helper.py import os
import sys
import ctypes
def dllist():
helper_path = os.path.join(os.path.dirname(__file__), "libdllist_helper.so")
helper = ctypes.CDLL(helper_path)
get_count = helper.get_library_count
get_count.restype = ctypes.c_int
count = get_count()
# Get the array of library names
get_names = helper.get_library_names_array
get_names.restype = ctypes.POINTER(ctypes.c_char_p)
names_array = get_names()
libraries = []
for i in range(count):
name_bytes = names_array[i]
if name_bytes:
name = os.fsdecode(name_bytes)
if name == "main_executable":
name = sys.executable
libraries.append(name)
# Free the memory allocated in C
free_names = helper.free_library_names
free_names.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.c_int]
free_names(names_array, count)
return libraries
print(dllist()) Output: ╰─$ ./Desktop/cpython/python test_libdllist_helper.py
['./Desktop/cpython/python', '/usr/lib/libintl.so.1', '/usr/lib/libpthread.so.1', '/usr/lib/libutil.so.7', '/usr/lib/libm.so.0', '/usr/lib/libgcc_s.so.1', '/usr/lib/libc.so.12', '/usr/lib/i18n/libUTF8.so.5.0', '/home/blue/Desktop/cpython/build/lib.netbsd-10.0-amd64-3.14/_ctypes.cpython-314d.so', '/usr/pkg/lib/libffi.so.8', '/home/blue/Desktop/cpython/build/lib.netbsd-10.0-amd64-3.14/_struct.cpython-314d.so', '/home/blue/libdllist_helper.so', '/usr/libexec/ld.elf_so']
╰─$ python3.12 test_libdllist_helper.py
['python3.12', '/usr/pkg/lib/libpython3.12.so.1.0', '/usr/lib/libintl.so.1', '/usr/lib/libpthread.so.1', '/usr/lib/libcrypt.so.1', '/usr/lib/libutil.so.7', '/usr/lib/libm.so.0', '/usr/lib/libc.so.12', '/usr/lib/libgcc_s.so.1', '/usr/lib/i18n/libUTF8.so.5.0', '/usr/pkg/lib/python3.12/lib-dynload/_ctypes.so', '/usr/pkg/lib/libffi.so.8', '/usr/pkg/lib/python3.12/lib-dynload/_struct.so', '/home/blue/libdllist_helper.so', '/usr/libexec/ld.elf_so'] |
I have tried the hardcoded libc path, but it doesn't seem to work. ╰─$ cat hard_coded_path.py import os
import sys
import ctypes
from ctypes.util import find_library
if sys.platform.startswith('netbsd'):
libc_path = "/usr/lib/libc.so.12" # NetBSD's libc path
_libc = ctypes.CDLL(libc_path)
# Define structure for NetBSD's dl_phdr_info
class _dl_phdr_info(ctypes.Structure):
_fields_ = [
("dlpi_addr", ctypes.c_void_p), # Elf_Addr
("dlpi_name", ctypes.c_char_p), # const char *
("dlpi_phdr", ctypes.c_void_p), # const Elf_Phdr *
("dlpi_phnum", ctypes.c_ushort), # Elf_Half
("dlpi_adds", ctypes.c_ulonglong), # unsigned long long int
("dlpi_subs", ctypes.c_ulonglong), # unsigned long long int
("dlpi_tls_modid", ctypes.c_size_t), # size_t
("dlpi_tls_data", ctypes.c_void_p) # void *
]
_dl_phdr_callback = ctypes.CFUNCTYPE(
ctypes.c_int,
ctypes.POINTER(_dl_phdr_info),
ctypes.c_size_t,
ctypes.c_void_p,
)
@_dl_phdr_callback
def _info_callback(info, size, data):
libraries = ctypes.cast(data, ctypes.POINTER(ctypes.py_object)).contents.value
if info.contents.dlpi_name:
name = os.fsdecode(info.contents.dlpi_name)
if name:
libraries.append(name)
return 0
_dl_iterate_phdr = _libc.dl_iterate_phdr
_dl_iterate_phdr.argtypes = [
_dl_phdr_callback,
ctypes.c_void_p,
]
_dl_iterate_phdr.restype = ctypes.c_int
def dllist():
libraries = []
data = ctypes.py_object(libraries)
_dl_iterate_phdr(_info_callback, ctypes.addressof(data))
# Add the Python executable if it's not already in the list
executable = sys.executable
if not any(executable in lib for lib in libraries):
libraries.insert(0, executable)
return libraries
print(dllist()) Output: ╭─blue@home ~/Desktop/cpython ‹main●›
╰─$ ./python hard_coded_path.py
['/home/blue/Desktop/cpython/python', '/home/blue/Desktop/cpython/./python'] |
Bug report
Bug description:
The
test_dllist
tests inLib/test/test_ctypes/test_dllist.py
are failing on NetBSD. The issue appears to be that thedl_iterate_phdr
function on NetBSD does not report the same information about loaded shared libraries as it does on Linux and other platforms.Specifically, on NetBSD,
dl_iterate_phdr
only returns the main Python executable and doesn't include any of the shared libraries that are actually loaded../python -m test test_ctypes -m test_dllist -v
Output:
Running
ldd
on the Python executable confirms that these libraries are actually loaded:CPython versions tested on:
CPython main branch, 3.14
Operating systems tested on:
Other
The text was updated successfully, but these errors were encountered: