AddressSanitizer

简介: AddressSanitizer

PerFace

AddressSanitizer (ASan) 是一种基于编译器的快速检测工具,用于检测原生代码中的内存错误。

ASan 可以检测以下问题:

  • 堆栈和堆缓冲区上溢/下溢
  • 释放之后的堆使用情况
  • 超出范围的堆栈使用情况
  • 重复释放/错误释放

ASan 可在 32 位和 64 位 ARM 以及 x86 和 x86-64 上运行。ASan 的 CPU 开销约为 2 倍,代码大小开销在一半到 2 倍之间,并且内存开销很大(具体取决于您的分配模式,但约为 2 倍)。

Android 10 和 AArch64 上的 AOSP master 分支支持硬件加速 ASan (HWASan),这是一种 RAM 开销更小、检测到的错误范围更大的类似工具。除了 ASan 可以检测到的错误之外,HWASan 还可以检测返回之后的堆栈使用情况。

HWASan 具有类似的 CPU 和代码大小开销,但 RAM 开销要小得多 (15%)。HWASan 具有不确定性。只有 256 个可能的标记值,因此忽略任何错误的概率为 0.4%。ASan 对检测溢出规定了有限大小的红色区域,并对检测释放后使用情况规定了有限容量隔离区,而 HWAsan 没有这些规定,因此溢出大小或多久之前内存解除分配对 HWAsan 而言并不重要。这使得 HWASan 优于 ASan。您可以详细了解 HWAsan 的设计或 Android 上的 HWASan 的使用。

除了堆溢出外,ASan 还能检测堆栈/全局溢出,并能以最低的内存开销实现很高的速度

硬件辅助的 AddressSanitizer (HWASan) 是一款类似于 AddressSanitizer 的内存错误检测工具。与 ASan 相比,HWASan 使用的内存少得多,因而更适合用于整个系统的清理。HWASan 仅适用于 Android 10 及更高版本,且只能用于 AArch64 硬件。

虽然 HWASan 主要适用于 C/C++ 代码,但也有助于调试导致用于实现 Java 接口的 C/C++ 代码崩溃的 Java 代码。HWASan 在这种情况下很有用,因为它能捕获到发生的内存错误,并直接将您带到导致问题发生的代码。

使用 ASan 清理各个可执行文件

将 LOCAL_SANITIZE:=address 或 sanitize: { address: true } 添加到可执行文件的构建规则中。您可以搜索现有示例的代码或查找其他可用的排错程序。

检测到错误时,ASan 会向标准输出和 logcat 发送一份详细报告,然后让进程崩溃。

使用 ASan 清理共享库

根据 ASan 的运行原理,只有通过 ASan 构建的可执行文件可以使用通过 ASan 构建的库。

注意:在运行时,如果将 ASan 库加载到错误的进程中,系统将显示以 _asan 或 _sanitizer 开头的消息,提示您有无法解析的符号。

如需清理多个可执行文件中使用的共享库,其中部分可执行文件并非使用 ASan 构建的,您需要该库的两个副本。为此,建议您针对相关模块向 Android.mk 中添加以下命令:

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

这样一来,系统会将库放到 /system/lib/asan 中而非 /system/lib 中。然后,使用以下方法运行您的可执行文件:

LD_LIBRARY_PATH=/system/lib/asan

对于系统守护程序,将以下命令添加到 /init.rc 或 /init.d e v i c e devicedevice.rc 的相应部分。

setenv LD_LIBRARY_PATH /system/lib/asan

警告:LOCAL_MODULE_RELATIVE_PATH 设置会将您的库移至 /system/lib/asan,这意味着,如果从头开始重写和重新构建,将会导致 /system/lib 中缺少该库,并且生成的映像可能无法启动。很遗憾,这是当前构建系统的一个限制。请不要执行 clobber 命令;而是使用 make -j $N 和 adb sync。

通过读取 /proc/$PID/maps,验证进程使用的库是否来自 /system/lib/asan(如果此库存在)。如果不是,您可能需要停用 SELinux:

adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID
# if it is a system service, or may be adb shell stop; adb shell start.

更出色的堆栈轨迹

ASan 使用基于帧指针的快速拆卷器 (unwinder),为程序中的每个内存分配和解除分配事件记录堆栈轨迹。Android 的大部分组件都未使用帧指针。因此,您通常只会获得一到两个有意义的帧。如需解决此问题,请使用 ASan(推荐)或以下选项重新构建库:

LOCAL_CFLAGS:=-fno-omit-frame-pointer
LOCAL_ARM_MODE:=arm

或者,在进程环境中设置 ASAN_OPTIONS=fast_unwind_on_malloc=0。后者可能对 CPU 要求极高,具体取决于负载情况。

符号化

最初,ASan 报告中包含对二进制文件和共享库中的偏移量的引用。您可以通过以下两种方法获取源文件和行信息:

  • 确保 /system/bin 中有 llvm-symbolizer 二进制文件。llvm-symbolizer 根据 third_party/llvm/tools/llvm-symbolizer 中的源代码构建而成。
  • 通过 external/compiler-rt/lib/asan/scripts/symbolize.py 脚本过滤报告。
    由于可以使用主机上的符号化库,因此第二种方法可以提供更多数据(即 file:line 位置)。

