有时候我们需要有这样的需求,需要在某些函数中打印函数的调用路径,经过查询主要有四种方式实现。
1. CallStack堆栈回溯
Android中提供了堆栈打印的类CallStack.cpp
,此类在的路径为/system/core/libutils/CallStack.cpp
,在不同的Android
版本中,此类所在的so
库不同,具体如下表所示。
Android版本 | 所在so库 | 依赖方法 |
---|---|---|
Andorid O以上 | libutilscallstack | Android.mk: LOCAL_SHARED_LIBRARIES里包含libutilscallstack;Android.bp: shared_libs里包含libutilscallstack |
Android O | libutils | Android.mk: LOCAL_SHARED_LIBRARIES里包含libutils; Android.bp: shared_libs里包含libutils |
Android N 以前版本 | libutils | Android.mk: LOCAL_SHARED_LIBRARIES里包含libutils |
具体的使用步骤:
(1)包含头文件
#include "utils/CallStack.h"
(2)引用动态库
根据所在的so库进行依赖使用,如上表所示。
(3)打印堆栈
android::CallStack cs("tagName")
注意:此种打印堆栈的方法,并不通用。比如自己编写的普通APP中无法找到CallStack头文件,还有在系统源代码中有些地方无法依赖libutilscallstack.so 、libutils.so库。
2. backtrace堆栈回溯
此方法是参照CallStack.cpp
堆栈打印的实现,查看CallStack.cpp
的源代码可以发现,实现打印堆栈的方法是在backtrace/Backtrace.h
头文件中,对应的实现文件是在/system/core/libbacktrace
目录下,通过查看Android.dp
文件可知,最终/system/core/libbacktrace
目录下的文件会编译成libbacktrace.so
文件。由此可知想要打印堆栈信息,可以借助libbacktrace.so
文件。
具体的使用步骤:
(1)包含头文件
#include <backtrace/Backtrace.h>
(2)引用动态库
Android.mk: LOCAL_SHARED_LIBRARIES里包含libbacktrace
Android.bp: shared_libs里包含libbacktrace
(3)打印堆栈
Vector<char*> mFrameLines;
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, -1));
if (!backtrace->Unwind(0)) {
ALOGW("%s: Failed to unwind callstack.", __FUNCTION__);
}
for (size_t i = 0; i < backtrace->NumFrames(); i++) {
mFrameLines.push_back(backtrace->FormatFrameData(i).c_str());
}
3. _Unwind_Backtrace堆栈回溯
具体的使用见下面的代码
#include <string>
#include <android/log.h>
#include <unwind.h>
#include <dlfcn.h>
#include <iomanip>
#include <sstream>
namespace {
struct BacktraceState
{
void** current;
void** end;
};
static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
{
BacktraceState* state = static_cast<BacktraceState*>(arg);
uintptr_t pc = _Unwind_GetIP(context);
if (pc) {
if (state->current == state->end) {
return _URC_END_OF_STACK;
} else {
*state->current++ = reinterpret_cast<void*>(pc);
}
}
return _URC_NO_REASON;
}
}
size_t captureBacktrace(void** buffer, size_t max)
{
BacktraceState state = {buffer, buffer + max};
_Unwind_Backtrace(unwindCallback, &state); //调用_Unwind_Backtrace方法,回调unwindCallback
return state.current - buffer;
}
void dumpBacktrace(std::ostream& os, void** buffer, size_t count)
{
for (size_t idx = 0; idx < count; ++idx) {
const void* addr = buffer[idx];
const char* symbol = "";
const char* fname = "";
void* baseAddr;
void* sAddr;
Dl_info info;
if (dladdr(addr, &info)) { //addr -> 调用堆栈中,在函数内部实际执行的位置(地址)
if(info.dli_sname) {
symbol = info.dli_sname; //一个指针,指向与指定的address最接近的符号的名称。该符号要么带有相同的地址,要么是带有低位地址的最接近符号。函数名称
fname = info.dli_fname; //一个指针,指向包含address的加载模块的文件名。so文件地址
baseAddr = info.dli_fbase; //,载模块的句柄。该句柄可用作dlsym() 的第一个参数。so在内存中的地址
sAddr = info.dli_saddr; //最接近符号的实际地址。对于代码符号,它包含最接近代码符号的OPD(正式Plabel 描述符)的地址。在内存中函数的相对地址
}else {
fname = info.dli_fname;
baseAddr = info.dli_fbase;
sAddr = info.dli_saddr;
}
//os << symbol << "\n";
//__android_log_print(ANDROID_LOG_INFO, "INJECT", "# %d: %0x %s %s %0x %0x",idx, addr, symbol, fname, baseAddr, sAddr);
os << " #" << std::setw(2) << idx << ": " << addr << " " << symbol << " "<< fname << " " << baseAddr << " "<< sAddr << "\n";
} else{
os <<"dladdr == NULL" <<"\n";
}
}
}
const char * backtraceToLogcat()
{
const size_t max = 100; // 调用的层数
void* buffer[max];
std::ostringstream oss;
dumpBacktrace(oss, buffer, captureBacktrace(buffer, max));
__android_log_print(ANDROID_LOG_INFO, "backtrace", "%s", oss.str().c_str());
return oss.str().c_str();
}
4. 手动实现backtrace–基于fp指针
栈回溯只需要lr指针和fp即可。lr指针记录了当前的函数的返回地址,fp指针则记录了当前栈帧的地址,一层一层向上回溯则可以完成栈回溯。
auto lr = (uint64_t) __builtin_return_address(0);
auto fp = (uint64_t) __builtin_frame_address(0);
while ((0 != fp) && (0 != *(unsigned long *) fp) &&
(fp != *(unsigned long *) fp)) {
lr = *(uint64_t *) (fp + sizeof(char *));
Dl_info info;
if (!dladdr((void *) lr, &info)) {
break;
}
if (!info.dli_fname) {
break;
}
LOG("backtrace pc = %p,module = %s", lr, info.dli_fname);
fp = *((uint64_t *) fp);
}
5. 参考
(1)Android backtrace探索(一)
(2)Android Debug】Android 加堆栈打印信息
(3)Android栈回溯这一篇就够了
(4)Android调用堆栈 android抓取native堆栈
(5)探索Android平台ARM unwind技术