From 622fdee51f0cf6beea47d8cf3b459759d9a03ece Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Sun, 5 Apr 2026 09:00:14 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20also=20link=20libc++abi=20for=20RTTI=20?= =?UTF-8?q?=E2=80=94=20resolve=20missing=20=5F=5Fclass=5Ftype=5Finfo=20vta?= =?UTF-8?q?ble?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous fix linked c++_static but not c++abi. Android NDK splits the static C++ runtime into two archives: libc++_static.a (STL) and libc++abi.a (RTTI/exceptions). Without c++abi, dlopen fails on _ZTVN10__cxxabiv117__class_type_infoE. Now using cpp_link_stdlib(None) to suppress cc crate auto-linking, then explicitly linking both c++_static and c++abi via cargo:rustc-link-lib. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/wzp-android/build.rs | 11 +++- issues/001-libc++-shared-crash.md | 92 +++++++++++++++++-------------- 2 files changed, 59 insertions(+), 44 deletions(-) diff --git a/crates/wzp-android/build.rs b/crates/wzp-android/build.rs index 8e7d8aa..1fb08c5 100644 --- a/crates/wzp-android/build.rs +++ b/crates/wzp-android/build.rs @@ -13,7 +13,7 @@ fn main() { cc::Build::new() .cpp(true) .std("c++17") - .cpp_link_stdlib(Some("c++_static")) + .cpp_link_stdlib(None) .file("cpp/oboe_bridge.cpp") .include("cpp") .include(oboe_path.join("include")) @@ -25,12 +25,19 @@ fn main() { cc::Build::new() .cpp(true) .std("c++17") - .cpp_link_stdlib(Some("c++_static")) + .cpp_link_stdlib(None) .file("cpp/oboe_stub.cpp") .include("cpp") .compile("oboe_bridge"); } } + + // Android NDK splits the static C++ runtime into two archives: + // libc++_static.a — STL (containers, strings, algorithms) + // libc++abi.a — ABI (RTTI, exceptions, typeinfo vtables) + // Both are required; cc crate's cpp_link_stdlib only handles the first. + println!("cargo:rustc-link-lib=static=c++_static"); + println!("cargo:rustc-link-lib=static=c++abi"); } else { // Non-Android: always use stub cc::Build::new() diff --git a/issues/001-libc++-shared-crash.md b/issues/001-libc++-shared-crash.md index 10a3651..7bf0622 100644 --- a/issues/001-libc++-shared-crash.md +++ b/issues/001-libc++-shared-crash.md @@ -1,6 +1,6 @@ -# Issue 001: App crashes on launch — missing libc++_shared.so +# Issue 001: App crashes on launch — C++ runtime not linked correctly -## Status: Fix committed, needs rebuild +## Status: Fix v2 committed, needs rebuild ## Symptom @@ -13,77 +13,85 @@ or on any code path that first loads the native library. - Nothing Phone, arm64-v8a, Android 15 - ADB device ID: `00142151B000973` -## Logcat crash trace +## Crash history + +### Attempt 1: `libc++_shared.so` not found ``` -E AndroidRuntime: FATAL EXCEPTION: main -E AndroidRuntime: Process: com.wzp.phone, PID: 6048 E AndroidRuntime: java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" - not found: needed by /data/app/.../base.apk!/lib/arm64-v8a/libwzp_android.so - in namespace clns-9 -E AndroidRuntime: at java.lang.Runtime.loadLibrary0(Runtime.java:1097) -E AndroidRuntime: at java.lang.System.loadLibrary(System.java:1765) -E AndroidRuntime: at com.wzp.engine.WzpEngine.(WzpEngine.kt:115) -E AndroidRuntime: at com.wzp.ui.call.CallViewModel.startCall(CallViewModel.kt:52) -E AndroidRuntime: at com.wzp.ui.call.InCallScreenKt$InCallScreen$1$1$1.invoke(InCallScreen.kt:96) + not found: needed by .../libwzp_android.so + at com.wzp.engine.WzpEngine.(WzpEngine.kt:115) ``` -## Root cause +**Cause**: `cc::Build` defaults to dynamic C++ linking. `libc++_shared.so` never +packaged into APK. + +**Attempted fix**: `.cpp_link_stdlib(Some("c++_static"))` — link STL statically. + +### Attempt 2: missing `__class_type_info` vtable (RTTI) + +``` +E AndroidRuntime: java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol + "_ZTVN10__cxxabiv117__class_type_infoE" referenced by .../libwzp_android.so + at com.wzp.engine.WzpEngine.(WzpEngine.kt:115) +``` + +**Cause**: Android NDK splits the static C++ runtime into two archives: +- `libc++_static.a` — STL (containers, strings, algorithms) +- `libc++abi.a` — ABI layer (RTTI typeinfo vtables, exception handling) + +The `cc` crate's `.cpp_link_stdlib(Some("c++_static"))` only emits +`cargo:rustc-link-lib=static=c++_static`. It does NOT pull in `libc++abi.a`, +so all RTTI symbols (`__class_type_info`, `__si_class_type_info`, etc.) +are unresolved at dlopen time. + +## Root cause (full) `crates/wzp-android/build.rs` uses the `cc` crate to compile C++17 code -(the Oboe audio bridge). On Android targets, `cc::Build` defaults to dynamically -linking the C++ standard library (`libc++_shared.so`). +(the Oboe audio bridge). Two things go wrong: -This means `libwzp_android.so` has a runtime dependency on `libc++_shared.so`. -However, the Gradle build (`cargoNdkBuild` task) only copies `libwzp_android.so` -into `jniLibs/arm64-v8a/`. The C++ runtime is never copied alongside it, so the -APK ships without `libc++_shared.so`. +1. Dynamic linking by default → `libc++_shared.so` not in APK +2. Even with `.cpp_link_stdlib("c++_static")`, the ABI half (`libc++abi.a`) + is not linked, leaving RTTI symbols unresolved -At runtime, `dlopen("libwzp_android.so")` fails because the linker can't find -the missing shared library in the app's namespace. +## Fix (v2) -### Why this wasn't caught earlier - -The previous APK (pre-QUIC wiring) was 2.0MB release / 8.9MB debug. The Oboe -C++ bridge was likely being compiled but the native library may not have been -loaded on the code path that was tested, or the dependency was satisfied by a -different linking configuration at the time. - -## Fix - -In `crates/wzp-android/build.rs`, add `.cpp_link_stdlib(Some("c++_static"))` to -all `cc::Build` invocations targeting Android. This tells the `cc` crate to link -`libc++_static.a` instead of `libc++_shared.so`, baking the C++ runtime directly -into `libwzp_android.so`. No separate shared library needed at runtime. +Suppress the `cc` crate's automatic C++ stdlib linking with `.cpp_link_stdlib(None)`, +then explicitly link both static archives: ```rust cc::Build::new() .cpp(true) .std("c++17") - .cpp_link_stdlib(Some("c++_static")) // <-- this line + .cpp_link_stdlib(None) // suppress cc crate's automatic linking .file("cpp/oboe_bridge.cpp") // ... + .compile("oboe_bridge"); + +// Manually link both halves of the Android NDK static C++ runtime +println!("cargo:rustc-link-lib=static=c++_static"); +println!("cargo:rustc-link-lib=static=c++abi"); ``` -Applied to both the Oboe build path and the stub fallback path. +This is placed once after the match block (applies to both Oboe and stub paths). ### Trade-off -Static linking increases `libwzp_android.so` size slightly (~200-400KB for -libc++ static). This is acceptable — the alternative (bundling libc++_shared.so -separately) adds complexity to the Gradle build and risks version mismatches if -multiple native libraries each bundle their own shared copy. +Static linking increases `libwzp_android.so` by ~300-500KB. Acceptable for +avoiding shared library packaging complexity. ## Rebuild steps ```bash -cd android && ./gradlew assembleRelease +cd android && ./gradlew clean assembleRelease adb install -r app/build/outputs/apk/release/app-release.apk ``` +Use `clean` to ensure the native library is fully relinked. + ## Verification After install, the app should: 1. Open without crashing -2. Load `libwzp_android.so` successfully (check logcat for absence of UnsatisfiedLinkError) +2. Load `libwzp_android.so` successfully (no UnsatisfiedLinkError in logcat) 3. Show the call UI with CALL button