在应用中使用 ASan

ASan 无法检查 Java 代码,但可以检测 JNI 库中的错误。为此,您需要使用 ASan 构建可执行文件(在此情况下是 /system/bin/app_process(32|64))。这将在设备上的所有应用中同时启用 ASan,因而负载非常大,但 2 GB RAM 的设备应该能够从容应对。

将 LOCAL_SANITIZE:=address 添加到 frameworks/base/cmds/app_process 中的 app_process 构建规则中。暂时忽略同一个文件中的 app_process__asan 目标(如果在您阅读本文时这个目标仍在其中)。

修改相应 system/core/rootdir/init.zygote(32|64).rc 文件的 service zygote 部分,将以下代码行添加到包含 class main 的缩进行代码块中,且缩进量相同:

setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
    setenv ASAN_OPTIONS allow_user_segv_handler=true

构建,然后依次执行以下命令:adb sync、fastboot flash boot、reboot。

使用 wrap 属性

上一部分中的方法将 ASan 放到系统的每个应用中(实际上是放到 Zygote 进程的每个子项中)。您可以只通过 ASan 运行一个或少数几个应用,从而节省一些内存开销,但是应用启动速度会变慢。

为实现这一目标,您可以通过 wrap. 属性启动应用。下面是在 ASan 下运行 Gmail 应用的示例:

adb root
adb shell setenforce 0  # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"

在此情况下,asanwrapper 会将 /system/bin/app_process 重写至使用 ASan 构建的 /system/bin/asan/app_process。此外,它还会在动态库搜索路径的开头添加 /system/lib/asan。这样一来,通过 asanwrapper 运行应用时,系统会优先使用 /system/lib/asan 中进行 ASan 插桩的库,而非 /system/lib 中的常规库。

如果发现错误,应用会崩溃,且系统会将报告输出到日志中。

SANITIZE_TARGET

Android 7.0 及更高版本支持使用 ASan 一次性构建整个 Android 平台。(如果您要构建的版本高于 Android 9,那么 HWASan 是更好的选择。)

请在同一构建树中运行以下命令。

make -j42
SANITIZE_TARGET=address make -j42

在此模式下,userdata.img 中包含其他库,必须也刷写到设备上。请使用以下命令行:

fastboot flash userdata && fastboot flashall

这样将构建两组共享库:/system/lib 中的常规库(第一次 make 调用)和 /data/asan/lib 中进行 ASan 插桩的库(第二次 make 调用)。第二次构建的可执行文件会覆盖第一次构建的可执行文件。通过使用 PT_INTERP 中的 /system/bin/linker_asan,进行 ASan 插桩的可执行文件会获得一个不同的库搜索路径,该路径中的 /system/lib 前面添加了 /data/asan/lib。

$SANITIZE_TARGET 的值变更时,构建系统会重写中间对象目录。这样一来,系统便会强制重新构建所有目标,同时保留 /system/lib 下已安装的二进制文件。

有些目标无法使用 ASan 构建:

  • 静态关联的可执行文件
  • LOCAL_CLANG:=false 目标
  • 不会针对 SANITIZE_TARGET=address 进行 ASan 操作的 LOCAL_SANITIZE:=false 目标
    在 SANITIZE_TARGET build 中,系统会跳过此类可执行文件,且会将第一次 make 调用中构建的版本留在 /system/bin 中。

此类库未使用 ASan 进行构建,但它们可以包含来自其依赖的静态库的 ASan 代码。

目录
相关文章
|
6月前
|
存储 NoSQL 算法
从一个crash问题展开,探索gcc编译优化细节
问题分析的过程也正是技术成长之路,本文以一个gcc编译优化引发的crash为切入点,逐步展开对编译器优化细节的探索之路,在分析过程中打开了新世界的大门……
|
6月前
|
机器学习/深度学习 人工智能 自然语言处理
AIGC盛行,带你轻松调用开发
本篇文章基于java和阿里云的通义千问大模型手把手带你使用AIGC开发,实现文本对话和图像分析。
416 2
|
6月前
|
监控 芯片
芯片测试:WAT、CP、FT
芯片测试:WAT、CP、FT
228 0
|
6月前
|
存储 程序员 Linux
从软硬件交互的角度去看中断的一生
从软硬件交互的角度去看中断的一生
137 0
|
6月前
|
存储 计算机视觉
10位和16位YUV视频格式
10位和16位YUV视频格式
150 0
|
6月前
|
存储 编解码 Java
视频渲染的推荐8位YUV格式
视频渲染的推荐8位YUV格式
188 0
|
6月前
|
存储 缓存 安全
内核地址清理器(KASAN)
内核地址清理器(KASAN)
330 0
|
6月前
|
存储 算法 Linux
一起聊聊内核中的线程:操作函数、进程状态、task_struct、举个例子、
一起聊聊内核中的线程:操作函数、进程状态、task_struct、举个例子、
228 0
|
6月前
|
安全 IDE NoSQL
RISC-V处理器的第一个可信执行环境:MultiZone Security
RISC-V处理器的第一个可信执行环境:MultiZone Security
234 0
|
6月前
|
算法 Android开发 芯片
FPMM(四):基于FPGA的原型设计能为我们做些什么?
FPMM(四):基于FPGA的原型设计能为我们做些什么?
89 0