##一. 獲取Trace 調用棧信息(Trace)是分析異常經常使用的,這裡簡單劃分兩類情況:
###1.1 當前線程Trace
Thread.currentThread().dumpStack(); //方法1
Log.d(TAG,"Gityuan", new RuntimeException("Gityuan")); //方法2
new RuntimeException("Gityuan").printStackTrace(); //方法3
2) Native層
#include <utils/CallStack.h>
android::CallStack stack(("Gityuan"));
adb shell kill -3 [pid] //方法1
Process.sendSignal(pid, Process.SIGNAL_QUIT) //方法2
生成trace文件保存在文件data/anr/traces.txt
2) Native層
adb shell debuggerd -b [tid] //方法1
Debug.dumpNativeBacktraceToFile(pid, tracesPath) //方法2
前兩條命令輸出內容相同:
對於debuggerd命令,若不帶參數則輸出tombstones文件,保存到目錄/data/tombstones
3) Kernel層
adb shell cat /proc/[tid]/stack //方法1
WatchDog.dumpKernelStackTraces() //方法2
其中dumpKernelStackTraces()只能用於打印當前進程的kernel線程
###1.3 小節 以下分別列舉輸出Java, Native, Kernel的調用棧方式:
類別 函數式 命令式
Java Process.sendSignal(pid, Process.SIGNAL_QUIT) kill -3 [pid]
Native Debug.dumpNativeBacktraceToFile(pid, tracesPath) debuggerd -b [pid]
Kernel WD.dumpKernelStackTraces() cat /proc/[tid]/stack
分析異常時往往需要關注的重要目錄:
/data/anr/traces.txt
/data/tombstones/tombstone_X
/data/system/dropbox/
##二. 時間調試 為了定位耗時過程,有時需要在關注點添加相應的systrace,而systrace可跟蹤系統cpu,io以及各個子系統運行狀態等信息,對於kernel是利用Linux的ftrace功能。當然也可以直接在方法前後加時間戳,輸出log的方式來分析。
###2.1 新增systrace
import android.os.Trace;
void foo() {
Trace.beginSection("app:foo");
...
Trace.endSection();
}
import android.os.Trace;
void foo() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "fw:foo");
...
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
3) Native Framework
#define ATRACE_TAG ATRACE_TAG_GITYUAN
#include <utils/Trace.h> // used for C++
#include <cutils/trace.h> // used for C
void foo()
{
ATRACE_CALL();
...
}
或者
#define ATRACE_TAG ATRACE_TAG_GITYUAN
#include <utils/Trace.h> // used for C++
#include <cutils/trace.h> // used for C
void foo() {
ATRACE_BEGIN();
...
ATRACE_END();
}
import android.util.Log;
void foo()
{
long startTime = System.currentTimeMillis();
...
long spendTime = System.currentTimeMillis() - startTime;
Log.i(TAG,"took " + spendTime + “ ms.”);
}
2) C/C++
#include <stdio.h>
#include <sys/time.h>
void foo()
{
struct timeval time;
gettimeofday(&time, NULL); //精度us
printf("took %lld ms.\n", time.tv_sec * 1000 + time.tv_usec /1000);
}
有時候Kernel log的輸出是由級別限制,可通過如下命令查看:
adb shell cat /proc/sys/kernel/printk
4 4 1 7
參數解讀:
日誌級別:
級別 | 值 | 說明 |
---|---|---|
KERN_EMERG | 0 | 致命錯誤 |
KERN_ALERT | 1 | 報告消息 |
KERN_CRIT | 2 | 嚴重異常 |
KERN_ERR | 3 | 出錯 |
KERN_WARNING | 4 | 警告 |
KERN_NOTICE | 5 | 通知 |
KERN_INFO | 6 | 常規 |
KERN_DEBUG | 7 | 調試 |
Log相關命令
##三. addr2line addr2line功能是將函數地址解析為函數名。分析過Native Crash,那麼對addr2line一定不會陌生。 addr2line命令參數:
Usage: addr2line [option(s)] [addr(s)]
The options are:
@<file> Read options from <file>
-a --addresses Show addresses
-b --target=<bfdname> Set the binary file format
-e --exe=<executable> Set the input file name (default is a.out)
-i --inlines Unwind inlined functions
-j --section=<name> Read section-relative offsets instead of addresses
-p --pretty-print Make the output easier to read for humans
-s --basenames Strip directory names
-f --functions Show function names
-C --demangle[=style] Demangle function names
-h --help Display this information
-v --version Display the program's version
Step 1: 獲取symbols表
先獲取對應版本的symbols,即可找到對應的so庫。(最好是對應版本addr2line,可確保完全匹配)
Step 2: 執行addr2line命令
// 64位
cd prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin
./aarch64-linux-android-addr2line -f -C -e libxxx.so <addr1>
//32位
cd /prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin
./arm-linux-androideabi-addr2line -f -C -e libxxx.so <addr1>
另外,有興趣可以研究下development/scripts/stack,地址批量轉換工具。
###3.2 kernel地址轉換 addr2line也適用於調試分析Linux Kernel的問題。例如,查詢如下命令所對應的代碼行號
[<0000000000000000>] binder_thread_read+0x2a0/0x324
Step 1: 獲取符號地址
通過命令arm-eabi-nm從vmlinux找到目標方法的符號地址,其中nm和vmlinux所在目錄:
執行如下命令:(需要帶上絕對目錄)
arm-eabi-nm vmlinux |grep binder_thread_read
則輸出結果: c02b2f28 T binder_thread_read
,可知binder_thread_read的符號地址為c02b2f28, 其偏移量為0x2a0,則計算後的目標符號地址= c02b2f28 + 2a0,然後再採用addr2line轉換得到方法所對應的行數
Step 2: 執行addr2line命令
./aarch64-linux-android-addr2line -f -C -e vmlinux [目標地址]
注意:對於kernel調用棧翻譯過程都是通過vmlinux來獲取的
##三、JAVA層堆棧列印 ###1. 在指定的函數內列印相關java調用
Log.d(TAG,Log.getStackTraceString(new Throwable()));
###2. 普通JAVA進程堆棧
ActivityManagerService.dumpStackTraces
保存在系統設置dalvik.vm.stack-trace-file指定的文件data/anr/traces.txt中。可以包含多個進程堆棧資訊。
###3. 內核進程堆棧
dumpKernelStackTraces,該函數為私有函數,不可調用。
代碼在frameworks/base/services/java/com/android/server/Watchdog.java
保存在系統設置dalvik.vm.stack-trace-file指定的文件data/anr/traces.txt中。
###4. 出異常時列印當前堆棧
Exception::printStackTrace()
try {
...
} catch (RemoteException e) {
e.printStackTrace();
...
}
###5. 輸出指定進程的堆棧
Process.sendSignal(pid, Process.SIGNAL_QUIT)
保存在data/anr/traces.txt。
這個只對java進程有效,由dalvikvm的SignalCatcher.c處理。
###1. CallStack 使用方式:
#include <utils/CallStack.h>
...
CallStack stack;
stack.update();
stack.dump(""); // the parameter is prefix of dump
在使用之前需要修改system/core/include/arch/linux-arm/AndroidConfig.h
在使用之前需要修改system/core/include/arch/linux-arm/AndroidConfig.h
#define HAVE_DLADDR 1
#define HAVE_CXXABI 1
並在文件frameworks/base/libs/utils/Android.mk中大約105行(LOCAL_SHARED_LIBRARIES)後添加
ifeq ($(TARGET_OS),linux)
LOCAL_SHARED_LIBRARIES += libdl
endif
重新編譯,push生成的libutils.so到/system/lib/目錄下,重啟設備。
##五、JAVA異常分析 這個android會輸出資訊到logcat。容易分析。
##六、Natvie異常分析 native進程異常會導致
debuggerd會輸出資訊到logcat並保存到/data/tombstones。
可以修改system/core/debuggerd/debuggerd.c中dump_stack_and_code的代碼滿足更深的調試資訊需求。
Natvie異常分析(dalvik方式) 用此方法調試由於GC導致的native異常。
修改vm/interp/Stack.c約456行的dvmCallMethodV函數,添加以下幾行。
LOGD(" YINGMINGBO class:%s\n", clazz->descriptor);
LOGD(" YINGMINGBO name:%s\n", method->name);
LOGD(" YINGMINGBO desc:%s\n", desc);
##七、日誌Log系統 在java中使用
import android.util.Log;
...
Log.d(TAG,"log info");
在Native代碼中使用
#define LOG_TAG "YOUR_LOGTAG"
...
#include <utils/Log.h>
#define LOG_NDEBUG 0
...
LOGD("log info");
或者
Log.d(LOG_TAG,“log info”);
使用adb logcat時可以只顯示特定類別的LOG,還可以通過參數 -v threadtime 顯示線程號及時間資訊。
普通標準輸出轉為Logcat
#system/bin/logwrapper 進程名
##八、其他調試手段(命令行) ###1. 列印指定JAVA進程的堆棧到文件中
#kill -3 pid
這裡的3就是3.5節的Process.SIGNAL_QUIT。
輸出在data/anr/traces.txt
文件中。
這個只對java進程有效,由dalvikvm處理
。
###2. 列印指定進程的堆棧到Logcat
#kill -11 pid
或者
#kill -7 pid
這個有時有效。其原理是利用了(六)節的機制。
可以用adb logcat看堆棧調用輸出。
###3. 列印指定進程的系統調用
#strace -f -p pid -o output
主要輸出文件、SOCKET、鎖等系統操作的資訊。
-f表示跟蹤所有子進程.
-o輸出log到指定文件,可不用。