2026/10/15 1:05:27

高通8155p QNX+Android9双系统ASAN实战:从编译到跨系统内存问题定位

高通8155p QNX+Android9双系统ASAN实战:从编译到跨系统内存问题定位 简介这份资源面向在高通8155p平台上进行QNXAndroid9双系统开发的工程师聚焦于开启ASANAddressSanitizer以定位Native层内存越界、泄漏等疑难问题。包内同时提供Android.mk与Android.bp两套构建脚本示例覆盖libAsanDebug、asan_debugd及其vnd变体、libclang_rt.asan运行时库等模块帮助开发者解决因构建配置不当导致ASAN无法使能、编译报错等常见障碍。资源共18个文件包含4个mk、4个bp构建脚本4个cpp源码2个so预编译库2个h头文件及2个mk~备份文件压缩包约1.21MB结构清晰便于对照移植。目前已有1994人学习下载适合不熟悉Android.mk与Android.bp编写、或反复遭遇编译问题的开发者参考可快速掌握ASAN在车机双系统环境下的集成方式与排错思路。1. 高通8155p上QNXAndroid9双系统ASAN到底能帮你抓到什么在高通8155p这类座舱平台上QNX负责仪表与安全域Android9跑IVI应用两边通过Hypervisor共享内存、GPU和音频通道。这种架构下最让人头疼的不是崩溃而是那种“偶发花屏、随机重启、日志里什么都没有”的内存问题。ASANAddressSanitizer就是用来把这类玄学问题变成可定位堆栈的利器。它能在运行时检测越界读写、释放后使用、双重释放、内存泄漏等直接告诉你哪一行代码踩了哪块内存。本文面向的是在8155p上做Android9系统开发、需要开启ASAN定位native内存问题的工程师。不管你的模块用的是Android.mk还是Android.bp核心思路一致让目标模块带上ASAN运行时并在QNXAndroid双系统环境下正确加载。下面从原理到落地一步步拆。2. ASAN在Android9上的编译链路从Android.mk到Android.bp2.1 为什么Android9的ASAN不能直接照搬高版本做法Android9API 28的编译系统正处于Android.mk向Android.bp过渡的阶段ASAN的支持方式和Android10以后有明显差异。高版本上你可以在Android.bp里写sanitize: { address: true }但在Android9的Soong里这个字段虽然存在对部分模块类型支持并不完整尤其是涉及JNI、HAL和可执行文件时。更稳妥的做法是走LOCAL_SANITIZE : address这条老路或者用全局开关SANITIZE_TARGETaddress做整包构建。但整包ASAN在8155p上不现实——QNX侧不参与Android构建Android侧全量ASAN会导致镜像巨大、启动极慢Hypervisor分区根本放不下。所以实际落地一定是按模块开启只对怀疑有内存问题的native库或可执行文件做ASAN插桩。另一个坑是Android9默认的ASAN运行时库路径。libclang_rt.asan-aarch64-android.so需要和你的模块一起推到设备上并且要在init阶段通过LD_PRELOAD或者链接依赖的方式加载。如果只编译不部署运行时运行时会直接报ASAN: failed to load runtime日志里只有一行很容易被忽略。2.2 Android.mk模块开启ASAN的最小改动假设你有一个native可执行文件或共享库用Android.mk管理。最小改动是在模块定义里加三行# Android.mk 中为单个模块开启ASAN LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : my_memory_test LOCAL_SRC_FILES : main.cpp LOCAL_SANITIZE : address LOCAL_SANITIZE_DIAG : address # 确保ASAN运行时被打包 LOCAL_REQUIRED_MODULES : libclang_rt.asan-aarch64-android include $(BUILD_EXECUTABLE)LOCAL_SANITIZE : address告诉编译器插入ASAN插桩代码LOCAL_SANITIZE_DIAG : address让诊断信息更完整。LOCAL_REQUIRED_MODULES保证运行时库被编进system分区。注意如果你的模块是BUILD_SHARED_LIBRARY同样适用但需要确认依赖它的上层模块不会因为ASAN的符号冲突而崩溃。实际项目中我遇到过JNI库开ASAN后Java层调用直接闪退原因是ASAN运行时和Android Runtime的malloc调试钩子冲突解决办法是在BoardConfig.mk里加MALLOC_DEBUG相关配置或者只对独立可执行文件开ASANJNI库用LOCAL_SANITIZE的hwaddress替代——但Android9对HWASAN支持更弱所以多数时候还是回到独立进程验证。2.3 Android.bp模块开启ASAN的写法与限制Android.bp在Android9里对ASAN的支持分两种情况。对于cc_binary和cc_library可以写// Android.bp 中为cc_binary开启ASAN cc_binary { name: my_memory_test_bp, srcs: [main.cpp], sanitize: { address: true, diag: { address: true, }, }, required: [libclang_rt.asan-aarch64-android], }这段配置在Android9的Soong里能通过编译但有一个隐藏问题required字段在部分Android9分支上不会自动把运行时库加入system.img需要你在device.mk里手动加PRODUCT_PACKAGES libclang_rt.asan-aarch64-android。另外cc_library_shared开ASAN后如果被未开ASAN的模块依赖链接阶段会报undefined reference to __asan_init。这是因为ASAN插桩后的库需要运行时符号而未插桩的模块不会链接ASAN运行时。解决办法是让依赖链上的所有模块都开ASAN或者把ASAN库做成独立可执行文件通过dlopen加载。我一般会优先选独立可执行文件方案因为对系统其他部分影响最小。2.4 编译参数与运行时库的对应关系ASAN在Android9上依赖几个关键编译参数-fsanitizeaddress、-fno-omit-frame-pointer、-fsanitize-recoveraddress可选。-fno-omit-frame-pointer必须加否则堆栈回溯会缺帧定位精度大打折扣。-fsanitize-recoveraddress让ASAN在检测到错误后继续运行适合抓多个问题但默认建议关掉因为继续运行可能掩盖后续错误。运行时库方面64位用libclang_rt.asan-aarch64-android.so32位用libclang_rt.asan-arm-android.so。8155p的Android9通常是64位为主但如果有32位兼容进程两个都要推。推库的位置一般是/system/lib64/和/system/lib/权限644selinux上下文要设为u:object_r:system_lib_file:s0否则init加载时会拒绝。3. QNXAndroid双系统下ASAN的部署与触发3.1 跨系统内存共享场景下ASAN的局限性QNX和Android通过Hypervisor共享内存时Android侧的ASAN只能检测Android进程内部的地址空间问题。如果内存越界发生在共享内存区域且写坏的是QNX侧的数据ASAN在Android侧可能完全无感——因为那块内存在Android的地址空间里是合法映射ASAN不会拦截。这种情况下需要配合QNX侧的内存保护工具或者在共享内存边界加哨兵值guard page来辅助判断。实际项目中我遇到过Android侧音频服务写共享内存越界导致QNX仪表进程崩溃但Android侧ASAN日志干净。最后是在共享内存分配时前后各加4KB guard page才定位到越界写。所以ASAN不是万能药跨系统问题要结合边界防护。3.2 把ASAN可执行文件推上8155p并运行编译出带ASAN的可执行文件后部署步骤如下# 推ASAN运行时库和测试程序到设备 adb root adb remount adb push out/target/product/xxx/system/bin/my_memory_test /system/bin/ adb push prebuilts/clang/host/linux-x86/clang-xxx/lib64/libclang_rt.asan-aarch64-android.so /system/lib64/ adb shell chmod 644 /system/lib64/libclang_rt.asan-aarch64-android.so adb shell chmod 755 /system/bin/my_memory_test # 设置ASAN选项并运行 adb shell ASAN_OPTIONSdetect_leaks0:abort_on_error1:symbolize1 /system/bin/my_memory_testdetect_leaks0在Android9上建议关掉因为LeakSanitizer在Android的init进程环境下经常误报而且需要ptrace权限selinux默认不允许。abort_on_error1让ASAN检测到错误立即abort方便抓core dump或tombstone。symbolize1让ASAN尝试符号化堆栈但需要llvm-symbolizer在设备上通常没有所以实际符号化要在host侧用ndk-stack或llvm-symbolizer离线做。运行后如果ASAN报错日志会出现在logcat或stderr格式是ERROR: AddressSanitizer: heap-buffer-overflow on address...后面跟着调用栈。3.3 用wrap.sh在Android9上给JNI库开ASAN如果问题出在JNI库且不想重编整个APK可以用wrap.sh机制。在APK的lib/arm64-v8a/目录下放一个wrap.sh#!/system/bin/sh # wrap.sh 为JNI库加载ASAN运行时 HERE$(cd $(dirname $0) pwd) export LD_PRELOAD$HERE/libclang_rt.asan-aarch64-android.so export ASAN_OPTIONSdetect_leaks0:abort_on_error1 exec $同时把ASAN运行时库放到同一目录。wrap.sh需要可执行权限且APK要设android:debuggabletrue。这个方案在Android9上对zygote启动的进程有效但对init启动的native服务无效。另外LD_PRELOAD加载ASAN运行时后所有malloc/free都会被拦截性能下降明显只适合调试阶段。我一般会在验证完问题后立即移除wrap.sh避免影响正常功能。3.4 符号化堆栈从设备日志到代码行ASAN在设备上输出的堆栈通常是地址偏移没有行号。符号化需要把out/target/product/xxx/symbols/下的带符号文件拿到host侧用llvm-symbolizer处理# 在host侧符号化ASAN日志 llvm-symbolizer --objout/target/product/xxx/symbols/system/bin/my_memory_test asan_log.txt如果日志里是#0 0x1234 in my_func /path/to/file.cpp:42这种格式说明编译时带了-g且符号表没被strip。Android9的release构建默认strip所以开ASAN的模块要加LOCAL_STRIP_MODULE : falseAndroid.mk或strip: { none: true }Android.bp。否则符号化出来全是??。这个坑我踩过不止一次血泪经验是开ASAN的同时一定关strip否则等于白抓。4. 避坑与常见问题排查4.1 ASAN报错但堆栈全是问号现象ASAN日志里调用栈显示#0 0x0000 in ??没有任何函数名和行号。原因通常是编译时strip了符号或者llvm-symbolizer找不到对应的带符号文件。解决在模块定义里关掉stripAndroid.mk加LOCAL_STRIP_MODULE : falseAndroid.bp加strip: { none: true }。同时确认out/target/product/xxx/symbols/下有对应模块的未strip版本。如果还是不行检查编译时是否带了-gAndroid9默认-g在eng构建下开启userdebug下可能关闭需要手动加LOCAL_CFLAGS -g。4.2 开启ASAN后模块加载失败报undefined symbol现象dlopen加载ASAN库时失败logcat报undefined reference to __asan_init或__asan_report_load8。原因是被ASAN插桩的库依赖ASAN运行时符号但运行时库没有被正确加载。解决确保libclang_rt.asan-aarch64-android.so在/system/lib64/下且权限正确同时检查依赖链上所有模块是否都开了ASAN。如果只有部分模块开未开的模块调用开了ASAN的模块时符号解析会失败。最稳妥的做法是让ASAN模块独立成可执行文件不与其他模块共享链接。4.3 ASAN误报LeakSanitizer导致进程被杀现象进程启动后不久被init杀掉日志显示LeakSanitizer: detected memory leaks。原因Android9的init进程和部分系统服务在启动阶段有大量一次性分配LeakSanitizer会把这些当成泄漏。解决在ASAN_OPTIONS里加detect_leaks0。如果确实需要查泄漏改用LSAN_OPTIONSsuppressions...加抑制文件把系统库的分配排除掉。但Android9上LSAN对ptrace的依赖经常和selinux冲突所以多数时候直接关掉泄漏检测只查越界和释放后使用。4.4 双系统共享内存越界ASAN抓不到现象QNX侧进程崩溃Android侧ASAN无任何输出。原因越界写发生在共享内存区域该区域在Android地址空间内是合法映射ASAN不拦截。解决在共享内存分配时前后加guard page或者用mprotect把边界页设为PROT_READ写越界会触发SIGSEGV再配合debuggerd抓tombstone。另外可以在共享内存结构里加magic number定期校验发现被改写就报错。这个方案比ASAN更原始但在跨系统场景下更可靠。4.5 Android.bp的sanitize字段不生效现象在Android.bp里写了sanitize: { address: true }但编译出的二进制没有ASAN插桩运行时也不报ASAN错误。原因Android9的Soong对部分模块类型的sanitize支持不完整尤其是cc_library_shared和cc_binary在特定分支上会忽略该字段。解决改用LOCAL_SANITIZE的Android.mk写法或者用全局SANITIZE_TARGETaddress构建后从out/里取插桩后的二进制手动推设备。验证是否插桩成功可以用readelf -s看二进制里有没有__asan_init符号有就是插了没有就是没生效。5. 进阶用ASAN自定义分配器定位共享内存踩踏ASAN默认只拦截标准malloc/free和栈/全局变量对自定义分配器比如共享内存池无能为力。在8155p上很多内存问题出在自定义的共享内存分配器上。一个进阶做法是给自定义分配器加ASAN的poison/unpoison接口。ASAN提供了__asan_poison_memory_region和__asan_unpoison_memory_region可以在分配时把内存标记为可访问释放时标记为不可访问。这样越界写或释放后写就会触发ASAN报错。具体实现在你的共享内存分配器里alloc时调用__asan_unpoison_memory_region(ptr, size)free时调用__asan_poison_memory_region(ptr, size)。编译时需要链接ASAN运行时并且确保这些接口在Android9的ASAN运行时里存在。我实测过libclang_rt.asan-aarch64-android.so确实导出了这两个符号。但要注意poison的粒度是8字节所以size要按8字节对齐否则会有误报。另一个技巧是用ASAN的start_deactivated模式。在进程启动时先不激活ASAN等关键初始化完成后再通过__asan_init激活。这样可以避免ASAN在init阶段和系统库的冲突。Android9上可以通过ASAN_OPTIONSstart_deactivated1开启然后在代码里调用__asan_init()。这个模式对调试启动阶段的内存问题特别有用因为默认ASAN在init阶段就会拦截所有分配导致启动失败。验证ASAN是否真正生效可以写一个故意越界的小程序// asan_check.cpp 验证ASAN是否生效 #include cstring int main() { char *buf new char[10]; memset(buf, 0, 20); // 故意越界写 delete[] buf; return 0; }编译时加-fsanitizeaddress -fno-omit-frame-pointer -g推设备运行。如果ASAN生效会立刻报heap-buffer-overflow并给出堆栈。如果没有报错说明插桩或运行时加载有问题回到第2章检查编译参数和库路径。最后说一个我自己的习惯每次开ASAN之前先把模块的strip关掉、-g加上、运行时库路径确认一遍这三件事做完再编译。否则大概率是白抓一轮日志里全是问号后悔药都没得吃。ASAN在8155p的QNXAndroid9双系统上不是一键开关而是一套需要按模块、按场景调整的调试链路。把编译、部署、符号化、跨系统边界这几步走通它就能把最玄学的内存问题变成一行明确的代码位置。希望帮到你。本文还有配套的精品资源点击获取