AddressSanitizer

‫AddressSanitizer (ASan) הוא כלי מהיר שמבוסס על קומפילר, שנועד לזהות באגים בזיכרון בקוד Native.

‫ASan מזהה:

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

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

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

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

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

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

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

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

כשמזוהה באג, 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 מהיר שמבוסס על מצביע מסגרת כדי להקליט מעקב אחר מחסנית (stack trace) לכל אירוע של הקצאת זיכרון וביטול הקצאת זיכרון בתוכנית. רוב מערכת 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 בכל האפליקציות במכשיר בו-זמנית, וזה עומס כבד, אבל מכשיר עם זיכרון RAM בנפח 2GB אמור להיות מסוגל להתמודד עם זה.

מוסיפים את LOCAL_SANITIZE:=address לכלל הבנייה app_process ב-frameworks/base/cmds/app_process.

עורכים את הקטע 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

‫Build,‏ adb sync,‏ fastboot flash boot ו-reboot.

שימוש במאפיין 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 (הפעלת make הראשונה), וספריות עם ASan ב-/data/asan/lib (הפעלת make השנייה). קבצים הפעלה מהגרסה השנייה מחליפים את אלה מהגרסה הראשונה. קובצי הפעלה עם 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

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

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

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