מכשיר לחיטוי כתובות

Android NDK תומך בהתחלה של ניקוי כתובות (שנקרא גם ASan) עם רמת API 27 (Android O MR 1).

ASan הוא כלי מבוסס מהדר מהיר לזיהוי באגים בזיכרון בקוד נייטיב. ASan מזהה:

  • גלישת נתונים/מאגר נתונים זמני של ערימה וערימה
  • שימוש בערימה אחרי בחינם
  • שימוש במקבץ מחוץ להיקף
  • מיטה זוגית חופשיה/חינמית

התקורה של המעבד (CPU) של ASan היא בערך פי 2. התקורה של גודל הקוד היא בין 50% לפי 2. והתקורה של הזיכרון גדולה (תלויה בדפוסי ההקצאה שלכם, פי 2).

אפליקציה לדוגמה

אפליקציה לדוגמה מראים איך להגדיר וריאנט build של Asan.

Build

כדי ליצור את קוד ה-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 במכשירים בסביבת הייצור.

  1. מוסיפים את android:debuggable למניפסט של האפליקציה.
  2. הגדרה של useLegacyPackaging אל true בקובץ build.gradle של האפליקציה. לעיון במדריך בנושא סקריפט מעטפת של מעטפת אפשר לקבל מידע נוסף.
  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

מעקבי ערימה

כלי לחיטוי כתובות צריך להסיר את הערימה בכל malloc/realloc/free שיחה. יש כאן שתי אפשרויות:

  1. תשובה "מהירה" תוכנת ביטול הרצה מבוססת-פריים. כדי לעשות את זה צריך לעקוב אחר מפורטות בקטע הבנייה.

  2. A "איטי" מפרק CFI. במצב הזה, ASan משתמשת ב-_Unwind_Backtrace. הוא נדרש רק -funwind-tables, שבדרך כלל מופעל כברירת מחדל.

השחרור המהיר מוגדר כברירת המחדל עבור Malloc/realloc/free. השחרור האיטי הוא ברירת המחדל של דוחות קריסות חמורים. אפשר להפעיל את הכלי האיטי דוחות הקריסות על ידי הוספת fast_unwind_on_malloc=0 למשתנה ASAN_OPTIONS ב-wrap.sh.