精华内容
下载资源
问答
  • 如何查看’[tombstoned]’ log issue: 测试中发现一例chrome无响应 09-07 09:45:06.580 I/cr_media(11809): Provisioning origin ID 7638CB090F8C8B0C66D9FCFB168A3509 09-07 09:45:06.625 I/InputDispatcher( 1393)...

    如何查看’[tombstoned]’ log

    issue:

    测试中发现一例chrome无响应

    09-07 09:45:06.580 I/cr_media(11809): Provisioning origin ID 7638CB090F8C8B0C66D9FCFB168A3509
    09-07 09:45:06.625 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3562.0ms processing the last input event: MotionEvent
    09-07 09:45:06.627 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3558.7ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3550.7ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3541.8ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3532.9ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3524.1ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3515.6ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3506.8ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3498.0ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3489.1ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3480.5ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3471.6ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3459.3ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3452.1ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3442.7ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3433.0ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3427.5ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3417.9ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3406.6ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3398.0ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3391.1ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3384.0ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3373.1ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3363.8ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3355.2ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3348.3ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3336.4ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3330.8ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3322.6ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3314.1ms processing the last input event: MotionEvent
    09-07 09:45:06.628 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3304.0ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3296.0ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3285.9ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3278.3ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3267.8ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3259.3ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3250.5ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3243.4ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3234.4ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3226.1ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3216.7ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3206.4ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3196.2ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3188.7ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3178.4ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3174.4ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/InputDispatcher( 1393): Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3156.7ms processing the last input event: MotionEvent
    09-07 09:45:06.629 I/Choreographer(11809): Skipped 678 frames!  The application may be doing too much work on its main thread.
    09-07 09:45:06.631 W/Looper  ( 1393): Slow delivery took 2091ms main h=android.view.GestureDetector$GestureHandler c=null m=1
    
    '''
    09-07 09:45:15.906 I//system/bin/tombstoned(  718): received crash request for pid 11809
    09-07 09:45:15.906 I//system/bin/tombstoned(  718): found intercept fd 512 for pid 11809 and type kDebuggerdJavaBacktrace
    09-07 09:45:15.907 I/.android.chrom(11809): Wrote stack traces to '[tombstoned]'
    09-07 09:45:15.907 I/system_server( 1393): libdebuggerd_client: done dumping process 11809
    09-07 09:45:15.907 I/system_server( 1393): libdebuggerd_client: started dumping process 1393
    09-07 09:45:15.908 I//system/bin/tombstoned(  718): registered intercept for pid 1393 and type kDebuggerdJavaBacktrace
    09-07 09:45:15.908 I/system_server( 1393): Thread[2,tid=1399,WaitingInMainSignalCatcherLoop,Thread*=0x713880e000,peer=0x13580000,"Signal Catcher"]: reacting to signal 3
    09-07 09:45:15.908 I/system_server( 1393): 
    09-07 09:45:16.126 I/HidlSensorManager( 1393): hidl_ssvc_poll: spurious wake up, back to work
    09-07 09:45:16.400 I//system/bin/tombstoned(  718): received crash request for pid 1393
    09-07 09:45:16.400 I//system/bin/tombstoned(  718): found intercept fd 512 for pid 1393 and type kDebuggerdJavaBacktrace
    09-07 09:45:16.401 I/system_server( 1393): Wrote stack traces to '[tombstoned]'
    ...
    09-07 09:45:19.078 E/ActivityManager( 1393): ANR in com.android.chrome (com.android.chrome/com.google.android.apps.chrome.Main)
    09-07 09:45:19.078 E/ActivityManager( 1393): PID: 11809
    09-07 09:45:19.078 E/ActivityManager( 1393): Reason: Input dispatching timed out (Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.  Wait queue length: 13.  Wait queue head age: 5948.2ms.)
    09-07 09:45:19.078 E/ActivityManager( 1393): Load: 4.83 / 3.7 / 3.39
    09-07 09:45:19.078 E/ActivityManager( 1393): CPU usage from 65863ms to 0ms ago (2021-09-07 09:44:08.848 to 2021-09-07 09:45:14.712) with 99% awake:
    09-07 09:45:19.078 E/ActivityManager( 1393):   19% 11840/com.android.chrome:sandboxed_process0: 17% user + 1.7% kernel / faults: 27207 minor
    09-07 09:45:19.078 E/ActivityManager( 1393):   11% 11809/com.android.chrome: 7.6% user + 3.7% kernel / faults: 8640 minor
    09-07 09:45:19.078 E/ActivityManager( 1393):   5.6% 542/surfaceflinger: 3.8% user + 1.8% kernel / faults: 17 minor
    09-07 09:45:19.078 E/ActivityManager( 1393):   5.1% 1393/system_server: 3.2% user + 1.8% kernel / faults: 2591 minor
    09-07 09:45:19.078 E/ActivityManager( 1393):   4.9% 2325/com.google.android.gms: 4.6% user + 0.3% kernel / faults: 13930 minor
    09-07 09:45:19.078 E/ActivityManager( 1393):   4.3% 11869/com.android.chrome:privileged_process0: 3.2% user + 1.1% kernel / faults: 8930 minor
    09-07 09:45:19.078 E/ActivityManager( 1393):   2.7% 537/dreamlogger: 0.1% user + 2.6% kernel
    
    

    分析

    提供的log 使用logcat 获取 只有如上信息
    通过观察 我们看到 anr具体信息被写入 tombstoned

    跟进

    如果是Android 8.1之前的系统 可执行adb pull /data/anr/traces.txt
    8.1之后 用adb shell bugreport命令来导出trace文件

    总结

    发生ANR 需要分析trace文件来定位问题

    如这例中

    Window 'Window{7186472 u0 com.android.chrome/com.google.android.apps.chrome.Main}' spent 3489.1ms processing the last input event: MotionEvent
    

    考虑到问题出在chrome中。 那只有在系统测解决了 -_-

    展开全文
  • 这系列相关博客,转载 Android开发高手课 Android开发高手课 - 02 | 崩溃优化(下):应用崩溃了,你应该如何去分析?崩溃现场崩溃分析疑难问题:系统崩溃总结课后作业补充⼀下获得logcat和Jave堆栈的方法获取logcat...

    这系列相关博客,转载 Android开发高手课
    了解更多相关内容,可去这个目录博客 Android开发高手课 - 目录

    在侦探漫画《名侦探柯南》中,无论柯南走到哪里都会遇到新的“案件”,这也很像程序员的“日常”,我们每天工作也会遇到各种各样的疑难问题,“崩溃”就是其中比较常见的一种问题。

    解决崩溃跟破案一样需要经验,我们分析的问题越多越熟练,定位问题就会越快越准。当然这里也有很多套路,比如对于“案发现场”我们应该留意哪些信息?怎样找到更多的“证人”和“线索”? “侦查案件”的一般流程是什么?对不同类型的“案件”分别应该使用什么样的调查方式?

    “真相永远只有一个”,崩溃也并不可怕。通过今天的学习,希望你能成为代码届的名侦探柯南。

    崩溃现场

    崩溃现场是我们的“第一案发现场”,它保留着很多有价值的线索。在这里我们挖掘到的信息越多,下一步分析的方向就越清晰,而不是去靠盲目猜测。

    操作系统是整个崩溃过程的“旁观者”,也是我们最重要的“证人”。一个好的崩溃捕获工具知道应该采集哪些系统信息,也知道在什么场景要深入挖掘哪些内容,从而可以更好地帮助我们解决问题。

    接下来我们具体来看看在崩溃现场应该采集哪些信息

    1. 崩溃信息
      从崩溃的基本信息,我们可以对崩溃有初步的判断。
    • 进程名、线程名。崩溃的进程是前台进程还是后台进程,崩溃是不是发生在UI线程。
    • 崩溃堆栈和类型。崩溃是属于Java崩溃、Native崩溃,还是ANR,对于不同类型的崩溃我们关注的点也不太一样。特别需要看崩溃堆栈的栈顶,看具体崩溃在系统的代码,还是我们自己的代码里面。
    Process Name: 'com.sample.crash'
    Thread Name: 'MyThread'
    java.lang.NullPointerException
    at ...TestsActivity.crashInJava(TestsActivity.java:275)
    

    有时候我们除了崩溃的线程,还希望拿到其他关键的线程的日志。就像上面的例子,虽然是MyThread线程崩溃,但是我也希望可以知道主线程当前的调用栈。

    1. 系统信息
      系统的信息有时候会带有一些关键的线索,对我们解决问题有非常大的帮助。
    • Logcat。这里包括应用、系统的运行曰志。由于系统权限问题,获取到的Logcat可能只包含与当前App相关的。其中系统的event logcat会记录App运行的一些基本情况,记录在文件/system/etc/event-log-tags中。
    system logcat:
    10-25 17:13:47.788 21430 21430 D dalvikvm: Trying to load lib ...
    event logcat:
    10-25 17:13:47.788 21430 21430 I am_on_resume_called:生命周期
    10-25 17:13:47.788 21430 21430 I am_low_memo ry:系统内存不足
    10-25 17:13:47.788 21430 21430 I am_destroy_activity:销毁 Activty
    10-25 17:13:47.888 21430 21430 I am_anr: ANR 以及原因
    10-25 17:13:47.888 21430 21430 I am_kill: APP 被杀以及原因
    
    • 机型、系统、厂商、CPU、ABI、Linux版本等。我们会采集多达几十个维度,这对后面讲到寻找共性问题会很有帮助。
    • 设备状态:是否root、是否是模拟器。一些问题是由Xposed或多开软件造成,对这部分问题我们要区别对待。
    1. 内存信息
      OOM、ANR、虚拟内存耗尽等,很多崩溃都跟内存有直接关系。如果我们把用户的手机内存分为 “2GB以下” 和 “2GB以上” 两个桶,会发现 “2GB以下” 用户的崩溃率是 “2GB以上” 用户的几倍。
    • 系统剩余内存。关于系统内存状态,可以直接读取文件/proc/meminfo。当系统可用内存很小(低于MemTotal的10%) 时,OOM、大量GC、系统频繁自杀拉起等问题都非常容易出现。
    • 应用使用内存。包括Java内存、RSS (Resident Set Size)、PSS (Proportional Set Size),我们可以得出应用本身内存的占用大小和分布。PSS和RSS通过/proc/self/smap计算,可以进一步得到例如 apk、dex、so 等更加详细的分类统计。
    • 虚拟内存。虚拟内存可以通过/proc/self/status得到,通过/proc/self/maps文件可以得到具体的分布情况。有时候我们一般不太重视虚拟内存,但是很多类似OOM、tgkill等问题都是虚拟内存不足导致的。
    Name:	com.sample.name	//进程名
    FDSize:	800	//当前进程申请的文件句柄个数
    VmPeak:	3004628 kB	//当前进程的虚拟内存峰值大小
    VmSize:	2997032 kB	//当前进程的虚拟内存大小
    Threads:	600	//当前进程包含的线程个数
    

    —般来说,对于32位进程,如果是32位的CPU,虚拟内存达到3GB就可能会引起内存申请失败的问题。如果是64位的CPU,虚拟内存一般在3~4GB之间。当然如果我们支持64位进程,虚拟内存就不会成为问题。Google Play要求2019年8月一定要支持64位,在国内虽然支持64位的设备已经在90%以上了,但是商店都不支持区分CPU架构类型发布,普及起来需要更长的时间。

    1. 资源信息
      有的时候我们会发现应用堆内存和设备内存都非常充足,还是会出现内存分配失败的情况,这跟资源泄漏可能有比较大的关系。
    • 文件句柄fd。文件句柄的限制可以通过/proc/self/limits获得,一般单个进程允许打开的最大文件句柄个数为1024。但是如果文件句柄超过800个就比较危险,需要将所有的fd以及对应的文件名输出到日志中,进一步排查是否出现了有文件或者线程的泄漏。
    opened files count 812:
    0 	-> /dev/null
    1	-> /dev/log/main4
    2	-> /dev/binder
    3	-> /data/data/com.crash.sample/files/test.config
    ……
    
    • 线程数。当前线程数大小可以通过上面的status文件得到,一个线程可能就占2MB的虚拟内存,过多的线程会对虚拟内存和文件句柄带来压力。根据我的经验来说,如果线程数超过400个就比较危险。需要将所有的线程id以及对应的线程名输出到曰志中,进一步排查是否出现了线程相关的问题。
    threads count 412:
    1820 com.sample.crashsdk
    1844 ReferenceQueueD
    1869 FinalizerDaemon
    ……
    
    • JNI。使用JNI时,如果不注意很容易出现引用失效、引用爆表等一些崩溃。我们可以通过DumpReferenceTables统计JNI的引用表,进一步分析是否出现了JNI泄漏等问题。
    1. 应用信息
      除了系统,其实我们的应用更懂自己,可以留下很多相关的信息。
    • 崩溃场景。崩溃发生在哪个Activity或Fragment,发生在哪个业务中。
    • 关键操作路径。不同于开发过程详细的打点日志,我们可以记录关键的用户操作路径,这对我们复现崩溃会有比较大的帮助。
    • 其他自定义信息。不同的应用关心的重点可能不太一样,比如网易云音乐会关注当前播放的音乐,QQ浏览器会关注当前打开的网址或视频。此外例如运行时间、是否加载了补丁、是否是全新安装或升级等信息也非常重要。

    除了上面这些通用的信息外,针对特定的一些崩溃,我们可能还需要获取类似磁盘空间、电量、网络使用等特定信息。所以说一个好的崩溃捕获工具,会根据场景为我们采集足够多的信息,让我们有更多的线索去分析和定位问题。当然数据的采集需要注意用户隐私,做到足够强度的加密和脱敏。

    崩溃分析

    有了这么多现场信息之后,我们可以开始真正的“破案”之旅了。绝大部分的“案件”只要我们肯花功夫,最后都能真相大白。不要畏惧问题,经过耐心和细心地分析,总能敏锐地发现一些异常或关键点,并且还要敢于怀疑和验证。下面我重点给你介绍崩溃分析“三部曲”。

    第一步:确定重点
    确认和分析重点,关键在于在曰志中找到重要的信息,对问题有一个大致判断。一般来说,我建议在确定重点这一步可以关注以下几点。

    1. 确认严重程度。解决崩溃也要看性价比,我们优先解决Top崩溃或者对业务有重大影响,例如启动、支付过程的崩溃。我曾经有一次辛苦了几天解决了一个大的崩溃,但下个版本产品就把整个功能都删除了,这令我很崩溃。
    2. 崩溃基本信息。确定崩溃的类型以及异常描述,对崩溃有大致的判断。一般来说,大部分的简单崩溃经过这一步已经可以得到结论。
      • Java崩溃。Java崩溃类型比较明显,比如NullPointerException是空指针,OutOfMemoryError是资源不足,这个时候需要去进一步查看日志中的“内存信息”和“资源信息”。
      • Native崩溃。需要观察signal、code、fault addr等内容,以及崩溃时Java的堆栈。关于各signal含义的介绍,你可以查看崩溃信号介绍。比较常见的是有SIGSEGV和SIGABRT,前者一般是由于空指针、非法指针造成,后者主要因为ANR和调用 abort()退出所导致。
      • ANR。我的经验是,先看看主线程的堆栈,是否是因为锁等待导致。接着看看ANR日志中iowait、CPU、GC、system server等信息,进一步确定是I/O问题,或是CPU竞争问题,还是由于大量GC导致卡死。
    3. Logcat。Logcat一般会存在一些有价值的线索,日志级别是Warning、Error的需要特别注意。从Logcat中我们可以看到当时系统的一些行为跟手机的状态,例如出现ANR时,会有“am_anr”; App被杀时,会有“am_kill”。不同的系统、厂商输出的日志有所差别,当从一条崩溃日志中无法看出问题的原因,或者得不到有用信息时,不要放弃,建议查看相同崩溃点下的更多崩溃日志。
    4. 各个资源情况。结合崩溃的基本信息,我们接着看看是不是跟“内存信息”有关,是不是跟“资源信息”有关。比如是物理内存不足、虚拟内存不足,还是文件句柄fd泄漏了。

    无论是资源文件还是Logcat,内存与线程相关的信息都需要特别注意,很多崩溃都是由于它们使用不当造成的。

    第二步:查找共性
    如果使用了上面的方法还是不能有效定位问题,我们可以尝试查找这类崩溃有没有什么共性。找到了共性,也就可以进一步找到差异,离解决问题也就更进一步。

    机型、系统、ROM、厂商、ABI,这些采集到的系统信息都可以作为维度聚合,共性问题例如是不是因为安装了Xposed,是不是只出现在x86的手机,是不是只有三星这款机型,是不是只在Android 5.0的系统上。应用信息也可以作为维度来聚合,比如正在打开的链接、正在播放的视频、国家、地区等。

    找到了共性,可以对你下一步复现问题有更明确的指引。

    第三步:尝试复现
    如果我们已经大概知道了崩溃的原因,为了进一步确认更多信息,就需要尝试复现崩溃。如果我们对崩溃完全没有头绪,也希望通过用户操作路径来尝试重现,然后再去分析崩溃原因。

    “只要能本地复现,我就能解”,相信这是很多开发跟测试说过的话。有这样的底气主要是因为在稳定的复现路径上面,我们可以采用增加日志或使用Debugger、GDB等各种各样的手段或工具做进一步分析。

    回想当时在开发Tinker的时候,我们遇到了各种各样的奇葩问题。比如某个厂商改了底层实现、新的Android系统实现有所更改,都需要去Google、翻源码,有时候还需要去抠厂商的ROM或手动刷ROM。这个痛苦的经历告诉我,很多疑难问题需要我们耐得住寂寞,反复猜测、反复发灰度、反复验证。

    疑难问题:系统崩溃

    系统崩溃常常令我们感到非常无助,它可能是某个Android版本的bug,也可能是某个厂商修改ROM导致。这种情况下的崩溃堆栈可能完全没有我们自己的代码,很难直接定位问题。针对这种疑难问题,我来谈谈我的解决思路。

    1. 查找可能的原因。通过上面的共性归类,我们先看看是某个系统版本的问题,还是某个厂商特定ROM的问题。虽然崩溃曰志可能没有我们自己的代码,但通过操作路径和曰志,我们可以找到一些怀疑的点。

    2. 尝试规避。查看可疑的代码调用,是否使用了不恰当的API,是否可以更换其他的实现方式规避。

    3. Hook解决。这里分为Java Hook和Native Hook。以我最近解决的一个系统崩溃为例,我们发现线上出现一个Toast相关的系统崩溃,它只出现在Android 7.0的系统中,看起来是在Toast显示的时候窗口的token已经无效了。这有可能出现在Toast需要显示时,窗口已经销毁了。

    android.view.WindowManager$BadTokenException:
    at android.view.ViewRootImpl.setView(ViewRootImpl.java)
    at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java4)
    at android.widget.Toast$TN.handleShow(Toast.java)
    

    为什么Android 8.0的系统不会有这个问题?在查看Android 8.0的源码后我们发现有以下修改:

    try {
    	mWM.addView(mView, mParams);
    	trySendAccessibilityEvent();
    } catch (WindowManager.BadTokenException e) {
    	/* ignore */
    }
    

    考虑再三,我们决定参考Android 8.0的做法,直接catch住这个异常。这里的关键在于寻找Hook点,这个案例算是相对比较简单的。Toast里面有一个变量叫mTN,它的类型为handler,我们只需要代理它就可以实现捕获。

    如果你做到了我上面说的这些,95%以上的崩溃都能解决或者规避,大部分的系统崩溃也是如此。当然总有一些疑难问题需要依赖到用户的真实环境,我们希望具备类似动态跟踪和调试的能力。专栏后面还会讲到xlog日志、远程诊断、动态分析等高级手段,可以帮助我们进一步调试线上疑难问题,敬请期待。

    崩溃攻防是一个长期的过程,我们希望尽可能地提前预防崩溃的发生,将它消灭在萌芽阶段。这可能涉及我们应用的整个流程,包括人员的培训、编译检查、静态扫描工作,还有规范的测试、灰度、发布流程等。

    而崩溃优化也不是孤立的,它跟我们后面讲到的内存、卡顿、I/O等内容都有关。可能等你学完整个课程后,再回头来看会有不同的理解。

    总结

    今天我们介绍了崩溃问题的一些分析方法、特殊技巧、以及疑难和常见问题的解决方法。当然崩溃分析要具体问题具体分析, 不同类型的应用侧重点可能也有所不同,我们不能只局限在上面所说的一些方法。

    讲讲自己的一些心得体会,在解决崩溃特别是一些疑难问题时,总会觉得患得患失。有时候解了一个问题,发现其他问题也跟“开心消消乐”一样消失了。有时候有些问题“解不出来郁闷,解出来更郁闷”,可能只是一个小的代码疏忽,换来了一个月的青春和很多根白头发。

    课后作业

    在崩溃的长期保卫战中,你肯定有一些经典的漂亮战役,希望可以拿出来跟其他同学分享。当然也会有一些百思不得其解的问题,今天的课后作业是分享你破解崩溃问题的思路和方法,总结一下通过Sample的练习有什么收获。

    如果想向崩溃发起挑战,那么Top 20崩溃就是我们无法避免的对手。在这里面会有不少疑难的系统崩溃问
    题,TimeoutException就是其中比较经典的一个。

    java.util.concurrent.TimeoutException:
    android.os.BinderProxy.finalize() timed out after 10 seconds
    at android.os.BinderProxy.destroy(Native Method)
    at android.os.BinderProxy.finalize(Binder.java:459)
    

    今天的Sample提供了一种“完全解决"TimeoutException的方法,主要是希望你可以更好地学习解决系统崩溃的套路。

    1. 通过源码分析。我们发现TimeoutException是由系统的FinalizerWatchdogDaemon抛出来的。
    2. 寻找可以规避的方法。尝试调用了它的Stop()方法,但是线上发现在Android 6.0之前会有线程同步问题。
    3. 寻找其他可以Hook的点。通过代码的依赖关系,发现一个取巧的Hook点。

    最终代码你可以参考Sample的实现,但是建议只在灰度中使用。这里需要提的是,虽然有一些黑科技可以帮助我们解决某些问题,但对于黑科技的使用我们需要慎重,比如有的黑科技对保活进程频率没有做限制,可能会导致系统卡死。

    补充⼀下获得logcat和Jave堆栈的方法

    获取logcat

    logcat⽇志流程是这样的,应⽤层 --> liblog.so --> logd,底层使⽤ring buffer来存储数据。
    获取的⽅式有以下三种:

    1. 通过logcat命令获取
      优点:⾮常简单,兼容性好。
      缺点:整个链路⽐较⻓,可控性差,失败率⾼,特别是堆破坏或者堆内存不⾜时,基本会失败。
    2. hook liblog.so实现。通过hook liblog.so 中__android_log_buf_write ⽅法,将内容重定向到⾃⼰的buffer中。
      优点:简单,兼容性相对还好。
      缺点:要⼀直打开。
    3. ⾃定义获取代码。通过移植底层获取logcat的实现,通过socket直接跟logd交互。
      优点:⽐较灵活,预先分配好资源,成功率也⽐较⾼。
      缺点:实现⾮常复杂

    获取Java 堆栈

    native崩溃时,通过unwind只能拿到Native堆栈。我们希望可以拿到当时各个线程的Java堆栈。

    1. Thread.getAllStackTraces()
      优点:简单,兼容性好。
      缺点:
      • 成功率不⾼,依靠系统接⼝在极端情况也会失败。
      • 7.0之后这个接⼝是没有主线程堆栈。
      • 使⽤Java层的接⼝需要暂停线程
    2. hook libart.so。通过hook ThreadList和Thread的函数,获得跟ANR⼀样的堆栈。为了稳定性,我们会在fork⼦进程执⾏。
      优点:信息很全,基本跟ANR的⽇志⼀样,有native线程状态,锁信息等等。
      缺点:⿊科技的兼容性问题,失败时可以⽤Thread.getAllStackTraces()兜底

    获取Java堆栈的⽅法还可以⽤在卡顿时,因为使⽤fork进程,所以可以做到完全不卡主进程。这块我们在后⾯会详细的去讲。

    课后作业指导

    这部分内容来自 sample 中的 readme.md。

    简介

    该例子主要演示了如何通过关闭FinalizerWatchdogDaemon来减少TimeoutException的触发。

    需要注意的是,此种方法并不是去解决问题,而是为了避免上报异常采取的一种 hack 方案,并没有真正的解决引起 finialize() 超时的问题。

    操作步骤

    1. 最好在模拟器下执行例子,因为各个手机设置的超时时长不同,不容易观看效果。
    2. 点击触发 Timeout按钮,等待10多秒后,应用会触发 TimeOut Crash,产生如下日志
    D/ghost: =============fire finalize=============FinalizerDaemon
    I/.watchdogkille: Thread[3,tid=4369,WaitingInMainSignalCatcherLoop,Thread*=0x76e6ece16400,peer=0x149802d0,"Signal Catcher"]: reacting to signal 3
    I/.watchdogkille: Wrote stack traces to '[tombstoned]'
    E/AndroidRuntime: FATAL EXCEPTION: FinalizerWatchdogDaemon
        Process: com.dodola.watchdogkiller, PID: 4363
        java.util.concurrent.TimeoutException: com.dodola.watchdogkiller.GhostObject.finalize() timed out after 10 seconds
            at java.lang.Thread.sleep(Native Method)
            at java.lang.Thread.sleep(Thread.java:373)
            at java.lang.Thread.sleep(Thread.java:314)
            at com.dodola.watchdogkiller.GhostObject.finalize(GhostObject.java:13)
            at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:250)
            at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:237)
            at java.lang.Daemons$Daemon.run(Daemons.java:103)
            at java.lang.Thread.run(Thread.java:764)
    I/Process: Sending signal. PID: 4363 SIG: 9
    
    1. 点击Kill WatchDog 按钮可以关闭 Timeout watchdog,然后点击触发 TimeOut 按钮观察情况,正常情况下不会产生 crash

    疑问点

    如果直接调用Daemons$FinalizerWatchdogDaemon的stop方法,在Android 6.0之前的版本可能会有问题。

    final Class clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
    final Field field = clazz.getDeclaredField("INSTANCE");
    field.setAccessible(true);
    final Object watchdog = field.get(null);
    final Method method = clazz.getSuperclass().getDeclaredMethod("stop");
    method.setAccessible(true);
    method.invoke(watchdog);
    

    这是为什么,你能告诉我们吗?

    其他问题

    1. 关于虚拟内存那块提到的32位进程和64位进程指的是native吗,我理解要是单纯的java层都是虚拟机解析字节码,根本不分32还是64的吧

    作者回复
    进程启动的时候,就会分zygote32跟zygote64 fork出来。

    2. 之前遇到过⼀个输⼊法的内存泄露,但是⾃⼰完全没有调⽤过输⼊法,后⾯查了⼀下⽹上说是系统的⼀个bug 张⽼师 这种应该怎么解决呢?

    作者回复
    那个问题很经典,解决⽅法就是反射将输⼊法的两个view置空。

    3. TimeoutException 直接调⽤stop存在什么安全问题?

    作者回复
    Stop的时候有⼀定概率导致即使没有超时也会报timeoutexception。

    4. 回答github上的问题:通过对⽐Android 6.0前后的源码发现,FinalizerWatchdogDaemon调⽤的是⽗类Deamon中的stop⽅法,区别在于:

    //6.0之前
    ...
    threadToStop.interrupt();
    ...
    //>=6.0
    ...
    interrupt(threadToStop);
    ...
    public synchronized void interrupt(Thread thread) {
    if (thread == null) {
    throw new IllegalStateException("not running");
    }
    thread.interrupt();
    }
    ...
    

    可以发现区别在于6.0之前调⽤threadToStop的interrupt⽅法是没有加锁的,所以可能会有线程同步的问题。
    PS:推荐⼀个在线看源码的⽹站,包括1.6到9.0全部的源码:http://androidxref.com/

    5. 张⽼师,看到你解决⼀些棘⼿的崩溃,会去翻看源码,然后对⽐版本的差异,然后寻找hook点去解决,这个要考虑机型适配吧?毕竟国产机型rom差异化严重。

    作者回复
    Hook的时候,前⼏个版本⼀般会把hook失败的机型 rom 堆栈传上来做进⼀步分析。top的机型也会保证可以兼容

    展开全文
  • Android死锁初探

    千次阅读 2020-05-21 07:30:00
    点击蓝字关注我们本文字数:7444字预计阅读时间:19分钟一、什么是死锁说到死锁,大家可能都不陌生,每次遇到死锁,总会让计算机产生比较严重的后果,比如资源耗尽,界面无响应等。死锁的精确定...

    点击蓝字关注我们

    本文字数:7444字

    预计阅读时间:19分钟

    一、什么是死锁

    说到死锁,大家可能都不陌生,每次遇到死锁,总会让计算机产生比较严重的后果,比如资源耗尽,界面无响应等。

    死锁的精确定义:

    集合中的每一个进程(或线程)都在等待只能由本集合中的其他进程(或线程)才能引发的事件,那么该组进程是死锁的。

    对于这个定义大家可能有点迷惑,换一种通俗的说法就是:

    死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    经典的“哲学家进餐问题”可以帮助我们形象的理解死锁问题。

    有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。

    当五个哲学家同时去取他左边的筷子,每人拿到一只筷子且不释放,即五个哲学家只得无限等待下去,这样就产生了死锁的问题。

    在计算机中也可以用有向图来描述死锁问题,首先假定每个线程为有向图中的一个节点,申请锁的线程A为起点, 拥有锁的线程B为终点,这样就形成线程A到线程B的一条有向边,而众多的锁(边)和线程(点), 就构成了一个有向图

    如果在有向图中形成一条环路,就会产生一个死锁,如上图所示。在很多计算机系统中,检测是否有死锁存在就是将问题抽象为寻找有向图中的环路。


    二、常见的死锁的场景

    下面分析几种常见的死锁形式:

    ????锁顺序死锁

    public class TestDeadLock {    private final Object lockA = new Object();    private final Object lockB = new Object();
        public void lockAtoB(){        synchronized (lockA){            synchronized (lockB){                doSomething();            }        }    }    public void lockBtoA(){        synchronized (lockB){            synchronized (lockA){                doSomething();            }        }    }    private void doSomething(){        System.out.println("doSomething");    }}
    

    上述代码中,如果一个线程调用lockAtoB(),另一个线程调用lockBtoA(),并且两个线程是交替执行,那么在程序运行期间是有一定几率产生死锁。

    而产生死锁的原因是:两个线程用不同的顺序去获取两个相同的锁,如果可以始终用相同的顺序,即每个线程都先获取lockA,然后再获取lockB,就不会出现循环的加锁依赖,也就不会产生死锁。

    当然上面的代码只是一个示例,实际的代码中不会这么简单,而有些函数中,虽然看似都是以相同的顺序加锁,但是由于外部调用的不确定性,仍然会导致实际以不同的顺序加锁而产生死锁。

    再看一个例子:

    //仓库public static interface IStore{    public void inCome(int count);    public void outCome(int count);}/** * 从 in 仓库 调用货物去 out仓库 * @param from * @param to * @param count 调用货物量 */public void transportGoods(IStore from,IStore to,int count){    synchronized (from){        synchronized (to){            from.outCome(count);//出仓库            to.inCome(count);//入仓库        }    }}
    

    货运公司将货物从一个仓库转运到另一个仓库,转运前,需要同时获得两个仓库的锁,以确保两个仓库中的货物数量是以原子方式更新。看起来这个函数都是以相同的顺序获取锁,但这只是函数内部的顺序,而真正的执行顺序,取决于外部传入的对象。

    transportGoods(storeA,storeB,100);
    transportGoods(storeB,storeA,40);
    

    如果用上述代码调用,在频繁的调用过程中,也很容易产生死锁。从上面的代码中可以看出,需要一个方法来确保在整个程序运行期间,锁都按照事先定义好的顺序来获取。这里提供一种方式:通过比较对象的hashcode值,来定义锁的获取顺序。

    下面来改造一下上述代码

    private static final Object extraLock = new Object();
    /**
     * 从 in 仓库 调用货物去 out仓库
     * @param from
     * @param to
     * @param count 调用货物量
     */
    public void transportGoods(IStore from,IStore to,int count){
        int fromHashCode = System.identityHashCode(from);
        int toHashCode = System.identityHashCode(to);
    
    
        if(fromHashCode > toHashCode){
            synchronized (from){
                synchronized (to){
                    transportGoodsInternal(from,to,count);
                }
            }
        }else if(fromHashCode < toHashCode){
            synchronized (to){
                synchronized (from){
                    transportGoodsInternal(from,to,count);
                }
            }
        }else {//hash散列冲突,需要用新的一个锁来保证这种低概率情况下不出现问题
            synchronized (extraLock){
                synchronized (from){
                    synchronized (to){
                        transportGoodsInternal(from,to,count);
                    }
                }
            }
        }
    }
    public void transportGoodsInternal(IStore from,IStore to,int count){
        from.outCome(count);//出仓库
        to.inCome(count);//入仓库
    }
    

    上述代码不难理解,使用hashcode的大小来唯一确定锁的顺序,需要值得注意的是,使用identityHashCode

    而不是对象自身的hashCode方法,这样可以降低用户重写hashcode后带来的冲突风险。具体可参考 System.identityHashCode(obj) 与 obj.hashcode()的区别

    当然使用identityHashCode也不能完全避免冲突,当identityHashCode也冲突的时候,引入了额外的一个锁extraLock,这个锁是static的,也就是说,整个应用程序只有一个,虽然理论上整个程序都使用一个 extraLock可能会导致并发性能的下降,但是考虑实际情况下,identityHashCode冲突的可能性非常小,所以并发性能问题也将不是问题。

    那么如果能从业务的层面找到IStore中唯一的,不可变的编码,例如,仓库在数据库中的唯一编码,就可以不使用hashcode了,也可以避免使用extraLock。当然这需要大家通过实际的业务逻辑来进行分析提取这个唯一编码。

    需要注意的是,使用hashcode这种方式是兼容性最好,成本最低也最不容易出错的方式,如果使用自有编码,你需要确保编码的唯一性,不可变性,这要保证这一点很不容易。

    ????多个对象协作发生的死锁

    之前讨论的死锁发生在一个对象内部,这样的死锁问题,比较明显,也容易发现。当互相调用的类变为两个或者更多,而两个类中又分别有各自加锁同步的逻辑,这样的死锁隐藏在代码逻辑中,不容易发现,也不容易寻找。

    首先来看一个例子。

    /** * 玩游戏者 */class Player {    private SystemMonitor monitor;    private int cardCount;//收集的卡片的数量
        public Player(SystemMonitor monitor) {        this.monitor = monitor;    }
        public synchronized int getCardCount() {        return cardCount;    }
        public synchronized void collectCard(int count){        cardCount += count;        if(cardCount >= 50){            monitor.notifyComplete(this);        }    }}
    /** * 监控系统 */class SystemMonitor {    private ArrayList<Player> playerArrayList;//所有玩家    private ArrayList<Player> completePlayerArrayList = new ArrayList<>();//完成的玩家
        //通知监控系统完成    public synchronized void notifyComplete(Player player){        System.out.println("玩家完成收集");        completePlayerArrayList.add(player);    }    //实时监控大家手中牌的数量    public synchronized void monitorAllPlayer(){        for (Player player : playerArrayList){            System.out.println("玩家有"+ player.getCardCount() + "张牌");        }    }}
    

    Player代表玩家,玩家收集完成,50张牌后通知监控系统自己完成游戏,而监控系统通过monitorAllPlayer来实时监控玩家目前手中的牌的数量。

    不难理解,在Player和SystemMonitor的方法中加锁,是为了避免数据的不一致性。粗略看这一段代码时,没有任何方法会显式的获取两个锁。

    但是collectCard方法与monitorAllPlayer方法由于调用了外部类的方法,所以他们其实是会拥有两个锁的。假设这样一种情形,当一个玩家收集满50张牌,他通知监控系统他已完成收集,玩家先后获取了Player对象的锁与SystemMonitor对象的锁,而这个时候,监控系统正在扫描所有玩家,而监控系统会先获取自身的锁,然后再获取玩家的锁。

    这样就有可能出现在两个线程中获取锁顺序不一致的情况,因此就有可能产生死锁。

    当一个对象的方法在持有锁期间调用外部方法,这时应该格外注意,因为无法显式判断外部方法是否有其他锁,而这样就有可能产生死锁。

    针对上述描述,该如何避免死锁呢?

    首先引入一个术语开放调用,即调用某个方法的时候,不需要持有锁,这种调用称为开放调用。通过尽可能地使用开放调用,更容易找出其他锁的路径,也更容易保证加锁的顺序,以此来避免死锁问题。

    上述的代码很容易修改为开放调用,此时需要做的就是缩小锁的粒度,使得同步方法只用来保护真正需要保护的变量或者代码段。

    public void collectCard(int count){    boolean isComplete = false;    synchronized (this){       cardCount += count;       if(cardCount >= 50){           isComplete = true;       }    }    if(isComplete){        monitor.notifyComplete(this);    }}
    
    
    //实时监控大家手中牌的数量
    public void monitorAllPlayer(){
        ArrayList<Player> copy;
        synchronized (this){
            copy = new ArrayList<>(playerArrayList);
        }
        for (Player player : copy){
            System.out.println("玩家有"+ player.getCardCount() + "张牌");
        }
    }
    


    ????线程饥饿死锁

    在线程池中,如果任务依赖于其他任务,就可能产生死锁。举一个简单的单线程Executor的例子,如果任务A已经在Executor中运行,而任务A又向相同的Executor中提交了一个任务B,通常情况下,这样会产生死锁。

    任务B在队列中一直等待任务A完成,而任务A由于是在单线程Executor中,所以又在等待任务B执行完成,这样就造成了死锁。在更大的线程池中,考虑极限情况,如果所有正在执行任务的线程,都在等待之前提交到线程池中排队的任务,这样线程会永远等待下去,这种问题称为线程饥饿死锁。

    下面的代码展示了线程饥饿死锁。

    private ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());;
    
    
    @Test
    public void test() throws ExecutionException, InterruptedException {
        int count = 0;
        while (true) {
            System.out.println("开始 = " + (count));
            start();
            System.out.println("结束 = " + (count++));
            Thread.sleep(10);
        }
    }
    
    
    public void start() throws ExecutionException, InterruptedException {
        Callable<String> second = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(100);
                return "second callable";
            }
        };
    
    
        Callable<String> first = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(10);
                Future<String> secondFuture = executor.submit(second);
                String secondResult = secondFuture.get();
                Thread.sleep(10);
                return "first callable. second result = " + secondResult;
            }
        };
    
    
        List<Future<String>> futures = new ArrayList<>();
        for(int i = 0; i< 5; i++){
            System.out.println("submit : " + i);
            Future<String> firstFuture = executor.submit(first);
            futures.add(firstFuture);
        }
        for(int i = 0; i < 5; i++){
            String firstrResult = futures.get(i).get();
            System.out.println(firstrResult + ":" + i);
        }
    }
    



    三、Android系统处理死锁方案

    Android系统的Framework层有一个WatchDog用于定期检测关键系统服务是否发生死锁。WatchDog功能主要是分析系统核心服务和重要线程是否处于Blocked状态。

    下面我们以Android 9.0为例分析WatchDog的实现原理。通过分析源码,也可以给自己实现一套死锁监控提供一些思路。源码见:WatchDog

    看源码之前,可以先自己思考下,如果让我们去实现一个WatchDog,我们会如何设计。其实原理倒是不难,无外乎需要做两件事情。

    1. 定期轮询检测系统中核心的线程的状态

    2. 检测到卡死后,将相关对应的线程,进程及其他软硬件信息输出

    其实WatchDog也是这么设计的。WatchDog是继承自Thread,那么我们分析它的工作流程也就从run方法开始吧。

    为了方便代码展示,下面源码只保留一些关键代码。run方法是整个检测的核心,我在代码片段里面标注了“代码关键点*”字样,方便在文中引用定位。

        public void run() {        boolean waitedHalf = false;        while (true) {//我们要在Android系统运行的整个过程中监控,当然我们需要一个死循环            final List<HandlerChecker> blockedCheckers;            final String subject;            final boolean allowRestart;            int debuggerWasConnected = 0;            synchronized (this) {                long timeout = CHECK_INTERVAL;                 //代码关键点1                for (int i=0; i<mHandlerCheckers.size(); i++) {                    HandlerChecker hc = mHandlerCheckers.get(i);                    hc.scheduleCheckLocked();                }                ....                //代码关键点2                long start = SystemClock.uptimeMillis();                while (timeout > 0) {                ...                    try {                        wait(timeout);                    } catch (InterruptedException e) {                        Log.wtf(TAG, e);                    }                ...                    timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);                }                ...                //代码关键点3                boolean fdLimitTriggered = false;                if (mOpenFdMonitor != null) {                    fdLimitTriggered = mOpenFdMonitor.monitor();                }               //代码关键点4                if (!fdLimitTriggered) {                    final int waitState = evaluateCheckerCompletionLocked();                    if (waitState == COMPLETED) {                        waitedHalf = false;                        continue;                    } else if (waitState == WAITING) {                        continue;                    } else if (waitState == WAITED_HALF) {                        if (!waitedHalf) {                            ArrayList<Integer> pids = new ArrayList<Integer>();                            pids.add(Process.myPid());                            ActivityManagerService.dumpStackTraces(true, pids, null, null,                                getInterestingNativePids());                            waitedHalf = true;                        }                        continue;                    }                    blockedCheckers = getBlockedCheckersLocked();                    subject = describeCheckersLocked(blockedCheckers);                } else {                    blockedCheckers = Collections.emptyList();                    subject = "Open FD high water mark reached";                }                allowRestart = mAllowRestart;            }
                //代码关键点5            //代码运行到这里,说明系统已经卡死
                final File stack = ActivityManagerService.dumpStackTraces(                    !waitedHalf, pids, null, null, getInterestingNativePids());            doSysRq('w');            doSysRq('l');            ...             IActivityController controller;            if (controller != null) {
                ...             int res =controller.systemNotResponding(subject);            if (res >= 0) {              ...               continue;             }             }            // 代码关键点6            if (Debug.isDebuggerConnected()) {                debuggerWasConnected = 2;            }            if (debuggerWasConnected >= 2) {            } else if (debuggerWasConnected > 0) {            } else if (!allowRestart) {            } else {//只有这种情况下,杀死system_server            ...           //代码关键点6                Process.killProcess(Process.myPid());                System.exit(10);            }            waitedHalf = false;        }    }
    

    整个run方法是一个死循环,这也是可以理解的,毕竟WatchDog需要在Android系统的整个运行期间进行监测。

    在“代码关键点1”这里,通过遍历所有需要检测的线程,需要检测的线程集合是在WatchDog的构造函数中初始化的。

     private Watchdog() {
            super("watchdog");
          ... 
            mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
                    "foreground thread", DEFAULT_TIMEOUT);
            mHandlerCheckers.add(mMonitorChecker);
            mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
                    "main thread", DEFAULT_TIMEOUT));
            mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
                    "ui thread", DEFAULT_TIMEOUT));
            mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
                    "i/o thread", DEFAULT_TIMEOUT));
            mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
                    "display thread", DEFAULT_TIMEOUT));
            addMonitor(new BinderThreadMonitor());
            mOpenFdMonitor = OpenFdMonitor.create();//这个monitor有额外作用,后面我们会有提到
          ...
        }
    

    WatchDog构造函数中,初始化了我们要监控的系统线程。包含FgThread,主线程,UiThread,IoThread,DisplayThread,Binder通信线程。

    (需要着重说明的是监控FgThread的mMonitorChecker通过向外部暴露接口,通过调用WatchDog的addMonitor方法,来监控所有实现了Monitor接口的服务。)

        public void addMonitor(Monitor monitor) {    				....            mMonitorChecker.addMonitor(monitor);        }    }
    

    代码中的HandlerChecker便是今天的主角之一,它的主要作用就是用来检测线程是否卡死。在“代码关键点1”的循环中,调用了scheduleCheckLocked,而这个方法是HandlerChecker的核心。

    下面HandlerChecker代码片段,这个方法通过postAtFrontOfQueue向被监控线程的Handler消息队列的头部插入当前HandlerChecker,如果被监控线程消息执行正常,则会回调HandlerChecker的run方法,在run方法里面遍历所有Monitor对象(实现Monitor接口的服务很多,包含AMS,WMS,IMS等),执行monitor方法,如果服务正常,最后我们便会将mCompleted置为true。

    这个mCompleted变量就是后续WatchDog用来判断对应线程是否卡死依据。

    public final class HandlerChecker implements Runnable {        ...        private final Handler mHandler;        private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();        private boolean mCompleted;        ...        public void scheduleCheckLocked() {            if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {//特殊的条件,需要注意,下面有解释                mCompleted = true;                return;            }            if (!mCompleted) {                return;            }            mCompleted = false;            mCurrentMonitor = null;            mStartTime = SystemClock.uptimeMillis();            mHandler.postAtFrontOfQueue(this);        }        ...        @Override        public void run() {            final int size = mMonitors.size();            for (int i = 0 ; i < size ; i++) {                synchronized (Watchdog.this) {                    mCurrentMonitor = mMonitors.get(i);                }                mCurrentMonitor.monitor();            }            synchronized (Watchdog.this) {                mCompleted = true;                mCurrentMonitor = null;            }        }    }
    

    scheduleCheckLocked方法中有一个代码引起了我们的注意,如果mHandler.getLooper().getQueue().isPolling()为true,那么直接将mCompleted置为true,这又是什么原理。

    通过查阅MessageQueue源码,里面的一段注释解决了我们的迷惑。

    Returns whether this looper's thread is currently polling for more work .This is a good signal that the loop is still alive rather than being stuck handling a callback

    这段话含义就是isPolling表示正在从队列中取消息,为true则代表Looper依然运行良好,通过这个标记就不需要等待回调来得知状态,这样效率更高。

    了解了检测卡死的原理,那我们继续回到WatchDog的run方法,来看“代码关键点2”。通过wait方法实现了每30s检测一次的效果,这里看到了Google工程师的一个小技巧,由于wait的timeout时间可能没那么准确,为了保证至少等待30s,使用了一个while循环,并且循环完毕通过timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);来保证时间够30s。

    “代码关键点3”中使用了OpenFdMonitor,这个类的主要作用是为了判断剩余可用文件句柄的数量,大家知道Linux中打开文件都需要分配文件句柄,系统的文件句柄数量是有限制的。当然这个OpenFdMonitor只在编译模式为userdebug 和 eng的Android编译版本起作用,这也是为了方便开发人员调试信息。

    “代码关键点4”中evaluateCheckerCompletionLocked便是用来评估当前所有线程的卡死情况

    private int evaluateCheckerCompletionLocked() {    int state = COMPLETED;    for (int i=0; i<mHandlerCheckers.size(); i++) {        HandlerChecker hc = mHandlerCheckers.get(i);        state = Math.max(state, hc.getCompletionStateLocked());    }    return state;}
    

    代码获取了当前线程中状态值最大的state;state的定义如下:

    COMPLETED = 0;已完成,不存在卡死情况

    WAITING = 1; 等待时间小于DEFAULT_TIMEOUT的一半,即<30s

    WAITED_HALF = 2; 等待时间超过DEFAULT_TIMEOUT的一半,即>=30s

    OVERDUE = 3;等待时间大于等于DEFAULT_TIMEOUT ,即 >=60s

    如果有线程状态已经是OVERDUE,那么说明被监控的线程有卡死情况。我们的流程也来到了“代码关键点5”。这里就比较好理解了,通过dumpStackTraces输出kernel栈信息,通过doSysRq触发系统dump所有阻塞线程堆栈。这样所有相关的信息就保存好了。

    “ 代码关键点6”中,以下几种情况,即使触发了WatchDog,也不杀死系统进程。

    • debuggerWasConnected>=0 debuggerWasConnected>=2 代表debugger正在连接调试中

    • allowRestart设置为true,是通过adb logcat am hang命令设置的

    最后通过下面两行代码将SystemServer进程杀死,当system_server被杀后,就会导致Zygote进程自杀,进而做到Zygote进程的重启。而这个现象也就是我们平常看到了手机死机了,然后又自动重启的现象。

    Process.killProcess(Process.myPid()); 

    System.exit(10);


    四、Android开发过程中死锁分析方法

    分析完系统如何处理死锁情况后,我们再来看看在Android开发中最容易碰到的死锁表现形式ANR。

    当然产生ANR的原因很多,死锁只是其中一种。如果ANR发生,对应的应用会收到SIGQUIT异常终止信号,dalvik虚拟机就会自动在/data/anr/目录下生成trace.txt(Android8.1以后文件名不是这个了)文件,这个文件记录了在发生ANR时刻系统各个线程的执行状态,trace文件中记录的线程执行状态详细描述了各个线程加锁等待的情况。

    通过分析,就可以相对容易的找到发生死锁所在的线程及代码。

    主线程死锁导致的问题,可以通过ANR的trace文件分析,如果是非主线程呢,这种死锁一般很难察觉,但是这种死锁有时候也会造成很严重的后果,因为线程可能一直在占用某些资源,比如端口,数据库连接,文件句柄等。对于普通的java程序,JVM提供了jstack工具,可以将线程信息dump出来进行分析。

    由于Android系统中没有提供类似jstack的工具,

    这里笔者给大家提供两种方法来检查是否有线程发生死锁。

    1.借助Android Studio的调试工具

    首先通过工具栏Run->Attach to Process 

    或者快捷入口  ,将App的进程attach进去。

    然后在Android Debugger窗口中,找到Get Thread Dump按钮,点击后,稍等片刻,Androd Studio就会将对应调试进程的线程堆栈信息dump出来。

    下图就是得到的线程信息,这样就可以分析线程中的死锁了。

    1. 借助ANR机制

    Android应用发生ANR时,系统会发出SIGQUIT信号给发生ANR的进程。利用系统这个机制,当监控线程发现被监控线程卡死时,主动向系统发送SIGQUIT信号,等待/data/anr/traces.txt文件生成。这样可以得到一个和ANR日志相同的线程堆栈信息,这样分析死锁的问题就和之前分析ANR那个trace文件就一样了。

    下面我举一个具体例子来看看如何借助发送SIGQUIT信号来生成trace文件。

    首先我们通过ps命令拿到我们进程的进程id

    adb shell ps | grep com.sohu.sohuvideo
    

    这取主进程 id : 22841,执行如下命令

    adb shell run-as com.sohu.sohuvideo kill -3 22841
    

    紧接着会在logcat中输出日志 com.sohu.sohuvideo I/.sohu.sohuvide: Wrote stack traces to '[tombstoned]',这时我们的trace文件便已经生成好了(这里需要注意Android8.1之前输出的日志为 Wrote stack traces to traces.txt)。

    在这里我们需要注意,run-as 命令需要在debug包下面才管用,如果是Release包则不行。

    如果是Android8.1之前的系统那么我们就可以愉快的通过 adb pull /data/anr/traces.txt 命令直接将文件拿到了,但是8.1之后trace文件便没有权限直接可以拿到了。这里我们可能想到了用adb shell bugreport命令来导出trace文件,但是当我们兴奋的打开bugreport文件,找到anr文件夹,却发现里面只有app真正发生anr时候的trace文件,却没有我们刚刚用命令执行完毕后生成的文件。

    通过adb shell直接进入手机目录查看发现,该目录下有我们刚刚生成的文件dumptrace_YbVvLP,只不过bugreport没有将其导出。

    最后经过一番探索终于找到一个途径,这样绕过了系统的权限,终于将我们自己生产的trace文件导出了。

    adb shell cat /data/anr/dumptrace_YbVvLP > ~/Desktop/dump
    

    结束语

    死锁问题是一个老大难问题,而且只要有死锁,一般都会引起严重的后果,我们需要不断强化自己的编程能力,写代码的过程中遇到多线程加锁同步的问题,多思考是否会产生死锁,只有多思考,多实践,才能将死锁问题发生的频率降到最低。

    参考资料

    1. 《手Q Android线程死锁监控与自动化分析实践》

    2. 《System.identityHashCode(obj) 与 obj.hashcode()》(https://www.jianshu.com/p/24fa4bdb9b9d)

    也许你还想看

    (▼点击文章标题或封面查看)

    文章推荐DialogFragment引起的内存泄露Android NestedScrolling 机制-基础篇Realm.Object与NSObject的转换中swift协议妙用的体现App 黑白化实现探索,有一行代码实现的方案吗?

    加入搜狐技术作者天团

    千元稿费等你来!

    戳这里!☛

    展开全文
  • Android tombstone文件是如何生成的

    千次阅读 2020-03-14 14:44:08
    当android系统出现异常时,会在/data/tombstones目录生成对应的tombstone文件 root:/data/tombstones # ls -l -rw-r----- 1 tombstoned system 3454991 2020-03-13 18:10 tombstone_00 -rw-rw-rw- 1 root root 0 ...

    本节内容我们聚焦到androidQ上,分析android中一个用于debug的功能,那就是tombstone,俗称“墓碑”。现实生活中墓碑一般是给死人准备的,而在android系统中“墓碑”则是给进程准备的。

    为何Android要设计出这样一个东西呢? 因为android系统是运行在Linux Kernel内核之上的,当内核出现异常,则内核异常机制会分辨出是什么原因,处理不了的直接panic。而对于运行在Linux Kernel内核之上的android系统,如果出现异常,一般会自动重启android层的,这就导致问题很难复现定位debug,则当android层出现异常,通常会将进程的上下文信息保存到tombstone(墓碑)中,方便后续的debug调试。

                   

    上图是一张经典的android系统架构图,而我们的墓碑主要是给Native 层的进程准备的,主要用于分析NativeCrash。因为Kernel Crash整个系统直接就panic了,内核会打印出对应的call trace,对于Java层的代码出错也会有对应的异常抛出的。所以墓碑主要是给Native层的进程准备的。

    Tombstone初识

    tombstone到底长啥样呢? 当android系统出现异常时,会在/data/tombstones目录生成对应的tombstone文件

    root:/data/tombstones # ls -l
    -rw-r----- 1 tombstoned system 3454991 2020-03-13 18:10 tombstone_00
    -rw-rw-rw- 1 root       root         0 2020-03-14 10:28 tombstone_01
    -rw-r----- 1 root       root   3454991 2020-03-14 10:29 tombstone_02
    -rw-r----- 1 root       root   3454991 2020-03-14 10:29 tombstone_03
    

    打开一个文件,看看tombstone到底长啥样

    *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
    Build fingerprint: 'dev/good_dev/2020.3.6_china_dev_test:user/test-keys'
    Revision: '0'
    ABI: 'arm'
    Timestamp: 2020-03-07 02:46:27+0800
    pid: 23051, tid: 23051, name: .tencent.qqlive  >>> com.tencent.qqlive <<<
    uid: 10256
    signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdb3fb000
        r0  db3f9000  r1  00000000  r2  00023780  r3  db3fb000
        r4  cd5ef340  r5  db3f9000  r6  e7a1e260  r7  cd62c6e0
        r8  cd668190  r9  ffc417a8  r10 00000001  r11 d1ffa760
        ip  00000000  sp  ffc41768  lr  b9466590  pc  e79bfd58
    
    backtrace:
          #00 pc 0005dd58  /apex/com.android.runtime/lib/bionic/libc.so (memset_a7+48) (BuildId: dcf0e174e93e33d22f35a631ba9c0de5)
          #01 pc 0001258c  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so (LogBuffer::__Clear()+36) (BuildId: 05f64491a5d2ace3d88e1623fefbc695c32396ba)
          #02 pc 0000ff68  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so (appender_open(TAppenderMode, char const*, char const*, char const*)+560) (BuildId: 05f64491a5d2ace3d88e1623fefbc695c32396ba)
          #03 pc 00010ca8  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so (appender_open_with_cache(TAppenderMode, std::string const&, std::string const&, char const*, char const*)+1652) (BuildId: 05f64491a5d2ace3d88e1623fefbc695c32396ba)
          #04 pc 000051cc  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so (Java_com_tencent_mars_xlog_Xlog_appenderOpen+428) (BuildId: 05f64491a5d2ace3d88e1623fefbc695c32396ba)
          #05 pc 000db673  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/oat/arm/base.odex (art_jni_trampoline+210)

    此文件比较长,我们目前只贴一部分,本节的内容不是分析tombstone内容的含义,本节重点分析此文件生成的过程,明白了是如何生成的,后续再分析此文件的内容是什么含义,以及如何去分析解决此类问题。

    NULL指针例子回顾

    我们在上一节NULL指针的奇妙之旅中详细讲解了当CPU去访问NULL指针,操作系统内部的一系列活动,最后在控制台打印出耳熟能详的"Segmetation Faule"。在这里我们回顾下,因为这个过程可以套用今天的tombstone的生产过程。

    • 当CPU去访问一个虚拟地址,肯定会经过MMU去查对应的虚实关系的
    • 一旦虚拟地址是非法的,MMU硬件单元则会触发异常,CPU则去异常向量表执行对应的异常
    • 经过处理后Linux内核对userspace的异常则通过信号的方式通知给对应的进程
    • 当进程一旦收到信号,则会执行对应的信号处理函数。
    • 信号处理函数的安装一般会在glibc中做的,glibc会对所有的通用信号做默认的处理的。

    回到android系统中,当一个Native的进程触发了NULL指针,首先CPU会收到对应异常,然后去执行异常,接着会通过发生SIGSEGV的信号,信号处理函数则会去处理信号,处理信号的过程中,则就会保存进程的现场,最后留下墓碑供后人膜拜。

    通过上面的描述,我们大概已经推测出tombstone的大致实现流程了,接下来就去验证猜想了。

     

    进程是如何运行起来的

    这里简单描述下android中一个进程是如何跑起来的。这里以微信app为例子说明

    • 微信app首先是存储在UFS,EMMC指令的存储设备上
    • 当用户去点击微信app图标时,操作系统则会将微信app从Flash load到主存中
      • 肯定要去通过fork类似命令去创建对应的进程
      • 进程创建完毕需要通过exec类似的命令去加载微信的内容
    • 最后由/system/bin/linker程序负责加载微信程序用到的一些共享库,
    • 最终跳转到微信程序的入口处执行

    以上就是一个简单的描述一个程序时如何运行起来的,我们直接看下android系统中/system/bin/linker代码

    代码路径: /bionic/linker/arch/arm64/begin.S
    
    ENTRY(_start)
      // Force unwinds to end in this function.
      .cfi_undefined x30
    
      mov x0, sp
      bl __linker_init
    
      /* linker init returns the _entry address in the main image */
      br x0
    END(_start)
    

    首先肯定是跳转到linker的代码段去执行,跳转到__linker_init函数处

    /*
     * This is the entry point for the linker, called from begin.S.
     */
    extern "C" ElfW(Addr) __linker_init(void* raw_args) {
    
      tmp_linker_so.base = linker_addr;
      tmp_linker_so.size = phdr_table_get_load_size(phdr, elf_hdr->e_phnum);
      tmp_linker_so.load_bias = get_elf_exec_load_bias(elf_hdr);
      tmp_linker_so.dynamic = nullptr;
      tmp_linker_so.phdr = phdr;
      tmp_linker_so.phnum = elf_hdr->e_phnum;
      tmp_linker_so.set_linker_flag();
    
      return __linker_init_post_relocation(args, tmp_linker_so);
    }

    在Linker_init中会根据链接地址,以及elf的头等信息,去重新计算是否需要重定位之类的

    static ElfW(Addr) __attribute__((noinline))
    __linker_init_post_relocation(KernelArgumentBlock& args, soinfo& tmp_linker_so) {
    
      // Initialize the linker's static libc's globals
      __libc_init_globals();
    
      // Initialize the linker's own global variables
      tmp_linker_so.call_constructors();
    
      ElfW(Addr) start_address = linker_main(args, exe_to_load);
    
      INFO("[ Jumping to _start (%p)... ]", reinterpret_cast<void*>(start_address));
    }

    通过linker_main函数返回可执行程序的开始地址,然后跳转过去执行。我们重点看下linker_main函数

    static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
      ProtectedDataGuard guard;
    
      // Register the debuggerd signal handler.
    #ifdef __ANDROID__
      debuggerd_callbacks_t callbacks = {
        .get_abort_message = []() {
          return __libc_shared_globals()->abort_msg;
        },
        .post_dump = &notify_gdb_of_libraries,
      };
      debuggerd_init(&callbacks);
    #endif
    
    

    重点关注这里,当时android系统的话,则会去初始化debggerd_init,此函数中会按照信号的默认处理函数的

    void debuggerd_init(debuggerd_callbacks_t* callbacks) {
    
      struct sigaction action;
      memset(&action, 0, sizeof(action));
      sigfillset(&action.sa_mask);
      action.sa_sigaction = debuggerd_signal_handler;
      action.sa_flags = SA_RESTART | SA_SIGINFO;
    
      // Use the alternate signal stack if available so we can catch stack overflows.
      action.sa_flags |= SA_ONSTACK;
      debuggerd_register_handlers(&action);
    }
    
    static void __attribute__((__unused__)) debuggerd_register_handlers(struct sigaction* action) {
      sigaction(SIGABRT, action, nullptr);
      sigaction(SIGBUS, action, nullptr);
      sigaction(SIGFPE, action, nullptr);
      sigaction(SIGILL, action, nullptr);
      sigaction(SIGSEGV, action, nullptr);
    #if defined(SIGSTKFLT)
      sigaction(SIGSTKFLT, action, nullptr);
    #endif
      sigaction(SIGSYS, action, nullptr);
      sigaction(SIGTRAP, action, nullptr);
      sigaction(DEBUGGER_SIGNAL, action, nullptr);
    }

    可以看到这里注册了一些异常信号,而信号的action处理函数是debuggerd_signal_handler。

    当异常发生

    比如当Native进程出现了null指针问题,则通过linux内核判断会发生信号,最终信号由debuggerd_signal_handler函数处理

    debuggerd_signal_handler

    static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* context) {
    
      // Only allow one thread to handle a signal at a time.
      int ret = pthread_mutex_lock(&crash_mutex);
      if (ret != 0) {
        async_safe_format_log(ANDROID_LOG_INFO, "libc", "pthread_mutex_lock failed: %s", strerror(ret));
        return;
      }
    
      log_signal_summary(info);
    }

    使用pthread_mutex_lock防止同一时间多个线程同时来处理信号,后面则调用log_signal_summary函数来打印一些信息

      async_safe_format_log(ANDROID_LOG_FATAL, "libc",
                            "Fatal signal %d (%s), code %d (%s%s)%s in tid %d (%s), pid %d (%s)",
                            info->si_signo, get_signame(info), info->si_code, get_sigcode(info),
                            sender_desc, addr_desc, __gettid(), thread_name, self_pid, main_thread_name)

    这里这么做的目的是防止后面动作出错,最终不能确定是那个进程出错的,此处先打印一些关键信息。可以从logcat中找到对应的信息

     libc    : Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdb3fb000 in tid 23051 (.tencent.qqlive), pid 23051 (.tencent.qqlive)
    • 信号的num,比如信号11代表的是SIGSEGV
    • 信号code,SEGV_MAPERR,就代表映射出错了
    • fault addr,出错时的地址
    • tid: 对应的线程ID
    • pid: 对应的进程ID,如果一个进程中有好多线程,则每个线程的id是不一样的。
      pid_t child_pid =
        clone(debuggerd_dispatch_pseudothread, pseudothread_stack,
              CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
              &thread_info, nullptr, nullptr, &thread_info.pseudothread_tid);
      if (child_pid == -1) {
        fatal_errno("failed to spawn debuggerd dispatch thread");
      }
    
      // Wait for the child to start...
      futex_wait(&thread_info.pseudothread_tid, -1);
    
      // and then wait for it to terminate.
      futex_wait(&thread_info.pseudothread_tid, child_pid);

    接着是通过clone系统调用,clone出一个伪线程pseudothread线程,去dispatch处理信号,这里则等待子进程的开始以及结束

    debuggerd_dispatch_pseudothread

      pid_t crash_dump_pid = __fork();
      if (crash_dump_pid == -1) {
        async_safe_format_log(ANDROID_LOG_FATAL, "libc",
                              "failed to fork in debuggerd signal handler: %s", strerror(errno));
      } else if (crash_dump_pid == 0) {
       
        async_safe_format_buffer(main_tid, sizeof(main_tid), "%d", thread_info->crashing_tid);
        async_safe_format_buffer(pseudothread_tid, sizeof(pseudothread_tid), "%d",
                                 thread_info->pseudothread_tid);
        async_safe_format_buffer(debuggerd_dump_type, sizeof(debuggerd_dump_type), "%d",
                                 get_dump_type(thread_info));
    
        execle(CRASH_DUMP_PATH, CRASH_DUMP_NAME, main_tid, pseudothread_tid, debuggerd_dump_type,
               nullptr, nullptr);
        async_safe_format_log(ANDROID_LOG_FATAL, "libc", "failed to exec crash_dump helper: %s",
                              strerror(errno));
        return 1;
      }

    在伪线程中通过fork去创建子线程,新创建的子线程中调动通过execle系统调用去执行crash_dump64程序,而父进程则在这里等待crash_dump进程退出

    crash_dump进程

      pid_t forkpid = fork();
      if (forkpid == -1) {
        PLOG(FATAL) << "fork failed";
      } else if (forkpid == 0) {
        fork_exit_read.reset();
      } else {
        // We need the pseudothread to live until we get around to verifying the vm pid against it.
        // The last thing it does is block on a waitpid on us, so wait until our child tells us to die.
        fork_exit_write.reset();
        char buf;
        TEMP_FAILURE_RETRY(read(fork_exit_read.get(), &buf, sizeof(buf)));
        _exit(0);
      }

    crash_dump进程则直接通过fork出一个新进程,父进程通过read去等待子进程,而子进程在继续执行crash_dump的任务

      // Get the process name (aka cmdline).
      std::string process_name = get_process_name(g_target_thread);
    
      // Collect the list of open files.
      OpenFilesList open_files;
      {
        ATRACE_NAME("open files");
        populate_open_files_list(&open_files, g_target_thread);
      }
    • 获取进程的name,通过/proc/PID/cmdline获取进程的名字
    • 获取此进程总共打开了多个文件,通过/proc/PID/fd/就可以获取此进程打开了多少个文件,每个文件都有一个文件描述符fd
    {
        ATRACE_NAME("ptrace");
        for (pid_t thread : threads) {
          // Trace the pseudothread separately, so we can use different options.
          if (thread == pseudothread_tid) {
            continue;
          }
    
          if (!ptrace_seize_thread(target_proc_fd, thread, &error)) {
            bool fatal = thread == g_target_thread;
            LOG(fatal ? FATAL : WARNING) << error;
          }
    
          ThreadInfo info;
          info.pid = target_process;
          info.tid = thread;
          info.uid = getuid();
          info.process_name = process_name;
          info.thread_name = get_thread_name(thread);
    
          if (!ptrace_interrupt(thread, &info.signo)) {
            PLOG(WARNING) << "failed to ptrace interrupt thread " << thread;
            ptrace(PTRACE_DETACH, thread, 0, 0);
            continue;
          }
    
          if (thread == g_target_thread) {
            // Read the thread's registers along with the rest of the crash info out of the pipe.
            ReadCrashInfo(input_pipe, &siginfo, &info.registers, &abort_msg_address,
                          &fdsan_table_address);
            info.siginfo = &siginfo;
            info.signo = info.siginfo->si_signo;
          } else {
            info.registers.reset(unwindstack::Regs::RemoteGet(thread));
            if (!info.registers) {
              PLOG(WARNING) << "failed to fetch registers for thread " << thread;
              ptrace(PTRACE_DETACH, thread, 0, 0);
              continue;
            }
          }
    
          thread_info[thread] = std::move(info);
        }
      }

    for循环遍历这个进程中的所有线程,对每一个进程中的线程进程ptrace操作,对目标线程读取其crashinfo。

      // Detach from all of our attached threads before resuming.
      for (const auto& [tid, thread] : thread_info) {
        int resume_signal = thread.signo == DEBUGGER_SIGNAL ? 0 : thread.signo;
        if (wait_for_gdb) {
          resume_signal = 0;
          if (tgkill(target_process, tid, SIGSTOP) != 0) {
            PLOG(WARNING) << "failed to send SIGSTOP to " << tid;
          }
        }
    
        LOG(DEBUG) << "detaching from thread " << tid;
        if (ptrace(PTRACE_DETACH, tid, 0, resume_signal) != 0) {
          PLOG(ERROR) << "failed to detach from thread " << tid;
        }
      }

    读取crashinfo完毕后,则对每个thead做detach操作

    tombstoned_connect

      {
        ATRACE_NAME("tombstoned_connect");
        LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type;
        g_tombstoned_connected =
            tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd, dump_type);
      }

    连接到tombstone进程,通过socket连接的。

      // si_value is special when used with DEBUGGER_SIGNAL.
      //   0: dump tombstone
      //   1: dump backtrace
      if (!fatal_signal) {
        int si_val = siginfo.si_value.sival_int;
        if (si_val == 0) {
          backtrace = false;
        } else if (si_val == 1) {
          backtrace = true;
        } else {
          LOG(WARNING) << "unknown si_value value " << si_val;
        }
      }

    根据tombstone传递的参数做不同的判断,当参数=0时代表dump tombstone,等于1时,只dump backtrace

      if (backtrace) {
        ATRACE_NAME("dump_backtrace");
        dump_backtrace(std::move(g_output_fd), &unwinder, thread_info, g_target_thread);
      } else {
        {
          ATRACE_NAME("fdsan table dump");
          populate_fdsan_table(&open_files, unwinder.GetProcessMemory(), fdsan_table_address);
        }
    
        {
          ATRACE_NAME("engrave_tombstone");
          engrave_tombstone(std::move(g_output_fd), &unwinder, thread_info, g_target_thread,
                            abort_msg_address, &open_files, &amfd_data);
        }
      }

    最终tombstone是通过engrave_tombstone来进程生成的。

    engrave_tombstone

    void engrave_tombstone(unique_fd output_fd, unwindstack::Unwinder* unwinder,
                           const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
                           uint64_t abort_msg_address, OpenFilesList* open_files,
                           std::string* amfd_data) {
      // don't copy log messages to tombstone unless this is a dev device
      bool want_logs = android::base::GetBoolProperty("ro.debuggable", false);
    
      log_t log;
      log.current_tid = target_thread;
      log.crashed_tid = target_thread;
      log.tfd = output_fd.get();
      log.amfd_data = amfd_data;
    
      _LOG(&log, logtype::HEADER, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
      dump_header_info(&log);
      dump_timestamp(&log, time(nullptr));
    
      auto it = threads.find(target_thread);
      if (it == threads.end()) {
        LOG(FATAL) << "failed to find target thread";
      }
      dump_thread(&log, unwinder, it->second, abort_msg_address, true);
    
      if (want_logs) {
        dump_logs(&log, it->second.pid, 50);
      }
    
      for (auto& [tid, thread_info] : threads) {
        if (tid == target_thread) {
          continue;
        }
    
        dump_thread(&log, unwinder, thread_info, 0, false);
      }
    
      if (open_files) {
        _LOG(&log, logtype::OPEN_FILES, "\nopen files:\n");
        dump_open_files_list(&log, *open_files, "    ");
      }
    
      if (want_logs) {
        dump_logs(&log, it->second.pid, 0);
      }
    

    tombstone文件实例分析

    tombstone标志性log开始: "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***"

    *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

    dump_header_info打印头信息

    static void dump_header_info(log_t* log) {
      auto fingerprint = GetProperty("ro.build.fingerprint", "unknown");
      auto revision = GetProperty("ro.revision", "unknown");
    
      _LOG(log, logtype::HEADER, "Build fingerprint: '%s'\n", fingerprint.c_str());
      _LOG(log, logtype::HEADER, "Revision: '%s'\n", revision.c_str());
      _LOG(log, logtype::HEADER, "ABI: '%s'\n", ABI_STRING);
    }

    示例如下:

    Build fingerprint: 'dev/good_dev/2020.3.6_china_dev_test:user/test-keys'
    Revision: '0'
    ABI: 'arm'

    dump_timestamp打印时间信息

    static void dump_timestamp(log_t* log, time_t time) {
      struct tm tm;
      localtime_r(&time, &tm);
    
      char buf[strlen("1970-01-01 00:00:00+0830") + 1];
      strftime(buf, sizeof(buf), "%F %T%z", &tm);
      _LOG(log, logtype::HEADER, "Timestamp: %s\n", buf);
    }

    打印出差的时间,示例如下:

    Timestamp: 2020-03-07 02:46:27+0800

    dump_thread_info打印thread信息

    static void dump_thread_info(log_t* log, const ThreadInfo& thread_info) {
      // Blacklist logd, logd.reader, logd.writer, logd.auditd, logd.control ...
      // TODO: Why is this controlled by thread name?
      if (thread_info.thread_name == "logd" ||
          android::base::StartsWith(thread_info.thread_name, "logd.")) {
        log->should_retrieve_logcat = false;
      }
    
      _LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s  >>> %s <<<\n", thread_info.pid,
           thread_info.tid, thread_info.thread_name.c_str(), thread_info.process_name.c_str());
      _LOG(log, logtype::HEADER, "uid: %d\n", thread_info.uid);
    }

    示例如下:

    pid: 23051, tid: 23051, name: .tencent.qqlive  >>> com.tencent.qqlive <<<
    uid: 10256

    dump_signal_info打印信号信息

      _LOG(log, logtype::HEADER, "signal %d (%s), code %d (%s%s), fault addr %s\n",
           thread_info.siginfo->si_signo, get_signame(thread_info.siginfo),
           thread_info.siginfo->si_code, get_sigcode(thread_info.siginfo), sender_desc, addr_desc);

    示例如下:

    signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xdb3fb000

    dump_probable_cause打印可能原因信息

          cause = StringPrintf("null pointer dereference");
        } else if (si->si_addr == reinterpret_cast<void*>(0xffff0ffc)) {
          cause = "call to kuser_helper_version";
        } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fe0)) {
          cause = "call to kuser_get_tls";
        } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fc0)) {
          cause = "call to kuser_cmpxchg";
        } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fa0)) {
          cause = "call to kuser_memory_barrier";
        } else if (si->si_addr == reinterpret_cast<void*>(0xffff0f60)) {
          cause = "call to kuser_cmpxchg64";
        }
    
    if (!cause.empty()) _LOG(log, logtype::HEADER, "Cause: %s\n", cause.c_str());

    示例如下:

    Cause: null pointer dereference

    dump_registers打印寄存器信息

        r0  db3f9000  r1  00000000  r2  00023780  r3  db3fb000
        r4  cd5ef340  r5  db3f9000  r6  e7a1e260  r7  cd62c6e0
        r8  cd668190  r9  ffc417a8  r10 00000001  r11 d1ffa760
        ip  00000000  sp  ffc41768  lr  b9466590  pc  e79bfd58

    log_backtrace打印backtrace的信息

    backtrace:
          #00 pc 0005dd58  /apex/com.android.runtime/lib/bionic/libc.so (memset_a7+48) (BuildId: dcf0e174e93e33d22f35a631ba9c0de5)
          #01 pc 0001258c  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so (LogBuffer::__Clear()+36) (BuildId: 05f64491a5d2ace3d88e1623fefbc695c32396ba)
          #02 pc 0000ff68  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so (appender_open(TAppenderMode, char const*, char const*, char const*)+560) (BuildId: 05f64491a5d2ace3d88e1623fefbc695c32396ba)
          #03 pc 00010ca8  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so (appender_open_with_cache(TAppenderMode, std::string const&, std::string const&, char const*, char const*)+1652) (BuildId: 05f64491a5d2ace3d88e1623fefbc695c32396ba)
          #04 pc 000051cc  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so (Java_com_tencent_mars_xlog_Xlog_appenderOpen+428) (BuildId: 05f64491a5d2ace3d88e1623fefbc695c32396ba)
          #05 pc 000db673  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/oat/arm/base.odex (art_jni_trampoline+210)

    dump_stack打印stack的信息

    stack:
             ffc41728  cd422400  [anon:libc_malloc]
             ffc4172c  b946cc2b  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so
             ffc41730  ffc417b0  [stack]
             ffc41734  db3f9000  /data/data/com.tencent.qqlive/files/log/QQLiveLog.mmap2
             ffc41738  db3f9000  /data/data/com.tencent.qqlive/files/log/QQLiveLog.mmap2
             ffc4173c  e7a1e260  [anon:.bss]
             ffc41740  ffc417b0  [stack]
             ffc41744  00001252
             ffc41748  e7a1e260  [anon:.bss]
             ffc4174c  b946cc7b  /data/app/com.tencent.qqlive-NGSZiLGPko8T894J1J65kQ==/lib/arm/libmarsxlog.so
             ffc41750  00000000

    dump_memory_and_code打印memory的信息

    memory near r0 (/data/data/com.tencent.qqlive/files/log/QQLiveLog.mmap2):
        db3f8fe0 -------- -------- -------- --------  ................
        db3f8ff0 -------- -------- -------- --------  ................
        db3f9000 02000109 00096a02 00000000 00000000  .....j..........
        db3f9010 bd0aa200 5ef280e3 000000cd 00000000  .......^........
        db3f9020 00000000 00000000 00000000 c6e85800  .............X..
        db3f9030 00000012 00000000 00000000 00000000  ................
        db3f9040 00000000 d5ef2800 838b8a0c 74d2c701  .....(.........t
        db3f9050 05132305 73430323 ccd0cf20 cadcd4ca  .#..#.Cs .......
        db3f9060 1d2e0cd0 6c646d17 03a86a60 622dcd24  .....mdl`j..$.-b
        db3f9070 8c0c8da3 8c0d740c 15cc0d75 0c2c0db4  .....t..u.....,.
        db3f9080 8c0c140c accc4cac b963cc8c 00000000  .....L....c.....
        db3f9090 494affff c92851cd c8554dcc 48512d2f  ..JI.Q(..MU./-QH
        db3f90a0 2d49cccb 14ad7306 0000b80c ffff0000  ..I-.s..........
        db3f90b0 512d4f4a 2c4dcdc8 72180a80 00000001  JO-Q..M,...r....
        db3f90c0 75f2ffff 0f8e0a0c 52b1f20d 294fcdc8  ...u.......R..O)
        db3f90d0 f7d549cd e62a2c4d 00000002 8b02ffff  .I..M,*.........

    dump_all_maps打印map的信息

    memory map (2172 entries): (fault address prefixed with --->)
        0a125000-0a126fff r--         0      2000  /system/bin/app_process32 (BuildId: 4b3fcbf21f6cb09225ed60ef016d3f87)
        0a127000-0a129fff r-x      2000      3000  /system/bin/app_process32 (BuildId: 4b3fcbf21f6cb09225ed60ef016d3f87)
        0a12a000-0a12afff r--      5000      1000  /system/bin/app_process32 (BuildId: 4b3fcbf21f6cb09225ed60ef016d3f)
        0a12b000-0a12bfff rw-         0      1000
        12c00000-15fbffff rw-         0   33c0000  [anon:dalvik-main space (region space)]
        15fc0000-161bffff rw-         0    200000  [anon:dalvik-main space (region space)]

      dump_log_file(log, pid, "system", tail);打印system log的信息
      dump_log_file(log, pid, "main", tail);打印mainlog的信息

    --------- tail end of log main
    03-07 02:46:27.072 23051 23051 W System.err: 	at com.tencent.qqlive.ona.base.QQLiveApplication.attachBaseContext(QQLiveApplication.java:1223)
    03-07 02:46:27.072 23051 23051 W System.err: 	at com.tencent.qqlive.ona.base.QQLiveApplicationWrapper.attachBaseContext(QQLiveApplicationWrapper.java:197)
    03-07 02:46:27.073 23051 23051 W System.err: 	at android.app.Application.attach(Application.java:376)

    总结

    • 当Native进程发生了异常,比如NULL指针
    • 操作系统会去异常向量表的地址去处理异常,然后发送信号
    • 在debuggred_init注册的信号处理函数就会收到处理
    • 创建伪线程去启动crash_dump进程,crash_dump则会获取当前进程中各个线程的crash信息
    • tombstoned进程是开机就启动的,开机时注册好了socket等待监听
    • 当在crash_dump中去连接tombstoned进程的时候,根据传递的dump_type类型会返回一个/data/tombstones/下文件描述符
    • crash_dump进程后续通过engrave_tombstone函数将所有的线程的详细信息写入到tombstone文件中
    • 则就在/data/tombstones下生成了此次对应的tombstone_XX文件

     

    展开全文
  • Android系统问题及日志分析

    千次阅读 2020-11-20 18:10:11
    针对网上系统类问题关键日志分析资料太少,自己总结了半年内分析了400~500问题单,列举出了工作中实际概率问题,只有日志分析.....
  • Android Crash实例分析与解决

    千次阅读 2020-01-17 18:41:59
    10-21 20:06:32.024 I/crash_dump64( 6995): obtaining output fd from tombstoned, type: kDebuggerdTombstone 10-21 20:06:32.025 I//system/bin/tombstoned( 292): received crash request for pid 6709 10-21 20...
  • 问题 用logcat抓取到报错如下 10-26 15:14:46.777 3174 3174 I crash_dump32: obtaining output fd from tombstoned, type: ...10-26 15:14:46.778 1810 1810 I /system/bin/tombstoned: received cr...
  • E//system/bin/tombstoned: Tombstone written to: /data/tombstones/tombstone_00 在上述崩溃信息的最后给出了提示 “Tombstone written to: /data/tombstones/tombstone_00” , 崩溃日志信息被保存到了 /data/...
  • ANR问题分析总结

    千次阅读 2019-08-21 17:31:19
    M501C42 06-09 07:14:39.392 514 514 I /system/bin/tombstoned: registered intercept for pid 8619 and type kDebuggerdJavaBacktrace 然后打开traces文件,定位到Cmd line,说明了发生ANR的进程id、时间和进程...
  • Android-jni(6)-常见错误异常

    千次阅读 2018-12-03 22:48:19
    NDK异常信息一般有三个要素: 信号 调用栈信息 寄存器信息 比如一下是一个空指针的错误信息: A/libc: Fatal signal 11 (SIGSEGV), code 1, ...I/crash_dump32: obtaining output fd from tombstoned I//system...
  • 105 PLOG(ERROR) << "libdebuggerd_client: failed to connect to tombstoned"; 106 return false; 107 } 124int socket_local_client_connect(int fd, const char* name, int namespaceId, int /*type*/) { 125 ...
  • Android TombStone 分析

    2020-09-15 17:30:57
    Android TombStone分析 1.什么是tombstone 当一个动态库(native 程序)开始执行时,系统会注册一些连接到debuggerd 的signal handlers,当系统 crash 的时候,会保存一个 tombstone 文件到/data/tombstones目录下...
  • E//system/bin/tombstoned: Tombstone written to: /data/tombstones/tombstone_6 注意最后一行,崩溃日志的位置。 通过 adb 执行: adb root adb pull /data/tombstones/ ./ 这个目录下的文件把拖...
  • Q 最近遇到一个问题:多个应用打不开,闪退。 A 调查发现闪退的应用都在首屏加载了webview,而在android p上webview的渲染是在另外一个进程上进行的,进程名字类似webview:sandboxed_process0,这个进程是由webview_...
  • jni 学习demo

    2019-11-14 20:03:57
    11-14 19:31:05.435 17274 17274 I crash_dump64: obtaining output fd from tombstoned, type: kDebuggerdTombstone 11-14 19:31:05.437 399 399 I /system/bin/tombstoned: received crash request for pid 17225...
  • Android死锁处理

    2021-03-19 23:29:17
    com.sohu.sohuvideo I/.sohu.sohuvide: Wrote stack traces to ‘[tombstoned]’,这时我们的trace文件便已经生成好了(这里需要注意Android8.1之前输出的日志为 Wrote stack traces to traces.txt)。 run-as 命令...
  • tombstone的log产生和分析工具

    千次阅读 2019-03-23 11:06:43
    1. 当tombstone发生时:看什么log? .当 tombstone的发生时: kernel log, logcat log里都有输出: 还有 /data/tombstones文件夹下也有tombstone文件 tombstone是发生在用户空间的程序: 分析问题需要看 logcat 或者是 ...
  • 分析tombstone文件

    2019-12-18 14:54:44
    Line 10248: 01-01 08:00:45.163 F/DEBUG ( 2531): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** Line 10249: 01-01 08:00:45.163 F/DEBUG ( 2531): Build fingerprint: 'xltt/msm89...
  • 提升了在tabletserver含有大量的tombstoned tablet时,tabletserver的启动的速度。 工具   kudu tablet leader_step_down,step down一个leader tablet。 kudu remote_replica copy拷贝tablet从一个...
  • ANR Trace tombstone文件获取 与详细分析

    千次阅读 2018-12-03 11:42:27
    移步:https://blog.csdn.net/qq_25804863/article/details/49111005
  • Android Tombstone 分析

    千次阅读 2019-07-29 16:51:08
    Android Tombstone 分析 1.什么是tombstone 当一个动态库(native 程序)开始执行时,系统会注册一些连接到 debuggerd 的 signal handlers,当系统 crash 的时候,会保存一个 tombstone 文件到/data/tombstones目录...
  • tombstone 分析

    万次阅读 2016-11-18 21:41:25
    Coredump 是分析Android native exception和kernel exception的利器,coredump是核心转储,可以理解为当进程发生异常无法挽救时,OS机制把这块出问题的内存取出来打包成核心转储供给离线分析用。...
  • Android SELinux avc denied解决

    千次阅读 2019-04-26 17:20:22
    参考:Android SELinux avc dennied权限问题解决方法 解决原则:缺什么权限补什么,直到没有avc denied为止。 解决方法:在对应的.te中增加allow语句。 ...avc: denied { 操作权限 } for pid=7201 comm=“进程名”...
  • =====项目中遇到进程挂掉的问题,需要分析Tombstone,本文帮了大忙 http://blog.csdn.net/helldevil/article/details/6682211   ... 命令使用有误: 原文: addr2line -e -f libc.so 0001173c 应该是 addr...
  • Android tombstone 分析

    千次阅读 2019-02-26 16:51:45
    一般看到这个log就容易让人头大, Fatal signal 11 (SIGSEGV), code 1, fault addr 0x6b0818 in tid 4364 (Thread-166) 写惯了java,直接看exception里看错误非常舒服,但是要查底层库崩溃总是让人无从下手。...
  • 报错模板: avc:denied{readwritegetattr}forpid=3944comm="handsetpowerlib"name="xxxx"dev="tmpfs"ino=5545scontext=u:r:AAA:s0:c512,c768tcontext=u:object_r:BBB:s0tclass=CCCpermissive=1 ...
  • 有一句话叫做常在河边走,哪有不湿鞋。我们这些研究和开发Android的工程师正应了这句话,相必大家在调试的时候经常会遇到这么个东西吧 *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ...
  • tombstone一般是由Dalvik错误、状态监视调试器、C层代码以及libc的一些问题导致的。当系统发生tombstone的时候,kernel首先会上报一个严重的警告信号(signal),上层接收到之后,进程的调试工具会把进程中当时的调用...
  • 如何获取android里tombstone文件

    千次阅读 2015-08-09 10:28:58
    首先,要获取的手机要ROOT权限。 然后,adb shell  chmod 777 /data/tombstone  crl+c 最后pull /data/tombstone /d: ok
  • Android NDK Tombstone/Crash 分析

    千次阅读 2017-06-17 22:44:59
    Android NDK Tombstone/Crash 分析 阅读 954 收藏 24 2016-06-18 原文链接:...Android NDK 程序的系统调试没那么复杂,虽然它长着一副恐怖的外

空空如也

空空如也

1 2 3 4 5 ... 15
收藏数 295
精华内容 118
关键字:

tombstoned