ניפוי באגים בקוד של פלטפורמת Android המקורית

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

הערה: הדפים בקטע הזה ובמקומות אחרים באתר הזה מומלץ להשתמש ב-adb בשילוב עם ארגומנט setprop לניפוי באגים בהיבטים מסוימים של Android. ב-Android 7.x ומטה, מגבלת האורך של שמות הנכסים הייתה 32 תווים. כלומר, כדי ליצור נכס wrap עם שם האפליקציה, היה צורך לחתוך את השם כדי שיתאים. ב-Android מגרסה 8.0 ואילך גדולה בהרבה וללא חיתוך.

בדף הזה מוסבר על היסודות של קובצי cookie של קריסה שנמצאים בפלט Logcat. בדפים אחרים יש הרבה יותר פרטים על אבחון קריסות מקוריות, לחקור שירותי מערכת באמצעות dumpsys, צפייה זיכרון האם, network, ו-RAM השימוש ב-AddressSanitizer כדי לזהות זיכרון באגים בקוד נייטיב, בעיות בביצועים (כולל systrace), ושימוש כלי לניפוי באגים.

זירות מסחר וקבורות

כשקובץ הפעלה שמקושר באופן דינמי מתחיל, יש כמה handlerים של אותות רשום שבמקרה של קריסה, לגרום לכתיבה של תמונת מצב בסיסית של הקריסה ב-Logcat. וקובץ tombstone מפורט יותר שייכתב אל /data/tombstones/. אבן המצב היא קובץ עם נתונים נוספים על תהליך הקריסה. באופן ספציפי, היא מכילה דוחות קריסות לכל השרשורים בתהליך הקריסה (לא רק השרשורים שתפסו אותנטי), מפת זיכרון מלאה ורשימה של כל תיאורי הקבצים הפתוחים.

לפני Android 8.0, קריסות טופלו על ידי debuggerd ו-debuggerd64 דימונים. ב-Android מגרסה 8.0 ואילך, crash_dump32 ו-crash_dump64 שמקורם לפי הצורך.

יכול להיות שדוח הקריסות יצרף רק אם שום דבר נוסף לא כלומר, שימוש בכלים כמו strace או lldb מונעים יצירה של קובצי קריסה.

פלט לדוגמה (לאחר הסרה של חותמות זמן ומידע מיותר):

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Android/aosp_angler/angler:7.1.1/NYC/enh12211018:eng/test-keys'
Revision: '0'
ABI: 'arm'
pid: 17946, tid: 17949, name: crasher  >>> crasher <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
    r0 0000000c  r1 00000000  r2 00000000  r3 00000000
    r4 00000000  r5 0000000c  r6 eccdd920  r7 00000078
    r8 0000461a  r9 ffc78c19  sl ab209441  fp fffff924
    ip ed01b834  sp eccdd800  lr ecfa9a1f  pc ecfd693e  cpsr 600e0030

backtrace:
    #00 pc 0004793e  /system/lib/libc.so (pthread_mutex_lock+1)
    #01 pc 0001aa1b  /system/lib/libc.so (readdir+10)
    #02 pc 00001b91  /system/xbin/crasher (readdir_null+20)
    #03 pc 0000184b  /system/xbin/crasher (do_action+978)
    #04 pc 00001459  /system/xbin/crasher (thread_callback+24)
    #05 pc 00047317  /system/lib/libc.so (_ZL15__pthread_startPv+22)
    #06 pc 0001a7e5  /system/lib/libc.so (__start_thread+34)
Tombstone written to: /data/tombstones/tombstone_06

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

אם יש לך את הקבצים הבינאריים שלא הועברו, אפשר לקבל לשחרר את המידע על מספרי השורות על ידי הדבקת הערימה בתוך development/scripts/stack:

development/scripts/stack

טיפ: לנוחיותכם, אם הרצתם lunch, ואז stack כבר נמצא ב-$PATH כך שאין צורך לספק את נתיב מלא.

פלט לדוגמה (על סמך פלט ה-Logcat שלמעלה):

