一. 获取Trace

调用栈信息(Trace)是分析异常经常使用的,这里简单划分两类情况:

  • 当前线程Trace: 当前执行流所在线程的调用栈信息;
  • 目标进程Trace:可获取目标进程的调用栈,用于动态调试;

1.1 当前线程Trace

1) Java层


 
  1. Thread.currentThread().dumpStack(); //方法1

  2. Log.d(TAG,"Gityuan", new RuntimeException("Gityuan")); //方法2

  3. new RuntimeException("Gityuan").printStackTrace(); //方法3

2) Native层


 
  1. #include <utils/CallStack.h>

  2. android::CallStack stack(("Gityuan"));

1.2 目标进程Trace

1) Java层


 
  1. adb shell kill -3 [pid] //方法1

  2. Process.sendSignal(pid, Process.SIGNAL_QUIT) //方法2

生成trace文件保存在文件data/anr/traces.txt

2) Native层


 
  1. adb shell debuggerd -b [tid] //方法1

  2. Debug.dumpNativeBacktraceToFile(pid, tracesPath) //方法2

前两条命令输出内容相同:

  • 命令1输出到控制台
  • 命令2输出到目标文件

对于debuggerd命令,若不带参数则输出tombstones文件,保存到目录/data/tombstones

3) Kernel层


 
  1. adb shell cat /proc/[tid]/stack //方法1

  2. WatchDog.dumpKernelStackTraces() //方法2

其中dumpKernelStackTraces()只能用于打印当前进程的kernel线程

1.3 小节

以下分别列举输出Java, Native, Kernel的调用栈方式:

分析异常时往往需要关注的重要目录:


 
  1. /data/anr/traces.txt

  2. /data/tombstones/tombstone_X

  3. /data/system/dropbox/

二. 时间调试

为了定位耗时过程,有时需要在关注点添加相应的systrace,而systrace可跟踪系统cpu,io以及各个子系统运行状态等信息,对于kernel是利用Linux的ftrace功能。当然也可以直接在方法前后加时间戳,输出log的方式来分析。

2.1 新增systrace

1) App


 
  1. import android.os.Trace;

  2. void foo() {

  3. Trace.beginSection("app:foo");

  4. ...

  5. Trace.endSection();

  6. }

2) Java Framework


 
  1. import android.os.Trace;

  2. void foo() {

  3. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "fw:foo");

  4. ...

  5. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

  6. }

3) Native Framework


 
  1. #define ATRACE_TAG ATRACE_TAG_GITYUAN

  2. #include <utils/Trace.h> // used for C++

  3. #include <cutils/trace.h> // used for C

  4. void foo() {

  5. ATRACE_CALL();

  6. ...

  7. }

或者


 
  1. #define ATRACE_TAG ATRACE_TAG_GITYUAN

  2. #include <utils/Trace.h> // used for C++

  3. #include <cutils/trace.h> // used for C

  4. void foo() {

  5. ATRACE_BEGIN();

  6. ...

  7. ATRACE_END();

  8. }

2.2 打印时间戳

1) Java


 
  1. import android.util.Log;

  2. void foo(){

  3. long startTime = System.currentTimeMillis();

  4. ...

  5. long spendTime = System.currentTimeMillis() - startTime;

  6. Log.i(TAG,"took " + spendTime + “ ms.”);

  7. }

2) C/C++


 
  1. #include <stdio.h>

  2. #include <sys/time.h>

  3. void foo() {

  4. struct timeval time;

  5. gettimeofday(&time, NULL); //精度us

  6. printf("took %lld ms.\n", time.tv_sec * 1000 + time.tv_usec /1000);

  7. }

2.3 kernel log

有时候Kernel log的输出是由级别限制,可通过如下命令查看:


 
  1. adb shell cat /proc/sys/kernel/printk

  2. 4 4 1 7

参数解读:

  • 控制台日志级别:优先级高于该值的消息将被打印至控制台。
  • 缺省的消息日志级别:将用该值来打印没有优先级的消息。
  • 最低的控制台日志级别:控制台日志级别可能被设置的最小值。
  • 缺省的控制台日志级别:控制台日志级别的缺省值

日志级别:

Log相关命令

  • dmesg 或 cat /proc/kmsg
  • logcat -L 或 cat /proc/last_kmsg
  • logcat -b events -b system

三. addr2line

addr2line功能是将函数地址解析为函数名。分析过Native Crash,那么对addr2line一定不会陌生。 addr2line命令参数:


 
  1. Usage: addr2line [option(s)] [addr(s)]

  2. The options are:

  3. @<file> Read options from <file>

  4. -a --addresses Show addresses

  5. -b --target=<bfdname> Set the binary file format

  6. -e --exe=<executable> Set the input file name (default is a.out)

  7. -i --inlines Unwind inlined functions

  8. -j --section=<name> Read section-relative offsets instead of addresses

  9. -p --pretty-print Make the output easier to read for humans

  10. -s --basenames Strip directory names

  11. -f --functions Show function names

  12. -C --demangle[=style] Demangle function names

  13. -h --help Display this information

  14. -v --version Display the program's version

3.1 Native地址转换

Step 1: 获取symbols表

先获取对应版本的symbols,即可找到对应的so库。(最好是对应版本addr2line,可确保完全匹配)

Step 2: 执行addr2line命令


 
  1. // 64位

  2. cd prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin

  3. ./aarch64-linux-android-addr2line -f -C -e libxxx.so <addr1>

  4.  
  5. //32位

  6. cd /prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin

  7. ./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来获取的

10-06 15:25