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使用非常方便,相比于加打印效率大大提高。非常感谢提供脚本的同学。

参考链接

  1. Android log日志分析 https://www.cnblogs.com/liyiran/p/7686296.html
  2. android动态库死机调试方法 https://blog.csdn.net/andyhuabing/article/details/7074979
  3. 如何读懂和分析Android logcat http://blog.chinaunix.net/uid-29728680-id-5054948.html
  4. 解读Android日志 https://blog.csdn.net/qq_33850776/article/details/120509127

 

posted @ 2022-01-19 12:33  青山牧云人  阅读(1876)  评论(0编辑  收藏  举报