在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