AddressSanitizer

AddressSanitizer‏ (ASan) הוא כלי מהיר מבוסס-מְהַמֵר לזיהוי באגים בזיכרון בקוד מקורי.

ASan מזהה:

  • זליגה/חוסר מקום במאגרים של סטאק ושל אשכול
  • שימוש בערימה אחרי בחינם
  • שימוש ב-Stack מחוץ להיקף
  • Double free/wild free

ASan פועלת גם ב-ARM של 32 ביט וגם ב-ARM של 64 ביט, וגם ב-x86 וב-x86-64. התקורה של ASan ל-CPU היא כפולה בערך, התקורה של גודל הקוד היא בין 50% ל-2x, ויש התקורה גדולה של זיכרון (תלוי בדפוסי ההקצאה, אבל בסדר גודל של פי 2).

ב-Android 10 ובהסתעפות הראשית של AOSP ב-AArch64 יש תמיכה ב-Hardware-assisted AddressSanitizer‏ (HWASan), כלי דומה עם פחות עומס על זיכרון ה-RAM ומגוון רחב יותר של באגים שזוהו. שירות HWASan מזהה שימוש בסטאק אחרי החזרה, בנוסף לבאגים שזוהו על ידי ASan.

ל-HWASan יש עלות ריצה דומה של מעבד וקוד, אבל עלות ריצה קטנה בהרבה של זיכרון RAM (15%). HWASan אינו קבוע. יש רק 256 ערכים אפשריים לתג, כך שיש סיכוי של 0.4% בלבד לפספס באג. ב-HWASan אין אזורים אדומים מוגבלים בגודלם זיהוי חריגות והסגר עם קיבולת מוגבלת לזיהוי חריגות לאחר השימוש בחינם, אז לא משנה ל-HWASan כמה גדול נפח הגלישה או כמה זמן עבר הזיכרון הוקצה. לכן, HWASan טוב יותר מ-ASan. אפשר לקרוא מידע נוסף על עיצוב של HWASan או מידע על השימוש ב-HWASan ב-Android.

ASan מזהה זליגות זיכרון בסטאק או ברמת המערכת, בנוסף לזליגות זיכרון ב-heap, והוא מהיר עם תקורה מינימלית של זיכרון.

במסמך הזה מתואר איך לפתח ולהפעיל חלקים של Android או את כל מכשירי Android עם אסן. אם אתם מפתחים אפליקציית SDK/NDK עם ASan, עיינו במאמר בנושא כלי לחיטוי כתובות במקום זאת.

ניטרול קבצים ניתנים להפעלה ספציפיים באמצעות ASan

מוסיפים את LOCAL_SANITIZE:=address או את sanitize: { address: true } לכלל ה-build של קובץ ההפעלה. אפשר לחפש בקוד דוגמאות קיימות או למצוא את שאר הכלי לניקוי נתונים שזמינים.

כשמזוהה באג, ASan מדפיסה דוח מפורט הפלט אל logcat ואז קורס של התהליך.

חיטוי ספריות משותפות באמצעות ASan

בשל אופן הפעולה של ASan, ספרייה שנבנתה באמצעות ASan יכולה לשמש רק שנוצר באמצעות ASan.

כדי לנקות ספרייה משותפת שנמצאת בשימוש במספר קובצי הפעלה, לא את כולם שנוצרו בעזרת ASan, תצטרכו שני עותקים של הספרייה. הדרך המומלצת לעשות זאת היא להוסיף את הפרטים הבאים למשתנה Android.mk של המודול הרלוונטי:

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

כך הספרייה תופיע ב-/system/lib/asan במקום ב-/system/lib. לאחר מכן מריצים את קובץ ההפעלה באמצעות:

LD_LIBRARY_PATH=/system/lib/asan

לדימונים של מערכת, מוסיפים את הקטע הבא לקטע המתאים ב-/init.rc או ב-/init.$device$.rc.

setenv LD_LIBRARY_PATH /system/lib/asan

צריך לוודא שהתהליך משתמש בספריות מ-/system/lib/asan כשהוא קיים, בקריאה /proc/$PID/maps. אם הוא לא מופיע, יכול להיות שתצטרכו להשבית את SELinux:

adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID
# if it is a system service, or may be adb shell stop; adb shell start.

דוחות קריסות טובים יותר

‏ASan משתמש ב-unwinder מהיר שמבוסס על מצביע מסגרת כדי לתעד מעקב סטאק לכל אירוע הקצאה וביטול הקצאה של זיכרון בתוכנית. רוב Android נוצר ללא מצביע מסגרת. כתוצאה מכך, לעיתים קרובות תקבלו רק מסגרת אחת או שתיים עם משמעות. כדי לפתור את הבעיה, צריך ליצור מחדש את הספרייה עם ASan (מומלץ!) או עם:

LOCAL_CFLAGS:=-fno-omit-frame-pointer
LOCAL_ARM_MODE:=arm

לחלופין, אפשר להגדיר את ASAN_OPTIONS=fast_unwind_on_malloc=0 בסביבת התהליך. האפשרות השנייה עשויה להיות מאוד דורשת משאבי מעבד (CPU), בהתאם לעומס.

