adb logcat使用及Debug技巧
在Android开发中,无论是app还是system的开发,logcat都是debug所必须的。本文整理了一下logcat的日常用法和最常用的Debug技巧。本文的目的不在于大而全,定位差不多是一个快速使用手册。
常用adb命令
#设置代理: $ adb shell settings put global http_proxy http_proxy:port_number #清除代理: $adb shell settings delete global http_proxy #dump Surfaceflinger $adb shell dumpsys SurfaceFlinger #其中当前view被标记为Visible layers,计算fps时需要作为参数,比如用gallry播放video时,需要在video正在播放时执行adb shell dumpsys SurfaceFlinger,
#然后获得output layer为SurfaceView[com.android.gallery3d/com.android.gallery3d.app.MovieActivity](BLAST)#0,那么获取fps的命令则为: $python FPStool2.py -c --debug=1 --legacy "SurfaceView[com.android.gallery3d/com.android.gallery3d.app.MovieActivity](BLAST)#0" | grep arithmetic
常用logcat命令
#最简单的,把log输出到文件arsenal.log: adb logcat > arsenal.log #带格式的输出,以"time"格式输出为例: adb logcat -v time > arsenal.log "time"格式 : "日期 时间 优先级 / 标签 (进程ID) : 进程名称 : 日志信息 adb logcat -v time "long"格式 : " [ 日期 时间 进程ID : 线程ID 优先级 / 标签] 日志信息 " adb logcat -v long
#清空日志缓存 adb logcat -c 输出缓存日志 adb logcat -d 输出最近的5行日志 adb logcat -t 5 使用管道过滤日志 过滤指定字符串 adb logcat | grep wifi 过滤忽略大小的字符串 adb logcat | grep -i wifi 正则匹配V/ActivityManager adb logcat | grep '^..Activity'
读懂logcat的输出
执行完logcat之后,输出往往是一大坨,眼花缭乱的,新手可能看不懂:
01-13 06:51:44.093 13334 13488 V isLocalApp: isLocalApp, bundle is:com.AlexandrJanashvili.MultiMaze3D is local: no 01-13 06:51:44.094 13334 13488 V isLocalApp: isLocalApp, bundle is:com.fun.blowup is local: no 01-13 06:51:44.094 13334 13488 V isLocalApp: isLocalApp, bundle is:com.newnormalgames.phonecasediy is local: no 01-13 06:51:44.095 13334 13484 D TTAnalyticsAgent: agentConfigure: mKey=0dXMtZWFzdC0xO3R0LWFuYWx5dGljcy1wcm9kO0FTSUFRWkZCQU5FVE1MTFVSRElQO3UxTDV4YVNZTEZlR0RxZEg2QUxmK1VsTkRaWUFnMVZoSWpSeXZXUVQ7SVFvSmIzSnBaMmx1WDJWakVEOGFDWFZ6TFdWaGMzUXRNU0pITUVVQ0lRRHZZWVJQalI3TEdtSS9tTWhpYkxCTGlXMmkrdEphKzZncWh4SklxZWNnV3dJZ2FwNGpYemJDMUxCODBJK1BYMFBtcDJXMU94YXZkek9CUGhlR3IxWG9VUWtxNkFJSVdCQUFHZ3d3TlRRd01qUTNOVGsxT1RBaURIcEZ1SzJIQjRjRUV0eTg5Q3JGQWc3K3FEUGdpRDN3NVFQUXQxUU9JeE9XQ01tQ0huVXR6Q2o0dGhqaGVwUkY4VzZBY3pySkRDMG1VVjZDcm5nY3l4b0JZZVNtUDlIZ2N5WmhMUUZ5WWVRMU9CRXhsRUltY3JwSGZialVaT2w3UDcvOCtsUFBHMlg1WTRYTXFXZWN0cGpvb28xR3Q3dnRSUS91ak9meTFta0p2dHJlWm5HbGtMNGkrNUFyaVNBSDZKRDJJbGVqNkdXeFJBcndBdTdZbW1QQXFmaFQra3V6ejA3RVFhWkZMS0ZkUDh1VDI0b3Q1ejVPdUlKV0E2OGVEUkhHb1NBS29vWUZqcVNibXRCdnVzZXdhOEt4VTM0UTJYeCtYYit2QTZVcC9LeHJwQjdFMnZYSW5JRE4wdUpVU2pEWjdZZjhmRkFueVR5VWJwS0t1a1lGa25LQTAzL3hZSG5BV2ZKOGMzcFlQNjFNTHhVbGlTVFUveTM3ZXZFTUgxc1p2UkxwV2VwUVB1K3ZPeGkxME9JdXdPeWRBWjgrckNrYlp6MTdUQmhRZHpzQWVIc3RwaE9nSmgyY01uaFRPNVM1Z2Nnd2hwai9qZ1k2bVFGYWVwUW9JSlNnQkJ0RkhiaFN6RFowOWtYUG5xRG5KZHMxTE1rMGI4TTlhM240SDJtN3JmY3Y1Yms1M2hQeW8zaDRSZm12SFF3ZXpLeUJ6cXR0L0xIMEZWdCtjOCthb2hQcjBnY3ZUSytqOTZiOE9NZ0xTV0svS0JuaHMvaVd4RitGcURlZy9oT2lCb2RKM1h4T3Nib1crZVREb2swSXA3WFh5SzlqTzJ4aXV0TjV4QWVFaEh2bHlqVkFsaEJRNXp5NmpLL01BdnJ3UXRBPTsyMDIyLTAxLTE0VDA2OjUxOjUwLjAwMFo= mPreviousKey=null mTTKinesisRecorder=null 01-13 06:51:44.095 13334 13488 V isLocalApp: isLocalApp, bundle is:com.sdpgames.sculptpeople is local: no 01-13 06:51:44.096 13334 13488 V isLocalApp: isLocalApp, bundle is:com.crazylabs.tie.dye.art is local: no 01-13 06:51:44.096 13334 13488 V isLocalApp: isLocalApp, bundle is:com.crazylabs.amaze.game is local: no 01-13 06:51:44.097 13334 13484 D TTAnalyticsAgent: setupKinesis: 01-13 06:51:44.105 13334 13484 D TTAnalyticsAgent: determineStorageMode:ttId=019e1e69-c0b0-438b-a3f7-815d63dbb6e9 mWaitForKinesisSetup=false mTTKinesisRecorder=ready 01-13 06:51:44.107 13334 13425 D AppCenter: Stored a log to the Persistence database for log type event with databaseId=238 01-13 06:51:44.108 13334 13425 D AppCenter: enqueue(group_analytics) pendingLogCount=14 01-13 06:51:44.108 13334 13425 D AppCenter: checkPendingLogs(group_analytics) pendingLogCount=14 batchTimeInterval=3000 01-13 06:51:44.109 13334 13425 D AppCenter: Storing a log to the Persistence database for log type event with flags=1
其实知道logcat输出的格式之后,就可以很简单的读懂了:
以上输出的信息包含了如下格式的内容:
日期 时间 PID TID Log级别 TAG Log内容
- PID: Process ID, 即进程id, 可以看成app运行时,在系统中的唯一的一个标识
- TID: Thread ID, 即线程ID, 因为一个进程可以包含多个线程,而同一个进程内的各个线程是资源共享的,所以TID也比较重要
- Log级别: 常用的5个分别是 V(Verbose 明细,最低级别)、D(Debug 调试)、I(Info 信息)、W(Warn 警告)、E(Error 错误)
- TAG:开发中,标记Log的一个标志。Developer添加Log的时候,其实写什么样的TAG都可以,主要是为了通过这个TAG告诉自己大概发生了什么。比如,如果TAG使用类名,就可以知道输出LOG是哪个类。你也可以写任意其他的,比如写自己的名字,表示是自己刚刚添加的Log。这里一般都是类名或者app的名字。
- Log内容:记载Log的具体内容,输出的内容是什么是由代码里对应的Log语句决定的。eg:
Log.e(TAG,message),e就是log级别,message就是log具体内容。
以如下log为例:
01-13 06:51:44.109 13334 13425 D AppCenter: Storing a log to the Persistence database for log type event with flags=1
就可以这样去解读:
日期: 01-13 时间: 06:51:44.109 PID: 13334 TID: 13425 Log级别: D Log内容: AppCenter: Storing a log to the Persistence database for log type event with flags=1
Debug技巧
Bug分类
Kernel space bug
kernel 顾名思义就是内核,在系统开发中会涉及到内核的bug,此时需要调取kernel log。kernel的log不是由logcat去抓,logcat只能抓取user space的log。可以用如下命令抓取kernel log:
adb shell cat /proc/kmsg > kernel.log
User space bug
我们学习的logcat就是抓user space bug的。我的习惯是直接导出所有的log,但看了网上的资料,也有很多人根据不同日志的分类去导出log,提高debug效率
radio
这个分类的日志主要适用于RIL(Radio Interface Layer)开发的人员,比如日常生活最重要的移动数据上网、通话、短信等均属于 radio 的研发。抓取 radio 日志的方法为:
adb logcat -b radio
event
event 的日志包含了对于手机基本操作的记录,如打开/关闭一个软件、按下返回键、打开/关闭通知栏等等,抓取方法:
adb logcat -b events
main
main 日志是开发软件中最常用到的日志了,在这里你可以看到应用崩溃的具体原因、调试的时候通过 Log.x(TAG, "msg");
所打印出来的日志,抓取方法:
adb logcat -b main
常用Debug思路及技巧
常见Issues
1、程序异常退出 uncaused exception 2、程序强制关闭 Force Closed (简称FC) 3、程序无响应 Application No Response(简称ANR),一般主线程超过5秒么有处理就会ANR
4、Crash
如何分析log信息
1、查找错误信息的关键字眼 "error" "failxx" "E/" "beginning of crash"等的错误信息 将这些问题先行解决掉 2、动态库死机 查看类似的“Build fingerprint:”这些关键字 I/DEBUG ( 692): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** I/DEBUG ( 692): Build fingerprint: 'generic/generic/generic:2.3.1/GRH78/eng.userdev-rd6-input.20120221.113348:eng/test-keys' I/DEBUG ( 692): pid: 694, tid: 694 >>> /system/bin/mediaserver <<< I/DEBUG ( 692): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000input module init --> 010 对于这此信息,可以查看动态库的分析: http://blog.csdn.net/andyhuabing/article/details/7074979
3、解决java抛异常的问题解决 E/UsbObserver( 957): java.lang.NullPointerException E/UsbObserver( 957): at com.android.server.UsbObserver.init(UsbObserver.java:131) E/UsbObserver( 957): at com.android.server.UsbObserver.(UsbObserver.java:65) E/UsbObserver( 957): at com.android.server.ServerThread.run(SystemServer.java:419) I/SystemServer( 957): UI Mode Manager Service 这个直接找到java代码,分析其实现即可解决 4、ANR问题 搜索“ANR”关键词,快速定位到关键事件信息 。 定位到关键的事件信息如下: I/dalvikvm( 1014): Wrote stack traces to '/data/anr/traces.txt' I/Process ( 957): Sending signal. PID: 1124 SIG: 9 指定哪个java包出问题 E/ActivityManager( 957): ANR in com.ipanel.join.appstore 进程号为957发生了如下错误:com.ipanel.join.appstore 包下面 Broadcast问题 ANR原因: E/ActivityManager( 957): Reason: Broadcast of Intent { act=android.appwidget.action.APPWIDGET_UPDATE cmp=com.ipanel.join.appstore/.widget.SmallWidget1 (has extras) } 这是ANR的堆栈调用文件 I/dalvikvm( 1014): Wrote stack traces to '/data/anr/traces.txt' 通过上面的log信息分析,应该是接收一个广播消息时超时了 我们再分析虚拟机信息 ,打开/data/anr/traces.txt,可有通过adb pull /data/anr/traces.txt . 这里每一段都是一个线程 ,当然我们还是看线程号为1的主线程了。通过分析发现关键问题是这样: 搜索“DALVIK THREADS”关键词,快速定位到本应用程序的虚拟机信息日志 ----- pid 1516 at 1970-01-02 08:03:07 ----- Cmd line: com.ipanel.join.appstore DALVIK THREADS: 。。。 at com.ipanel.join.appstore.widget.AbsSmallWidget.getRemoteViews(AbsSmallWidget.java:56) 其实从这句话: at org.apache.harmony.luni.platform.OSNetworkSystem.connect(Native Method) 基本上确认是 socket ->connect 连接超时了,导致主线程5s内没有响应从而产生ANR错误。默认的connect连接timeout时间是75s 其实解决办法就是利用非阻塞方式进行连接即可。 从CPU占用率上也可以看出是在kernel中执行堵塞住了 E/ActivityManager( 957): 75% TOTAL: 4.7% user + 70% kernel 5、执行DexOpt错误 W/dalvikvm( 1803): DexOpt: --- END 'SettingsProvider.apk' --- status=0x000a, process failed E/dalvikvm( 1803): Unable to extract+optimize DEX from '/system/app/SettingsProvider.apk' 。。。。android.app.ActivityThread.installProvider(ActivityThread.java:3557) E/SystemServer( 1803): at android.app.ActivityThread.getProvider(ActivityThread.java:3356) 从上面的打印看,是在解压或优化extract+optimize DEX的apk文件时出错了 1、没有出现magic number错误,这个原因与原子操作无关(这是一快速的加锁和解锁的轻量级操作函数) 2、执行dexopt出错 查明是服务器硬盘没空间了,导致引导文件系统的时候没有空间进行解压而失败 6、系统启动后默认其妙或随机死机情况 出现这种错误: 12-01 08:11:56.027: WARN/SharedBufferStack(312): waitForCondition(LockCondition) timed out (identity=19, status=0). CPU may be pegged. trying again. 12-01 08:11:57.315: WARN/SharedBufferStack(312): waitForCondition(LockCondition) timed out (identity=19, status=0). CPU may be pegged. trying again. 12-01 08:11:59.318: WARN/SharedBufferStack(312): waitForCondition(LockCondition) timed out (identity=19, status=0). CPU may be pegged. trying again. 12-01 08:12:03.332: WARN/SharedBufferStack(312): waitForCondition(LockCondition) timed out (identity=19, status=0). CPU may be pegged. trying again. 12-01 08:12:05.329: WARN/SharedBufferStack(312): waitForCondition(LockCondition) timed out (identity=19, status=0). CPU may be pegged. trying again. 12-01 08:12:07.216: WARN/KeyCharacterMap(312): No keyboard for id 0 12-01 08:12:07.216: WARN/KeyCharacterMap(312): Using default keymap: /system/usr/keychars/qwerty.kcm.bin
简要说明:
android系统中调试Java非常容易,一般遇到错误都在logcat中打印出错时函数的调用关系,
而C库中出错时只看到一些二进制信息,使用gdbserver调试环境搭建又比较复杂。下面是一种debug C库的方法
方法一:
下在介绍一个简单的调试库的方法,当然需要有so库的源代码
举例
a) 错误信息如下,它表示了出错时的函数调用关系(下面调上面的)
I/DEBUG ( 634): #00 pc 000078e6 /system/lib/libmultiplayerservice.so
I/DEBUG ( 634): #01 pc 000087bc /system/lib/libmultiplayerservice.so
I/DEBUG ( 634): #02 pc 0000e94e /system/lib/libsensorservice.so
I/DEBUG ( 634): #03 pc 0000a790 /system/lib/libsensorservice.so
I/DEBUG ( 634): #04 pc 0000d4b2 /system/lib/libsensorservice.so
I/DEBUG ( 634): #05 pc 0000d852 /system/lib/libsensorservice.so
I/DEBUG ( 634): #06 pc 00015ece /system/lib/libutils.so
I/DEBUG ( 634): #07 pc 0000153a /system/lib/libsystem_server.so
I/DEBUG ( 634): #08 pc 00001756 /system/lib/libsystem_server.so
I/DEBUG ( 634): #09 pc 0000adb8 /system/lib/libandroid_servers.so
I/DEBUG ( 634): #10 pc 00011cb4 /system/lib/libdvm.so
b)进入源码中带符号表的so库所在目录
$ cd out/target/product/generic/obj/SHARED_LIBRARIES/libmultiplayerservice_intermediates/LINKED
这个有个需要注意的地方:
对于可执行程序及动态库,一般在LINKED子目录中是带有符号的库(没有经过符号剥离),如果可执行文件中没有包括调试符号,您将获得??:0 作为响应。
c)用addr2line命令找到地址对应的程序位置,动态库为libmultiplayerservice.so
arm-eabi-addr2line 000078e6 -e libmultiplayerservice.so
结果:,显示出对应的程序文件和行数,如果不是debug版本,可能有一两行偏差
frameworks/base/services/multiplayerservice/PlayerSocket.cpp 421 行
d)注意
arm-eabi_addr2line在prebuild/linux-x86/toolchain/arm-eabi-xxx/bin目录下,
运行build/envsetup.sh后即可直接使用它,同目录下的objdump, nm也是常用调试命令
方法二:
1、首先需要一个重要的脚本文件:
#!/usr/bin/python # stack symbol parser import os import string import sys #define android product name ANDROID_PRODUCT_NAME = 'generic' ANDROID_WORKSPACE = os.getcwd()+"/" # addr2line tool path and symbol path addr2line_tool = ANDROID_WORKSPACE + 'prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/bin/arm-eabi-addr2line' symbol_dir = ANDROID_WORKSPACE + 'out/target/product/' + ANDROID_PRODUCT_NAME +'/symbols' symbol_bin = symbol_dir + '/system/bin/' symbol_lib = symbol_dir + '/system/lib/' class ReadLog: def __init__(self,filename): self.logname = filename def parse(self): f = file(self.logname,'r') lines = f.readlines() if lines != []: print 'read file ok' else: print 'read file failed' result =[] for line in lines: if line.find('stack') != -1: print 'stop search' break elif line.find('system') != -1: #print 'find one item' + line result.append(line) return result class ParseContent: def __init__(self,addr,lib): self.address = addr # pc address self.exename = lib # executable or shared library def addr2line(self): cmd = addr2line_tool + " -C -f -s -e " + symbol_dir + self.exename + " " + self.address #print cmd stream = os.popen(cmd) lines = stream.readlines(); list = map(string.strip,lines) return list inputarg = sys.argv if len(inputarg) < 2: print 'Please input panic log' exit() filename = inputarg[1] readlog = ReadLog(filename) inputlist = readlog.parse() for item in inputlist: itemsplit = item.split() test = ParseContent(itemsplit[-2],itemsplit[-1]) list = test.addr2line() print "%-30s%s" % (list[1],list[0])
将以上文件保存hy.panic.py
2、相关的死机堆栈信息保存 error.txt
例如:
I/DEBUG ( 634): #00 pc 000078e6 /system/lib/libmultiplayerservice.so
I/DEBUG ( 634): #01 pc 000087bc /system/lib/libmultiplayerservice.so
I/DEBUG ( 634): #02 pc 0000e94e /system/lib/libsensorservice.so
I/DEBUG ( 634): #03 pc 0000a790 /system/lib/libsensorservice.so
I/DEBUG ( 634): #04 pc 0000d4b2 /system/lib/libsensorservice.so
I/DEBUG ( 634): #05 pc 0000d852 /system/lib/libsensorservice.so
I/DEBUG ( 634): #06 pc 00015ece /system/lib/libutils.so
I/DEBUG ( 634): #07 pc 0000153a /system/lib/libsystem_server.so
I/DEBUG ( 634): #08 pc 00001756 /system/lib/libsystem_server.so
I/DEBUG ( 634): #09 pc 0000adb8 /system/lib/libandroid_servers.so
I/DEBUG ( 634): #10 pc 00011cb4 /system/lib/libdvm.so
3、将以上两个文件拷贝到android的编译根路径下面,执行
python hy.panic.py error.txt
方法2使用非常方便,相比于加打印效率大大提高。非常感谢提供脚本的同学。
参考链接
- Android log日志分析 https://www.cnblogs.com/liyiran/p/7686296.html
- android动态库死机调试方法 https://blog.csdn.net/andyhuabing/article/details/7074979
- 如何读懂和分析Android logcat http://blog.chinaunix.net/uid-29728680-id-5054948.html
- 解读Android日志 https://blog.csdn.net/qq_33850776/article/details/120509127