1. ASAN基础概念与工作原理
AddressSanitizer(简称ASAN)是Google开发的一种内存错误检测工具,它通过编译时插桩和运行时库的组合,能够检测多种内存安全问题。ASAN目前已经集成到LLVM/Clang和GCC编译器中,成为C/C++开发者排查内存问题的利器。
ASAN的核心检测能力包括:
- 堆栈缓冲区溢出
- 全局缓冲区溢出
- 释放后使用(use-after-free)
- 返回后使用(use-after-return)
- 双重释放(double-free)
- 内存泄漏(memory leaks)
1.1 ASAN的实现原理
ASAN通过在编译时对内存访问操作插入检查代码,并维护一个"影子内存"(shadow memory)来记录内存状态。具体实现上:
-
内存映射:ASAN将虚拟地址空间分为两部分:
- 主内存(应用程序实际使用的内存)
- 影子内存(每8字节应用程序内存对应1字节影子内存)
-
插桩机制:编译器会在每次内存访问前插入检查代码,例如:
cpp复制// 原始代码 *ptr = 42; // 插桩后代码 if (IsPoisoned(ptr)) { ReportError(ptr, kAccessSize, kIsWrite); } *ptr = 42; -
运行时库:ASAN运行时库会:
- 管理内存分配/释放
- 维护影子内存状态
- 生成错误报告
1.2 ASAN的性能开销
ASAN会带来一定的性能开销,主要包括:
- 内存开销:额外占用约2倍内存(原始内存+影子内存)
- CPU开销:通常会使程序运行速度降低2-5倍
- 二进制大小:增加约1.5-2倍
尽管有这些开销,ASAN仍然是调试阶段最有效的内存问题检测工具之一,因为它能捕获许多传统调试方法难以发现的问题。
2. 在Android平台启用ASAN
在Android系统中启用ASAN需要针对不同层次的组件进行配置。下面详细介绍在展锐平台和其他Android设备上启用ASAN的具体步骤。
2.1 在Android模块中启用ASAN
对于单个模块,可以通过修改构建文件来启用ASAN:
-
在Android.bp中启用:
python复制cc_binary { name: "my_target", sanitize: { address: true, }, srcs: ["src/*.cpp"], } -
在Android.mk中启用:
makefile复制LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SANITIZE := address LOCAL_MODULE := my_module LOCAL_SRC_FILES := my_source.cpp include $(BUILD_SHARED_LIBRARY)
2.2 展锐平台特殊配置
根据提供的资料,展锐平台需要额外的配置:
-
相机服务配置:
python复制# /vendor/sprd/modules/libcamera/aidl_service/aidl_default/Android.bp cc_library { name: "libcameraservice", sanitize: { address: true, }, // 其他配置... } -
禁用看门狗abort:
cpp复制// platform/frameworks/av/services/camera/libcameraservice/CameraServiceWatchdog.cpp void CameraServiceWatchdog::onTimeout() { // 注释掉abort()调用 // abort(); } -
内存池检测启用:
makefile复制# device/sprd/vnd_mpool/module/vendor/camera/mosc/xxx/xxx.mk TARGET_BOARD_CAMERA_ASAN_MEM_DETECT := true
2.3 全系统启用ASAN
要全局启用ASAN,可以在编译时添加环境变量:
bash复制SANITIZE_TARGET=address m -j64
或者在device.mk中添加:
makefile复制# 全局启用ASAN
PRODUCT_SANITIZE_MEMORY := address
3. ASAN错误分析与符号化
当ASAN检测到内存错误时,会生成包含内存地址的错误报告。这些报告需要经过符号化才能转换为有意义的源代码位置信息。
3.1 ASAN错误报告结构
典型的ASAN错误报告包含以下部分:
code复制==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000010 at pc 0x7f8b1c2a3d40 bp 0x7fff5c1b9b40 sp 0x7fff5c1b9b30
READ of size 4 at 0x602000000010 thread T0
#0 0x7f8b1c2a3d40 in my_function /path/to/source.cpp:45:10
#1 0x7f8b1c2a3e20 in main /path/to/main.cpp:10:5
3.2 符号化过程详解
符号化是将内存地址转换为源代码位置的过程,涉及以下步骤:
-
收集必要文件:
- 带调试符号的可执行文件/库
- ASAN日志文件
- 源代码路径映射
-
使用symbolize.py脚本:
bash复制
python external/compiler-rt/lib/asan/scripts/symbolize.py \ --binary=out/target/product/xxx/symbols/system/bin/myapp \ --demangle \ --strip-path-prefix=/home/user/src \ < asan_log.txt > symbolized_log.txt -
关键参数说明:
--binary: 指定带符号的可执行文件路径--demangle: 还原C++名称修饰--strip-path-prefix: 移除源代码路径前缀
3.3 自动化符号化流程
在Android构建系统中,符号化通常自动完成:
-
构建时符号化:
- 构建系统会为ASAN-enabled的二进制生成符号文件
- 符号文件通常位于
out/target/product/xxx/symbols/目录
-
运行时符号化:
- 通过
logcat捕获ASAN错误 - 使用
development/scripts/stack工具自动符号化
- 通过
4. 实战案例:相机服务内存问题排查
让我们通过一个实际案例来演示如何使用ASAN排查Android相机服务中的内存问题。
4.1 问题现象
相机服务在特定场景下崩溃,logcat中显示:
code复制E AddressSanitizer: heap-buffer-overflow on address 0x612000000010
4.2 排查步骤
-
启用ASAN:
按照前面章节的配置,为相机服务启用ASAN并重新编译。 -
复现问题:
bash复制adb shell setprop wrap.com.android.camera '"LIBC_DEBUG_MALLOC_OPTIONS=backtrace logwrapper"' adb shell am start -n com.android.camera/.Camera -
收集日志:
bash复制adb logcat -c adb logcat | grep -A 50 "AddressSanitizer" > asan_log.txt -
符号化分析:
bash复制
python external/compiler-rt/lib/asan/scripts/symbolize.py \ --binary=out/target/product/xxx/symbols/system/lib64/libcameraservice.so \ < asan_log.txt > symbolized_log.txt
4.3 问题定位与修复
符号化后的日志显示:
code复制==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x612000000010
READ of size 4 at 0x612000000010 thread T0
#0 0x7f8b1c2a3d40 in CameraService::Client::copyParameters
frameworks/av/services/camera/libcameraservice/CameraService.cpp:1234
#1 0x7f8b1c2a3e20 in CameraService::Client::setParameters
frameworks/av/services/camera/libcameraservice/CameraService.cpp:567
分析发现是拷贝相机参数时未检查缓冲区大小,修复方法是添加边界检查:
cpp复制status_t CameraService::Client::copyParameters(const char* params) {
size_t paramSize = strlen(params);
if (paramSize >= MAX_PARAM_SIZE) { // 添加边界检查
return BAD_VALUE;
}
strncpy(mParameters, params, MAX_PARAM_SIZE);
return OK;
}
5. ASAN高级用法与技巧
5.1 抑制已知问题
对于暂时无法修复的已知问题,可以创建抑制文件:
text复制# asan_suppressions.txt
leak:libLeakyLibrary.so
然后通过环境变量加载:
bash复制export ASAN_OPTIONS="suppressions=/path/to/asan_suppressions.txt"
5.2 自定义ASAN选项
通过ASAN_OPTIONS环境变量可以调整ASAN行为:
bash复制export ASAN_OPTIONS="detect_leaks=1:halt_on_error=0:log_path=/data/asan.log"
常用选项包括:
detect_leaks=1:启用内存泄漏检测halt_on_error=0:发现错误后继续运行alloc_dealloc_mismatch=1:检测分配/释放不匹配detect_stack_use_after_return=1:检测返回后使用栈变量
5.3 与其他工具结合使用
-
与GDB/LLDB调试器结合:
bash复制
gdb --args /path/to/asan_enabled_binary (gdb) run -
与Valgrind对比使用:
- ASAN:速度快,检测类型有限
- Valgrind:速度慢,检测更全面
-
与fuzzing工具结合:
bash复制
afl-fuzz -i input_dir -o output_dir -- /path/to/asan_enabled_binary @@
6. 常见问题与解决方案
6.1 ASAN未生效的可能原因
-
编译器不支持:
- 确保使用Clang或GCC 4.8+版本
- 检查编译器是否包含ASAN运行时
-
构建系统未正确配置:
- 确认
LOCAL_SANITIZE := address已设置 - 检查是否被后续构建规则覆盖
- 确认
-
运行时环境问题:
- 确保设备上有ASAN运行时库
- 检查环境变量是否被清除
6.2 符号化失败的解决方法
-
确保使用带调试符号的二进制:
bash复制
file out/target/product/xxx/symbols/system/lib/libcameraservice.so -
检查路径映射是否正确:
- 使用
--strip-path-prefix参数匹配构建机器路径 - 对于Android系统组件,可能需要保留
/buildbot/前缀
- 使用
-
尝试手动符号化:
bash复制
llvm-symbolizer --obj=libcameraservice.so 0x7f8b1c2a3d40
6.3 性能优化建议
-
针对性启用ASAN:
- 只对怀疑有问题的模块启用ASAN
- 使用
SANITIZE_TARGET=address而非全局启用
-
调整检测级别:
bash复制export ASAN_OPTIONS="detect_stack_use_after_return=0:check_initialization_order=0" -
使用ASAN的LSAN模式:
bash复制export ASAN_OPTIONS="detect_leaks=1"
7. ASAN在持续集成中的应用
将ASAN集成到CI/CD流程中可以提前捕获内存问题。以下是典型实现方案:
7.1 Jenkins流水线配置
groovy复制pipeline {
agent any
stages {
stage('Build with ASAN') {
steps {
sh 'SANITIZE_TARGET=address m -j64'
}
}
stage('Run Tests') {
steps {
sh 'adb shell setprop wrap.com.android.camera "logwrapper"'
sh 'adb shell am instrument -w com.android.camera.tests'
sh 'adb logcat -d | grep -A 50 "AddressSanitizer" > asan_log.txt'
}
}
stage('Analyze') {
steps {
script {
def hasErrors = sh(script: 'grep -q "ERROR: AddressSanitizer" asan_log.txt', returnStatus: true)
if (hasErrors == 0) {
sh 'python symbolize.py < asan_log.txt > symbolized_log.txt'
archiveArtifacts artifacts: 'symbolized_log.txt'
error("ASAN errors detected")
}
}
}
}
}
}
7.2 关键实践要点
-
基线管理:
- 维护已知问题的抑制文件
- 设置合理的错误阈值
-
资源优化:
- 使用高内存CI机器(ASAN需要更多内存)
- 并行运行测试但要控制并发数
-
结果分析:
- 自动分类ASAN错误
- 与问题跟踪系统集成
在实际项目中,我们通过CI集成的ASAN检测发现了多个潜在的内存安全问题,包括相机服务中的资源泄漏和媒体播放器中的缓冲区溢出。这些问题的早期发现显著提高了系统稳定性。