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 ในอุปกรณ์ที่ใช้งานจริงได้
- เพิ่ม
android:debuggableลงในไฟล์ Manifest ของแอปพลิเคชัน - ตั้งค่า
useLegacyPackagingเป็นtrueในไฟล์build.gradleของแอป ดูข้อมูลเพิ่มเติมได้ที่คู่มือสคริปต์ของ Shell Wrapper - เพิ่มไลบรารีรันไทม์ 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
คุณมี 2 ตัวเลือกดังนี้
โปรแกรมยกเลิกการเรียกใช้ที่ "รวดเร็ว" ซึ่งอิงตามตัวชี้เฟรม ซึ่งจะใช้โดยทำตาม วิธีการในส่วนการสร้าง
โปรแกรมคลาย CFI ที่ "ช้า" ในโหมดนี้ ASan จะใช้
_Unwind_Backtraceโดยต้องใช้เพียง-funwind-tablesซึ่งโดยปกติจะเปิดใช้โดยค่าเริ่มต้น
Fast Unwinder เป็นค่าเริ่มต้นสำหรับ malloc/realloc/free การคลายสแต็กแบบช้าเป็นค่าเริ่มต้นสำหรับสแต็กเทรซที่ร้ายแรง คุณเปิดใช้การคลายสแต็กแบบช้าสำหรับ
การติดตามสแต็กทั้งหมดได้โดยการเพิ่ม fast_unwind_on_malloc=0 ลงในตัวแปร ASAN_OPTIONS
ใน wrap.sh