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 // Oboe requires Android log + OpenSLES backends
println!("cargo:rustc-link-lib=log"); println!("cargo:rustc-link-lib=log");
println!("cargo:rustc-link-lib=OpenSLES"); 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. /// Recursively add all .cpp files from a directory to a cc::Build.

View File

@@ -1,19 +1,19 @@
/* pthread_shim.c /* pthread_shim.c
* *
* Interpose pthread_create to bypass the broken static stub that Rust's * Interpose pthread_create via linker --wrap to bypass Rust libstd's broken
* pre-compiled libstd for aarch64-linux-android drags in. * 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, * Link flags (see build.rs):
* which SIGSEGVs in .so libraries because __init_tcb expects TCB state that * -Wl,--wrap=pthread_create
* 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 * The wrap flag makes the linker redirect every unresolved reference to
* over the one dragged in by libstd) and forwarding it via dlsym(RTLD_NEXT) * `pthread_create` → `__wrap_pthread_create` (below). Inside the shim we
* to the REAL pthread_create in libc.so, we completely sidestep the static * explicitly open libc.so and look up the real, fully working runtime
* stub — libc.so's pthread_create is the fully working runtime version. * 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__ #ifdef __ANDROID__
@@ -26,20 +26,29 @@
typedef int (*pthread_create_fn)(pthread_t *, const pthread_attr_t *, typedef int (*pthread_create_fn)(pthread_t *, const pthread_attr_t *,
void *(*)(void *), void *); 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) { void *(*start_routine)(void *), void *arg) {
static pthread_create_fn real_pthread_create = NULL; static pthread_create_fn real = NULL;
if (real_pthread_create == NULL) { if (real == NULL) {
/* RTLD_NEXT: skip the symbol we're currently defining and return /* Explicitly open libc.so and fetch the runtime pthread_create.
* the next one in the search order — which is the real pthread_create * RTLD_NOLOAD would be wrong here — we want the mapping even if
* exported from libc.so. */ * bionic already loaded libc. The standard filename "libc.so" is
real_pthread_create = * safe on Android; bionic's dynamic linker resolves it to
(pthread_create_fn)dlsym(RTLD_NEXT, "pthread_create"); * /apex/com.android.runtime/lib64/bionic/libc.so at runtime. */
if (real_pthread_create == NULL) { 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 -1;
} }
} }
return real_pthread_create(thread, attr, start_routine, arg); return real(thread, attr, start_routine, arg);
} }
#endif /* __ANDROID__ */ #endif /* __ANDROID__ */