ตัวฆ่าเชื้อที่อยู่

Android NDK รองรับ Address Sanitizer (หรือที่เรียกว่า ASan) ตั้งแต่ระดับ API 27 (Android O MR 1) เป็นต้นไป

ASan เป็นเครื่องมือที่ใช้คอมไพเลอร์ที่รวดเร็วสำหรับการตรวจหาข้อบกพร่องของหน่วยความจำในโค้ดแบบเนทีฟ ASan ตรวจพบ

  • Stack และ Heap Buffer Overflow/Underflow
  • การใช้ฮีปหลังจากปล่อย
  • การใช้สแต็กนอกขอบเขต
  • ดับเบิลฟรี/ไวลด์ฟรี

ค่าใช้จ่ายของ CPU ของ ASan จะอยู่ที่ประมาณ 2 เท่า ค่าใช้จ่ายของขนาดโค้ดจะอยู่ระหว่าง 50% ถึง 2 เท่า และค่าใช้จ่ายของหน่วยความจำจะสูง (ขึ้นอยู่กับรูปแบบการจัดสรร แต่จะอยู่ที่ประมาณ 2 เท่า)

แอปตัวอย่าง

แอปตัวอย่างแสดงวิธีกําหนดค่าตัวแปรบิลด์สําหรับ asan

สร้าง

หากต้องการสร้างโค้ดเนทีฟ (JNI) ของแอปด้วย Address Sanitizer ให้ทำดังนี้

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) เป็นต้นไป แอปพลิเคชันสามารถระบุสคริปต์ของ Shell Wrapper ที่สามารถ Wrap หรือแทนที่กระบวนการของแอปพลิเคชันได้ ซึ่งจะช่วยให้ แอปพลิเคชันที่แก้ไขข้อบกพร่องได้ปรับแต่งการเริ่มต้นแอปพลิเคชันของตนเองได้ ซึ่งจะช่วยให้ ใช้ ASan ในอุปกรณ์ที่ใช้งานจริงได้

  1. เพิ่ม android:debuggable ลงในไฟล์ Manifest ของแอปพลิเคชัน
  2. ตั้งค่า useLegacyPackaging เป็น true ในไฟล์ build.gradle ของแอป ดูข้อมูลเพิ่มเติมได้ที่คู่มือสคริปต์ของ Shell Wrapper
  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 คุณมี 2 ตัวเลือกดังนี้

  1. โปรแกรมยกเลิกการเรียกใช้ที่ "รวดเร็ว" ซึ่งอิงตามตัวชี้เฟรม ซึ่งจะใช้โดยทำตาม วิธีการในส่วนการสร้าง

  2. โปรแกรมคลาย CFI ที่ "ช้า" ในโหมดนี้ ASan จะใช้ _Unwind_Backtrace โดยต้องใช้เพียง -funwind-tables ซึ่งโดยปกติจะเปิดใช้โดยค่าเริ่มต้น

Fast Unwinder เป็นค่าเริ่มต้นสำหรับ malloc/realloc/free การคลายสแต็กแบบช้าเป็นค่าเริ่มต้นสำหรับสแต็กเทรซที่ร้ายแรง คุณเปิดใช้การคลายสแต็กแบบช้าสำหรับ การติดตามสแต็กทั้งหมดได้โดยการเพิ่ม fast_unwind_on_malloc=0 ลงในตัวแปร ASAN_OPTIONS ใน wrap.sh