You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

修复建议与排查方向

  1. 禁用GWP-ASan:错误中的gwp_asan是Android内存调试工具,NDK25默认可能启用该特性。在Rust编译时添加RUSTFLAGS="-C link-arg=-fno-gwp-asan",或在clang编译SO的命令中加入-fno-gwp-asan参数,强制关闭该工具以避免TLS符号冲突。
  2. 调整TLS符号处理:尝试在clang编译命令中添加-Wl,--unresolved-symbols=ignore-in-shared-libs,临时忽略未解析符号,验证是否由符号解析顺序导致问题。
  3. 改用C++编译器链接:虽然对外暴露C接口,但Rust静态库可能包含C相关符号,尝试将clang替换为clang编译SO文件,确保C++标准库正确链接。
  4. 配置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"]
    
  5. 控制符号可见性:在Rust编译时添加-C link-arg=-fvisibility=hidden,减少不必要的符号暴露,避免与系统库符号冲突,同时确保对外接口符号已正确标记#[no_mangle]。

内容的提问来源于stack exchange,提问作者Martin Ellison

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:45:13