Address Sanitizer

Android NDK 從 API 級別 27 (Android O MR 1) 開始支援 Address Sanitizer (也稱為 ASan)。

ASan 是一款以編譯器為基礎的快速工具,用於偵測原生程式碼中的記憶體錯誤。 ASan 可偵測到:

  • 堆疊和堆積緩衝區溢位/反向溢位
  • 釋放後的堆積使用情況
  • 超出範圍的堆疊使用情況
  • 重複釋放/錯誤釋放

ASan 可以在 32 位元和 64 位元 ARM 以及 x86 和 x86-64 上執行。ASan 的 CPU 負擔約為 2 倍,程式碼大小負擔介於 50% 到 2 倍之間,記憶體負擔也相當大 (取決於您的配置模式,但約為 2 倍)。

針對 64 位元 ARM,我們強烈建議使用 HWAddress Sanitizer

建構

如要透過 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)

執行

從 Android O MR1 (API 級別 27) 開始,應用程式可以提供可包裝或取代應用程式程序的包裝殼層指令碼。這樣可進行偵錯的應用程式就能自訂應用程式啟動程序,進而得以在實際工作環境裝置使用 ASan。

  1. android:debuggable 新增至應用程式資訊清單。
  2. 在應用程式的 build.gradle 檔案中將 useLegacyPackaging 設為 true。詳情請參閱包裝殼層指令碼指南。
  3. 將 ASan 執行階段程式庫新增至應用程式模組的 jniLibs
  4. 將含有下列內容的 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
    "$@"
    

假設專案的應用程式模組名稱為 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 時解開堆疊,以下提供兩種選項:

  1. 以框架指標為基礎的「快速」解開器。這也是建構部分操作說明所使用的解開器。

  2. CFI「慢速」解開器。在這個模式中,ASan 使用 _Unwind_Backtrace。這個選項只需要 -funwind-tables (通常預設為啟用)。

快速解開器是 malloc/realloc/free 的預設選項;而依照預設,慢速解開器則是針對附有嚴重錯誤的堆疊追蹤。只要將 fast_unwind_on_malloc=0 新增至 wrap.sh 中的 ASAN_OPTIONS 變數,即可為所有堆疊追蹤啟用慢速解開器。