Android NDK 從 API 級別 27 (Android O MR 1) 開始支援 Address Sanitizer (也稱為 ASan)。
ASan 是一款以編譯器為基礎的快速工具,用於偵測原生程式碼中的記憶體錯誤。ASan 可偵測到:
- 堆疊和堆積緩衝區溢位/反向溢位
- 釋放後的堆積使用情況
- 超出範圍的堆疊使用情況
- 重複釋放/錯誤釋放
ASan 的 CPU 負擔約為 2 倍,程式碼大小負擔介於 50% 到 2 倍之間,記憶體負擔也相當大 (取決於您的配置模式,但約為 2 倍)。
範例應用程式
建構
如要透過 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。
- 將
android:debuggable
新增至應用程式資訊清單。 - 在應用程式的
build.gradle
檔案中將useLegacyPackaging
設為true
。詳情請參閱包裝殼層指令碼指南。 - 將 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 "$@"
假設專案的應用程式模組名稱為 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
(通常預設為啟用)。
快速解開器是 malloc/realloc/free 的預設選項;而依照預設,慢速解開器則是針對附有嚴重錯誤的堆疊追蹤。只要將 fast_unwind_on_malloc=0
新增至 wrap.sh 中的 ASAN_OPTIONS
變數,即可為所有堆疊追蹤啟用慢速解開器。