当前位置: 首页 > news >正文

部署 AddressSanitizer(ASan)定位内存泄漏、内存越界

 AddressSanitizer(ASan)

ASan(AddressSanitizer,地址消毒器)是一个内存错误检测工具,主要用于帮程序员:

检测内存越界访问(比如访问数组边界外的数据)

检测使用已释放的内存(Use-after-free)

检测堆、栈、全局变量的内存错误

发现内存泄漏(部分版本支持)

它是编译器提供的一种运行时检测机制,通过插桩程序,在程序运行时监控内存访问行为,及时发现和定位内存安全问题,帮助开发者快速修复难以排查的 bug。

但是NDK r21e 不支持内存泄漏检查:

(base) wyl9738@arc-PowerEdge-R740xd:/data/standard_sdk/toolchains/NDK21E/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/9.0.9/lib/linux$ strings ./libclang_rt.asan-aarch64-android.so | grep detect_leaks

detect_leaks

%s: detect_leaks is not supported on this platform.

Invoke leak checking in an atexit handler. Has no effect if detect_leaks=false, or if __lsan_do_leak_check() is called before the handler has a chance to run.

升级方案建议

  1. 下载 NDK r25c。
  2. 修改你的 CMake 工程,改成使用新的 toolchain(路径替换原来的 r21e)。
  3. 确保编译命令中保留:

部署 AddressSanitizer(ASan)流程(以Android 平台为例)

1、部署Asan  

cmake文件中添加:

project(***)

set(CMAKE_CXX_STANDARD 11)

# 原本的 warning 设置也要保留原有值

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++11 -Wpedantic -Wextra -Wshadow -Wundef -Wmaybe-uninitialized \

    -Wno-unused-parameter -Wno-missing-field-initializers -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default \

    -Wreturn-type -Wmultichar -Wformat -Wformat-security -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body -Wstack-usage=2048 \

    -Wtype-limits -Wsizeof-pointer-memaccess -Wpointer-arith -Werror=return-type")

set(CMAKE_SYSROOT "/data/standard_sdk/toolchains/NDK21E/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/sysroot")

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath-link=${CMAKE_SYSROOT}/usr/lib/aarch64-linux-android/29")

 # 启用 AddressSanitizer    添加 ASan 相关 flags(必须放在最后)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,leak -fno-omit-frame-pointer")

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,leak -fno-omit-frame-pointer")

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")

# 添加库搜索路径

link_directories("${CMAKE_SYSROOT}/usr/lib/aarch64-linux-android/29")

如果找不到动态库,可以在交叉编译工具路径下搜索

安卓:find ./ -name "libclang_rt.asan-aarch64-android.so"

linux:find ./ -name "libasan.so*"

standard_sdk/toolchains/arm-ca53-linux-gnueabihf-8.4$ find ./ -name "libasan.so*"

./arm-ca53-linux-gnueabihf/lib/libasan.so.5

./arm-ca53-linux-gnueabihf/lib/libasan.so

./arm-ca53-linux-gnueabihf/lib/libasan.so.5.0.0

2compile.sh中添加  -DANDROID_TOOLCHAIN=clang  \

#!/bin/sh

ANDROID_NDK=/data/standard_sdk/toolchains/NDK21E/android-ndk-r21e

cd ../../src_code/

rm -rf build

mkdir -p build

cd build

cmake ..  -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \

-DANDROID_ABI="arm64-v8a" \

-DANDROID_NDK=$ANDROID_NDK \

-DANDROID_PLATFORM=android-29 \

-DCMAKE_BUILD_TYPE=Debug  \

-DANDROID_TOOLCHAIN=clang  \

-DMODULE="$MODULE"   \

-DLEVEL="$LEVEL"

make -j$(nproc)

3、编译通过后检查是否生效

方法1:查看编译时的链接信息

如果你能拿到编译好的可执行文件(比如 Api_Test_Tool),可以在编译环境用 readelf 或 nm 等工具检查:

readelf -d Api_Test_Tool | grep asan

方法2:用 strings 查找符号

在编译好的二进制中用 strings 搜索 __asan_ 符号:

strings Api_Test_Tool | grep __asan_

4、asan动态库拷贝到设备上, export ASAN_OPTIONS=detect_leaks=1

命令查找asan库:find /data/standard_sdk/ -name "libclang_rt.asan-aarch64-android.so"

输出:

/data/standard_sdk/toolchains/NDK21E/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/9.0.9/lib/linux/libclang_rt.asan-aarch64-android.so

把这个动态库放到设备上和别的so一起

 (查找所有依赖的动态库 readelf -d Api_Test_Tool 或 ldd (如果支持) 查看依赖库)

linux平台:

standard_sdk/toolchains/arm-ca53-linux-gnueabihf-8.4$ find ./ -name "libasan.so*"

./arm-ca53-linux-gnueabihf/lib/libasan.so.5

./arm-ca53-linux-gnueabihf/lib/libasan.so

./arm-ca53-linux-gnueabihf/lib/libasan.so.5.0.0

5在设备上执行程序,看到发生问题的偏移地址

 

