fix(android): use --wrap=pthread_create instead of raw symbol override
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:
@@ -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.
|
||||||
|
|||||||
@@ -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__ */
|
||||||
|
|||||||
Reference in New Issue
Block a user