Android NDK תומך ב-Address Sanitizer (שנקרא גם ASan) החל מרמת API 27 (Android O MR 1).
ASan הוא כלי מהיר שמבוסס על קומפילציה, שמאפשר לזהות באגים בזיכרון בקוד Native. ASan מזהה:
- גלישה או חריגה ממאגרים (buffer) בזיכרון המחסנית (stack) ובזיכרון הערימה (heap)
- שימוש בערימה אחרי שחרור
- שימוש במערך מחוץ להיקף
- שחרור כפול של זיכרון/שחרור זיכרון לא מנוהל
תקורת ה-CPU של ASan היא בערך פי 2, תקורת גודל הקוד היא בין 50% לבין פי 2, ותקורת הזיכרון גדולה (תלויה בדפוסי ההקצאה, אבל בסדר גודל של פי 2).
אפליקציה לדוגמה
אפליקציה לדוגמה מראה איך להגדיר וריאנט build עבור 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), אפליקציה יכולה לספק סקריפט מעטפת שיכול לעטוף או להחליף את תהליך האפליקציה. ההרשאה הזו מאפשרת לאפליקציה שניתן לבצע בה ניפוי באגים להתאים אישית את הפעלת האפליקציה, וכך להשתמש ב-ASan במכשירי ייצור.
- מוסיפים
android:debuggableלמניפסט של האפליקציה. - מגדירים את
useLegacyPackagingלערךtrueבקובץbuild.gradleשל האפליקציה. מידע נוסף זמין במדריך בנושא סקריפט מעטפת. - מוסיפים את ספריית זמן הריצה של 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
. יש שתי אפשרויות:
מבטל את ההרשמה של פונקציית ה-unwinder המבוססת על מצביע פריים 'מהיר'. ההוראות האלה מפורטות בקטע על בנייה.
מבטל את ההרשמה של CFI 'איטי'. במצב הזה, ASan משתמש ב-
_Unwind_Backtrace. היא דורשת רק את-funwind-tables, שבדרך כלל מופעלת כברירת מחדל.
ה-unwinder המהיר הוא ברירת המחדל עבור malloc/realloc/free. האפשרות הזו מוגדרת כברירת מחדל עבור מעקב אחר מחסנית (stack trace) שגורם לשגיאה קריטית. אפשר להפעיל את האפשרות 'הילוך איטי של פתיחת הקובץ' לכל עקבות המחסנית על ידי הוספת fast_unwind_on_malloc=0 למשתנה ASAN_OPTIONS בקובץ wrap.sh.