在 Java 层中,可以通过 Thread.currentThread().getStackTrace() 方法获取当前线程的调用堆栈。这个方法会返回一个 StackTraceElement 数组,其中每一个元素都包含了调用堆栈中的一个帧的信息,如类名、方法名、文件名和行号等。很多开源框架都是类似做法来获取Java堆栈 , 使用如下:
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTraceElements) {
Log.d("TAG", element.toString());
}
另外在Native层,我们还可以通过 JNI 接口和 JNIEnv 指针在 Native 层获取当前线程的 Java 虚拟机堆栈信息。需要调用 env->GetJavaVM() 方法获取Java虚拟机实例,并使用 env->GetStackTrace() 方法获取当前线程的 Java 虚拟机堆栈信息。
但是获取Native的堆栈就比较麻烦了.
运行demo , 可以看到logcat打印如下:
检测正确性
通过NDK给我们提供的addr2line工具来验证 , 执行addr2line命令
aarch64-linux-android-addr2line -e libmyapplication.so 00023774 00023bfa 00023d18 00023d28 00023d38
可以看到当前函数调用的堆栈确实被还原出来了, 并且和我们实际代码行数确实是一致的.
关键代码如下
static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context, void *arg) {
std::vector<_Unwind_Word> &stack = *(std::vector<_Unwind_Word> *) arg;
stack.push_back(_Unwind_GetIP(context));
return _URC_NO_REASON;
}
void call_stack_dump(std::string &dump) {
std::vector<_Unwind_Word> stack;
_Unwind_Backtrace(unwind_callback, (void *) &stack);
dump.append("backtrace:\n");
char buff[256];
for (size_t i = 0; i < stack.size(); i++) {
Dl_info info;
uintptr_t pc = stack[i];
if (dladdr((void *) pc, &info) > 0) {
int addr = (char *) pc - (char *) info.dli_fbase - 1;
if (info.dli_sname == NULL || strlen(info.dli_sname) == 0) {
sprintf(buff, "#%02x pc %08x %s\n", i, addr, info.dli_fname);
} else {
char *demangled = abi::__cxa_demangle(info.dli_sname, nullptr, 0, nullptr);
sprintf(buff, "#%02x pc %08x %s (%s+%" PRIuPTR")\n", i, addr, info.dli_fname,
demangled, pc - (uintptr_t) info.dli_saddr);
}
dump.append(buff);
}
}
}
_Unwind_Backtrace 是一个名为「回溯接口」(Unwinding API)的函数中的一部分,用于在 C/C++ 代码中获取函数调用堆栈。其实现方式是利用 GCC 的内部 ABI(Application Binary Interface)规范,在编译时向目标文件中插入相关的元数据,再在运行时解析这些元数据。因此,_Unwind_Backtrace 只能用于支持 GCC 内部 ABI 的编译器,如 GCC、Clang、ICC 等。
Android NDK 默认使用Clang 编译器作为 C/C++ 编译器 , 经测试android 5.0 和 13.0版本 , 使用_Unwind_Backtrace堆栈都能还原出来.
_Unwind_GetIP 函数用于获取当前堆栈帧程序计数器(Program Counter)的方法 , 程序计数器pc是一种处理器内部特殊寄存器。在CPU执行程序时,程序计数器指向下一条要执行的指令的地址,因此可以理解为程序计数器代表了CPU正在执行SO的地址 , 地址用16进制表示。