Reading native crash info from stdin
03-02 23:53:49.477 17951 17951 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
03-02 23:53:49.477 17951 17951 F DEBUG   : Build fingerprint: 'Android/aosp_angler/angler:7.1.1/NYC/enh12211018:eng/test-keys'
03-02 23:53:49.477 17951 17951 F DEBUG   : Revision: '0'
03-02 23:53:49.477 17951 17951 F DEBUG   : ABI: 'arm'
03-02 23:53:49.478 17951 17951 F DEBUG   : pid: 17946, tid: 17949, name: crasher  >>> crasher <<<
03-02 23:53:49.478 17951 17951 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
03-02 23:53:49.478 17951 17951 F DEBUG   :     r0 0000000c  r1 00000000  r2 00000000  r3 00000000
03-02 23:53:49.478 17951 17951 F DEBUG   :     r4 00000000  r5 0000000c  r6 eccdd920  r7 00000078
03-02 23:53:49.478 17951 17951 F DEBUG   :     r8 0000461a  r9 ffc78c19  sl ab209441  fp fffff924
03-02 23:53:49.478 17951 17951 F DEBUG   :     ip ed01b834  sp eccdd800  lr ecfa9a1f  pc ecfd693e  cpsr 600e0030
03-02 23:53:49.491 17951 17951 F DEBUG   :
03-02 23:53:49.491 17951 17951 F DEBUG   : backtrace:
03-02 23:53:49.492 17951 17951 F DEBUG   :     #00 pc 0004793e  /system/lib/libc.so (pthread_mutex_lock+1)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #01 pc 0001aa1b  /system/lib/libc.so (readdir+10)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #02 pc 00001b91  /system/xbin/crasher (readdir_null+20)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #03 pc 0000184b  /system/xbin/crasher (do_action+978)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #04 pc 00001459  /system/xbin/crasher (thread_callback+24)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #05 pc 00047317  /system/lib/libc.so (_ZL15__pthread_startPv+22)
03-02 23:53:49.492 17951 17951 F DEBUG   :     #06 pc 0001a7e5  /system/lib/libc.so (__start_thread+34)
03-02 23:53:49.492 17951 17951 F DEBUG   :     Tombstone written to: /data/tombstones/tombstone_06
Reading symbols from /huge-ssd/aosp-arm64/out/target/product/angler/symbols
Revision: '0'
pid: 17946, tid: 17949, name: crasher  >>> crasher <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
     r0 0000000c  r1 00000000  r2 00000000  r3 00000000
     r4 00000000  r5 0000000c  r6 eccdd920  r7 00000078
     r8 0000461a  r9 ffc78c19  sl ab209441  fp fffff924
     ip ed01b834  sp eccdd800  lr ecfa9a1f  pc ecfd693e  cpsr 600e0030
Using arm toolchain from: /huge-ssd/aosp-arm64/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin/

Stack Trace:
  RELADDR   FUNCTION                   FILE:LINE
  0004793e  pthread_mutex_lock+2       bionic/libc/bionic/pthread_mutex.cpp:515
  v------>  ScopedPthreadMutexLocker   bionic/libc/private/ScopedPthreadMutexLocker.h:27
  0001aa1b  readdir+10                 bionic/libc/bionic/dirent.cpp:120
  00001b91  readdir_null+20            system/core/debuggerd/crasher.cpp:131
  0000184b  do_action+978              system/core/debuggerd/crasher.cpp:228
  00001459  thread_callback+24         system/core/debuggerd/crasher.cpp:90
  00047317  __pthread_start(void*)+22  bionic/libc/bionic/pthread_create.cpp:202 (discriminator 1)
  0001a7e5  __start_thread+34          bionic/libc/bionic/clone.cpp:46 (discriminator 1)

אפשר להשתמש ב-stack על מצבה שלמה. דוגמה:

stack < FS/data/tombstones/tombstone_05

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

קבלת דוח קריסות או מצבה בתהליך ריצה

אפשר להשתמש בכלי debuggerd כדי לקבל תמונת מצב של הזיכרון מתהליך הרצה. משורת הפקודה, מפעילים את debuggerd באמצעות מזהה תהליך (PID) כדי להסיר מצבה מלאה של stdout. לראות רק את המקבץ בכל שרשור התהליך, כולל את הדגל -b או --backtrace.

להבין תהליך מורכב של רגיעה

כאשר אפליקציה קורסת, המקבץ בדרך כלל מורכב למדי. הדוגמה המפורטת הבאה מדגישה רבות מהמורכבות:

    #00 pc 00000000007e6918  /system/priv-app/Velvet/Velvet.apk (offset 0x346b000)
    #01 pc 00000000001845cc  /system/priv-app/Velvet/Velvet.apk (offset 0x346b000)
    #02 pc 00000000001847e4  /system/priv-app/Velvet/Velvet.apk (offset 0x346b000)
    #03 pc 00000000001805c0  /system/priv-app/Velvet/Velvet.apk (offset 0x346b000) (Java_com_google_speech_recognizer_AbstractRecognizer_nativeRun+176)

