在日常的软件开发过程中,总是避免不了出现程序运行中突然的崩溃,如果已知一个必然崩溃的现象且可复现的话,这种bug还方便排查,加打印或者用
gdb
运行程序崩溃后可以追溯栈信息。但如果是一个偶现的崩溃,这就比较棘手了,需要我们在程序开发时就增加一些机制,帮助我们更高效地定位程序错误。
目录
- 1.关于backward-cpp
- 2.依赖backward库
- 3.在代码中启用backward-cpp和日志功能
-
- [3.1 增加头文件](#3.1 增加头文件)
- [3.2 安装信号处理器](#3.2 安装信号处理器)
- [3.3 处理崩溃信号的函数](#3.3 处理崩溃信号的函数)
- [3.4 重新编译](#3.4 重新编译)
- 四:运行测试
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
的主入口,它会自动包含 StackTrace
和 Printer
这两个核心类。
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::cout
、std::cerr
、std::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