崩溃信息追溯——backward-cpp

在日常的软件开发过程中,总是避免不了出现程序运行中突然的崩溃,如果已知一个必然崩溃的现象且可复现的话,这种bug还方便排查,加打印或者用gdb运行程序崩溃后可以追溯栈信息。

但如果是一个偶现的崩溃,这就比较棘手了,需要我们在程序开发时就增加一些机制,帮助我们更高效地定位程序错误。

目录

1.关于backward-cpp

源码:

两者区别:

  • C++版本与ROS2版本功能一样;
  • ROS2版本在C++版本的基础上修改了CMakeLists,其他节点依赖backward库时,可以使用 find_package(backward_ros REQUIRED) 依赖,只是依赖更方便。

注意:

  • backward 库只能捕捉 一些特定类型的崩溃,主要是:程序崩溃导致信号触发:SIGSEGV (段错误,比如野指针)SIGABRT (程序调用 abort())SIGFPE (数学错误,比如除以零)SIGILL (非法指令)SIGBUS(总线错误)
  • 不能捕捉的情况:
    1.普通 C++ 异常(如 std::runtime_error)
    2.第三方库(如 toml库)里本身处理的异常

2.依赖backward库

2.1 C++版本

安装方式

  • header only 模式:只需将 backward.hpp 放入项目中即可使用基本功能
  • 完整功能模式:添加 backward.cpp 到项目中,可实现自动捕获致命错误并打印栈跟踪

CMake集成

  • 作为子目录添加到项目
  • 让backward-cpp参与编译
    target_link_libraries(${PROJECT_NAME} ...) 里添加:
powershell 复制代码
target_link_libraries(${PROJECT_NAME}
    backward
    dw      # backward依赖dw库,不添加dw库可能会编译报错
)

2.2 ROS2版本

  • 在CMakeLists.txt中添加backward_ros依赖
    在CMakeLists.txt文件中添加:
powershell 复制代码
find_package(backward_ros REQUIRED)
  • 在package.xml文件中添加
xml 复制代码
<depend>backward_ros</depend>
  • 可执行文件依赖backeard_ros
powershell 复制代码
target_link_libraries(${PROJECT_NAME}
    backward_ros
)

3.在代码中启用backward-cpp和日志功能

3.1 增加头文件

cpp 复制代码
#include <backward.hpp>

这是 backward-cpp 的主入口,它会自动包含 StackTracePrinter 这两个核心类。

3.2 安装信号处理器

cpp 复制代码
int main(int argc, char **argv)
{
    std::string log_file      = std::filesystem::current_path().string() + "/bjx_elevator_driver.INFO";
    std::string agvc_log_path = "/mnt/agvc/log";
    if (std::filesystem::exists(agvc_log_path)) {
        std::string log_path = agvc_log_path + "/bjx_elevator_driver";
        if (!std::filesystem::exists(log_path)) {
            std::filesystem::create_directory(log_path);
        }
        log_file = log_path + "/bjx_elevator_driver.INFO";
    }

    google::InitGoogleLogging(argv[0]); // 设置GLOG参数
    FLAGS_max_log_size     = 100;       // 最大日志大小(MB), 如果设置为0将默认为1
    FLAGS_alsologtostderr  = true;      // 同时输出到文件
    FLAGS_colorlogtostderr = true;      // 显示颜色
    FLAGS_logbufsecs       = 0;         // 设置日志滚动时间间隔为的秒数
    FLAGS_v                = 0;         // 设置日志输出等级
    google::SetLogDestination(google::INFO, log_file.c_str());
    google::EnableLogCleaner(std::chrono::hours(24 * 7)); // 7天清理一次日志

    LOG(INFO) << "log file: " << log_file;

    signal(SIGSEGV, signal_handler); // 监听 SIGSEGV 崩溃信号

    LOG(INFO) << "程序启动!";
    
    // 触发崩溃
    int* ptr = nullptr;
    *ptr = 42;

    return 0;
}
cpp 复制代码
signal(SIGSEGV, signal_handler);

这行代码注册了一个信号处理函数 signal_handler,放在需要检测的程序或节点中,当程序遇到 SIGSEGV(段错误)时就会跳进这个函数。也就是说,当程序崩了,交给此函数处理,而不是让程序直接终止。

3.3 处理崩溃信号的函数

