Android应用加载SO库崩溃:找不到getThreadLocalsEv入口点如何修复?
升级NDK后加载Rust编译的SO库时出现TLS符号加载失败问题
问题现象
- 执行
System.loadLibrary("hypoboleus");时崩溃,报错找不到入口点 - 升级NDK到
25.2.9519653后触发该问题,旧版本NDK可正常运行 - 删除Rust代码中
Arc<Mutex<UserData>>相关代码后,程序可正常运行(即使未调用new_engine())
错误详情
java.lang.UnsatisfiedLinkError: dlopen failed: TLS symbol "_ZZN8gwp_asan15getThreadLocalsEvE6Locals" in dlopened "/data/app/~~nqWwoXRjQhq9nhyacG54hA==/hk.jennyemily.hypoboleus-EFD9ul4kulUwExi3Ee0LJQ==/base.apk!/lib/arm64-v8a/libhypoboleus.so" referenced from "/data/app/~~nqWwoXRjQhq9nhyacG54hA==/hk.jennyemily.hypoboleus-EFD9ul4kulUwExi3Ee0LJQ==/base.apk!/lib/arm64-v8a/libhypoboleus.so" using IE access model
排查情况
- 已将生成的
libhypoboleus.so放入app/src/main/jniLibs/arm64-v8a/目录 - 通过
nm -D检查SO文件,确认符号_ZZN8gwp_asan15getThreadLocalsEvE6Locals存在(标记为W类型) - 编译命令:
*path-to-ndk*/25.2.9519653/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -target aarch64-linux-android-clang -nostartfiles -shared *path*/hypoboleus_wrap.c *path*/libhypoboleus_c.a -lm -lz -o *path*/libhypoboleus.so -I *path* -fPIC - 代码由Rust编写(对外暴露C接口),通过SWIG生成Java绑定,怀疑是编译C代码时引用C++入口点导致冲突
相关代码与配置
Rust代码
use std::sync::{Arc, Mutex}; pub struct UserHandle { ptr: Arc<Mutex<UserData>>, // delete me } pub struct UserData {} #[no_mangle] pub unsafe extern "C" fn new_engine() -> *mut UserHandle { Box::into_raw(Box::new(UserHandle { ptr: (Arc::new(Mutex::new(UserData {}))), // delete me })) } #[no_mangle] pub unsafe extern "C" fn get_answer() -> std::ffi::c_int { 57 }
Cargo配置
[package] name = "exper2" version = "0.1.0" edition = "2021" [lib] crate-type = ["staticlib"]
Android主活动
package com.example.experiment1; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import exper2.*; public class MainActivity extends AppCompatActivity { private final static String TAG = "exper2"; static { try { Log.d(TAG, "loading library [Java]..."); System.loadLibrary("exper2"); Log.d(TAG, "loaded library [Java]."); } catch (Exception e) { Log.e(TAG, "exception in load library [Java]: " + e); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d(TAG, "looking for answer [Java]..."); int answer = exper2.get_answer(); Log.d(TAG, "answer is " + answer + " [Java]..."); TextView textView = (TextView) findViewById(R.id.answerText); textView.setText("The answer is " + answer); } }
SWIG接口文件
%module exper2 %{ #include "cbindgen/exper2.h" %} %include "cbindgen/exper2.h"
编译脚本
LIBID=$1 GITDIR="$HOME/git" export ARCH=aarch64-linux-android case $LIBID in exper2) RUSTID=exper2 REPO=hypoboleus ANDAPP=Experiment1 ;; *) echo "parameter missing or unknown \"'$LIBID'\", valid hypoboleus or exper2" exit 1 ;; esac RUSTID2=${RUSTID//-/_} TARGETBASE=$GITDIR/$REPO/$RUSTID/target TARGETDIR=$TARGETBASE/$ARCH/release export BASE2=$GITDIR/$REPO/$RUSTID export APPBASE=$GITDIR/$REPO/$ANDAPP export APPMAIN=$APPBASE/app/src/main export ANDROID_HOME=/work/android/sdk export NDK_VERSION=25.2.9519653 export API=33 export NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION export TOOLCHAIN=$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 export CC=$TOOLCHAIN/bin/clang export AR=$TOOLCHAIN/bin/llvm-ar export RANLIB=$TOOLCHAIN/bin/llvm-ranlib cd $BASE2 echo 'base for rust' $BASE2 ', target dir for rust build' $TARGETDIR # rm -rf $TARGETBASE mkdir -p $TARGETDIR RUSTEX_ANDR=$TARGETDIR/lib${RUSTID2}.a # rm -f $RUSTEX_ANDR cargo build --target $ARCH --target-dir $TARGETBASE --release --lib echo 'archive to' $RUSTEX_ANDR if [[ ! -f $RUSTEX_ANDR ]]; then echo "no library file" $RUSTEX_ANDR ls $TARGETDIR/lib* exit 1 fi CBINDGEN_DIR=$BASE2/cbindgen/ rm -rf $CBINDGEN_DIR mkdir -p $CBINDGEN_DIR cbindgen $BASE2/src/lib.rs --output $CBINDGEN_DIR/${LIBID}.h --lang c LIBDIR=$APPMAIN/java/$LIBID echo 'library is called' $LIBID ', stored in' $LIBDIR rm -rf $LIBDIR mkdir -p $LIBDIR swig -outdir $LIBDIR -java -package $LIBID $BASE2/$LIBID.i SODIR=$APPMAIN/jniLibs/arm64-v8a rm -rf $SODIR mkdir -p $SODIR SONAME=lib${LIBID}.so echo 'dynamic library to' $SODIR/$SONAME $CC -target $ARCH-clang -nostartfiles -shared $BASE2/${LIBID}_wrap.c $RUSTEX_ANDR -lm -lz -o $SODIR/$SONAME -I $BASE2 -fPIC
修复建议与排查方向
- 禁用GWP-ASan:错误中的
gwp_asan是Android内存调试工具,NDK25默认可能启用该特性。在Rust编译时添加RUSTFLAGS="-C link-arg=-fno-gwp-asan",或在clang编译SO的命令中加入-fno-gwp-asan参数,强制关闭该工具以避免TLS符号冲突。 - 调整TLS符号处理:尝试在clang编译命令中添加
-Wl,--unresolved-symbols=ignore-in-shared-libs,临时忽略未解析符号,验证是否由符号解析顺序导致问题。 - 改用C++编译器链接:虽然对外暴露C接口,但Rust静态库可能包含C相关符号,尝试将clang替换为clang编译SO文件,确保C++标准库正确链接。
- 配置Rust目标专用参数:在
.cargo/config.toml中添加Android目标配置,指定NDK的clang作为链接器并禁用GWP-ASan:[target.aarch64-linux-android] linker = "aarch64-linux-android33-clang" rustflags = ["-C", "link-arg=-fno-gwp-asan"] - 控制符号可见性:在Rust编译时添加
-C link-arg=-fvisibility=hidden,减少不必要的符号暴露,避免与系统库符号冲突,同时确保对外接口符号已正确标记#[no_mangle]。
内容的提问来源于stack exchange,提问作者Martin Ellison
相关产品推荐
相关产品推荐

