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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM