Android NDK开发详解调试和减少内存错误之Address Sanitizer

Android NDK开发详解调试和减少内存错误之Address Sanitizer


注意:本文档将介绍如何在 Address Sanitizer 下运行使用 NDK 构建的 Android 应用。如需了解如何对 Android 平台组件使用 Address Sanitizer,请参阅 AOSP 文档。
已废弃:自 2023 年起,ASan 不再受支持。建议改用 HWASan。HWASan 可在搭载 Android 14(API 级别 34)或更高版本的 ARM64 设备上使用;或通过刷写特殊系统映像在搭载 Android 10(API 级别 29)的 Pixel 设备上使用。ASan 仍然可以使用,但可能存在 bug。
重要提示:Asan 是用于调试和减少内存错误的众多工具之一。如需简要了解所有工具,请参阅调试和减少内存错误。

从 API 级别 27 (Android O MR 1) 开始,Android NDK 可支持 Address Sanitizer(也称为 ASan)。

ASan 是一种基于编译器的快速检测工具,用于检测原生代码中的内存错误。ASan 可以检测以下问题:

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

ASan 的 CPU 开销约为 2 倍,代码大小开销在 50% 到 2 倍之间,并且内存开销很大(具体取决于您的分配模式,但约为 2 倍)。

示例应用

示例应用展示了如何为 asan 配置 build 变体。

构建

如需使用 Address Sanitizer 构建应用的原生 (JNI) 代码,请添加以下代码:
ndk-build
在 Application.mk 中:


APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address

对于 Android.mk 中的每个模块:

LOCAL_ARM_MODE := arm

CMake

在模块的 build.gradle 中:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                // Can also use system or none as ANDROID_STL.
                arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
            }
        }
    }
}

对于 CMakeLists.txt 中的每个目标:


target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)

注意:在使用 libc++_static 时,ASan 目前不兼容 C++ 异常处理。使用 libc++_shared 或不使用异常处理的应用或者不受影响,或者有相应解决方法。如需了解详情,请参阅问题 988。

运行

从 Android O MR1(API 级别 27)开始,应用可以提供可封装或替换应用进程的封装 Shell 脚本。这样一来,可调试的应用就可对其应用启动过程进行自定义,以便在生产设备上使用 ASan。
注意:以下说明将介绍如何在 Android Studio 项目中使用 ASan。对于非 Android Studio 项目,请参阅封装 Shell 脚本文档。

将 android:debuggable 添加到应用清单中。
在应用的 build.gradle 文件中将 useLegacyPackaging 设置为 true。如需了解详情,请参阅封装 Shell 脚本指南。
将 ASan 运行时库添加到应用模块的 jniLibs 中。

将包含以下内容的 wrap.sh 文件添加到 src/main/resources/lib 目录中的每个目录。
    #!/system/bin/sh
    HERE="$(cd "$(dirname "$0")" && pwd)"
    export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
    ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so)
    if [ -f "$HERE/libc++_shared.so" ]; then
        # Workaround for https://github.com/android-ndk/ndk/issues/988.
        export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
    else
        export LD_PRELOAD="$ASAN_LIB"
    fi
    "$@"

注意:NDK 在此处提供了适用于 ASan、推荐使用的 wrap.sh 文件。

假设您项目的应用模块的名称为 app,最终的目录结构应包含以下内容:

<project root>
└── app
    └── src
        └── main
            ├── jniLibs
            │   ├── arm64-v8a
            │   │   └── libclang_rt.asan-aarch64-android.so
            │   ├── armeabi-v7a
            │   │   └── libclang_rt.asan-arm-android.so
            │   ├── x86
            │   │   └── libclang_rt.asan-i686-android.so
            │   └── x86_64
            │       └── libclang_rt.asan-x86_64-android.so
            └── resources
                └── lib
                    ├── arm64-v8a
                    │   └── wrap.sh
                    ├── armeabi-v7a
                    │   └── wrap.sh
                    ├── x86
                    │   └── wrap.sh
                    └── x86_64
                        └── wrap.sh

堆栈轨迹

Address Sanitizer 需要在每次调用 malloc/realloc/free 时都展开堆栈。这里介绍两个选项:

基于帧指针的“快速”展开程序。按照构建部分中的说明执行操作时使用的就是此展开程序。

“慢速”CFI 展开程序。在此模式下,ASan 会使用 _Unwind_Backtrace。它只需要使用 -funwind-tables(通常默认处于启用状态)。
注意:“慢速”展开程序速度缓慢(速度差距达 10 倍或更多,具体取决于您调用 malloc/free 的频率)。

快速展开程序是 malloc/realloc/free 的默认选项。慢速展开程序是严重异常所对应堆栈轨迹的默认选项。通过将 fast_unwind_on_malloc=0 添加到 wrap.sh 的 ASAN_OPTIONS 变量中,即可为所有堆栈轨迹启用慢速展开程序。

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2023-08-09。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五一编程

程序之路有我与你同行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值