פריימים #00–#03 הם מקוד JNI מקורי שנשמר באופן לא דחוס ב-APK כדי לחסוך בדיסק במקום לחלץ אותם לקובץ .so נפרד. שחרור המקבצים ב-Android 9 ואילך לא נדרש הקובץ .so שחולץ כדי להתמודד עם הבעיה הנפוצות הזו מקרה ספציפי ל-Android.

למסגרות #00–#02 אין שמות סמלים כי המפתח הסיר אותן.

מסגרת מס' 03 מראה שבמקומות שבהם הסמלים זמינים, מכשיר ה-Unwinder משתמש בהם.

    #04 pc 0000000000117550  /data/dalvik-cache/arm64/system@priv-app@Velvet@Velvet.apk@classes.dex (offset 0x108000) (com.google.speech.recognizer.AbstractRecognizer.nativeRun+160)

מסגרת מס' 04 היא קוד Java שעבר הידור מראש. המפענח הישן היה עוצר כאן, כדי להירגע באמצעות Java.

    #05 pc 0000000000559f88  /system/lib64/libart.so (art_quick_invoke_stub+584)
    #06 pc 00000000000ced40  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
    #07 pc 0000000000280cf0  /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344)
    #08 pc 000000000027acac  /system/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+948)
    #09 pc 000000000052abc0  /system/lib64/libart.so (MterpInvokeDirect+296)
    #10 pc 000000000054c614  /system/lib64/libart.so (ExecuteMterpImpl+14484)

פריימים #05–#10 שייכים להטמעה של 'תרגום שיחה פעילה' ב-ART. מפרק הערימה של הגרסאות הקודמות ל-Android 9 היה מציג את הפריימים האלה ללא ההקשר של מסגרת מס' 11 שמסבירה איזה קוד המתורגמן פרש. המסגרות האלה שימושיות אם אתם מנסים לנפות באגים ב-ART עצמו. אם אתם מנקים באגים באפליקציה, אתם יכולים להתעלם מהם. כלים מסוימים, כמו simpleperf, השמטת פריימים אלה באופן אוטומטי.

    #11 pc 00000000001992d6  /system/priv-app/Velvet/Velvet.apk (offset 0x26cf000) (com.google.speech.recognizer.AbstractRecognizer.run+18)

מסגרת מס' 11 היא קוד Java שמפוענח.

    #12 pc 00000000002547a8  /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.780698333+496)
    #13 pc 000000000025a328  /system/lib64/libart.so (art::interpreter::ArtInterpreterToInterpreterBridge(art::Thread*, art::CodeItemDataAccessor const&, art::ShadowFrame*, art::JValue*)+216)
    #14 pc 000000000027ac90  /system/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+920)
    #15 pc 0000000000529880  /system/lib64/libart.so (MterpInvokeVirtual+584)
    #16 pc 000000000054c514  /system/lib64/libart.so (ExecuteMterpImpl+14228)

מסגרות 12-#16 הן מסגרות 'תרגום שיחה' עצמן.

    #17 pc 00000000002454a0  /system/priv-app/Velvet/Velvet.apk (offset 0x1322000) (com.google.android.apps.gsa.speech.e.c.c.call+28)

מסגרת מס' 17 היא קוד Java שמפוענח. שיטת Java הזו תואמת למסגרות #12–#16 של המתורגמן.

    #18 pc 00000000002547a8  /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.780698333+496)
    #19 pc 0000000000519fd8  /system/lib64/libart.so (artQuickToInterpreterBridge+1032)
    #20 pc 00000000005630fc  /system/lib64/libart.so (art_quick_to_interpreter_bridge+92)

פריימים מס' 18–#20 הם המכונה הווירטואלית עצמה, קוד למעבר מקוד Java שעבר הידור לקוד Java מפורש.

    #21 pc 00000000002ce44c  /system/framework/arm64/boot.oat (offset 0xdc000) (java.util.concurrent.FutureTask.run+204)

מסגרת מס' 21 היא שיטת Java שעברה הידור שקוראת לשיטת Java בגרסה 17.

    #22 pc 0000000000559f88  /system/lib64/libart.so (art_quick_invoke_stub+584)
    #23 pc 00000000000ced40  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
    #24 pc 0000000000280cf0  /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344)
    #25 pc 000000000027acac  /system/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+948)
    #26 pc 0000000000529880  /system/lib64/libart.so (MterpInvokeVirtual+584)
    #27 pc 000000000054c514  /system/lib64/libart.so (ExecuteMterpImpl+14228)

