fix(android): use --wrap=pthread_create instead of raw symbol override
Some checks failed
Mirror to GitHub / mirror (push) Failing after 38s
Build Release Binaries / build-amd64 (push) Failing after 3m39s

Build #10 failed with:
  ld.lld: error: duplicate symbol: pthread_create
    >>> defined at pthread_shim.c:30
    >>> ... in archive libpthread_shim.a
  (the other definition coming from libstd's bundled libc.a stub)

The raw-symbol-override approach was naive: when two static archives
both define the same symbol the linker refuses instead of picking one.

Switch to GNU-ld's `--wrap=pthread_create` mechanism:
  - All `pthread_create` references get rewritten to `__wrap_pthread_create`
  - Our shim now defines `__wrap_pthread_create` (no symbol clash)
  - Inside the shim we `dlopen("libc.so")` + `dlsym("pthread_create")` to
    get the real runtime symbol directly, bypassing BOTH the broken static
    stub (libstd's libc.a copy) AND libstd's own pthread_create path
  - `--real_pthread_create` is deliberately NOT used — it would alias the
    same broken stub the wrap exists to avoid

The wrap flag is emitted via `cargo:rustc-link-arg` in build.rs so it
only affects the Android target (the Android-branch of build.rs is the
only place that emits it).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-09 13:08:41 +04:00
parent 79e876126c
commit f015be63ec
2 changed files with 37 additions and 22 deletions

View File

@@ -112,6 +112,12 @@ fn build_oboe_android(target: &str) {
// Oboe requires Android log + OpenSLES backends
println!("cargo:rustc-link-lib=log");
println!("cargo:rustc-link-lib=OpenSLES");
// Wrap pthread_create: redirect every `pthread_create` reference to our
// `__wrap_pthread_create` in pthread_shim.c, which forwards to the real
// libc.so symbol via dlsym. Without this the linker binds to libstd's
// bundled broken static pthread_create stub (see pthread_shim.c).
println!("cargo:rustc-link-arg=-Wl,--wrap=pthread_create");
}
/// Recursively add all .cpp files from a directory to a cc::Build.

View File

@@ -1,19 +1,19 @@
/* pthread_shim.c
*
* Interpose pthread_create to bypass the broken static stub that Rust's
* pre-compiled libstd for aarch64-linux-android drags in.
* 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.
*
* 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.
* Link flags (see build.rs):
* -Wl,--wrap=pthread_create
*
* 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 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.
*
* The same trick handles `getauxval` via getauxval_fix.c.
* We deliberately do NOT call `__real_pthread_create` — that alias is the
* SAME broken stub the wrap is designed to get around.
*/
#ifdef __ANDROID__
@@ -26,20 +26,29 @@
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,
int __wrap_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) {
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");
}
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;
}
}
return real_pthread_create(thread, attr, start_routine, arg);
return real(thread, attr, start_routine, arg);
}
#endif /* __ANDROID__ */