From 1a8288c95f21ce3889cd538a2e7eabc9ae464523 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Thu, 9 Apr 2026 13:15:47 +0400 Subject: [PATCH] debug(android): instrument pthread_shim with logcat tracing + try RTLD_DEFAULT first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build #11 linked cleanly with --wrap=pthread_create but crashed at launch on tao::ndk_glue::create with a Rust .expect() panic — meaning the shim's __wrap_pthread_create successfully intercepted the call but returned non-zero, triggering std::thread::spawn's Result::expect panic. Add __android_log_print tracing so logcat shows exactly which resolver path fired (RTLD_DEFAULT vs dlopen fallback) and what dlerror reports when they fail. Also try RTLD_DEFAULT first — it's the simplest and should find libc.so's pthread_create in the process's global symbol table without any namespace games. --- desktop/src-tauri/cpp/pthread_shim.c | 82 ++++++++++++++++++---------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/desktop/src-tauri/cpp/pthread_shim.c b/desktop/src-tauri/cpp/pthread_shim.c index 3670dd7..8fa5b44 100644 --- a/desktop/src-tauri/cpp/pthread_shim.c +++ b/desktop/src-tauri/cpp/pthread_shim.c @@ -1,51 +1,77 @@ /* pthread_shim.c * - * Interpose pthread_create via linker --wrap to bypass Rust libstd's broken - * static pthread_create stub (pulled in from an old NDK libc.a), which - * transitively calls __init_tcb+4 and SIGSEGVs in any .so loaded via dlopen. + * Interpose pthread_create via `--wrap=pthread_create` to bypass Rust libstd's + * broken static pthread_create stub. The stub (from an old bundled libc.a) + * calls __init_tcb+4 and SIGSEGVs in dlopen-loaded .so libraries. * - * Link flags (see build.rs): - * -Wl,--wrap=pthread_create - * - * The wrap flag makes the linker redirect every unresolved reference to - * `pthread_create` → `__wrap_pthread_create` (below). Inside the shim we - * explicitly open libc.so and look up the real, fully working runtime - * pthread_create, bypassing libstd's bundled archive entirely. - * - * We deliberately do NOT call `__real_pthread_create` — that alias is the - * SAME broken stub the wrap is designed to get around. + * Approach: + * 1. The linker rewrites every `pthread_create` reference inside libstd + * (and everywhere else) into `__wrap_pthread_create` — our function. + * 2. Inside __wrap_pthread_create we call libc.so's real pthread_create + * via dlsym(RTLD_DEFAULT). Because --wrap redirects only references, + * the symbol `pthread_create` itself is NOT defined in our .so, so + * RTLD_DEFAULT finds the one exported by libc.so (always loaded). + * 3. We log every step via __android_log_print so we can diagnose any + * lookup failure from logcat (tag: WZP_pthread_shim). */ #ifdef __ANDROID__ #define _GNU_SOURCE +#include #include #include #include +#include + +#define TAG "WZP_pthread_shim" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) typedef int (*pthread_create_fn)(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *); +static pthread_create_fn resolve_real_pthread_create(void) { + /* RTLD_DEFAULT: search the global symbol table starting with the main + * executable (app_process64), then every shared library loaded into the + * process in load order. libc.so is always loaded first, so its + * pthread_create export is the first match — and since we never define + * a symbol literally named "pthread_create" (only __wrap_pthread_create), + * there's no ambiguity. */ + pthread_create_fn fn = (pthread_create_fn)dlsym(RTLD_DEFAULT, "pthread_create"); + if (fn != NULL) { + LOGI("resolved pthread_create via RTLD_DEFAULT at %p", (void *)fn); + return fn; + } + LOGE("dlsym(RTLD_DEFAULT, pthread_create) returned NULL: %s", dlerror()); + + /* Fall back to explicit dlopen of libc.so — bionic is normally happy + * to hand back the already-loaded mapping. */ + void *libc = dlopen("libc.so", RTLD_LAZY | RTLD_NOLOAD); + if (libc == NULL) { + libc = dlopen("libc.so", RTLD_LAZY); + } + if (libc == NULL) { + LOGE("dlopen(libc.so) failed: %s", dlerror()); + return NULL; + } + fn = (pthread_create_fn)dlsym(libc, "pthread_create"); + if (fn == NULL) { + LOGE("dlsym(libc, pthread_create) returned NULL: %s", dlerror()); + return NULL; + } + LOGI("resolved pthread_create via dlopen(libc.so) at %p", (void *)fn); + return fn; +} + int __wrap_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { static pthread_create_fn real = NULL; if (real == NULL) { - /* Explicitly open libc.so and fetch the runtime pthread_create. - * RTLD_NOLOAD would be wrong here — we want the mapping even if - * bionic already loaded libc. The standard filename "libc.so" is - * safe on Android; bionic's dynamic linker resolves it to - * /apex/com.android.runtime/lib64/bionic/libc.so at runtime. */ - void *libc = dlopen("libc.so", RTLD_LAZY | RTLD_GLOBAL); - if (libc != NULL) { - real = (pthread_create_fn)dlsym(libc, "pthread_create"); - } + real = resolve_real_pthread_create(); if (real == NULL) { - /* Fallback: try RTLD_DEFAULT — may or may not work depending on - * link order but it's better than segfaulting. */ - real = (pthread_create_fn)dlsym(RTLD_DEFAULT, "pthread_create"); - } - if (real == NULL) { - return -1; + LOGE("__wrap_pthread_create: no real pthread_create found, returning EAGAIN"); + return 11; /* EAGAIN — best we can do */ } } return real(thread, attr, start_routine, arg);