Builds #7, #8 and #9 all crashed at launch with the same SIGSEGV inside
__init_tcb(bionic_tcb*, pthread_internal_t*)+4 called via pthread_create
from std::sys::thread::unix::Thread::new.
Digging further: the problem is NOT the final linker we pass to cargo.
It's that rustup ships a PRE-COMPILED libstd for aarch64-linux-android
which was built statically against an old NDK libc archive. That archive
has a pthread_create stub which calls a static __init_tcb stub that
assumes libc's static init path has set up the TCB — which never happens
in a .so loaded via dlopen. Bumping minSdk to 26 or forcing the
android26-clang linker (903a07c) doesn't rebuild libstd and therefore
doesn't fix the bundled broken stub.
The legacy wzp-android crate dodged this with a getauxval_fix.c shim that
interposes getauxval via RTLD_NEXT. The same trick works for pthread_create
here: define our own `int pthread_create(...)` in cpp/pthread_shim.c that
forwards to `dlsym(RTLD_NEXT, "pthread_create")` — the real, fully working
version exported from libc.so. The linker processes our static lib before
libstd.rlib, so libstd's unresolved pthread_create reference binds to our
symbol, and the broken libc.a stub inside libstd is never pulled in.
build.rs compiles cpp/pthread_shim.c right after cpp/getauxval_fix.c so
both symbol overrides are in place before any Rust code gets linked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
46 lines
1.7 KiB
C
46 lines
1.7 KiB
C
/* pthread_shim.c
|
|
*
|
|
* Interpose pthread_create to bypass the broken static stub that Rust's
|
|
* pre-compiled libstd for aarch64-linux-android drags in.
|
|
*
|
|
* The stub (from an old NDK libc.a) calls __init_tcb(bionic_tcb*, ...)+4,
|
|
* which SIGSEGVs in .so libraries because __init_tcb expects TCB state that
|
|
* only the static-libc init path sets up. In a dlopen-loaded shared lib
|
|
* nothing ever initialises that state.
|
|
*
|
|
* By providing our own pthread_create at link time (which takes priority
|
|
* over the one dragged in by libstd) and forwarding it via dlsym(RTLD_NEXT)
|
|
* to the REAL pthread_create in libc.so, we completely sidestep the static
|
|
* stub — libc.so's pthread_create is the fully working runtime version.
|
|
*
|
|
* The same trick handles `getauxval` via getauxval_fix.c.
|
|
*/
|
|
|
|
#ifdef __ANDROID__
|
|
|
|
#define _GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <pthread.h>
|
|
#include <stddef.h>
|
|
|
|
typedef int (*pthread_create_fn)(pthread_t *, const pthread_attr_t *,
|
|
void *(*)(void *), void *);
|
|
|
|
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
|
void *(*start_routine)(void *), void *arg) {
|
|
static pthread_create_fn real_pthread_create = NULL;
|
|
if (real_pthread_create == NULL) {
|
|
/* RTLD_NEXT: skip the symbol we're currently defining and return
|
|
* the next one in the search order — which is the real pthread_create
|
|
* exported from libc.so. */
|
|
real_pthread_create =
|
|
(pthread_create_fn)dlsym(RTLD_NEXT, "pthread_create");
|
|
if (real_pthread_create == NULL) {
|
|
return -1;
|
|
}
|
|
}
|
|
return real_pthread_create(thread, attr, start_routine, arg);
|
|
}
|
|
|
|
#endif /* __ANDROID__ */
|