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 מהספריות הסטטיות עליהן הן תלויות.