C++的Linux程序在崩溃(coredump)后,打印调用堆栈的方法,支持x86和ARM

1、概述

我们知道Linux程序如果使用-g编译,若程序发生崩溃(coredump),是可以使用gdb调试生成的dump文件,找到崩溃的位置的。

然后C++有一些组件是提供崩溃堆栈打印的,本文给出如下几种方法。
+ 本文的测试代码已上传至gitee:https://gitee.com/liudegui/linux-cpp-crash-trace

2 、使用boost::stacktrace

  • boost::stacktrace是boost1.65版本以后支持的堆栈打印功能,使用前请确保安装了libboost-stacktrace;

如果没有安装,对于ubuntu系统,可使用如下方法安装

sudo apt-get install libboost-stacktrace-dev
  • 测试代码
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

char *str = (char *)"test";
void core_test()
{
    
    
    str[1] = 'T';
}

// This is definitely not async-signal-safe. Let's hope it doesn't crash or hang.
void handler(int signo)
{
    
    
    if (std::ofstream file_stream("my_stacktrace.log", std::ios::trunc); file_stream.is_open())
    {
    
    
        std::stringstream ss;
        ss <<  boost::stacktrace::stacktrace();
        std::cout << boost::stacktrace::stacktrace() << std::endl;
        file_stream.write(ss.str().c_str(), ss.str().size());
        file_stream.close();
    }

    // Raise signal again. Should usually terminate the program.
    raise(signo);
}

int main()
{
    
    
    // Repeat this for each signal you want to catch and log.
    struct sigaction act{
    
    };
    // Allow signal handler to re-raise signal and reset to default handler after entering.
    act.sa_flags = SA_NODEFER | SA_RESETHAND;
    act.sa_handler = &handler;
    sigfillset(&act.sa_mask);
    sigdelset(&act.sa_mask, SIGSEGV /* or other signal */);
    if (sigaction(SIGSEGV /* or other signal */, &act, nullptr) == -1)
        std::exit(EXIT_FAILURE);

    core_test();
}

输出结果为:

 0# handler(int) at /usr/local/include/boost/stacktrace/stacktrace.hpp:129
 1# 0x00007FF3691A0090 in /usr/lib/x86_64-linux-gnu/libc.so.6
 2# core_test() at /home/dgliu/boost_stacktrace_demo/src/boost_stacktrace_demo.cpp:14
 3# main at /home/dgliu/boost_stacktrace_demo/src/boost_stacktrace_demo.cpp:46
 4# __libc_start_main in /usr/lib/x86_64-linux-gnu/libc.so.6
 5# _start in ./bin/boost_stacktrace_demo
  • cmake编译需添加如下
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -fno-pie -ggdb3 -O0 -no-pie")

关于no-pie解释如下:

-no-pie (default): 生成position-dependent executable (ET_EXEC)。要求最宽松,源文件可用-fno-pic,-fpie,-fpic编译
-pie: 生成position-independent executable (ET_DYN)。源文件须要用-fpie,-fpic编译

3、使用第三方库Backward-cpp

  • Backward-cpp是一个支持多个平台堆栈打印的组件,支持x86和ARM平台.

  • 它依赖与Linux其它组件,可以选择安装libbfdlibdw或者libdwarf。

    • 如果是安装libbfd,使用时需启用#define BACKWARD_HAS_BFD 1宏;
    • 如果是安装libdw,使用时需启用#define BACKWARD_HAS_DW 1宏;
    • 如果是安装libdwarf,使用时需启用#define BACKWARD_HAS_DWARF 1宏;

3.1 以使用BACKWARD_HAS_DW举例

  • 如果是Ubuntu系统,首先安装第三方库libdw-dev
sudo apt-get install libdw-dev
  • 下载头文件backward.hpp;

  • 在CMakeLists target_link_libraries 中添加 dw

  • 在主程序中添加 backward

    #define BACKWARD_HAS_DW 1
    #include "backward.hpp"
    namespace backward{
          
          
        backward::SignalHandling sh;
    }
    
  • 非常简单的测试代码为:

#include<stdio.h>
#include<stdlib.h>
#define BACKWARD_HAS_DW 1
#include "backward.hpp"
namespace backward{
    
    
    backward::SignalHandling sh;
}

int main(){
    
    
    char *c = "hello world";
    c[1] = 'H';
}
  • 如果想输出到日志文件,需要使用到backward的Print接口,完整实例代码可为:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fstream>
#include <iostream>

#define BACKWARD_HAS_DW 1
#include "backward.hpp"
namespace backward
{
    
    
    backward::SignalHandling sh;
}

char *str = (char *)"test";
void core_test()
{
    
    
    str[1] = 'T';
}

