اشکال‌زدایی کد پلاتفرم بومی Android

این بخش ابزارهای مفید و فرمان‌های مرتبط را برای اشکال‌زدایی، ردیابی، و نمایه‌سازی کد پلاتفرم بومی Android هنگام توسعه ویژگی‌های سطح پلاتفرم خلاصه می‌کند.

توجه: صفحه‌های این بخش و جاهای دیگر در این سایت استفاده از adb را همراه با setprop آرگومان برای اشکال‌زدایی کردن جنبه‌های خاصی از Android توصیه می‌کنند. در Android 7.x و نسخه‌های پایین‌تر، نام دارایی‌ها حداکثر ۳۲ نویسه طول داشتند. این یعنی برای ایجاد ویژگی wrap با نام برنامه، لازم بود نام را کوتاه کنیم تا جا شود. در Android 8.0 و نسخه‌های بالاتر، این حد بسیار بیشتر است و نباید به برش نیاز داشته باشد.

این صفحه اصول اولیه مربوط به رونوشت‌های خرابی موجود در خروجی logcat را پوشش می‌دهد. صفحه‌های دیگر جزئیات بسیار بیشتری درباره تشخیص خرابی‌های بومی، کاوش سرویس‌های سیستم با dumpsys، مشاهده حافظه بومی، شبکه، و استفاده از RAM ، استفاده از AddressSanitizer برای شناسایی اشکالات حافظه در کد بومی، ارزیابی مشکلات عملکرد (شامل systrace)، و استفاده از اشکال‌زداها ارائه می‌دهند.

تخلیه خرابی و سنگ قبر

وقتی یک فایل اجرایی با پیوند پویا شروع می‌شود، چندین مدیریت‌کننده سیگنال ثبت می‌شوند که درصورت خرابی، باعث می‌شوند یک تخلیه خرابی پایه در logcat و یک فایل سنگ قبر دقیق‌تر در /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 برای دریافت تخلیه پشته از فرایند درحال اجرا استفاده کنید. از خط فرمان، بااستفاده از شناسه فرایند (PID)، debuggerd را فراخوانی کنید تا سنگ قبر کامل را در 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)

قاب‌های #۰۰ تا #۰۳ از کد JNI بومی است که به‌صورت فشرده‌نشده در APK ذخیره شده است تا در فضای دیسک صرفه‌جویی شود و به‌جای اینکه در فایل .so جداگانه‌ای استخراج شود. در Android 9 و نسخه‌های بالاتر، برای مقابله با این مورد رایج مختص Android، بازکننده پشته نیازی به فایل .so استخراج‌شده ندارد.

قاب‌های شماره ۰۰ تا ۰۲ نام نماد ندارند چون توسعه‌دهنده آن‌ها را حذف کرده است.

قاب #۰۳ نشان می‌دهد که درصورت دردسترس بودن نمادها، بازکننده از آن‌ها استفاده می‌کند.

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

قاب شماره ۰۴ کد جاوا ازپیش‌کامپایل‌شده است. بازکننده قدیمی در اینجا متوقف می‌شد و نمی‌توانست ازطریق 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)

قاب‌های #۰۵ تا #۱۰ از پیاده‌سازی مفسر ART است. بازکننده پشته در نسخه‌های پایین‌تر از Android 9 این قاب‌ها را بدون بافت قاب شماره ۱۱ که توضیح می‌دهد مفسر چه کدی را تفسیر می‌کرده است نشان می‌داد. این قاب‌ها زمانی مفیدند که در حال اشکال‌زدایی خود ART باشید. اگر درحال اشکال‌زدایی برنامه‌ای هستید، می‌توانید آن‌ها را نادیده بگیرید. برخی ابزارها، مانند simpleperf، به‌طور خودکار این فریم‌ها را حذف می‌کنند.

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

قاب شماره ۱۱ کد 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)

قاب‌های شماره ۱۲ تا ۱۶ خود پیاده‌سازی مترجم هستند.

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

قاب شماره ۱۷ کد Java است که درحال تفسیر شدن است. این روش Java با چارچوب‌های مترجم #۱۲ تا #۱۶ مطابقت دارد.

    #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)

قاب‌های ۱۸ تا ۲۰ خود VM هستند، کدی برای انتقال از کد Java کامپایل‌شده به کد Java تفسیرشده.

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

قاب شماره ۲۱ روش کامپایل‌شده Java است که روش Java را در شماره ۱۷ فراخوانی می‌کند.

    #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)

قاب‌های شماره ۲۲ تا ۲۷ پیاده‌سازی مفسر هستند که فراخوانی روش را از کد تفسیرشده به روش کامپایل‌شده انجام می‌دهند.

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

قاب شماره ۲۸ کد 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)

قاب‌های ۲۹ تا ۳۱ انتقال دیگری بین کد کامپایل‌شده و کد تفسیرشده است.

    #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)

قاب‌های شماره ۳۲ تا ۳۴ قاب‌های 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)

قاب‌های شماره ۳۵ تا ۴۰ خود مترجم هستند.

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

قاب شماره ۴۱ کد 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)

قاب‌های شماره ۴۲ تا ۴۹ خود ماشین مجازی هستند. این بار کد است که اجرای جاوا را در رشته‌ای جدید شروع می‌کند.

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

قاب‌های ۵۰ تا ۵۱ نحوه شروع همه رشته‌ها را نشان می‌دهد. این کد شروع رشته جدید libc است.