资讯详情

车机双系统下用ASAN抓内存踩踏:从编译开关到符号还原的完整路径

📅 2026/10/19 3:59:06 | 华诺云谱 👁 阅读
车机双系统下用ASAN抓内存踩踏:从编译开关到符号还原的完整路径
简介这份资源面向在高通8155p平台上进行QNXAndroid9双系统开发的工程师聚焦于开启ASANAddressSanitizer以定位内存越界、泄漏等疑难问题的编译配置方法。包内同时提供Android.mk与Android.bp两套构建脚本示例覆盖应用层与vendor分区的库、可执行文件及ASAN运行时so的集成写法适合不熟悉构建规则或反复遭遇编译报错的开发者对照参考。资源共18个文件包含4个mk、4个bp、4个cpp源码、2个so动态库、2个头文件及2个mk备份压缩包约1.21MB结构紧凑便于快速定位所需模块。目前已有1995人学习下载。通过其中的构建脚本与源码示例读者可掌握ASAN在Android9上的使能方式、vendor分区适配要点及常见编译问题的排查思路减少反复试错成本提升内存问题定位效率。1. 车机双系统下用 ASAN 抓内存踩踏从编译开关到符号还原的完整路径在高通 8155p 这类座舱平台上QNX 管仪表和安全域Android 9 管娱乐域两边通过共享内存和 Hypervisor 通信。问题往往出在 Android 侧的 native 服务某个 HAL 或 vendor 进程跑着跑着就 SIGSEGVtombstone 里只有一行abort堆栈指向 libc 的 malloc/free看不出是谁先越界写坏了堆。这种堆内存踩踏靠 logcat 和 gdb 基本是玄学得把 AddressSanitizer 挂上去。这个项目就是一套在 8155p 的 Android 9 源码树里同时兼容 Android.mk 和 Android.bp 两套构建系统开启 ASAN 的实操方法适合正在做车机 native 层稳定性排查、又不想大改编译框架的开发者。它解决的不是ASAN 是什么而是在车机这种混合构建环境里怎么让 ASAN 真正编进去、跑起来、还能还原出符号。2. 架构与构建系统拆解ASAN 在 8155p Android 9 里怎么落地2.1 为什么车机平台开 ASAN 比手机麻烦手机上的 ASAN 基本是SANITIZE_TARGETaddress一把梭AOSP 原生支持。但 8155p 的 Android 9 源码树里混入了大量高通 vendor 模块这些模块有的还在用 Android.mk有的已经迁到 Android.bp还有一部分是 prebuilt 的 .so。ASAN 要求被检测的模块和它依赖的 libc、libclang_rt.asan 必须一起编一旦某个 vendor 模块是 prebuilt 且没带 ASAN链接阶段就会报undefined reference to __asan_init。另一个坑是 QNX 侧。ASAN 只能覆盖 Android 域QNX 进程的内存问题得用 QNX 自己的工具。所以做这个项目时边界要划清楚只对 Android 侧需要排查的 native 模块开 ASAN不要全局开否则整个系统镜像体积翻倍开机都成问题。常见做法是维护一个白名单只对目标模块及其依赖链开启。下面这张表是我在实际项目里用的模块分类策略模块类型构建文件是否开 ASAN原因目标 HAL 服务Android.bp是主要排查对象目标依赖的 native libAndroid.mk是需一起插桩高通 prebuilt vendor libprebuilt否无源码无法插桩系统 libc / liblogAOSP 原生是ASAN 运行时依赖QNX 侧进程QNX Makefile否不在 ASAN 覆盖范围2.2 Android.bp 里开 ASAN 的正确写法Android.bp 的 sanitize 配置比 Android.mk 干净但要注意sanitize块的位置和misc_undefined的配合。很多人只写sanitize: { address: true }结果链接时报一堆__asan_*未定义就是因为没把 ASAN 运行时库带进来。// Android.bp 中为目标 cc_library 开启 ASAN cc_library { name: libtarget_hal, srcs: [src/*.cpp], sanitize: { address: true, // 开启 AddressSanitizer 插桩 // 关键允许未定义符号否则 prebuilt 依赖会链接失败 misc_undefined: [address], }, shared_libs: [ liblog, libcutils, ], // ASAN 需要静态链接运行时避免运行时找不到符号 static_libs: [ libclang_rt.asan, ], }逻辑说明address: true让编译期插入 ASAN 检查代码misc_undefined: [address]是告诉链接器遇到 ASAN 相关未定义符号时不要直接报错留给运行时库解析static_libs里加libclang_rt.asan是把 ASAN 运行时静态链进模块这样即使系统里没有预装 ASAN 的 libc模块也能自己跑起来。参数说明libclang_rt.asan这个库名在不同 AOSP 版本里可能带架构后缀比如libclang_rt.asan-aarch64。8155p 是 arm64实际写的时候要确认prebuilts/clang/host/linux-x86/clang-*/lib64/clang/*/lib/linux/下的文件名别照抄。2.3 Android.mk 里开 ASAN 的兼容写法Android.mk 的写法更老派但车机项目里大量 vendor 模块还在用。核心是LOCAL_SANITIZE和LOCAL_SANITIZE_DIAG两个变量。# Android.mk 中为目标模块开启 ASAN LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : libtarget_hal_mk LOCAL_SRC_FILES : src/target_hal.cpp # 开启 ASAN 插桩 LOCAL_SANITIZE : address # 诊断模式遇到问题打印详细报告而不是直接 abort LOCAL_SANITIZE_DIAG : address # 关键把 ASAN 运行时库加进依赖 LOCAL_STATIC_LIBRARIES : libclang_rt.asan LOCAL_SHARED_LIBRARIES : liblog libcutils include $(BUILD_SHARED_LIBRARY)逻辑说明LOCAL_SANITIZE : address等价于 Android.bp 的sanitize.addressLOCAL_SANITIZE_DIAG是诊断模式区别在于遇到内存错误时diag 模式会打印完整报告再退出非 diag 模式可能直接 abort 导致报告丢失。车机调试阶段建议开 diag。参数说明LOCAL_SANITIZE还支持address之外的值比如hwaddress、thread但 8155p 的 Android 9 上 thread sanitizer 支持不完整别混用。另外LOCAL_SANITIZE只对当前模块生效依赖的静态库如果没开插桩会不完整所以依赖链上的模块要逐个确认。2.4 混合构建下的依赖链处理实际项目里最头疼的是 Android.bp 模块依赖 Android.mk 模块或者反过来。AOSP 的 soong 和 make 之间有转换层但 sanitize 属性不会自动传递。也就是说A 模块开了 ASAN它依赖的 B 模块没开B 模块里的内存错误 ASAN 抓不到。处理办法是手动梳理依赖链把需要排查的模块及其直接依赖都开上。可以用mmma的依赖输出或者soong_ui的--dump参数看依赖树。我一般会先只开目标模块跑一遍看 ASAN 报告里有没有指向未插桩模块的堆栈有的话再把那个模块加进白名单。提示不要试图给整个vendor/qcom目录开 ASANprebuilt 太多链接阶段会炸。白名单控制在 5 个模块以内排查完就关掉。3. 编译与运行实操从 lunch 到 ASAN 报告落盘3.1 环境准备与 lunch 选择8155p 的 Android 9 源码树通常带高通自己的 lunch target比如msmnile或qssi变体。开 ASAN 之前先确认当前 lunch 能正常编出 userdebug 版本因为 ASAN 需要 debug 符号和 root 权限。# 进入源码根目录初始化编译环境 source build/envsetup.sh # 选择 8155p 对应的 lunch target具体名字以项目为准 lunch qssi-userdebug # 确认 ASAN 运行时库存在 ls prebuilts/clang/host/linux-x86/clang-*/lib64/clang/*/lib/linux/ | grep asan逻辑说明source build/envsetup.sh加载编译命令lunch选定目标最后一步是确认 ASAN 运行时库的路径和文件名因为 Android.bp 和 Android.mk 里引用的库名必须和实际文件对得上。参数说明qssi-userdebug里的qssi是高通单系统镜像的缩写不同项目可能叫msmnile或带_64后缀。userdebug 是必须的user 版本没有 rootASAN 报告写不到文件里。3.2 单模块编译与镜像打包不要一上来就make -j全编先单模块验证 ASAN 有没有插进去。# 单模块编译确认 ASAN 插桩成功 mmma -j8 vendor/qcom/proprietary/target_hal # 检查生成的 .so 里有没有 ASAN 符号 readelf -s out/target/product/*/system/lib64/libtarget_hal.so | grep asan逻辑说明mmma是编译指定目录下的模块readelf -s看符号表如果输出里有__asan_init、__asan_report_load8这类符号说明插桩成功。没有的话回去检查 Android.bp 或 Android.mk 的 sanitize 配置。参数说明-j8是并行编译线程数按机器配置调。readelf的路径里*要替换成实际的 product 名比如msmnile。确认插桩成功后再打包 system.img 和 vendor.img# 打包镜像ASAN 模块会进 system 或 vendor 分区 make -j8 systemimage vendorimage # 刷入设备fastboot 或高通专用刷机工具 fastboot flash system out/target/product/*/system.img fastboot flash vendor out/target/product/*/vendor.img逻辑说明ASAN 模块编出来后需要重新打包镜像并刷入设备才能生效。8155p 的开发板通常用 fastboot 或高通 QFIL 工具刷机。参数说明systemimage和vendorimage是 make 的目标名不同项目可能叫system.img直接作为目标。刷机前确认设备进了 fastboot 模式。3.3 运行目标进程与 ASAN 报告落盘ASAN 默认把报告打到 stderr车机上 stderr 可能被 logd 吞掉。所以要在启动目标进程时设置环境变量让报告写到文件。# 在设备 shell 里设置 ASAN 选项 adb root adb shell # 设置 ASAN 报告输出到文件并开启详细模式 export ASAN_OPTIONSlog_path/data/local/tmp/asan_log:verbosity1:detect_leaks0 # 启动目标进程或重启对应服务 stop target_hal_service start target_hal_service # 查看 ASAN 报告 ls /data/local/tmp/asan_log.* cat /data/local/tmp/asan_log.*逻辑说明log_path指定报告前缀ASAN 会自动加 pid 后缀verbosity1输出更多细节detect_leaks0关掉泄漏检测因为车机进程常驻泄漏检测会误报。stop/start是 Android init 的命令用来重启服务。参数说明/data/local/tmp/是可写目录别写/sdcard权限不对。如果进程是 init 拉起的环境变量要写进对应的.rc文件用setenv指令。3.4 符号还原把地址变成函数名ASAN 报告里给的是相对地址和模块名比如libtarget_hal.so0x1234。要还原成函数名和行号需要llvm-symbolizer和带符号的 .so。# 用 llvm-symbolizer 还原地址 prebuilts/clang/host/linux-x86/clang-*/bin/llvm-symbolizer \ --objout/target/product/*/symbols/system/lib64/libtarget_hal.so \ 0x1234逻辑说明--obj指定带符号的 .so路径在symbols/目录下不是system/目录下的 stripped 版本。地址从 ASAN 报告里抄。参数说明llvm-symbolizer的路径要跟编译用的 clang 版本一致否则符号解析可能错位。如果报告里是pc 0x1234这种直接把这个地址传给 symbolizer。注意刷机用的 .so 是 stripped 的符号在out/target/product/*/symbols/下。别拿system/lib64/下的去解析解析不出来。4. 避坑与常见问题排查4.1 链接报 undefined reference to __asan_init现象编译目标模块时链接阶段报一堆__asan_init、__asan_report_load8未定义。原因ASAN 运行时库没链进来或者链接顺序不对。Android.bp 里static_libs没加libclang_rt.asan或者 Android.mk 里LOCAL_STATIC_LIBRARIES漏了。解决确认libclang_rt.asan在static_libs或LOCAL_STATIC_LIBRARIES里并且放在依赖列表靠后位置。如果还报错检查misc_undefined或LOCAL_SANITIZE_DIAG有没有配。4.2 刷机后进程起不来报 cannot locate symbol现象刷入 ASAN 镜像后目标进程启动失败logcat 报cannot locate symbol __asan_init。原因ASAN 运行时库没有随模块一起打包进镜像或者模块是动态链接 ASAN 但系统里没有对应的 .so。解决把libclang_rt.asan改成静态链接即放在static_libs里。如果必须动态链接确认libclang_rt.asan.so被 push 到/system/lib64/并加了PRODUCT_PACKAGES。4.3 ASAN 报告里堆栈全是问号现象报告能出来但堆栈里函数名全是??只有地址。原因解析用的 .so 是 stripped 版本或者llvm-symbolizer版本和编译版本不一致。解决用symbols/目录下的 .so 解析确认llvm-symbolizer路径和编译 clang 一致。如果还不行检查编译时有没有加-gASAN 默认带调试信息但有些项目会全局 strip。4.4 开了 ASAN 后系统卡顿或开机失败现象全编 ASAN 后系统开机卡在开机动画或者开机后极度卡顿。原因ASAN 插桩让代码体积和内存占用翻倍车机内存本来就紧张全局开 ASAN 扛不住。解决只对白名单模块开 ASAN其他模块保持原样。排查完把 sanitize 配置关掉重新编正式版本。别在 ASAN 版本上做性能测试。4.5 QNX 侧进程报内存错误但 ASAN 抓不到现象QNX 侧进程崩溃但 Android 侧 ASAN 没有任何报告。原因ASAN 只覆盖 Android 域QNX 是独立内核ASAN 插桩进不去。解决QNX 侧用 QNX 自己的内存检测工具比如pidin看内存映射或者 QNX Momentics 的调试器。两边边界要清楚别指望 ASAN 跨域抓问题。5. 二次开发与改造把 ASAN 排查流程固化成一键脚本这套方法跑通之后最值得做的是把它固化下来下次排查不用再翻笔记。我一般会在项目根目录放一个asan_debug.sh把编译、刷机、抓报告、符号还原串起来。#!/bin/bash # asan_debug.sh - 一键 ASAN 排查脚本 # 用法: ./asan_debug.sh module_path process_name MODULE_PATH$1 PROCESS_NAME$2 PRODUCT$(ls out/target/product/ | head -1) # 1. 编译目标模块 echo [1/4] 编译模块 $MODULE_PATH mmma -j8 $MODULE_PATH || exit 1 # 2. 检查 ASAN 插桩 SO_FILE$(find out/target/product/$PRODUCT/system/lib64 -name *.so -newer $MODULE_PATH 2/dev/null | head -1) if readelf -s $SO_FILE | grep -q __asan_init; then echo [2/4] ASAN 插桩确认: $SO_FILE else echo [2/4] 未检测到 ASAN 插桩检查 sanitize 配置 exit 1 fi # 3. 打包并刷入 echo [3/4] 打包镜像 make -j8 systemimage || exit 1 adb reboot bootloader fastboot flash system out/target/product/$PRODUCT/system.img fastboot reboot # 4. 等待开机设置 ASAN 选项并重启目标进程 echo [4/4] 等待设备启动 adb wait-for-device sleep 30 adb root adb shell export ASAN_OPTIONSlog_path/data/local/tmp/asan_log:verbosity1:detect_leaks0; stop $PROCESS_NAME; start $PROCESS_NAME sleep 10 adb pull /data/local/tmp/asan_log.* ./asan_report/ 2/dev/null echo 报告已拉取到 ./asan_report/逻辑说明脚本把四个步骤串起来mmma编模块readelf验证插桩make systemimage打包fastboot刷机最后设置 ASAN 选项重启进程并拉报告。PRODUCT变量自动取 product 名避免手写。参数说明$1是模块路径比如vendor/qcom/proprietary/target_hal$2是进程名对应 init .rc 里的 service 名。sleep 30是等开机机器慢的话要加长。adb pull拉报告时如果目录不存在会报错可以先mkdir -p asan_report。改造方向有几个。一是把符号还原也加进脚本用llvm-symbolizer批量解析报告里的地址输出带函数名的堆栈。二是把白名单配置抽成独立文件编译时用sed或python脚本自动改 Android.bp 和 Android.mk避免手动改漏。三是和 CI 结合 nightly build 出一个 ASAN 版本跑自动化测试提前抓内存问题。有个血泪经验ASAN 报告里的地址是运行时地址每次重启进程都会变所以抓完报告要立刻解析别等第二天再弄符号对不上。从那以后我每次开 ASAN 排查都强制走一遍「编译→验证插桩→刷机→抓报告→立即解析」的流程中间不隔夜。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