Address Sanitizer

Android NDK mendukung Address Sanitizer (juga dikenal sebagai ASan) mulai dari level API 27 (Android O MR 1).

ASan adalah alat cepat berbasis compiler untuk mendeteksi bug memori dalam kode native. ASan dapat mendeteksi:

  • Stack dan luapan/underflow buffer heap
  • Penggunaan heap setelah tersedia
  • Penggunaan stack di luar cakupan
  • Double free/wild free

Overhead CPU ASan kurang lebih 2x, overhead ukuran kodenya antara 50% hingga 2x, dan overhead memorinya besar (bergantung pada pola alokasi, tetapi kurang lebih 2x).

Aplikasi Contoh

Aplikasi contoh menunjukkan cara mengonfigurasi varian build untuk ASan.

Build

Untuk membangun kode native (JNI) aplikasi dengan Address Sanitizer, lakukan hal berikut:

ndk-build

Dalam Application.mk Anda:

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

Untuk setiap modul dalam Android.mk:

LOCAL_ARM_MODE := arm

CMake

Dalam build.gradle modul Anda:

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

Untuk setiap target dalam CMakeLists.txt:

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

Run

Mulai dari Android O MR1 (API level 27), aplikasi dapat menyediakan skrip shell wrap yang dapat menggabungkan atau mengganti proses aplikasi. Hal ini memungkinkan aplikasi yang dapat di-debug menyesuaikan proses memulai aplikasinya, sehingga memungkinkan penggunaan ASan di perangkat produksi.

  1. Tambahkan android:debuggable ke manifes aplikasi.
  2. Tetapkan useLegacyPackaging ke true dalam file build.gradle aplikasi Anda. Lihat panduan skrip shell wrap untuk informasi selengkapnya.
  3. Tambahkan library runtime ASan ke jniLibs modul aplikasi.
  4. Tambahkan file wrap.sh dengan konten berikut ini ke setiap direktori di direktori 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
    "$@"
    

Dengan asumsi bahwa modul aplikasi project diberi nama app, struktur direktori akhir Anda harus menyertakan hal berikut:

<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

Pelacakan tumpukan

Address Sanitizer perlu melepaskan stack pada setiap panggilan malloc/realloc/free. Ada dua opsi di sini:

  1. Unwinder berbasis pointer frame yang "cepat". Unwinder inilah yang perlu Anda gunakan jika mengikuti petunjuk di bagian membuat.

  2. Unwinder CFI yang "lambat". Dalam mode ini, ASan menggunakan _Unwind_Backtrace. Unwinder ini hanya memerlukan -funwind-tables, yang biasanya diaktifkan secara default.

Unwinder cepat merupakan default untuk malloc/realloc/free. Unwinder lambat adalah default untuk pelacakan tumpukan fatal. Unwinder lambat dapat diaktifkan untuk semua pelacakan tumpukan dengan menambahkan fast_unwind_on_malloc=0 ke variabel ASAN_OPTIONS dalam wrap.sh.