资讯动态

车机双系统下用ASAN抓内存踩踏:8155p上Android.mk与Android.bp配置实战

发布时间:2026/10/15 1:57:18 来源:尧图企业网站定制
简介这份资源面向在高通8155p平台上进行QNX与Android 9双系统开发的工程师聚焦于开启ASANAddressSanitizer以定位内存越界、泄漏等疑难问题的编译配置方法。资源包共18个文件包含4个Android.mk、4个Android.bp、4个cpp源码、2个so动态库、2个头文件及2个mk文件覆盖libAsanDebug、asan_debugd及vnd变体等模块完整展示了ASAN在Android.mk与Android.bp两种构建体系下的写法示例压缩包约1.21MB。已有1994人学习下载。对于不熟悉Android.mk与Android.bp编写、常遇编译报错或无法使能ASAN的开发者可参考其中的配置模板与模块结构快速排查构建问题理解ASAN库的链接与导出头文件方式从而在车机项目中高效定位内存缺陷。1. 车机双系统下用 ASAN 抓内存踩踏为什么 8155p 上这事值得折腾在高通 8155p 这类座舱平台上QNX 管仪表和安全域Android 9 管娱乐域两边通过 Hypervisor 共享内存和 IPC 通信。这种架构下最让人头疼的不是功能跑不通而是跑着跑着随机崩溃、花屏、音频断流logcat 里只留一句 SIGSEGV堆栈还指不到具体位置。这类问题十有八九是内存踩踏——越界写、释放后使用、栈溢出常规排查手段基本抓瞎。ASANAddressSanitizer就是干这个的它在编译期插桩运行时给每块内存加红区一旦越界或 UAF 立刻报出精确的调用栈。但 8155p 的 Android 9 源码树里模块构建方式混着 Android.mk 和 Android.bp 两套ASAN 的开启方式完全不同踩错一步就是编译报错或者运行时无输出。这篇把我实际在 8155p 上跑通 ASAN 的配置、参数和翻车点拆开讲适合正在定位 native 层内存问题的系统工程师和 BSP 开发者。2. ASAN 在 Android 9 上的工作原理与 8155p 适配前提2.1 ASAN 插桩到底做了什么ASAN 的核心是编译期插桩加运行时影子内存。编译器在每次内存访问前后插入检查代码影子内存用 1/8 的地址空间映射记录每 8 字节内存的可访问状态。当代码写越界时检查代码发现目标地址对应的影子字节标记为“不可访问”立即调用__asan_report_error输出调用栈并终止进程。在 Android 9 上ASAN 的运行时库是libclang_rt.asan-aarch64-android.so它需要和你的可执行文件或共享库一起推送到设备。和桌面 Linux 不同Android 的 linker 对 ASAN 库的加载顺序有要求必须让 ASAN 运行时最先初始化否则影子内存没建好就执行代码直接段错误。8155p 是 aarch64 架构Android 9 的 NDK 版本对应 clang 8.0 左右这个版本的 ASAN 对 8155p 的 Kryo 核心支持是完整的但有一个前提内核必须开启CONFIG_ARM64_VA_BITS48否则影子内存映射会失败。常见做法是先adb shell cat /proc/cpuinfo确认 CPU 架构再zcat /proc/config.gz | grep VA_BITS看内核配置。如果 VA_BITS 是 39ASAN 跑不起来得先让 BSP 团队改内核配置重新编译 boot.img。2.2 为什么 8155p 上要区分 Android.mk 和 Android.bpAndroid 9 正处于构建系统从 Make 向 Soong 迁移的中间阶段。高通 8155p 的 vendor 代码里老的高通私有模块大量用 Android.mk而 AOSP 原生模块和部分新驱动用 Android.bp。这两套构建系统对 ASAN 的支持方式不一样Android.mk 用LOCAL_SANITIZE : address开启链接时自动带上 ASAN 运行时。Android.bp 用sanitize: { address: true }但需要模块类型支持sanitize属性且cc_binary和cc_library的行为有差异。更麻烦的是如果你的模块依赖了预编译的 vendor 库比如高通的libqti*系列这些库没有 ASAN 插桩ASAN 运行时会在加载时报警告甚至直接 abort。我一般会在Android.mk里加LOCAL_SANITIZE_DIAG : address先跑诊断模式确认没有误报后再切到完整模式。2.3 开启 ASAN 前的环境检查清单在改任何构建文件之前先确认这几项否则后面全是无用功检查项命令期望结果CPU 架构adb shell getprop ro.product.cpu.abiarm64-v8a内核 VA_BITSadb shell zcat /proc/config.gz | grep CONFIG_ARM64_VA_BITS48ASAN 库存在ls $OUT/system/lib64/libclang_rt.asan-aarch64-android.so文件存在SELinux 模式adb shell getenforcePermissive调试阶段可用内存adb shell cat /proc/meminfo | grep MemAvailable 2GBSELinux 这一项特别关键。ASAN 运行时需要在/data/local/tmp下创建影子内存的映射文件Enforcing 模式下会被 avc denied 拦住。调试阶段先adb shell setenforce 0定位完问题再恢复。生产版本绝对不能带 ASAN性能开销在 2 到 3 倍8155p 的 CPU 扛不住。3. Android.mk 模块开启 ASAN从编译到推送的完整链路3.1 修改 Android.mk 的四个关键变量假设你要排查的模块叫libmemtest源码在vendor/qcom/proprietary/memtest/下原本的 Android.mk 长这样LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : libmemtest LOCAL_SRC_FILES : memtest.cpp LOCAL_SHARED_LIBRARIES : liblog libcutils LOCAL_MODULE_TAGS : optional include $(BUILD_SHARED_LIBRARY)开启 ASAN 只需要加一行但这一行的位置和配套变量有讲究LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : libmemtest LOCAL_SRC_FILES : memtest.cpp LOCAL_SHARED_LIBRARIES : liblog libcutils LOCAL_MODULE_TAGS : optional # 开启 ASAN 插桩 LOCAL_SANITIZE : address # 诊断模式只报告不终止适合首次排查 LOCAL_SANITIZE_DIAG : address # 关闭 LTOASAN 和 LTO 同时开启会有符号丢失问题 LOCAL_LTO : false # 保留调试符号否则堆栈全是地址 LOCAL_STRIP_MODULE : false include $(BUILD_SHARED_LIBRARY)LOCAL_SANITIZE : address是核心开关它让编译器在编译这个模块时插入 ASAN 检查代码并在链接时把 ASAN 运行时库加进依赖。LOCAL_SANITIZE_DIAG是诊断模式区别在于遇到内存错误时只打印报告不调用abort()适合第一次跑的时候观察有没有误报。LOCAL_LTO : false必须加Android 9 的 clang 8.0 在 LTO 模式下 ASAN 插桩会丢符号报错信息里全是??。LOCAL_STRIP_MODULE : false保留符号表不然堆栈里只有地址没有函数名等于白跑。3.2 编译命令与产物验证改完 Android.mk 后不要直接make整个工程8155p 的全编要几个小时。用模块级编译# 在 Android 源码根目录执行 source build/envsetup.sh lunch aosp_8155p-userdebug # 单模块编译-j 后面跟 CPU 核心数 mmm vendor/qcom/proprietary/memtest/ -j16 # 检查产物是否带 ASAN 符号 readelf -d out/target/product/8155p/system/lib64/libmemtest.so | grep asan如果readelf输出里有libclang_rt.asan-aarch64-android.so的 NEEDED 条目说明链接成功。如果没有检查LOCAL_SANITIZE是否写在了include $(BUILD_SHARED_LIBRARY)之前——写在之后不生效这是最常见的翻车点。3.3 推送与运行时的环境变量编译产物在out/target/product/8155p/system/lib64/下推送到设备adb root adb remount adb push out/target/product/8155p/system/lib64/libmemtest.so /system/lib64/ adb push out/target/product/8155p/system/lib64/libclang_rt.asan-aarch64-android.so /system/lib64/ adb shell chmod 644 /system/lib64/libmemtest.so adb shell chmod 644 /system/lib64/libclang_rt.asan-aarch64-android.so adb rebootASAN 运行时库必须一起推否则模块加载时会报library libclang_rt.asan-aarch64-android.so not found。重启后设置 ASAN 选项adb shell setenforce 0 adb shell setprop wrap.libmemtest ASAN_OPTIONSdetect_leaks0:abort_on_error1:log_path/data/local/tmp/asanwrap.前缀是 Android 特有的属性机制格式是wrap.进程名系统在启动该进程时会自动注入后面的环境变量。detect_leaks0关掉泄漏检测因为 Android 的 bionic libc 在进程退出时的清理逻辑会触发大量误报。abort_on_error1让 ASAN 发现错误后立即终止方便抓现场。log_path指定报告输出路径不设的话默认打到 logcat容易和系统日志混在一起。3.4 触发与报告解读跑你的测试用例触发内存问题后报告会写到/data/local/tmp/asan.pidadb shell ls /data/local/tmp/asan.* adb pull /data/local/tmp/asan.1234 ./报告里关键看三块ERROR 类型heap-buffer-overflow / use-after-free / stack-buffer-overflow、WRITE/READ 方向、以及下面的调用栈。调用栈里带libmemtest.so的帧就是你的代码位置用addr2line还原行号aarch64-linux-android-addr2line -e libmemtest.so -f -C 0x1234-f输出函数名-C做 C 符号 demangle。如果地址还原出来是??说明编译时没保留调试信息回去检查LOCAL_STRIP_MODULE和LOCAL_CFLAGS里有没有-g。4. Android.bp 模块开启 ASANSoong 构建下的差异与坑4.1 Android.bp 的 sanitize 属性写法Android.bp 里开启 ASAN 的语法和 Make 完全不同。假设模块定义如下cc_library_shared { name: libmemtest_bp, srcs: [memtest_bp.cpp], shared_libs: [liblog, libcutils], cflags: [-g, -O1], }开启 ASAN 加sanitize块cc_library_shared { name: libmemtest_bp, srcs: [memtest_bp.cpp], shared_libs: [liblog, libcutils], cflags: [-g, -O1], sanitize: { address: true, diag: { address: true, }, never: false, }, strip: { none: true, }, }sanitize.address: true对应 Make 的LOCAL_SANITIZE : address。sanitize.diag.address: true对应诊断模式。strip.none: true保留符号。注意sanitize块里不能同时写address: true和never: trueSoong 会报配置冲突。4.2 cc_binary 与 cc_library 的行为差异这是 Android.bp 下最容易踩的坑cc_binary开启 ASAN 后链接器会自动把 ASAN 运行时静态链进去生成的可执行文件体积会膨胀到几十 MB。而cc_library_shared是动态链接 ASAN 运行时需要手动推送libclang_rt.asan-aarch64-android.so。如果你排查的是可执行文件比如某个 native service用cc_binary加sanitize.address: true后直接推 bin 文件就行不用管 ASAN 库。但如果是共享库必须确认设备上有 ASAN 运行时。我一般会在cc_library_shared的shared_libs里显式加上libclang_rt.asan-aarch64-android虽然 Soong 在 sanitize 开启时会自动加但显式写出来能避免某些分支版本上的链接遗漏。4.3 混合构建树下的依赖处理8155p 的源码树里你的 Android.bp 模块很可能依赖了 Android.mk 编译出来的库。这种情况下ASAN 插桩只对你的模块生效依赖库没有插桩。如果内存错误发生在依赖库内部ASAN 抓不到。常见做法是先用 ASAN 确认错误不在你的模块里。如果错误在依赖库把依赖库也改成 ASAN 编译或者用LOCAL_SANITIZE_DIAG在 Make 侧开启诊断。对于高通预编译的 vendor 库无法重新编译只能在 ASAN_OPTIONS 里加intercept_tls_get_addr0绕过 TLS 相关的误报。还有一个隐蔽的坑Android.bp 模块的sanitize属性在android_app类型上不生效APK 里的 native 库要用jni_libs配合sanitize块写法是android_app { name: MemTestApp, jni_libs: [libmemtest_bp], sanitize: { address: true, }, }4.4 验证 ASAN 是否真正生效改完 Android.bp 后用soong_ui编译# 在源码根目录 source build/envsetup.sh lunch aosp_8155p-userdebug m libmemtest_bp -j16 # 检查产物 readelf -d out/target/product/8155p/system/lib64/libmemtest_bp.so | grep -i asan nm -D out/target/product/8155p/system/lib64/libmemtest_bp.so | grep __asannm输出里应该有__asan_init、__asan_report_load8这类符号。如果没有说明 sanitize 没生效检查 Android.bp 的语法是否正确——Soong 对 JSON 格式很严格少一个逗号就静默忽略整个 sanitize 块不报错。这是最阴的坑编译通过但 ASAN 没开跑半天没输出还以为问题不复现。5. 避坑与排查8155p 上 ASAN 不生效的五个血泪经验5.1 编译通过但运行时无 ASAN 输出现象模块编译成功readelf也能看到 ASAN 符号但跑测试用例时进程正常退出没有任何 ASAN 报告。原因最常见的是wrap.属性没生效。Android 9 的wrap.机制要求进程由 init 启动如果你是用adb shell手动执行二进制wrap.属性不会被应用。另一个原因是 SELinux 拦截了 ASAN 运行时的 mmap 调用avc log 里有denied { mmap }但没注意看。解决手动执行时直接带环境变量ASAN_OPTIONS... ./libmemtest不要依赖wrap.。SELinux 用adb shell dmesg | grep avc确认调试阶段setenforce 0。5.2 ASAN 报告堆栈全是 ??现象报告里有 ERROR 类型和地址但调用栈每一帧都是??无法定位代码行。原因编译时 strip 了符号或者LOCAL_CFLAGS里没有-g。Android 默认的 release 配置会 strip 模块LOCAL_STRIP_MODULE : false只控制最终产物中间目标文件可能已经被 strip。解决在LOCAL_CFLAGS里显式加-g -fno-omit-frame-pointerAndroid.bp 里在cflags加同样内容。-fno-omit-frame-pointer保证栈回溯能走通不然 ASAN 的 unwind 会断在某个帧。5.3 依赖预编译库导致 ASAN 启动即 abort现象进程一启动就 abort报告里写ASAN: failed to intercept或者unexpected memory mapping。原因高通预编译的 vendor 库如libqti-perfd-client.so没有 ASAN 插桩且可能用了自定义的内存分配器和 ASAN 的影子内存冲突。解决在ASAN_OPTIONS里加intercept_tls_get_addr0:handle_segv0绕过 TLS 和段错误拦截。如果还不行用LD_PRELOAD把 ASAN 运行时排到最前面但 Android 的 linker 对LD_PRELOAD支持有限更可靠的做法是把有问题的依赖库用dlopen延迟加载避开 ASAN 初始化阶段。5.4 内存泄漏检测误报刷屏现象detect_leaks1时进程退出前打印几百条泄漏报告全是 bionic libc 和系统库的分配。原因Android 的 bionic libc 在进程退出时不会清理所有全局分配ASAN 的泄漏检测把这些当成泄漏。这是已知问题不是你的代码问题。解决调试内存踩踏时直接detect_leaks0。如果确实要查泄漏用LSAN_OPTIONSsuppressions...加载抑制文件把系统库的分配路径加进去。抑制文件格式是leak:libc.so这样一行一条。5.5 性能骤降导致测试用例超时现象开启 ASAN 后原本 10 秒跑完的用例跑了 2 分钟还没结束被测试框架判定超时。原因ASAN 的插桩让每次内存访问多了几条指令整体性能开销 2 到 3 倍。如果用例里有大量循环内存操作放大效应更明显。解决调大测试框架的超时阈值或者用ASAN_OPTIONSquarantine_size_mb16减小隔离区大小默认 256MB内存紧张时影响性能。另外-O1优化比-O0在 ASAN 下性能好很多编译时别用-O0。6. 进阶技巧用 ASAN 配合 HWASAN 做 8155p 上的内存问题二分定位ASAN 在 8155p 上跑通后下一步是提高定位效率。纯 ASAN 只能告诉你“哪一行访问了非法地址”但如果是多线程竞争导致的内存踩踏ASAN 的报告可能指向受害者而不是肇事者。这时候可以配合 HWASANHardware-assisted AddressSanitizer做交叉验证。HWASAN 利用 ARMv8 的 Top Byte Ignore 特性把指针的高 8 位作为标签每次内存访问比对指针标签和内存标签。它的优势是能抓到 ASAN 漏掉的跨线程踩踏而且性能开销比 ASAN 低约 1.5 倍。8155p 的 Kryo 核心支持 TBI硬件条件满足。开启 HWASAN 的方式和 ASAN 类似Android.mk 里把LOCAL_SANITIZE : address改成LOCAL_SANITIZE : hwaddressAndroid.bp 里sanitize.hwaddress: true。但注意 HWASAN 和 ASAN 不能同时开启需要分别编译两个版本做对比。我的实际流程是先用 ASAN 跑一遍拿到初步报告。如果报告指向的代码逻辑上不可能越界怀疑是竞争踩踏换 HWASAN 版本重跑。HWASAN 报告里会显示“标签不匹配”同时给出分配时的调用栈和访问时的调用栈两个栈一对比肇事者就出来了。还有一个技巧是用 ASAN 的start_deactivated1选项做延迟开启。有些内存问题只在特定初始化序列后才出现一开始就开 ASAN 会干扰启动流程。start_deactivated1让 ASAN 先不插桩等你在代码里调用__asan_activate()后再开启这样能精确控制检测窗口。# 延迟开启的 ASAN_OPTIONS ASAN_OPTIONSstart_deactivated1:abort_on_error1:log_path/data/local/tmp/asan然后在你的代码里在怀疑出问题的逻辑之前调用extern void __asan_activate(void); __asan_activate();这个函数在 ASAN 运行时里导出链接时不会报错。调用之后ASAN 开始插桩检查之前的代码不受影响。最后说一个习惯每次定位完内存问题我会把 ASAN 的编译配置和 ASAN_OPTIONS 存成一个脚本放在模块目录下。下次再查类似问题直接source asan_env.sh就能复现环境不用重新翻构建文件。8155p 的编译一次二十分钟起步省下来的时间够多跑好几轮测试。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