C++造轮子之: 打印调用栈

安装addr2line后才能有更详细的信息

common/stacktrace.h

直接复制过去用。注意编译选项。

  1. 监听SIGSEGV信号,即段错误信号收到后,输出调用栈。
  2. 捕获std::terminate异常,设置了handler,handler中打调用栈。处理了C++抛异常导致的崩溃
  3. 输出行号:加上编译选项-g和链接选项-rdynamic.保证打出来的二进制符号表中有函数名和行号
  4. VMA地址转换:backtrace_symbols返回的地址可能是不是二进制中的地址。因为不同链接器可能链接的起始相对地址不同。这还需要加上链接选项-ldl。通过dladdr2获取二进制中的地址
  5. 使用addr2line将backtrace_symbols返回的地址转为好看的代码,函数名,行号信息。这要求系统安装了addr2line
  6. popen用来调用addr2line,并拿到返回结果
#pragma once
#include <signal.h>
#include <unistd.h>
#include <execinfo.h>
#include <malloc.h>
#include <cstdio>
#include <cstring>
#include <link.h>
namespace AdsonLib {
class PrintCoreStack{
public:
    static void RegisterCoreHandler() {
        struct sigaction sig;
        memset(&sig, 0, sizeof(sig));
        sig.sa_handler = &PrintCoreStack::CoreHandler;
        sig.sa_flags |= SA_RESETHAND; //one-time only
        sigaction(SIGSEGV, &sig, NULL);
        std::set_terminate([](){
            PrintStack();
            std::abort();
        });
    }
private:
    static std::string RunCmd(const std::string &cmd) {
        FILE* fd = popen(cmd.c_str(), "r");
        if(fd == NULL) return "";
        char buf[2048]={'\0'};
        std::string line;
        while(fgets(buf, 2048, fd)){
            line += buf;
        }
        while(line.size() > 0 && line.back() == '\n'){
            line.pop_back();
        }
        return line;
    }
    static std::string Int2Hex(size_t d){
        char tmp[128];
        snprintf(tmp, 128, "%p", d);
        return tmp;
    }

    static std::string DetailInfo(const std::string &binary_file, size_t vma_addr){
        return RunCmd("addr2line -f -C -p -e " + binary_file + " " + Int2Hex(vma_addr));
    }
    static size_t AddrToVMA(size_t addr){
        Dl_info dinfo;
        link_map* lm;
        dladdr1((void*)addr, &dinfo,(void**)&lm, RTLD_DL_LINKMAP);
        return addr - lm->l_addr;
    }
    static void PrintStack() {
        char *full_stack[100] = {0};
        int depth = backtrace(reinterpret_cast<void**>(full_stack), sizeof(full_stack)/sizeof(full_stack[0]));
        if (depth){
            char** syms = backtrace_symbols(reinterpret_cast<void**>(full_stack), depth);
            if (syms){
                for(size_t i = 0; i < depth; i++){
                    int j = 0;
                    while(syms[i][j] != '(') j++;
                    std::string f(syms[i], j);
                    size_t vma_addr = AddrToVMA((size_t)full_stack[i]);
                    printf("=== [%lu]:%s [vma: %p] [%s]\n", (i+1), syms[i], vma_addr, DetailInfo(f, vma_addr).c_str());
                }
            }
            free(syms);
        };
    }
    static void CoreHandler(int signo){
        PrintStack();
        raise(SIGSEGV);
    }
};
} //namespace AdsonLib

使用 

trans_acc.cpp

使用起来非常简单,在main函数里调用RegisterCoreHandler即可

#include "stacktrace.h"
int main(int argc, char *argv[]) {
    PrintCoreStack::RegisterCoreHandler();
    throw 333;
}

bazel BUILD文件

注意链接要加 "-ldl"和"-rdynamic"

cc_library(
    name = "common",
    srcs = glob(["common/*.cpp"]),
    hdrs = glob(["common/*.h"]),
)
cc_binary(
    name = "trans_acc",
    srcs = ["trans_acc.cpp"],
    deps = [
        ":common",
    ],
    linkopts = [
        "-lpthread", "-ldl", "-rdynamic",
    ]
)

.bazel文件

注意编译要加-g选项

build --cxxopt="-std=c++17" --cxxopt="-g" -c dbg

build.sh

bazel build :trans_acc

运行结果

可以看出,前边打出来的是backtrace返回 symbold.最后一个中括号里返回的是addr2line的详细信息。都打出来是因为防止addr2line转换失败。

# bazel-bin/trans_acc
=== [1]:bazel-bin/trans_acc(_ZN8AdsonLib14PrintCoreStack10PrintStackEv+0x46) [0x40b90a] [vma: 0x40b90a] [AdsonLib::PrintCoreStack::PrintStack() at /proc/self/cwd/common/stacktrace.h:54]
=== [2]:bazel-bin/trans_acc(_ZZN8AdsonLib14PrintCoreStack19RegisterCoreHandlerEvENKUlvE_clEv+0x11) [0x40b41d] [vma: 0x40b41d] [AdsonLib::PrintCoreStack::RegisterCoreHandler()::{lambda()#1}::operator()() const at /proc/self/cwd/common/stacktrace.h:20]
=== [3]:bazel-bin/trans_acc(_ZZN8AdsonLib14PrintCoreStack19RegisterCoreHandlerEvENUlvE_4_FUNEv+0xe) [0x40b430] [vma: 0x40b430] [AdsonLib::PrintCoreStack::RegisterCoreHandler()::{lambda()#1}::_FUN() at /proc/self/cwd/common/stacktrace.h:21]
=== [4]:/lib64/libstdc++.so.6(+0x91e26) [0x7f0e2bd69e26] [vma: 0x91e26] [__cxxabiv1::__terminate(void (*)()) at /opt/gcc-gcc-8_3_0-release/x86_64-pc-linux-gnu/libstdc++-v3/libsupc++/../../.././libstdc++-v3/libsupc++/eh_terminate.cc:47]
=== [5]:/lib64/libstdc++.so.6(+0x91e61) [0x7f0e2bd69e61] [vma: 0x91e61] [std::terminate() at ??:?]
=== [6]:/lib64/libstdc++.so.6(+0x92094) [0x7f0e2bd6a094] [vma: 0x92094] [__cxa_throw at ??:?]
=== [7]:bazel-bin/trans_acc(_Z7TestLogv+0x428) [0x40a67b] [vma: 0x40a67b] [TestLog() at /proc/self/cwd/trans_acc.cpp:48 (discriminator 10)]
=== [8]:bazel-bin/trans_acc(main+0x19) [0x40a783] [vma: 0x40a783] [main at /proc/self/cwd/trans_acc.cpp:53]
=== [9]:/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f0e2b412555] [vma: 0x22555] [__libc_start_main at ??:?]
=== [10]:bazel-bin/trans_acc() [0x409bd9] [vma: 0x409bd9] [_start at ??:?]
Aborted (core dumped)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值