fix: also link libc++abi for RTTI — resolve missing __class_type_info vtable
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) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ fn main() {
|
|||||||
cc::Build::new()
|
cc::Build::new()
|
||||||
.cpp(true)
|
.cpp(true)
|
||||||
.std("c++17")
|
.std("c++17")
|
||||||
.cpp_link_stdlib(Some("c++_static"))
|
.cpp_link_stdlib(None)
|
||||||
.file("cpp/oboe_bridge.cpp")
|
.file("cpp/oboe_bridge.cpp")
|
||||||
.include("cpp")
|
.include("cpp")
|
||||||
.include(oboe_path.join("include"))
|
.include(oboe_path.join("include"))
|
||||||
@@ -25,12 +25,19 @@ fn main() {
|
|||||||
cc::Build::new()
|
cc::Build::new()
|
||||||
.cpp(true)
|
.cpp(true)
|
||||||
.std("c++17")
|
.std("c++17")
|
||||||
.cpp_link_stdlib(Some("c++_static"))
|
.cpp_link_stdlib(None)
|
||||||
.file("cpp/oboe_stub.cpp")
|
.file("cpp/oboe_stub.cpp")
|
||||||
.include("cpp")
|
.include("cpp")
|
||||||
.compile("oboe_bridge");
|
.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 {
|
} else {
|
||||||
// Non-Android: always use stub
|
// Non-Android: always use stub
|
||||||
cc::Build::new()
|
cc::Build::new()
|
||||||
|
|||||||
@@ -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
|
## Symptom
|
||||||
|
|
||||||
@@ -13,77 +13,85 @@ or on any code path that first loads the native library.
|
|||||||
- Nothing Phone, arm64-v8a, Android 15
|
- Nothing Phone, arm64-v8a, Android 15
|
||||||
- ADB device ID: `00142151B000973`
|
- 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"
|
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
|
not found: needed by .../libwzp_android.so
|
||||||
in namespace clns-9
|
at com.wzp.engine.WzpEngine.<clinit>(WzpEngine.kt:115)
|
||||||
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.<clinit>(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)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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.<clinit>(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
|
`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
|
(the Oboe audio bridge). Two things go wrong:
|
||||||
linking the C++ standard library (`libc++_shared.so`).
|
|
||||||
|
|
||||||
This means `libwzp_android.so` has a runtime dependency on `libc++_shared.so`.
|
1. Dynamic linking by default → `libc++_shared.so` not in APK
|
||||||
However, the Gradle build (`cargoNdkBuild` task) only copies `libwzp_android.so`
|
2. Even with `.cpp_link_stdlib("c++_static")`, the ABI half (`libc++abi.a`)
|
||||||
into `jniLibs/arm64-v8a/`. The C++ runtime is never copied alongside it, so the
|
is not linked, leaving RTTI symbols unresolved
|
||||||
APK ships without `libc++_shared.so`.
|
|
||||||
|
|
||||||
At runtime, `dlopen("libwzp_android.so")` fails because the linker can't find
|
## Fix (v2)
|
||||||
the missing shared library in the app's namespace.
|
|
||||||
|
|
||||||
### Why this wasn't caught earlier
|
Suppress the `cc` crate's automatic C++ stdlib linking with `.cpp_link_stdlib(None)`,
|
||||||
|
then explicitly link both static archives:
|
||||||
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.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
cc::Build::new()
|
cc::Build::new()
|
||||||
.cpp(true)
|
.cpp(true)
|
||||||
.std("c++17")
|
.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")
|
.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
|
### Trade-off
|
||||||
|
|
||||||
Static linking increases `libwzp_android.so` size slightly (~200-400KB for
|
Static linking increases `libwzp_android.so` by ~300-500KB. Acceptable for
|
||||||
libc++ static). This is acceptable — the alternative (bundling libc++_shared.so
|
avoiding shared library packaging complexity.
|
||||||
separately) adds complexity to the Gradle build and risks version mismatches if
|
|
||||||
multiple native libraries each bundle their own shared copy.
|
|
||||||
|
|
||||||
## Rebuild steps
|
## Rebuild steps
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd android && ./gradlew assembleRelease
|
cd android && ./gradlew clean assembleRelease
|
||||||
adb install -r app/build/outputs/apk/release/app-release.apk
|
adb install -r app/build/outputs/apk/release/app-release.apk
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Use `clean` to ensure the native library is fully relinked.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
After install, the app should:
|
After install, the app should:
|
||||||
1. Open without crashing
|
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
|
3. Show the call UI with CALL button
|
||||||
|
|||||||
Reference in New Issue
Block a user