文章目錄
- 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 terminationsignal(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 herebreak;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");elsefd = 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 applicationsleep(1); // Example of a long-running loop, replace with your logic}return 0;
}