סימבוליזציה

בהתחלה, הדוחות של ASan מכילים הפניות להיסט בקבצים בינאריים ומשותפים של הספריות. יש שתי דרכים לקבל מידע על קובץ המקור ועל השורה:

  • מוודאים שהקובץ הבינארי llvm-symbolizer נמצא ב-/system/bin. llvm-symbolizer נוצר ממקורות ב-third_party/llvm/tools/llvm-symbolizer.
  • מסננים את הדוח באמצעות הסקריפט external/compiler-rt/lib/asan/scripts/symbolize.py.

הגישה השנייה יכולה לספק יותר נתונים (כלומר, file:line מיקומים) בגלל הזמינות של ספריות סימבוליות במארח.

ASan באפליקציות

ASan לא יכול לראות בקוד Java, אבל הוא יכול לזהות באגים בספריות JNI. לשם כך, צריך ליצור את קובץ ההפעלה באמצעות ASan, שהוא /system/bin/app_process(32|64) במקרה הזה. כך אפשר להפעיל את ASan בכל האפליקציות במכשיר בו-זמנית, וזה עומס כבד, אבל מכשיר עם 2GB RAM אמור להתמודד עם זה.

מוסיפים את LOCAL_SANITIZE:=address לכלל ה-build app_process בקובץ frameworks/base/cmds/app_process. בינתיים, אפשר להתעלם מהיעד app_process__asan באותו קובץ (אם הוא עדיין שם בזמן הקריאה שלכם).

עורכים את הקטע service zygote בקובץ system/core/rootdir/init.zygote(32|64).rc המתאים, ומוסיפים את השורות הבאות לבלוק השורות עם הפסקה שמכיל את class main, עם אותה כמות הפסקה:

    setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
    setenv ASAN_OPTIONS allow_user_segv_handler=true

פיתוח, סנכרון adb, אתחול מהיר ב-fastboot ואתחול מחדש.

שימוש במאפיין wrap

הגישה בקטע הקודם שמה את ASan בכל באפליקציה במערכת (למעשה, לכל צאצא של Zygote ). אפשר להריץ רק אפליקציה אחת (או כמה) עם ASan, בתמורה לזמן הפעלה ארוך יותר של האפליקציה.

כדי לעשות זאת, צריך להפעיל את האפליקציה עם הנכס wrap.. בדוגמה הבאה מריצים את אפליקציית Gmail באמצעות ASan:

adb root
adb shell setenforce 0  # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"

בהקשר הזה, asanwrapper משכתב את /system/bin/app_process ל-/system/bin/asan/app_process, שנוצר באמצעות ASan. היא גם מוסיפה /system/lib/asan בתחילת נתיב החיפוש בספרייה הדינמית. כך, עדיפות ניתנת לספריות עם ASan מ-/system/lib/asan על פני ספריות רגילות ב-/system/lib כשהן פועלות עם asanwrapper.

אם נמצא באג, האפליקציה קורסת והדוח מודפס ביומן.

SANITIZE_TARGET

ב-Android 7.0 ואילך יש תמיכה ביצירה של כל פלטפורמת Android באמצעות ASan בבת אחת. (אם אתם מפתחים גרסה חדשה יותר מ-Android 9, עדיף להשתמש ב-HWASan).

מריצים את הפקודות הבאות באותו עץ build.

make -j42
SANITIZE_TARGET=address make -j42

במצב הזה, userdata.img מכיל ספריות נוספות וצריך גם להטמיע אותו במכשיר. משתמשים בשורת הפקודה הבאה:

fastboot flash userdata && fastboot flashall

הדבר יוצר שתי קבוצות של ספריות משותפות: נורמלית ב- /system/lib (ההפעלה הראשונה), ו-ASan עם אינסטרומנטציה /data/asan/lib (השנייה מפעילה). קובצי הרצה מ- ה-build השני יחליף את ה-build מה-build הראשון. עם אינסטלציה ASan קובצי הפעלה מקבלים נתיב חיפוש אחר בספרייה שכולל /data/asan/lib לפני /system/lib באמצעות שימוש ב- /system/bin/linker_asan בPT_INTERP.

מערכת ה-build מאחזרת ספריות אובייקטים מתווכות כאשר הערך של $SANITIZE_TARGET השתנה. פעולה זו מחייבת בנייה מחדש של כל יעדים תוך שמירה על הקבצים הבינאריים המותקנים במסגרת /system/lib.

לא ניתן ליצור יעדים מסוימים באמצעות ASan:

  • קובצי הפעלה מקושרים באופן סטטי
  • יעדים של LOCAL_CLANG:=false
  • LOCAL_SANITIZE:=false לא עברו בדיקת ASan עבור SANITIZE_TARGET=address

קובצי הפעלה כאלה מושמטים מה-build של SANITIZE_TARGET, והגרסה מהקריאה הראשונה של make נשארת ב-/system/bin.

ספריות כאלה נוצרות ללא ASan. הן יכולות להכיל קוד ASan מהספריות הסטטיות עליהן הן תלויות.

מסמכים תומכים