From bef4a959a9cbc60d5746b833d13b086434d3ef01 Mon Sep 17 00:00:00 2001 From: Tobias Fischer Date: Tue, 15 Sep 2026 17:50:38 +1000 Subject: [PATCH] Add emscripten guards to shared_library.c's dlopen path (#591) * fix: add emscripten guards to shared_library.c's dlopen path Two related gaps in rcutils_load_shared_library() / rcutils_get_platform_library_name(), found while getting rcl_logging_implementation's dlopen-by-name backend selection working on emscripten-wasm32: - rcutils_get_platform_library_name() had no emscripten branch, so it always fell through with written == 0 ("failed to format library name"), regardless of which RCL_LOGGING_IMPLEMENTATION backend was requested. wasm32 side modules use the same "lib.so" convention as Linux. - The post-dlopen path-resolution code took the _GNU_SOURCE branch (which emscripten's headers define) and called dlinfo(..., RTLD_DI_LINKMAP, ...). Emscripten's dlopen()/dlinfo() are a JS-backed shim, not glibc's, and don't support reading back a real struct link_map -- so a successful dlopen() got treated as a failure once dlinfo() returned -1. The #else branch (reuse the path dlopen() was given) already covers this platform correctly. Verified end-to-end on a ROS 2 rolling + rmw_zenoh_pico + real-pthreads emscripten-wasm32 build: a wasm32 rclpy talker's logging initialization (rcl_logging_configure -> rcl_logging_implementation -> this dlopen path) now succeeds instead of aborting with "failed to load any logging implementations". Co-Authored-By: Claude Sonnet 5 Signed-off-by: Tobias Fischer Signed-off-by: Alejandro Hernandez Cordero Co-authored-by: Alejandro Hernandez Cordero (cherry picked from commit a2d8f50a9b847d0c8aae1f20d9c069f91ec88ef0) --- src/shared_library.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/shared_library.c b/src/shared_library.c index 077478f7..2439c6a4 100644 --- a/src/shared_library.c +++ b/src/shared_library.c @@ -124,7 +124,14 @@ rcutils_load_shared_library( goto fail; } lib->library_path = rcutils_strdup(image_name, lib->allocator); -#elif defined(_GNU_SOURCE) && !defined(__QNXNTO__) && !defined(__ANDROID__) && !defined(__OHOS__) + // Emscripten defines _GNU_SOURCE but its dlopen()/dlinfo() are a JS-backed + // shim, not glibc's -- RTLD_DI_LINKMAP support (reading back a real + // struct link_map) doesn't exist there. A successful dlopen() would + // otherwise get treated as a failure once dlinfo() returns -1 below; the + // #else branch already covers this correctly (it just reuses the path we + // opened the library from, no introspection needed). +#elif defined(_GNU_SOURCE) && !defined(__QNXNTO__) && !defined(__ANDROID__) && \ + !defined(__OHOS__) && !defined(__EMSCRIPTEN__) struct link_map * map = NULL; if (dlinfo(lib->lib_pointer, RTLD_DI_LINKMAP, &map) != 0) { RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("dlinfo error: %s", dlerror()); @@ -291,7 +298,12 @@ rcutils_get_platform_library_name( int written = 0; -#if defined(__linux__) || defined(__QNXNTO__) + // rcl_logging_implementation dlopens its backend (spdlog or noop) by name + // at runtime via this function -- with no emscripten case it always falls + // through with written == 0 ("failed to format library name"), regardless + // of which backend RCL_LOGGING_IMPLEMENTATION selects. wasm32 side modules + // use the same "lib.so" naming convention as Linux. +#if defined(__linux__) || defined(__QNXNTO__) || defined(__EMSCRIPTEN__) if (debug) { if (buffer_size >= (strlen(library_name) + 8)) { written = rcutils_snprintf(