מסגרות #22–#27 הן הטמעת המתרגם, ויוצרות שיטה להפעלת שיטה לשיטה שעברה הידור.

    #28 pc 00000000003ed69e  /system/priv-app/Velvet/Velvet.apk (com.google.android.apps.gsa.shared.util.concurrent.b.e.run+22)

מסגרת מס' 28 היא קוד ה-Java שמפוענח.

    #29 pc 00000000002547a8  /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.780698333+496)
    #30 pc 0000000000519fd8  /system/lib64/libart.so (artQuickToInterpreterBridge+1032)
    #31 pc 00000000005630fc  /system/lib64/libart.so (art_quick_to_interpreter_bridge+92)

פריימים מס' 29 עד #31 הם מעבר נוסף בין קוד שעבר הידור לקוד מפורש.

    #32 pc 0000000000329284  /system/framework/arm64/boot.oat (offset 0xdc000) (java.util.concurrent.ThreadPoolExecutor.runWorker+996)
    #33 pc 00000000003262a0  /system/framework/arm64/boot.oat (offset 0xdc000) (java.util.concurrent.ThreadPoolExecutor$Worker.run+64)
    #34 pc 00000000002037e8  /system/framework/arm64/boot.oat (offset 0xdc000) (java.lang.Thread.run+72)

פריימים מס' 32–#34 הם פריימים שעברו הידור של מסגרות Java שמתקשרים ישירות זו אל זו. במקרה הזה, הקריאה המותאמת זהה לערימת הקריאות של Java.

    #35 pc 0000000000559f88  /system/lib64/libart.so (art_quick_invoke_stub+584)
    #36 pc 00000000000ced40  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
    #37 pc 0000000000280cf0  /system/lib64/libart.so (art::interpreter::ArtInterpreterToCompiledCodeBridge(art::Thread*, art::ArtMethod*, art::ShadowFrame*, unsigned short, art::JValue*)+344)
    #38 pc 000000000027acac  /system/lib64/libart.so (bool art::interpreter::DoCall<false, false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, art::JValue*)+948)
    #39 pc 0000000000529f10  /system/lib64/libart.so (MterpInvokeSuper+1408)
    #40 pc 000000000054c594  /system/lib64/libart.so (ExecuteMterpImpl+14356)

מסגרות #35 עד #40 הן המתרגם עצמו.

    #41 pc 00000000003ed8e0  /system/priv-app/Velvet/Velvet.apk (com.google.android.apps.gsa.shared.util.concurrent.b.i.run+20)

מסגרת מס' 41 היא קוד Java המפורש.

    #42 pc 00000000002547a8  /system/lib64/libart.so (_ZN3art11interpreterL7ExecuteEPNS_6ThreadERKNS_20CodeItemDataAccessorERNS_11ShadowFrameENS_6JValueEb.llvm.780698333+496)
    #43 pc 0000000000519fd8  /system/lib64/libart.so (artQuickToInterpreterBridge+1032)
    #44 pc 00000000005630fc  /system/lib64/libart.so (art_quick_to_interpreter_bridge+92)
    #45 pc 0000000000559f88  /system/lib64/libart.so (art_quick_invoke_stub+584)
    #46 pc 00000000000ced40  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+200)
    #47 pc 0000000000460d18  /system/lib64/libart.so (art::(anonymous namespace)::InvokeWithArgArray(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::(anonymous namespace)::ArgArray*, art::JValue*, char const*)+104)
    #48 pc 0000000000461de0  /system/lib64/libart.so (art::InvokeVirtualOrInterfaceWithJValues(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, jvalue*)+424)
    #49 pc 000000000048ccb0  /system/lib64/libart.so (art::Thread::CreateCallback(void*)+1120)

פריימים #42–#49 הם המכונה הווירטואלית עצמה. הפעם מדובר בקוד שמתחיל להריץ Java בשרשור חדש.

    #50 pc 0000000000082e24  /system/lib64/libc.so (__pthread_start(void*)+36)
    #51 pc 00000000000233bc  /system/lib64/libc.so (__start_thread+68)

פריימים #50–#51 הם האופן שבו כל השרשורים צריכים להתחיל. כאן libc הקוד של התחלת שרשור חדש.