cpp 复制代码
void signal_handler(int signum) {
    backward::StackTrace st;
    st.load_here(32);

    // 使用 Printer 格式化堆栈信息
    backward::Printer p;
    std::ostringstream oss;
    p.print(st, oss);

    // 记录到 glog
    LOG(ERROR) << "程序崩溃!信号: " << signum;
    LOG(ERROR) << "崩溃堆栈信息:\n" << oss.str();

    exit(1);
}
  • backward::StackTrace 是一个类,用于保存当前调用栈的快照。
  • load_here(32) 会捕捉当前线程最多 32 层的调用栈信息。
  • Printer 是用来格式化并打印 StackTrace 的工具类。
  • 它可以打印到 std::coutstd::cerrstd::ostringstream 等。
  • 打印出来的内容会包括:哪个函数、哪个源码文件、哪一行调用的
  • 使用 glog 来将堆栈信息作为错误日志输出,这样你不仅能看到程序崩溃,还能知道在哪一行出了问题。

3.4 重新编译

bash 复制代码
rm -rf build
mkdir build && cd build
cmake ..
make -j

四:运行测试

运行可执行文件

如果崩溃,你会看到:

bash 复制代码
I20250403 13:28:27.121705 134548333981312 jsonrpc_client_test.cpp:127] 程序启动!
E20250403 13:28:27.210820 134548333981312 jsonrpc_client_test.cpp:96] 程序崩溃!信号: 11
E20250403 13:28:27.211076 134548333981312 jsonrpc_client_test.cpp:97] 崩溃堆栈信息:
Stack trace (most recent call last):
#8    Object "", at 0xffffffffffffffff, in 
#7    Object "/home/lik/bjx_elevator_driver/build/jsonrpc_client_test", at 0x5630b994b1c4, in _start
#6    Source "../csu/libc-start.c", line 392, in __libc_start_main_impl
#5    Source "../sysdeps/nptl/libc_start_call_main.h", line 58, in __libc_start_call_main
#4    Source "/home/lik/bjx_elevator_driver/test/jsonrpc_client_test.cpp", line 131, in main
        129:     // 触发崩溃
        130:     int* ptr = nullptr;
      > 131:     *ptr = 42;
        132: 
        133:     return 0;
        134: }
#3    Object "/usr/lib/x86_64-linux-gnu/libc.so.6", at 0x7a5ef944251f, in 
#2    Source "/home/lik/bjx_elevator_driver/test/jsonrpc_client_test.cpp", line 88, in signal_handler
         85: // 处理崩溃信号的函数
         86: void signal_handler(int signum) {
         87:     backward::StackTrace st;
      >  88:     st.load_here(32);
         89: 
         90:     // 使用 Printer 格式化堆栈信息
         91:     backward::Printer p;
#1    Source "/home/lik/.cache/CPM/backward-cpp/backward.hpp", line 879, in load_here
        876:       return 0;
        877:     }
        878:     _stacktrace.resize(depth);
      > 879:     size_t trace_cnt = details::unwind(callback(*this), depth);
        880:     _stacktrace.resize(trace_cnt);
        881:     skip_n_firsts(0);
        882:     return size();
#0    Source "/home/lik/.cache/CPM/backward-cpp/backward.hpp", line 861, in unwind<backward::StackTraceImpl<backward::system_tag::linux_tag>::callback>
        859: template <typename F> size_t unwind(F f, size_t depth) {
        860:   Unwinder<F> unwinder;
      > 861:   return unwinder(f, depth);
        862: }
        863: 
        864: } // namespace details
相关推荐
Hankin_Liu的技术研究室4 小时前
深入理解 C++ happens-before:高级并发程序员的必修课
c++
liu****4 小时前
20.哈希
开发语言·数据结构·c++·算法·哈希算法
爱和冰阔落4 小时前
【C++多态】虚函数/虚表机制与协变 、override和final关键字全解析
开发语言·c++·面试·腾讯云ai代码助手
码住懒羊羊4 小时前
【C++】stack|queue|deque
java·开发语言·c++
“αβ”5 小时前
了解“网络协议”
linux·服务器·网络·c++·网络协议·tcp/ip·tcp
恒者走天下5 小时前
选cpp /c++方向工作职业发展的优缺点
c++
一匹电信狗5 小时前
【LeetCode_160】相交链表
c语言·开发语言·数据结构·c++·算法·leetcode·stl
AA陈超6 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P05-11 消息小部件
c++·游戏·ue5·游戏引擎·虚幻
再卷也是菜6 小时前
C++篇(14)二叉树进阶算法题
c++·算法