zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

Android ANR分析(trace文件的产生流程)

Android流程文件 分析 产生 Trace
2023-09-27 14:25:57 时间
ANR信息获取 trace文件的产生流程 首先收集需要dump trace的进程并给对应进程发送dump trace的信号接着分析最后一步向收集到的进程发送信号首先收集需要dump trace的进程并给对应进程发送dump trace的信号

1.当一些带有超时机制的系统消息 如 Service的创建 判定超时后 会调用系统服务AMS接口 收集ANR相关信息并存档 data/anr/trace, data/system/dropbox

image

2.进入到AMS中 AppError会先进行筛选 1.当前进程正在进行dump流程 2.已经发生crash 3. 已经被系统kill 4.系统是否正在关机等情况 如果都不符合 则认为当前进程发生了anr。

image

3.接下来系统在判断当前ANR进程对用户是否可感知 然后开始统计与该进程由关联的进程 或者一些系统核心服务进程的信息 例如与应用交互的SurfaceFligner System Server等系统进程 如果这些系统服务进程在响应时被阻塞 那么将导致应用进程IPC通信过程被卡死。接着获取其他系统核心进程 因为这些服务进程是init进程直接创建的 并不在SystemServer或Zygote进程管理范围。

firstPids队列 第一个是ANR进程 第二个是system_server 剩余是所有persistent进程

Native队列 是指/system/bin/目录的mediaserver,sdcard 以及surfaceflinger进程

lastPids队列: 是指mLruProcesses中的不属于firstPids的所有进程。

image

4.在收集完第一步信息后 接下来便开始统计各进程本地的更多信息 如虚拟机信息 java线程状态及堆栈。首先会弹出一个ANR的对话框 然后向UI线程发送SHOW_NOT_RESPONDING_MSG消息

image

5.当UI线程收到该消息后 会调用dumpStackTraces函数

image

最重要的一点 向目标进程发送SINAL_QUIT 进程中的Signal Catcher会进行阻塞检测收集信息后面讲 firstPids列表中的进程, 两个进程之间会休眠200ms, 可见persistent进程越多,则时间越长. top 5进程的traces过程中, 同样是间隔200ms, 另外进程使用情况的收集也是比较耗时.

总结;

将am_anr信息输出到EventLog(分析anr问题时先看该log)

获取重要进程的信息 java进程的 和native的进程

将ANR的Reason和CPU使用的情况输出到main_log

在将CPU使用情况和进程的trace文件信息 在保存到drpobox文件下

向收集到的进程发送SINAL_QUIT信号。


接着分析最后一步向收集到的进程发送信号

Android5.0之前是dump用的SuspendAll线程 收集信息之后用ResumeAll恢复。在5.0之后采用的是checkPoint进行dump信息

发生ANR时 systemServer进程会执行dumpStackTraces函数 在该函数中发SIGQUIT信号给对应的进程 上面有分析到

处于安全考虑 进程之间是相互隔离的 即使系统进程也无法获取其他进程的信息 所以要借助于IPC通信 将指令发送到目标进程 目标进程接收到消息后 协助完成自身进程Dump信息并发送给系统进程。Android P 流程

image

1.一个进程接收到了SIGQIUT信号的时候 SingaCatcher线程的WaitForSignal函数会返回接着会调用到HandlerSigQuit()函数。

image

2.hindleSigQuit 函数为

image

3.DumpForSigQuit 函数

image

这是读取的信息 但是什么时候去读取呢 什么时候才能保证获取到的却是是需要的东西 例如GC信息 当前分配了多少对象 这些打印一般都需要在suspend当前进程里面的所有的线程 接下来先分析这个suspend过程

image

这个挂起SupendAll实在Thread_list.cc中实现的 他的作用就是用来suspend当前进程里面所有其他的线程 一般发生在GC DumpForSigQuit等过程中 。SuspendAll过程实现最重要的就是ModifySupendCount(self 1 false)这段语句他会修改对应Thread对象的suspend引用计数

image

因为传入的delta值是 1所以会先执行AtmoicSetFlag()利用原子操作设置了KSuspendRequest标志位 代表当前这个线程有挂起请求。什么时候会进行检测这个标志位呢?这里涉及到了checkPoint的知识点最后讲解 在线程运行中进行上下文切换 例如java线程转换为Native线程 时就会运行CheckSuspend函数 这个函数才是真正的把当前线程suspend:

image

可以看到检测到了KSuspendRequest标记就会执行FullSuspend函数 KSuspendRequest标志位是用来dump线程的堆栈的 分析完了SuspendAll之后 再继续分析FullSuspendCheck函数

image

调用TransitionFromRunnableToSuspend()这个函数后 线程就进入了KSuspended状态 然后在调用TransitionFromSuspendedToRunnablecpm函数从Suspend状态切换到Runnable状态的时候会阻塞在一个条件变量上 除非调用SuspendAll的线程接着又调用了ResumeAll 函数 要不然这些线程就会一直被阻塞住。

4.现在就把SuspendAll的流程分析完了 但是dump线程堆栈的时候并不是在设置了挂起标志位(KSuspendRequest)后执行的 与他相关的是另外一个标志位KCheckpointRequest 接下来看一下Thread_list的Dump函数,这个函数会在Thread_list的DumpForSigQuit中会被调用到 也就是在Signal Cathcer线程处理SIGQUIT信号的过程中。

