NDK里面好像没有专门打印Call Stack的函数,正好又要用到这个功能,Google了一翻,可以用stack unwind相关的API实现。
关于什么是stack unwind的解释如下:
http://www.cnblogs.com/catch/p/3604516.html (中文)
简单的说就是一套专门用来处理异常的函数,通过它就可以拿到当前的Call Stack.
Android hardward层其实也有使用这些函数,比如下面的这个文件:
hardware/ti/omap4xxx/heaptracker.c
好了,废话不多说,直接上代码:
#include <unwind.h>
#include <dlfcn.h>
struct BacktraceState
{
intptr_t* current;
intptr_t* end;
};
static _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
{
BacktraceState* state = static_cast<BacktraceState*>(arg);
intptr_t ip = (intptr_t)_Unwind_GetIP(context);
if (ip) {
if (state->current == state->end) {
return _URC_END_OF_STACK;
} else {
state->current[0] = ip;
state->current++;
}
}
return _URC_NO_REASON;
}
size_t captureBacktrace(intptr_t* buffer, size_t maxStackDeep)
{
BacktraceState state = {buffer, buffer + maxStackDeep};
_Unwind_Backtrace(unwindCallback, &state);
return state.current - buffer;
}
void dumpBacktraceIndex(char *out, intptr_t* buffer, size_t count)
{
for (size_t idx = 0; idx < count; ++idx) {
intptr_t addr = buffer[idx];
const char* symbol = " ";
const char* dlfile=" ";
Dl_info info;
if (dladdr((void*)addr, &info)) {
if(info.dli_sname){
symbol = info.dli_sname;
}
if(info.dli_fname){
dlfile = info.dli_fname;
}
}else{
strcat(out,"# \n");
continue;
}
char temp[50];
memset(temp,0,sizeof(temp));
sprintf(temp,"%zu",idx);
strcat(out,"#");
strcat(out,temp);
strcat(out, ": ");
memset(temp,0,sizeof(temp));
sprintf(temp,"%zu",addr);
strcat(out,temp);
strcat(out, " " );
strcat(out, symbol);
strcat(out, " ");
strcat(out, dlfile);
strcat(out, "\n" );
}
}
然后在想打印Call Stack的地方调用上面的函数:
const size_t maxStackDeep = 20;
intptr_t stackBuf[maxStackDeep];
char outBuf[2048];
memset(outBuf,0,sizeof(outBuf));
dumpBacktraceIndex(outBuf, stackBuf, captureBacktrace(stackBuf, maxStackDeep));
LogYc(" %s\n", outBuf);
打印出来的效果:
#1: 3047786506 _Z14dvmCallMethodVP6ThreadPK6MethodP6ObjectbP6JValueSt9__va_list libdvm.so
#2: 3047787112 _Z13dvmCallMethodP6ThreadPK6MethodP6ObjectP6JValuez libdvm.so
#3: 3047840152 _Z18dvmFindClassNoInitPKcP6Object libdvm.so
#4: 3047773520 _Z18dvmOptResolveClassP11ClassObjectjP11VerifyError libdvm.so
#5: 3047754016 _Z17dvmVerifyCodeFlowP12VerifierData libdvm.so
#6: 3047771452 libdvm.so
#7: 3047771642 _Z14dvmVerifyClassP11ClassObject libdvm.so
#8: 3047840596 dvmInitClass libdvm.so
#9: 3047844830 dvmResolveMethod libdvm.so
#10: 3047590400 _Z20dvmInterpretPortableP6Thread libdvm.so
#11: 3047531408 _Z12dvmInterpretP6ThreadPK6MethodP6JValue libdvm.so
#12: 3047787002 _Z14dvmCallMethodVP6ThreadPK6MethodP6ObjectbP6JValueSt9__va_list libdvm.so
#13: 3047704384 libdvm.so
#14: 3047652026 libdvm.so
#15: 2851196260 libbaiduprotect.so
#16: 2851190688 libbaiduprotect.so
#17: 3047475600 dvmPlatformInvoke libdvm.so
#18: 3047711070 _Z16dvmCallJNIMethodPKjP6JValuePK6MethodP6Thread libdvm.so
#19: 3047622360 _Z21dvmCheckCallJNIMethodPKjP6JValuePK6MethodP6Thread libdvm.so
说说原理:
整个过程的核心就在_Unwind_Backtrace(unwindCallback, &state)这个函数调用上。
它的作用是什么呢?
它会遍历当前的Call stack,并且会在每个call stack上都调用unwindCallback函数,state是传递给给它的参数。
于是我们就在unwindCallback这个回调中通过_Unwind_GetIP(context)获取到当前call stack上正在执行的指令的地址,最后再通过dladdr()获取到对应的so信息。
dladdr()会根据传递给它的addr遍历当前进程中打开的so....
下面的是dladdr()寻找so的具体实现:
soinfo* find_containing_library(const void* p) {
Elf32_Addr address = reinterpret_cast<Elf32_Addr>(p);
for (soinfo* si = solist; si != NULL; si = si->next) {
if (address >= si->base && address - si->base < si->size) {
return si;
}
}
return NULL;
}