Description
When compiling standalone binaries for Android with debug information, there seems to be no backtrace information available. However it is possible to get a backtrace when using backtrace
as external dependency.
I am building native binaries for Android devices. I noticed that I could not get a backtrace on panic
. This is reproducible for me on a physical Samsung Galaxy Tab A9 (aarch64-linux-android
, with Android 13 and Android API 33), on a Pixel 3a emulator (x86_64-linux-android
with Android 14 and Android API 34) as well as on another target device for which I cannot disclose version information.
For the rest of the report, I will only provide the instructions for the Samsung Galaxy Tab A9, but the same behavior happened with the emulator and the other Android device.
Meta
$ rustc --version --verbose
rustc 1.75.0 (82e1608df 2023-12-21)
binary: rustc
commit-hash: 82e1608dfa6e0b5569232559e3d385fea5a93112
commit-date: 2023-12-21
host: x86_64-unknown-linux-gnu
release: 1.75.0
LLVM version: 17.0.6
Setup
Here is a sample of code for a test binary called stack_trace
:
fn outer_func0() {
inner_func(false);
}
fn outer_func1() {
inner_func(true);
}
fn inner_func(param: bool) {
if param {
panic!("Intentional panic");
}
}
fn main() {
outer_func0();
outer_func1();
}
For linking, I tried two versions.
- Using
clang
as part of the latest Android NDK. - Using the linker coming with
cross
(apparently-C linker=aarch64-linux-android-gcc
)
# .cargo/config.toml
# For the Galaxy Tab A9
[target.aarch64-linux-android]
linker = "/home/user/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang"
Building
# With plan cargo
cargo build --target aarch64-linux-android
# With cross
cross build --target aarch64-linux-android
Issue
When running RUST_BACKTRACE=1 ./stack_trace
, I would expect to see the backtrace of the panic, telling me which call chain led to the panic.
Instead, I see this output (backtrace is missing):
$ RUST_BACKTRACE=1 ./stack_trace
thread 'main' panicked at src/main.rs:11:9:
Intentional panic
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Analysis
You may wonder if the debug symbols got stripped, but they were not:
$ file stack_trace
stack_trace: ELF shared object, 64-bit LSB arm64, dynamic (/system/bin/linker64), for Android 33, built by NDK r26c (11394342), not stripped
So after some searching, I stumbled over this comment by @zarik5 so I tried using backtrace
directly, setting a custom panic hook:
fn panic_with_backtrace_rs() {
std::panic::set_hook(Box::new(|panic_info| {
print!(
"thread '{}' panicked",
std::thread::current().name().unwrap_or("unknown")
);
if let Some(location) = panic_info.location() {
println!(" at {}:{}:", location.file(), location.line());
} else {
println!("");
}
if let Some(payload) = panic_info.payload().downcast_ref::<&str>() {
println!("{}", payload);
}
if let Ok("1") = std::env::var("RUST_BACKTRACE").as_deref() {
println!("{:?}", backtrace::Backtrace::new());
}
}));
}
If I call this function in main
and build with cargo
(linking with clang
), I get this output:
$ RUST_BACKTRACE=1 ./stack_trace
thread 'main' panicked at src/main.rs:11:
Intentional panic
0: <unknown>
1: <unknown>
2: <unknown>
3: <unknown>
4: <unknown>
5: <unknown>
6: <unknown>
7: <unknown>
8: <unknown>
9: <unknown>
10: <unknown>
11: <unknown>
12: <unknown>
13: <unknown>
14: <unknown>
15: <unknown>
And if I keep the custom hook and build with cross
(linking with gcc
), I finally get a usable backtrace:
$ RUST_BACKTRACE=1 ./stack_trace
thread 'main' panicked at src/main.rs:11:
Intentional panic
0: stack_trace::panic_with_backtrace_rs::{{closure}}
at /home/user/workspace/examples/stack_trace/src/main.rs:40:30
1: <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/alloc/src/boxed.rs:2021:9
std::panicking::rust_panic_with_hook
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panicking.rs:783:13
2: std::panicking::begin_panic_handler::{{closure}}
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panicking.rs:649:13
3: std::sys_common::backtrace::__rust_end_short_backtrace
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/sys_common/backtrace.rs:170:18
4: rust_begin_unwind
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panicking.rs:645:5
5: core::panicking::panic_fmt
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/core/src/panicking.rs:72:14
6: stack_trace::inner_func
at /home/user/workspace/examples/stack_trace/src/main.rs:11:9
7: stack_trace::outer_func1
at /home/user/workspace/examples/stack_trace/src/main.rs:6:5
8: stack_trace::main
at /home/user/workspace/examples/stack_trace/src/main.rs:19:5
9: core::ops::function::FnOnce::call_once
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/core/src/ops/function.rs:250:5
10: std::sys_common::backtrace::__rust_begin_short_backtrace
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/sys_common/backtrace.rs:154:18
11: std::rt::lang_start::{{closure}}
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/rt.rs:167:18
12: core::ops::function::impls::<impl core::ops::function::FnOnce<A> for &F>::call_once
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/core/src/ops/function.rs:284:13
std::panicking::try::do_call
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panicking.rs:552:40
std::panicking::try
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panicking.rs:516:19
std::panic::catch_unwind
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panic.rs:142:14
std::rt::lang_start_internal::{{closure}}
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/rt.rs:148:48
std::panicking::try::do_call
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panicking.rs:552:40
std::panicking::try
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panicking.rs:516:19
std::panic::catch_unwind
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/panic.rs:142:14
std::rt::lang_start_internal
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/rt.rs:148:20
13: std::rt::lang_start
at /rustc/82e1608dfa6e0b5569232559e3d385fea5a93112/library/std/src/rt.rs:166:17
14: main
15: __libc_init
Now you may assume it has something to do with cross
. But on the third Android device which I tested (for which I cannot disclose further info), we also use clang
for linking with a specific NDK version. There, I also get the "unknown" stack trace when using the default panic hook and a proper one when using backtrace
as external dependency - however I do not need to compile with cross
, it also works with cargo
.
So unfortunately, I need to include cross
in the example here to reproduce it, but I have reason to assume that this is not related to cross
and the issue happens the same when linking with clang
. Without a physical device, I was also able to reproduce it in the Android emulator (came as part of Android Studio). Given that I am apparently not the first person missing backtraces on Android I wonder, is a bug in the stdlib's usage of backtrace
for Android?