6、用addr2line 查看是哪一行代码

命令:addr2line -e Api_Test_Tool(可执行程序) 0x89354(偏移地址)

7、Asan输出解析

🚨 第一部分:检测到的错误类型和位置

==11188==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xfec86eb0705a

  • ==11188==:当前进程 ID 是 11188,ASan 对这个进程做了监控。
  • ERROR: AddressSanitizer::说明是由 ASan 发现的问题。
  • heap-buffer-overflow:表示在堆上分配的内存发生了越界访问,比如 malloc(10) 之后访问 buf[10]。
  • on address 0xfec86eb0705a:问题发生的内存地址。

🧭 第二部分:发生错误时的调用栈(Backtrace)

at pc 0xc73027ab9358 bp 0xffffe6f33750 sp 0xffffe6f33748

  • pc:程序计数器(Program Counter)当前在哪条指令。
  • bp:栈基址(Base Pointer)。
  • sp:栈顶指针(Stack Pointer)。
  • 都是当前线程在执行越界访问指令时的寄存器值。

🛠 第三部分:哪一行代码越界写了内存?

WRITE of size 1 at 0xfec86eb0705a thread T0

  • 说明写入了 1 字节的数据,地址是 0xfec86eb0705a
  • thread T0:在主线程 T0 中发生。

🧱 第四部分:调用堆栈(stack trace)

你看到的是每一帧函数调用,最底层是 main,往上是调用链:

#0  0xc73027ab9354  (/data/local/tmp/DMS/API_TEST/Api_Test_Tool_0805+0x89354)
#1  0xc73027ac9850  ...
...
#10 0xfee776275798  (/apex/com.android.runtime/lib64/bionic/libc.so+0x7d798)

 

#0  0xc73027ab9354  (/data/local/tmp/DMS/API_TEST/Api_Test_Tool_0805+0x89354)

0xc73027ab9354 是程序运行时的 虚拟地址

0x89354 是可执行文件中的 偏移地址,也就是函数或语句的位置

🔍 第五部分:越界写的区域在哪里?

0xfec86eb0705a is located 0 bytes to the right of 10-byte region [0xfec86eb07050,0xfec86eb0705a)

  • 说明你 malloc(10) 了一个缓冲区,地址从 0x7050 到 0x705a(不含)。
  • 而你写入了 0x705a,也就是第 11 个字节,正好越界 1 字节

💡 第六部分:ASan 追踪到了谁分配的这个内存?

allocated by thread T0 here:
#0  0xfee773af4e24  (libclang_rt.asan-aarch64-android.so+0x9fe24)
#1  0xc73027ab92f0  (/data/local/tmp/DMS/API_TEST/Api_Test_Tool_0805+0x892f0)
...

  • ASan 自动记录了这块内存的分配栈。
  • 第一帧是 libclang_rt.asan 内部的 malloc。
  • 第二帧 onward 是你程序分配内存的代码位置。你可以用 addr2line 查出具体源代码位置

🧪 第七部分:Shadow Bytes(阴影字节)

ruby

复制编辑

Shadow bytes around the buggy address:
0x1fe90dd60db0: fa fa 00 fa fa fa 04 fa ...
=>0x1fe90dd60e00: fa fa 00 fa fa fa 00 00 fa fa 00[02]fa fa fa fa

  • ASan 在内存两侧加了 红区(Redzone),保护内存边界。
  • 如果你写入了红区,它会立即报警。
  • 00 代表合法使用,fa 是红区,02 代表只允许访问前 2 字节。
  • 你的访问命中了非法的 redzone,触发错误。

 

mtrace和ASan 核心对比

特性

ASan

mtrace

✅ 能检测内存泄漏

是(部分版本支持 LeakSanitizer)

✅ 检测越界访问(heap/stack)

✅ 检测 use-after-free

✅ 检测未初始化内存访问

是(部分支持)

⚙️ 实现方式

编译期插桩 + 运行时监控

glibc 提供的 malloc/free hook

⚠️ 使用成本

需要重新编译(加 -fsanitize=address)

只需 glibc 和设置环境变量

📊 性能开销

中等偏高(约 2~3 倍内存+CPU)

很低

🛠️ 运行平台

多平台(x86、arm、Android)

仅限使用 GNU glibc 的 Linux

📋 报告输出

实时终端输出,或日志

生成 log 文件,要用 mtrace 命令查看

📦 嵌入式适配性

✅ 可交叉编译,适合嵌入式调试

❌ glibc 限制,不适用于 musl/uClibc/Android

 

 

ASan原理

通过 编译器插桩(compiler instrumentation)+ runtime 库

 实现以下检测:

功能

原理说明

越界访问

为每个对象分配前后 redzone 并填充特殊值,运行时访问触发检查

use-after-free

free 后填充为不可访问 shadow 值,一旦访问即报错

内存泄漏 (via LSan)

程序退出前做 GC-like 遍历,发现无法访问的 heap block 就认为是泄漏

双重释放

内部记录每次释放,第二次访问将触发错误

Shadow Memory 机制:

