Android打印Trace堆栈

本文介绍了在Android系统中获取和分析调用栈信息的方法,包括当前线程和目标进程的Trace,以及在Java、Native和Kernel层的实现。同时,讲述了使用systrace进行性能调试的技巧,以及如何通过addr2line将函数地址转换为可读的函数名,用于NativeCrash和Kernel问题的调试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 获取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

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的调用栈方式:

类别函数式命令式
JavaProcess.sendSignal(pid, Process.SIGNAL_QUIT)kill -3 [pid]
NativeDebug.dumpNativeBacktraceToFile(pid, tracesPath)debuggerd -b [pid]
KernelWD.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); 
}

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_EMERG0致命错误
KERN_ALERT1报告消息
KERN_CRIT2严重异常
KERN_ERR3出错
KERN_WARNING4警告
KERN_NOTICE5通知
KERN_INFO6常规
KERN_DEBUG7调试

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

### Android 中实现跨进程打印堆栈功能的方式 在 Android 平台中,要实现在不同进程中打印目标进程的堆栈信息,可以利用 `debuggerd` 工具以及 Java 的异常处理机制来完成。以下是具体的实现方式: #### 使用 Debuggerd 进程 Debuggerd 是 Android 提供的一个守护进程,用于捕获并记录崩溃日志到 `/data/tombstone/` 文件夹中[^3]。它也可以作为一个命令行工具,在不终止目标进程的前提下打印其原生堆栈。 可以通过以下命令获取指定 PID 的原生堆栈: ```bash debuggerd -b <pid> ``` 此命令会将目标进程的堆栈信息输出至标准流或文件中。这种方式适用于需要查看其他进程中的 C/C++ 层级堆栈的情况。 #### 跨进程调用 Java 堆栈 对于 Java 层面的堆栈信息,由于每个进程都有独立的 JVM 实例,因此无法直接访问另一个进程的内存空间。然而,如果两个进程之间存在某种通信协议(如 AIDL 或者 Messenger),则可以从远程服务请求触发堆栈打印逻辑。 假设有一个 Binder 接口定义如下: ```aidl // IStackDumper.aidl package com.example.stack; interface IStackDumper { void dumpStack(); } ``` 服务器端实现该接口时,只需抛出一个新的异常对象并将堆栈跟踪写入 Logcat 即可: ```java public class StackDumperService extends Service { private final IBinder binder = new IStackDumper.Stub() { @Override public void dumpStack() throws RemoteException { Throwable t = new Throwable("Remote stack trace request"); android.util.Log.e("STACK_TAG", "Dumping remote process stack:", t); } }; @Nullable @Override public IBinder onBind(Intent intent) { return binder; } } ``` 客户端绑定到上述服务之后即可调用 `dumpStack()` 方法从而获得远端进程内的 Java 堆栈详情[^1][^2]。 #### 综合方案 为了全面支持包括 Native 和 Dalvik/ART 双层架构下的所有可能场景,建议结合以上两种技术手段共同构建完整的解决方案框架。即先尝试通过 IPC 请求让对方主动暴露自己的状态;若失败再借助系统级别的调试工具进一步深入探究根本原因所在。 ```java try { // 尝试通过AIDL接口请求远程进程自行打印堆栈 iStackDumper.dumpStack(); } catch (RemoteException e) { // 如果IPC失败,则考虑采用更底层的办法比如使用Shell命令执行debuggerd Runtime.getRuntime().exec(String.format(Locale.US, "debuggerd -b %d", targetPid)); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值