Android NDK Address Sanitizer

文章目录

  • 构建
  • 运行
  • 堆栈轨迹
  • 二进制测试

此文章是基于官方文档 Address Sanitizer的基础上做了一些扩展说明。

从 API 级别 27 (Android O MR 1) 开始,Android NDK 可支持 Address Sanitizer(也称为 ASan)。为啥从27开始呢?因为wrap.sh 仅适用于 API 级别 27 及更高级别。
ASan 是一种基于编译器的快速检测工具,用于检测原生代码中的内存错误。ASan 可以检测以下问题:

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

ASan 可在 32 位和 64 位 ARM 以及 x86 和 x86-64 上运行。ASan 的 CPU 开销约为 2 倍,代码大小开销在 50% 到 2 倍之间,并且内存开销很大(具体取决于您的分配模式,但约为 2 倍)。
对于 64 位 ARM,强烈建议使用HWAddress Sanitizer。

构建

如需使用 Address Sanitizer 构建应用的原生 (JNI) 代码,请执行以下操作:

# 在 Application.mk 中:
APP_PLATFORM  := android-17
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

如果APP_PLATFORM版本小于17,那么编译的时候就会出现如下错误:

/Users/stone/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0.300080/lib/linux/libclang_rt.asan-arm-android.so: error: undefined reference to '__vsnprintf_chk', version 'LIBC'
clang++: error: linker command failed with exit code 1 (use -v to see invocation)

在链接选项中添加-fsanitize=address(属于llvm的特性),那么它在链接的时候会到llvm的库目录寻找这个库,库的位置在如下位置:

./toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0/lib/linux/libclang_rt.asan-arm-android.so
./toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0/lib/linux/libclang_rt.asan-mips64-android.so
./toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0/lib/linux/libclang_rt.asan-i686-android.so
./toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0/lib/linux/libclang_rt.asan-x86_64-android.so
./toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0/lib/linux/libclang_rt.asan-aarch64-android.so
./toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0/lib/linux/libclang_rt.asan-mips-android.so

查看编译出来的库依赖关系:readelf -d libs/armeabi-v7a/santest,可以看到它确实依赖了libclang_rt.asan-arm-android.so

Dynamic section at offset 0xe98 contains 34 entries:
  Tag        Type                         Name/Value
 0x00000003 (PLTGOT)                     0x1fe4
 0x00000002 (PLTRELSZ)                   32 (bytes)
 0x00000017 (JMPREL)                     0x4a4
 0x00000014 (PLTREL)                     REL
 0x00000011 (REL)                        0x474
 0x00000012 (RELSZ)                      48 (bytes)
 0x00000013 (RELENT)                     8 (bytes)
 0x6ffffffa (RELCOUNT)                   6
 0x00000015 (DEBUG)                      0x0
 0x00000006 (SYMTAB)                     0x224
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000005 (STRTAB)                     0x2b4
 0x0000000a (STRSZ)                      340 (bytes)
 0x00000004 (HASH)                       0x408
 0x00000001 (NEEDED)                     Shared library: [libclang_rt.asan-arm-android.so]
 0x00000001 (NEEDED)                     Shared library: [libc++_shared.so]
 0x00000001 (NEEDED)                     Shared library: [liblog.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so]
 0x00000001 (NEEDED)                     Shared library: [libm.so]
 0x00000001 (NEEDED)                     Shared library: [libstdc++.so]
 0x00000001 (NEEDED)                     Shared library: [libdl.so]
 0x0000001a (FINI_ARRAY)                 0x1e74
 0x0000001c (FINI_ARRAYSZ)               8 (bytes)
 0x00000019 (INIT_ARRAY)                 0x1e7c
 0x0000001b (INIT_ARRAYSZ)               20 (bytes)
 0x00000020 (PREINIT_ARRAY)              0x1e90
 0x00000021 (PREINIT_ARRAYSZ)            0x8
 0x0000000f (RPATH)                      Library rpath: [/Users/stone/Library/Android/sdk/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/5.0.300080/lib/linux/arm]
 0x0000001e (FLAGS)                      BIND_NOW
 0x6ffffffb (FLAGS_1)                    Flags: NOW
 0x6ffffff0 (VERSYM)                     0x440
 0x6ffffffe (VERNEED)                    0x454
 0x6fffffff (VERNEEDNUM)                 1
 0x00000000 (NULL)                       0x0

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

运行

从 Android O MR1(API 级别 27)开始,应用可以提供可封装或替换应用进程的封装 Shell 脚本。这样一来,可调试的应用就可对其应用启动过程进行自定义,以便在生产设备上使用 ASan。

注意:以下说明将介绍如何将 ASan 与 Android Studio 项目结合使用。对于非 Android Studio 项目,请参阅封装 Shell 脚本文档。

  1. android:debuggableandroid:extractNativeLibs=true 添加到应用清单。请注意,后者是某些配置的默认设置。如需了解详情,请参阅封装 Shell 脚本。
  2. 将 ASan 运行时库添加到应用模块的 jniLibs 中。
  3. 将包含以下内容的 wrap.sh 文件添加到每个相同的目录中。
#!/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
"$@"

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


└── 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 变量中,即可为所有堆栈轨迹启用慢速展开程序。

二进制测试

由于是二进制程序测试,所以API版本只要高于17就可以了。

  • 测试代码
#include 

int main()
{
    std::cout << "Android NDK Address Sanitizer." << std::endl;
    char *p = new char[5];
    p[5] = 5;
    delete (p+1);
    return 0;
}
  • wrap.sh
#!/system/bin/sh
HERE="$(cd "$(dirname "$0")" && pwd)"
export ASAN_OPTIONS="log_to_syslog=true,allow_user_segv_handler=1,fast_unwind_on_malloc=0"
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
"$@"
  • 推送wrap.sh和编译的可执行程序santest到手机
$ adb push libs/armeabi-v7a/santest /data/local/tmp
$ adb push wrap.sh /data/local/tmp
  • 执行
$ adb shell
$ cd /data/local/tmp
$ ./wrap.sh ./santest
  • 奔溃信息
WARNING: linker: /data/local/tmp/santest: unused DT entry: type 0xf arg 0x331
ASAN:DEADLYSIGNAL
=================================================================
==8874==ERROR: AddressSanitizer: SEGV on unknown address 0x0000001f (pc 0xe98ffa54 bp 0xffadf268 sp 0xffadeb00 T0)
==8874==The signal is caused by a READ memory access.
==8874==Hint: address points to the zero page.
    

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV
==8874==ABORTING
Aborted

你可能感兴趣的:(Android,Sanitizer,ASan,wrap.sh,NDK,fsanitize)