// This is definitely not async-signal-safe. Let's hope it doesn't crash or hang.
void handler(int signo)
{
    
    
    if (std::ofstream file_stream("my_stacktrace.log", std::ios::trunc); file_stream.is_open())
    {
    
    
        file_stream << "Caught signal " << signo << ".\nTrace:\n";
        backward::StackTrace st;
        st.load_here(32);
        backward::Printer p;
        p.print(st, file_stream);   // print to file
        p.print(st, std::cout);     // print to console
    }

    // Raise signal again. Should usually terminate the program.
    raise(signo);
}

int main()
{
    
    
    // Repeat this for each signal you want to catch and log.
    struct sigaction act {
    
    };
    // Allow signal handler to re-raise signal and reset to default handler after entering.
    act.sa_flags = SA_NODEFER | SA_RESETHAND;
    act.sa_handler = &handler;
    sigfillset(&act.sa_mask);
    sigdelset(&act.sa_mask, SIGSEGV /* or other signal */);
    if (sigaction(SIGSEGV /* or other signal */, &act, nullptr) == -1)
        std::exit(EXIT_FAILURE);

    core_test();
}

输出结果为:

Stack trace (most recent call last):
#8    Object "", at 0xffffffffffffffff, in 
#7    Object "/home/dgliu/backward-cpp_demo/bin/backward-cpp_demo", at 0x5619693b2f4d, in _start
#6    Source "../csu/libc-start.c", line 308, in __libc_start_main
#5    Source "/home/dgliu/backward-cpp_demo/src/backward-cpp_demo.cpp", line 51, in main
         48:     if (sigaction(SIGSEGV /* or other signal */, &act, nullptr) == -1)
         49:         std::exit(EXIT_FAILURE);
         50: 
      >  51:     core_test();
         52: }
#4    Source "/home/dgliu/backward-cpp_demo/src/backward-cpp_demo.cpp", line 17, in core_test
         14: char *str = (char *)"test";
         15: void core_test()
         16: {
      >  17:     str[1] = 'T';
         18: }
         19: 
         20: // This is definitely not async-signal-safe. Let's hope it doesn't crash or hang.
#3    Object "/usr/lib/x86_64-linux-gnu/libc-2.31.so", at 0x7ff6ce6ec08f, in 
#2    Source "/home/dgliu/backward-cpp_demo/src/backward-cpp_demo.cpp", line 27, in handler
         24:     {
         25:         file_stream << "Caught signal " << signo << ".\nTrace:\n";
         26:         backward::StackTrace st;
      >  27:         st.load_here(32);
         28:         backward::Printer p;
         29:         p.print(st, file_stream);
         30:         p.print(st, std::cout);
#1    Source "/home/dgliu/backward-cpp_demo/include/backward.hpp", line 880, in load_here
        877:       return 0;
        878:     }
        879:     _stacktrace.resize(depth);
      > 880:     size_t trace_cnt = details::unwind(callback(*this), depth);
        881:     _stacktrace.resize(trace_cnt);
        882:     skip_n_firsts(0);
        883:     return size();
#0    Source "/home/dgliu/backward-cpp_demo/include/backward.hpp", line 862, in unwind<backward::StackTraceImpl<backward::system_tag::linux_tag>::callback>
        860: template <typename F> size_t unwind(F f, size_t depth) {
        861:   Unwinder<F> unwinder;
      > 862:   return unwinder(f, depth);
        863: }
        864: 
        865: } // namespace details
  • cmake编译需添加如下
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")

4、Qt Creator启用内存泄漏/越界检查工具

编译选项配置:

  1. 开启内存泄露检查功能:-fsanitize=leak
  2. 开启地址越界检查功能:-fsanitize=address
  3. 开启越界详细错误信息:-fno-omit-frame-pointer

以Qt工程为例子

  • .pro项目文件:
SOURCES += main.cpp
 
# -fsanitize=leak意思为开启内存泄露检查
QMAKE_CXXFLAGS += "-fsanitize=leak"
QMAKE_CFLAGS   += "-fsanitize=leak"
QMAKE_LFLAGS   += "-fsanitize=leak"
 
# -fsanitize=address意思为开启内存越界检查
# -fno-omit-frame-pointer意思为显示更详细的信息
QMAKE_CXXFLAGS += "-fsanitize=address -fno-omit-frame-pointer"
QMAKE_CFLAGS   += "-fsanitize=address -fno-omit-frame-pointer"
QMAKE_LFLAGS   += "-fsanitize=address"

5、参考

其他

windows下的堆栈打印一般使用Crashrpt
几年前,我也修改过一个版本,代码地址在:https://gitee.com/liudegui/CrashExporter

猜你喜欢

转载自blog.csdn.net/stallion5632/article/details/125968445