בקטע הזה מופיע סיכום של כלים שימושיים ופקודות קשורות לניפוי באגים, מעקב ופרפיילינג של קוד פלטפורמת 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
הקוד של התחלת שרשור חדש.