文章目录
- 0. 引言
- 1. 获取堆栈信息流程图
- 2. 实现进程守护与信号处理
- 2.1 进程如何守护化?
- 2.2 信号处理hook函数注册
- 2.3 守护进程代码熟宣
- 3. 堆栈信息捕获与打印逻辑
- 4. 其他说明
- 5. 附录完整代码
0. 引言
在软件开发中,特别是对于需要高可靠性的后台服务或守护进程,进程意外退出时的信息捕获和打印至关重要。
本文将介绍如何基于 <execinfo.h> 利用进程信号处理机制,实现C++进程崩溃时的堆栈信息捕获与打印。
1. 获取堆栈信息流程图
以下是一个简单的堆栈处理流程图,展示了当程序捕获到崩溃信号时,如何处理堆栈信息并将其写入到文件中的过程:
流程图说明:
- Signal Handler: 信号处理函数,如
SigCrash(int sig)
。 - Open Crash File: 打开用于记录崩溃信息的文件。
- Get Current Time: 获取当前时间,用于记录时间戳。
- Write Header to File: 将崩溃信息的头部(时间、信号等信息)写入文件。
- Capture Stack Trace: 捕获当前线程的堆栈信息。
- Write Stack Trace to File: 将堆栈信息写入文件。
- Close File: 关闭崩溃信息文件。
- Restore Default Signal Handler: 恢复默认的信号处理函数,以便后续的崩溃可以正常处理。
2. 实现进程守护与信号处理
本节将介绍如何将程序转化为守护进程,确保其在后台稳定运行,并有效地注册和处理不同的信号。
2.1 进程如何守护化?
为了使程序能够在后台运行,我们需要进行以下步骤:
-
第一次
fork
:- 创建子进程并退出父进程,确保子进程不是进程组的组长。
-
设置进程组:
- 使用
setpgid
将子进程设置为新的进程组,确保程序不受终端会话的影响。
- 使用
-
第二次
fork
:- 创建第二个子进程并退出父进程,进一步确保程序完全脱离终端会话。
-
信号忽略设置:
- 忽略常见的终端相关信号,如
SIGINT
、SIGHUP
、SIGQUIT
等,以确保程序稳定运行。
- 忽略常见的终端相关信号,如
2.2 信号处理hook函数注册
为了响应关键信号并执行相应的处理逻辑,需要注册以下常见信号的处理函数:
SIGTERM
:程序终止信号,用于正常关闭进程。SIGUSR1
和SIGUSR2
:自定义信号,可用于触发特定的操作或事件。
通过以上优化,程序能够稳定运行在后台,并能够有效处理关键的系统信号,提升了程序的可靠性和稳定性。。
2.3 守护进程代码熟宣
基于以上分析,在开始捕获进程崩溃时的堆栈信息之前,我们确保创建一个可以在后台持续运行的守护进程。以下是初始化守护进程的示例代码:
void MainHelper::InitDaemon() {
std::cout << "Initializing daemon..." << std::endl;
pid_t pid;
if ((pid = fork()) != 0) {
if (pid == -1) {
std::cerr << "Failed to fork the first child process." << std::endl;
exit(EXIT_FAILURE);
}
std::cout << "First exit" << std::endl;
exit(EXIT_SUCCESS);
}
setpgid(0, 0);
// Ignore various signals to avoid termination
signal(SIGINT, SIG_IGN);
signal(SIGHUP, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
if ((pid = fork()) != 0) {
if (pid == -1) {
std::cerr << "Failed to fork the second child process." << std::endl;
exit(EXIT_FAILURE);
}
std::cout << "Second exit" << std::endl;
exit(EXIT_SUCCESS);
}
umask(0);
}
3. 堆栈信息捕获与打印逻辑
SigCrash
函数是在程序接收到 SIGSEGV
或 SIGABRT
信号时被调用,用于捕获和打印程序崩溃时的堆栈信息。通过 backtrace
函数族,我们可以获取当前线程的堆栈帧,这些帧包含了程序执行到崩溃点的函数调用信息。
详细见后面完整的代码。
4. 其他说明
-
依赖库和函数:
- 本文使用了
<execinfo.h>
头文件中的backtrace
和backtrace_symbols
函数族来获取和格式化程序的堆栈信息。
- 本文使用了
-
Debug与Release模式的区别:
- 在 Debug模式 下,编译器会将源代码中的详细信息(如变量名、函数名、行号)嵌入到可执行文件中。这些信息对于准确追踪和定位崩溃位置至关重要。
- 而在 Release模式 下,编译器为了提升程序的性能和效率,通常会进行代码优化。这可能导致部分函数名被缩短或优化掉,使得堆栈跟踪信息不够清晰或部分信息丢失。
-
平台适用性:
- 本文示例适用于Linux平台,因为
<execinfo.h>
头文件在该平台下通常可用。对于其他类UNIX平台如QNX等,由于可能缺乏对应的头文件或功能支持,本文的方法可能不适用。在这些平台上,需要根据实际情况选择合适的堆栈跟踪解决方案。
- 本文示例适用于Linux平台,因为
5. 附录完整代码
#include <iostream>
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <sys/stat.h>
#include <execinfo.h>
#include <memory>
class MainHelper {
public:
static void InitDaemon();
static void RegSignal(const bool use_crash_file, const std::string& crash_filename);
static void RegSignal(const bool use_crash_file, const std::string& crash_filename,
const std::string& repo_branch, const std::string& repo_commit);
private:
static void CatchSignal(int iSignal);
static void SigCrash(int sig);
static bool is_use_crash_file_;
static std::string crash_filename_;
static std::string repo_branch_;
static std::string repo_commit_;
static bool exit_flag_;
};
bool MainHelper::is_use_crash_file_ = false;
std::string MainHelper::crash_filename_;
std::string MainHelper::repo_branch_;
std::string MainHelper::repo_commit_;
bool MainHelper::exit_flag_ = false;
constexpr int MAX_STACK_FRAMES = 128;
constexpr size_t MAX_CRASH_FILE_SIZE = 1 * 1000 * 1000;
void MainHelper::InitDaemon() {
fprintf(stdout, "init_daemon\n");
pid_t pid;
if ((pid = fork()) != 0) {
if (-1 == pid)
fprintf(stderr, "fork sub fail will exit, errmsg:%s\n", strerror(errno));
fprintf(stdout, "1st exit\n");
exit(0);
}
setpgid(0, 0);
signal(SIGINT, SIG_IGN);
signal(SIGHUP, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
if ((pid = fork()) != 0) {
if (-1 == pid)
fprintf(stderr, "fork sub fail will exit, errmsg:%s\n", strerror(errno));
fprintf(stdout, "2nd exit\n");
exit(0);
}
umask(0);
}
void MainHelper::RegSignal(const bool use_crash_file, const std::string& crash_filename) {
fprintf(stdout, "RegSignal\n");
is_use_crash_file_ = use_crash_file;
crash_filename_ = "/tmp/"; // 修改为你的crash文件存放目录
crash_filename_ += basename(const_cast<char*>(crash_filename.c_str()));
signal(SIGINT, SIG_IGN);
signal(SIGHUP, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGTTOU, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
if (is_use_crash_file_) {
fprintf(stdout, "use %s\n", crash_filename_.c_str());
signal(SIGSEGV, SigCrash);
signal(SIGABRT, SigCrash);
}
signal(SIGTERM, CatchSignal);
signal(SIGUSR1, CatchSignal);
signal(SIGUSR2, CatchSignal);
}
void MainHelper::RegSignal(const bool use_crash_file, const std::string& crash_filename,
const std::string& repo_branch, const std::string& repo_commit) {
repo_branch_ = repo_branch;
repo_commit_ = repo_commit;
RegSignal(use_crash_file, crash_filename);
}
void MainHelper::CatchSignal(int iSignal) {
fprintf(stdout, "CatchSignal: %d\n", iSignal);
switch (iSignal) {
case SIGTERM:
MainHelper::exit_flag_ = true;
break;
case SIGUSR1:
// Handle your custom signal actions here
break;
case SIGUSR2:
fprintf(stdout, "sync\n");
sync();
break;
default:
break;
}
}
void MainHelper::SigCrash(int sig) {
std::string filename = crash_filename_;
FILE* fd = nullptr;
struct stat buf;
if (stat(filename.c_str(), &buf) == 0 && buf.st_size > MAX_CRASH_FILE_SIZE)
fd = fopen(filename.c_str(), "w");
else
fd = fopen(filename.c_str(), "a");
if (!fd) {
fprintf(stderr, "Failed to open crash file: %s\n", filename.c_str());
return;
}
try {
char time_buffer[80];
time_t t = time(nullptr);
strftime(time_buffer, sizeof(time_buffer), "[%Y-%m-%d %H:%M:%S]", localtime(&t));
fprintf(fd, "#########################################################\n");
fprintf(fd, "%s [crash signal number: %d]\n", time_buffer, sig);
fprintf(fd, "build time=%s %s\n", __DATE__, __TIME__);
fprintf(fd, "branch=%s, commit=%s\n", repo_branch_.c_str(), repo_commit_.c_str());
void* array[MAX_STACK_FRAMES];
size_t size = backtrace(array, MAX_STACK_FRAMES);
char** strings = backtrace_symbols(array, size);
if (strings) {
fprintf(fd, "Stack trace:\n");
for (size_t i = 0; i < size; ++i) {
fprintf(fd, "%zu %s\n", i, strings[i]);
}
free(strings);
}
} catch (const std::exception& e) {
fprintf(stderr, "Exception in crash handler: %s\n", e.what());
}
fclose(fd);
signal(sig, SIG_DFL);
}
int main() {
MainHelper::InitDaemon();
MainHelper::RegSignal(true, "your_crash_filename");
while (!MainHelper::exit_flag_) {
// Main loop of your application
sleep(1); // Example of a long-running loop, replace with your logic
}
return 0;
}