Android NDK中wrap.sh使用与Sanitizers相关技术问题咨询
我一直在尝试通过日志排查原生C共享库中的偶现Bug(涉及多线程与网络场景)。我们的架构是Android Java代码通过JNI实例化C,C++又在独立线程中调用Java网络接口、扫描网络设备、执行逻辑后通过JNI返回结果,这个老旧架构暂时无法轻易修改。在ARM64平台上偶现如下崩溃:
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x493e000000002 in tid 21973 (myapp), pid 21973 (myap)
崩溃地址并非固定,但常出现同一地址。
我尝试使用Sanitizers定位根因,参考NDK示例调试后,成功在Android单元测试App中启用地址Sanitizers,但它在libjingle封装的GetEnv处报错,无法继续排查真正的问题:
void* jni = NULL; jint status = g_jvm->GetEnv(&jni, JNI_VERSION_1_6); // 报错位置
报错日志如下:
01-09 21:13:15.469 26761 26761 F DEBUG : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE) 01-09 21:13:15.470 26761 26761 F DEBUG : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr -------- 01-09 21:13:15.470 26761 26761 F DEBUG : Abort message: '================================================================= 01-09 21:13:15.470 26761 26761 F DEBUG : ==26092==ERROR: AddressSanitizer: SEGV on unknown address 0x1680001f89a932a8 (pc 0x007a6fa365c8 bp 0x007ffaa76ed0 sp 0x007ffaa76e90 T0) 01-09 21:13:15.470 26761 26761 F DEBUG : ==26092==The signal is caused by a READ memory access.
崩溃堆栈:
Abort message: '================================================================= #0 0x29bb25c8 /data/app/~~jBYt60JYrcx_vZkc_7_yBg==/com.test.demo.app-d3ECb5onsDtPo9mfifbLnA==/lib/arm64/libmynative.so _JavaVM::GetEnv(void**, int)
核心编译标志如下:
--target=aarch64-none-linux-android26 --sysroot=/Users/nikola/Library/Android/sdk/ndk/android-ndk-r23/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -DANDROID_LANGUAGE_WRAPPER -DANDROID_STL=c++_shared -DTARGET_OS_ANDROID -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fexceptions -frtti -stdlib=libc++ -fsanitize-address-use-after-scope -fsanitize-recover=address -fno-optimize-sibling-calls -fsanitize=address,null -fno-omit-frame-pointer -g -fno-limit-debug-info -std=c++17 -fPIC -fvisibility=hidden -fvisibility-inlines-hidden
我们使用ant构建原生代码,生成*.so文件后复制到Android App的对应目录中。
1. -fsanitize=address与-fsanitize=address,null的区别是什么?
-fsanitize=address:仅启用地址Sanitizer(ASAN),专注检测内存越界、使用释放后的内存、内存泄漏等内存相关错误。-fsanitize=address,null:同时启用ASAN和空指针检查模块(UBSan的子集)。ASAN对空指针解引用的检测不够精准,补充空指针检查后,会在代码尝试解引用空指针时直接触发报错,覆盖更多未定义行为场景。
2. 未使用wrap.sh但Sanitizers仍能工作,是否因为手动复制了相关so文件?
是的。当你手动将libc++_shared.so、libclang_rt.asan-aarch64-android.so,以及带-fsanitize=address编译的libmytests.so、libmynative.so放到App的lib目录后,Android系统加载这些库时会自动链接ASAN运行时。只要原生库编译时包含ASAN标志,系统就能正确初始化ASAN环境,不需要wrap.sh设置LD_PRELOAD——这种直接打包ASAN运行时库的场景是可行的。
3. wrap.sh未被打包进APK,该如何配置?
wrap.sh的正确放置路径是src/main/resources/lib/[abi]/wrap.sh(比如src/main/resources/lib/arm64-v8a/wrap.sh),而非libs/arm64-v8a目录。同时需要在build.gradle中确保资源被正确打包:
android { sourceSets { main { resources { srcDirs += ['src/main/resources'] } } } }
如果使用AGP 4.0及以上版本,也可以直接把wrap.sh放在src/main/jniLibs/[abi]/目录下,AGP会自动识别并打包该文件。
4. 如何验证wrap.sh是否被实际调用?
有两种简单的验证方式:
- 在
wrap.sh中添加日志输出命令,比如:
运行App后,通过echo "wrap.sh executed at $(date)" > /sdcard/wrap_test.log exec "$@"adb pull /sdcard/wrap_test.log查看是否有内容生成。 - 修改
wrap.sh,让App启动时尝试加载不存在的库,比如修改LD_PRELOAD为一个不存在的路径,若App启动崩溃,说明wrap.sh已被执行。
内容的提问来源于stack exchange,提问作者nikoladsp