image

这个函数先创建了一个叫DumpCheckPoint对象checkpoint 然后调用了RunCheckpoint将这个对象传入 这个函数会返回现在处于Runnable状态的线程个数 接着

调用了WaitForThreadsToRunThroughCheckpoint 等待这些处于Runnable的线程都执行完DumpCheckpoint的Run函数 如果等待超时就会报错。

image

接着分析RunCheckPoint函数 先看前一部分

image

对于处于Runnable状态的线程执行它的RequestCheckpoint函数会返回true 其他状态的线程则会返回false。对于这些非Runnable状态的线程就会像SuspendAll一样会设置KSuspendRequest标志位 后面状态切换的时候就会检查这个标志位挂起。同事RunCheckPoint函数会把这些线程统计到suspend_count_modified_threads这个Vector变量中 在这个变量中的线程 Singal Catcher线程会主动触发他们的dump堆栈过程。接下来再看看这个RequestCheckpoint函数

image

最后一行设置kCheckpointRequest标志位 在刚才线程切换运行状态时会执行CheckSuspend函数在检测kCheckpointRequest标志位的时候会执行RunCheckpointFunction函数 接着会执行这个checkpoints里面元素的run函数

image

(这个存储的其实就是Thread中的RequestCheckpoint在这里不仅设置了标志位还把参数设置为元素的值 这个参数就是Dump里面调用RunCheckpoint传过来的 其实就是DumpCheckpoint)。

,所以也就是执行DumpCheckpoint的run函数

image

其实就是调用了Thread的Dump函数 线程的java堆栈 Native堆栈和Kernel堆栈就是在这里打印的 刚才说对于处于Runnable状态的线程是通过调用他们的RequestCkeckPoint函数 然后它们自己去dump当前堆栈的 而那些不处于Runnable状态的线程则是添加到了一个Vector的变量中 接着就分析RunCheckPoint函数的第二部分

image

对于这些不是Runnable状态的线程 他们可能不会主动去调用Run函数 所以只能有Signal Catcher线程去帮他们Dump 至于DumpCheckpoint的Run函数的功能和Runnable状态的线程是一样的 都是打印线程堆栈 并且最后修改引用计数让这些线程在切换状态时继续运行。

总结

1.SingalCatcher线程接收到信号后 首先Dump当前虚拟机有关信息 内存状态。对象 加载class GC等相关信息

2.接下来会设置每个线程的标记为 check_point 和请求线程状态 suspend 。当线程运行过程中进行上下文切换时 会检查该标记。如果发现有挂起请求 会将自己主动挂起。等到所有线程都挂起之后 SingalCatcher线程开始遍历Dump各个线程的堆栈和线程数据后再唤醒线程。如果某个线程一直无法挂起导致超时 那么本次Dump流程失败抛出异常.

大致流程 Android5.0之前

image

checkPoint

先讲解safePoint 对于ART编译的代码 可以定期轮询当前Runtime来确认是否需要执行某些特定代码 可以认为这些轮询时的点 就是safepoint safepoint可以用来实现暂定一个java线程 也可以用来实现Checkpoint机制

当正在执行java代码的线程A执行到safepoint时 会执行CheckSuspend函数 在发现当前线程有 checkpoint request时

会在这个点执行线程的CheckPoint函数 如果发现当前线程有suspend request时 会进行SuspendCheck 使得线程进入Suspend状态 暂停

所以说 ART CheckPoint应该是safepoint的一个功能实现

safePoint讲解链接

作者 RednaxelaFX

链接 https://www.zhihu.com/question/48996839/answer/113801448

来源 知乎

著作权归作者所有。商业转载请联系作者获得授权 非商业转载请注明出处。

从编译器和解释器的角度看 ART的safepoint有两种

主动safepoint 编译生成的代码里或者解释代码里有主动检查safepoint的动作 并在发现需要进入safepoint时跳转到相应的处理程序里。

ART的解释器安插主动safepoint的位置在循环的回跳处 backedge 具体来说是在跳转前的源头处 以及方法返回处 return / throw exception 。

ART Optimizing Compiler安插主动safepoint的位置在循环回跳处 backedge 具体来说是在跳转前的源头处 以及方法入口处 entry 。

被动safepoint 所有未内联的方法调用点 call site 都是被动safepoint。这里并没有任何需要主动执行的代码 而就是个普通的方法调用。

之所以要作为safepoint 是因为执行到方法调用点之后 控制就交给了被调用的方法 而被调用的方法可能会进入safepoint safepoint中可能需要遍历栈帧 因此caller也必须处于safepoint。

安插safepoint的位置的思路是 程序要能够在runtime发出需要safepoint的请求后 及时地执行到最近的safepoint然后把控制权交给runtime。

怎样算“及时” 只要执行时间是有上限 bounded 就可以了 实时性要求并不是很高。

于是进一步假设 向前执行 直线型、带条件分支都算 的代码都会在有限时间内执行完 所以可以不用管 而可能导致长时间执行的代码 要么是循环 要么是方法调用 所以只要在这两种地方插入safepoint就可以保证及时性了。

至于具体在方法入口还是出口、循环回边的源头还是目标处插入safepoint 这是个具体实现的细节 只要选择一边插入就可以了。