anr处理日常记录

上周的时候出现了一个anr错误,系统级别的,导致systemserver被锁死,导致系统重新启动。这是一个相当严重的bug。我就把当时寻找这个anr错误的过程记录下来吧。
当时系统组发给我bug的时候整个anr的日志足足有350kb,确实挺难翻的。

首先先说一说基本的知识吧:
1.anr的log都是放在data/anr/traces.txt中的。
2.traces中都是按照进程去进行的,会有pid提示。
3.当一个线程占有一个锁的时候,会打印-locked<0x7402c48>
4.当该线程正在等待别的线程释放该锁,会打印wait to lock <0xxxxxx>
5.如果代码中有wait()调用的话,首先是locked,然后会打印waiting on <0xxxxxx>

当我拿到log的时候,根据现象描述手机卡死之后重启,说明就是系统服务被锁死了,systemserver被锁死,于是迅速的在log中找到了关于systemserver的相关进程

----- pid 26195 at 2015-02-05 15:17:34 -----
Cmd line: system_server
ABI: arm64
.........
kernel: cpu_switch_to+0x48/0x4c
----- end 26195 -----

中间的这一段就是system_server所对应的所有anr相关的信息了。
我们在其中找到:
DALVIK THREADS (109): "main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 obj=0x75290fb8 self=0x7f7fc97000
| sysTid=26195 nice=-2 cgrp=apps sched=0/0 handle=0x7f833180f0
| state=S schedstat=( 4853180231 604326531 4853 ) utm=378 stm=107 core=6 HZ=100
| stack=0x7ffe63f000-0x7ffe641000 stackSize=8MB
| held mutexes=
at com.android.server.LocationManagerService $NetworkStateReceiver.onReceive(LocationManagerService.java:499)
- waiting to lock <0x3391afb6> (a java.lang.Object) held by thread 80
at android.app.LoadedApk$ReceiverDispatcher$Args.run(LoadedApk.java:912)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at com.android.server.SystemServer.run(SystemServer.java:343)
at com.android.server.SystemServer.main(SystemServer.java:224)
at java.lang.reflect.Method.invoke!(Native method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:955)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:750)

这里可以看到,这个进程就是system_server的主进程main了,而这个进程在locationmanagerservice.java的499行waiting住了,因为它正在等待<0x3391afb6>的锁,而这个锁目前是被进程号为80的线程所持有,这样我们搜索到线程为80的线程,得到下面的log:
"Binder_6" prio=5 tid=80 Waiting
| group="main" sCount=1 dsCount=0 obj=0x132150e0 self=0x7f6370a800
| sysTid=26925 nice=0 cgrp=apps sched=0/0 handle=0x7f68345000
| state=S schedstat=( 368437685 191519005 1171 ) utm=24 stm=12 core=5 HZ=100
| stack=0x7f614e4000-0x7f614e6000 stackSize=1012KB
| held mutexes=
at java.lang.Object.wait!(Native method)
- waiting on <0x29751f45> (a com.android.server.PermissionDialogResult$Result)
at com.android.server.PermissionDialogResult $Result.get(PermissionDialogResult.java:40)
- locked <0x29751f45> (a com.android.server.PermissionDialogResult$Result)
at com.android.server.AppOpsService.startOperation(AppOpsService.java:976)
at android.app.AppOpsManager.startOpNoThrow(AppOpsManager.java:1516)
at com.android.server.LocationManagerService $Receiver.updateMonitoring(LocationManagerService.java:1103)
at com.android.server.LocationManagerService $Receiver.updateMonitoring(LocationManagerService.java:1073)
at com.android.server.LocationManagerService.requestLocationUpdatesLocked(LocationManagerService.java:2006)
at com.android.server.LocationManagerService.requestLocationUpdates(LocationManagerService.java:1966)
- locked <0x3391afb6> (a java.lang.Object)
at android.location.ILocationManager$Stub.onTransact(ILocationManager.java:71)
at android.os.Binder.execTransact(Binder.java:451)

通过上面这段log我们可以了解到倒数第三行,也就是在binder进程中,申请了requestlocationupdates方法,而这个方法会锁住locationmanagerservice的锁,获取锁之后,但是会申请appopsservice的权限检查的方法,这里有一个locked <0x29751f45>然后紧接着就是waiting on <0x29751f45>这就是之前说的如果类中有wait()方法的话就会出现了,可是这个一点问题都没有啊,permissiondialogresult的锁锁住了之后7秒钟就会释放锁(permissiondialog默认7秒后自动消失)的,所以就算上面的main函数wait<0x3391afb6>这个锁,是不会阻塞main进程的。

再回到main进程,找到被锁住的地方,发现原来是499行(代码在服务器上,就没办法copy代码了)是一个broadcastreceiver的onreceive方法,但是为什么这个方法被阻塞了,主线程就阻塞了呢,然后顺着AMS查到registerbroadcastreceiver的时候,是可以传一个handler的,如果没呀传handler的话,那默认就是使用的main进程的handler,也就是说:onreceive方法,没传handler的话,就是在main进程中执行的。这样一切都可以解释通了:
1.某应用申请定位权限,获取了locationmanagerservice的锁。
2.然后在屏幕上弹出对话框的时候,正好此时receiver收到广播,阻塞了主进程。
3.主进程被锁,导致dialog无法响应点击事件,也无法释放被locked的锁,导致系统进程崩溃。

这样问题就分析出来了,同时中间也会有很多无用的log,我就略过啦。