debug(android): instrument pthread_shim with logcat tracing + try RTLD_DEFAULT first
Some checks failed
Mirror to GitHub / mirror (push) Failing after 38s
Build Release Binaries / build-amd64 (push) Failing after 3m43s

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.
This commit is contained in:
Siavash Sameni
2026-04-09 13:15:47 +04:00
parent f015be63ec
commit 1a8288c95f

View File

@@ -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 <android/log.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stddef.h>
#include <stdio.h>
#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);