JNI中如何打印Call Stack

NDK里面好像没有专门打印Call Stack的函数,正好又要用到这个功能,Google了一翻,可以用stack unwind相关的API实现。


关于什么是stack unwind的解释如下:

http://www.ibm.com/support/knowledgecenter/SSAE4W_9.0.0/com.ibm.xlcpp111.aix.doc/language_ref/cplr155.html

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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值