ASan 使用一段shadow memory区域,按 8:1 比例映射应用内存,并标记每个内存块的状态(有效、redzone、已释放等)。

 

 ASAN_OPTIONS 环境变量参数

参数名

说明

示例设置

abort_on_error

出错时是否调用 abort 终止程序

abort_on_error=0(不终止)

log_path

将 ASan 日志写入指定文件

log_path=asan.log

detect_stack_use_after_return

检测函数返回后栈空间使用

detect_stack_use_after_return=1

detect_leaks

是否开启 LeakSanitizer 检测

detect_leaks=1

allocator_may_return_null

分配失败时是否返回 nullptr 而非崩溃

allocator_may_return_null=1

fast_unwind_on_malloc

malloc/free 调用栈回溯是否使用快速模式

fast_unwind_on_malloc=0

suppressions

指定抑制文件,忽略特定错误

suppressions=suppress.txt

verbosity

日志详细程度(0-3)

verbosity=2

max_malloc_fill_size

分配内存时填充最大字节数

max_malloc_fill_size=0

 

设置环境变量

?终止,日志写文件,开启额外检测

export ASAN_OPTIONS="abort_on_error=1:log_path=./asan.log:detect_stack_use_after_return=1:disable_coredump=0"

ASAN_OPTIONS="abort_on_error=0:log_path=./asan.log:detect_stack_use_after_return=1" ./your_program

确认环境变量生效

env | grep ASAN_OPTIONS

 

关于 AddressSanitizer (ASan) 对内存泄漏检测(LeakSanitizer,简称 LSan)的支持情况:

  1. LLVM/Clang 支持情况
    • LeakSanitizer 是 ASan 的一个子集,主要负责内存泄漏检测。
    • LeakSanitizer 在 Clang 3.8 及以后版本中开始得到较好的支持。
    • Android NDK 从 r15 版本开始集成了 ASan 和 LeakSanitizer 支持,但不同 NDK 版本的支持情况可能会有所不同。
  2. GCC 支持情况
    • GCC 在较新的版本(9.0 及以后)也支持 ASan 和 LeakSanitizer,但 Android NDK 默认使用的 GCC 较旧,可能不支持 LeakSanitizer。
  3. Android NDK 版本支持
    • Android NDK r16 及以后的版本,ASan 与 LeakSanitizer 支持更完善。
    • Android NDK r21 及以后版本对 LeakSanitizer 支持较好,能检测内存泄漏。
  4. 注意
    • LeakSanitizer 并不是所有 ASan 版本默认启用,有些版本需要手动开启。
    • 需要确保链接时带上 -fsanitize=leak 或 -fsanitize=address(新版 ASan 会自动包含 LeakSanitizer)。
    • 在某些交叉编译环境下,LeakSanitizer 可能无法正常工作,特别是早期的 Android NDK。
http://www.dtcms.com/a/319883.html

相关文章:

  • Java+Vue合力开发固定资产条码管理系统,移动端+后台管理,集成资产录入、条码打印、实时盘点等功能,助力高效管理,附全量源码
  • 【保姆级喂饭教程】python基于mysql-connector-python的数据库操作通用封装类(连接池版)
  • SPI TFT全彩屏幕驱动开发及调试
  • Sentinel原理之责任链详解
  • imx6ull-驱动开发篇12——GPIO子系统驱动LED
  • C++高频知识点(十五)
  • Qwen-Image开源模型实战
  • 【Floyd】Shortest Routes II
  • 显卡服务器的作用主要是什么?-哈尔滨云前沿
  • 使用内网穿透工具1分钟上线本地网站至公网可访问,局域网电脑变为服务器
  • Mysql数据仓库备份脚本
  • 2.7 (拓展)非父子通信(事件总线和provide-inject)详解
  • 2025 年华数杯全国大学生数学建模竞赛B题 网络切片无线资源管理方案设计--完整成品、思路、代码、模型结果分享,仅供学习~
  • java 生成pdf导出
  • 【tip】font-family的设置可能导致的文字奇怪展示
  • 《P3275 [SCOI2011] 糖果》
  • 运营商面向政企客户推出的DICT项目
  • 【ee类保研面试】数学类---概率论
  • 5G专网提高产业生产力
  • 别墅泳池设计综述:从理念创新到技术实现的系统性研究
  • 基于 PyTorch 从零实现 Transformer 模型:从核心组件到训练推理全流程
  • Java 大视界 -- Java 大数据在智能安防门禁系统中的人员行为分析与异常事件预警(385)
  • nvm安装,nvm管理node版本
  • Java设计模式总结
  • 【设计模式精解】什么是代理模式?彻底理解静态代理和动态代理
  • Vue自定义流程图式菜单解决方案
  • [激光原理与应用-171]:测量仪器 - 能量型 - 激光能量计(单脉冲能量测量)
  • DicomObjects COM 8.XX
  • VUE+SPRINGBOOT从0-1打造前后端-前后台系统-文章列表
  • [TIP 2025] 轻量级光谱注意力LSA,极致优化,减少99.8%参数,提升性能!