android 除錯技巧
阿新 • • 發佈:2018-12-24
一. 獲取Trace
呼叫棧資訊(Trace)是分析異常經常使用的,這裡簡單劃分兩類情況:
當前執行緒Trace: 當前執行流所線上程的呼叫棧資訊;
目標程序Trace:可獲取目標程序的呼叫棧,用於動態除錯;
1.1 當前執行緒Trace
1) Java層
Thread.currentThread().dumpStack(); //方法1
Log.d(TAG,"Gityuan", new RuntimeException("Gityuan")); //方法2
new RuntimeException("Gityuan").printStackTrace(); //方法3
public static void getThreadTrace() {
for (StackTraceElement i : Thread.currentThread().getStackTrace()) {
Log.i(TAG, i.toString());
}
}
2) Native層程式碼除錯
#include <utils/CallStack.h>
android::CallStack stack(("Gityuan"));
1.2 目標程序Trace
1) Java層
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
前兩條命令輸出內容相同:
命令1輸出到控制檯
命令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
1) App
import android.os.Trace;
void foo() {
Trace.beginSection("app:foo");
...
Trace.endSection();
}
2) Java Framework
import android.os.Trace;
void foo() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "fw:foo");
...
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
這個需要開啟trace 程序(systrace),抓取log 使用瀏覽器分析。
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();
}
2.2 列印時間戳
1) Java
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);
}
2.3 kernel log
有時候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相關命令
dmesg 或 cat /proc/kmsg
logcat -L 或 cat /proc/last_kmsg
logcat -b events -b system
三. 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
3.1 Native地址轉換
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位於目錄prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/
vmlinux位於目錄out/target/product/xxx/obj/KERNEL_OBJ/
執行如下命令:(需要帶上絕對目錄)
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來獲取的
呼叫棧資訊(Trace)是分析異常經常使用的,這裡簡單劃分兩類情況:
當前執行緒Trace: 當前執行流所線上程的呼叫棧資訊;
目標程序Trace:可獲取目標程序的呼叫棧,用於動態除錯;
1.1 當前執行緒Trace
1) Java層
Thread.currentThread().dumpStack(); //方法1
Log.d(TAG,"Gityuan", new RuntimeException("Gityuan")); //方法2
new RuntimeException("Gityuan").printStackTrace(); //方法3
public static void getThreadTrace() {
for (StackTraceElement i : Thread.currentThread().getStackTrace()) {
Log.i(TAG, i.toString());
}
}
2) Native層程式碼除錯
#include <utils/CallStack.h>
android::CallStack stack(("Gityuan"));
1.2 目標程序Trace
1) Java層
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
前兩條命令輸出內容相同:
命令1輸出到控制檯
命令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
1) App
import android.os.Trace;
void foo() {
Trace.beginSection("app:foo");
...
Trace.endSection();
}
2) Java Framework
import android.os.Trace;
void foo() {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "fw:foo");
...
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
這個需要開啟trace 程序(systrace),抓取log 使用瀏覽器分析。
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();
}
2.2 列印時間戳
1) Java
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);
}
2.3 kernel log
有時候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相關命令
dmesg 或 cat /proc/kmsg
logcat -L 或 cat /proc/last_kmsg
logcat -b events -b system
三. 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
3.1 Native地址轉換
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位於目錄prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/
vmlinux位於目錄out/target/product/xxx/obj/KERNEL_OBJ/
執行如下命令:(需要帶上絕對目錄)
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來獲取的