Android framework C/C++中打印堆栈

有时候我们需要有这样的需求,需要在某些函数中打印函数的调用路径,经过查询主要有四种方式实现。

1. CallStack堆栈回溯

Android中提供了堆栈打印的类CallStack.cpp,此类在的路径为/system/core/libutils/CallStack.cpp,在不同的Android版本中,此类所在的so库不同,具体如下表所示。

Android版本所在so库依赖方法
Andorid O以上libutilscallstackAndroid.mk: LOCAL_SHARED_LIBRARIES里包含libutilscallstack;Android.bp: shared_libs里包含libutilscallstack
Android OlibutilsAndroid.mk: LOCAL_SHARED_LIBRARIES里包含libutils; Android.bp: shared_libs里包含libutils
Android N 以前版本libutilsAndroid.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技术

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值