實現Linux C++進程意外退出時信號處理與堆棧打印

文章目錄

    • 0. 引言
    • 1. 獲取堆棧信息流程圖
    • 2. 實現進程守護與信號處理
      • 2.1 進程如何守護化?
      • 2.2 信號處理hook函數注冊
      • 2.3 守護進程代碼熟宣
    • 3. 堆棧信息捕獲與打印邏輯
    • 4. 其他說明
    • 5. 附錄完整代碼

0. 引言

在軟件開發中,特別是對于需要高可靠性的后臺服務或守護進程,進程意外退出時的信息捕獲和打印至關重要。

本文將介紹如何基于 <execinfo.h> 利用進程信號處理機制,實現C++進程崩潰時的堆棧信息捕獲與打印。

1. 獲取堆棧信息流程圖

以下是一個簡單的堆棧處理流程圖,展示了當程序捕獲到崩潰信號時,如何處理堆棧信息并將其寫入到文件中的過程:

Signal Handler
Open Crash File
Get Current Time
Write Header to File
Capture Stack Trace
Write Stack Trace to File
Close File
Restore Default Signal Handler

流程圖說明:

  • 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 進程如何守護化?

為了使程序能夠在后臺運行,我們需要進行以下步驟:

  1. 第一次 fork

    • 創建子進程并退出父進程,確保子進程不是進程組的組長。
  2. 設置進程組

    • 使用 setpgid 將子進程設置為新的進程組,確保程序不受終端會話的影響。
  3. 第二次 fork

    • 創建第二個子進程并退出父進程,進一步確保程序完全脫離終端會話。
  4. 信號忽略設置

    • 忽略常見的終端相關信號,如 SIGINTSIGHUPSIGQUIT 等,以確保程序穩定運行。

2.2 信號處理hook函數注冊

為了響應關鍵信號并執行相應的處理邏輯,需要注冊以下常見信號的處理函數:

  • SIGTERM:程序終止信號,用于正常關閉進程。
  • SIGUSR1SIGUSR2:自定義信號,可用于觸發特定的操作或事件。

通過以上優化,程序能夠穩定運行在后臺,并能夠有效處理關鍵的系統信號,提升了程序的可靠性和穩定性。。

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 函數是在程序接收到 SIGSEGVSIGABRT 信號時被調用,用于捕獲和打印程序崩潰時的堆棧信息。通過 backtrace 函數族,我們可以獲取當前線程的堆棧幀,這些幀包含了程序執行到崩潰點的函數調用信息。

詳細見后面完整的代碼。

4. 其他說明

  • 依賴庫和函數

    • 本文使用了 <execinfo.h> 頭文件中的 backtracebacktrace_symbols 函數族來獲取和格式化程序的堆棧信息。
  • Debug與Release模式的區別

    • Debug模式 下,編譯器會將源代碼中的詳細信息(如變量名、函數名、行號)嵌入到可執行文件中。這些信息對于準確追蹤和定位崩潰位置至關重要。
    • 而在 Release模式 下,編譯器為了提升程序的性能和效率,通常會進行代碼優化。這可能導致部分函數名被縮短或優化掉,使得堆棧跟蹤信息不夠清晰或部分信息丟失。
  • 平臺適用性

    • 本文示例適用于Linux平臺,因為 <execinfo.h> 頭文件在該平臺下通常可用。對于其他類UNIX平臺如QNX等,由于可能缺乏對應的頭文件或功能支持,本文的方法可能不適用。在這些平臺上,需要根據實際情況選擇合適的堆棧跟蹤解決方案。

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;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/38933.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/38933.shtml
英文地址,請注明出處:http://en.pswp.cn/web/38933.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

掌握Go語言郵件發送:net/smtp實用教程與最佳實踐

掌握Go語言郵件發送&#xff1a;net/smtp實用教程與最佳實踐 概述基本配置與初始化導入net/smtp包設置SMTP服務器基本信息創建SMTP客戶端實例身份驗證 發送簡單文本郵件配置發件人信息構建郵件頭部信息編寫郵件正文使用SendMail方法發送郵件示例代碼 發送帶附件的郵件郵件多部分…

大模型知識學習

大模型訓練過程 數據清洗 擬人化描述&#xff1a;知識庫整理 預訓練 擬人化描述&#xff1a;知識學習可以使用基于BERT預訓練模型進行訓練 指令微調 擬人化描述&#xff1a;實際工作技能學習實際操作&#xff1a;讓大模型模仿具體的輸入輸出進行擬合&#xff0c;即模仿學…

Study--Oracle-06-Oracler網絡管理

一、ORACLE的監聽管理 1、ORACLE網絡監聽配置文件 cd /u01/app/oracle/product/12.2.0/db_1/network/admin 2、在Oracle數據庫中&#xff0c;監聽器&#xff08;Listener&#xff09;是一個獨立的進程&#xff0c;它監聽數據庫服務器上的特定端口上的網絡連接請求&#xff0c…

Vitis AI - 量化流程詳解

目錄 1. 簡介 2. 具體流程 2.1 校準激活 2.2 量化感知訓練 2.3 量化校準配置 2.4 quantization 函數 3. 總結 1. 簡介 想象一下&#xff0c;你有一個非常聰明的機器人朋友&#xff0c;它可以幫你做很多事情&#xff0c;比如預測天氣。但是&#xff0c;這個機器人的大腦…

01 數據采集層 流量分發第一步規范采集海量數據

《易經》&#xff1a;“初九&#xff1a;潛龍勿用”。潛龍的意思是隱藏&#xff0c;陽氣潛藏&#xff0c;陽爻位于最下方稱為“初九”&#xff0c;龍潛于淵&#xff0c;是學而未成的階段&#xff0c;此時需要打好基礎。 而模塊一我們就是講解推薦系統有關的概念、基礎數據體系…

基于SpringBoot+Vue商戶點評管理與數據分析系統設計和實現(源碼+LW+調試文檔+講解等)

&#x1f497;博主介紹&#xff1a;?全網粉絲10W,CSDN作者、博客專家、全棧領域優質創作者&#xff0c;博客之星、平臺優質作者、專注于Java、小程序技術領域和畢業項目實戰?&#x1f497; Java精品實戰案例《1000套》 2025-2026年最值得選擇的Java畢業設計選題大全&#xff…

使用 Vanna 生成準確的 SQL 查詢:工作原理和性能分析

Vanna工作原理 從本質上講,Vanna 是一個 Python 包,它使用檢索增強功能來幫助您使用 LLM 為數據庫生成準確的 SQL 查詢。 Vanna 的工作分為兩個簡單的步驟 - 在您的數據上訓練 RAG“模型”,然后提出問題,這些問題將返回可設置為在您的數據庫上自動運行的 SQL 查詢。 vn.t…

【后端面試題】【中間件】【NoSQL】MongoDB提高可用性的方案(主從結構、仲裁節點、分片、寫入語義)

主從結構 MongoDB的高可用和別的中間件的高可用方案基本類似。比如在MySQL里&#xff0c;接觸了分庫分表和主從同步&#xff1b;在Redis里&#xff0c;Redis也有主從結構&#xff1b;在Kafka里&#xff0c;分區也是有主從結構的。 所以先介紹啟用了主從同步 我們的系統有一個關…

基于Java的微信記賬小程序【附源碼】

摘 要 隨著我國經濟迅速發展&#xff0c;人們對手機的需求越來越大&#xff0c;各種手機軟件也都在被廣泛應用&#xff0c;但是對于手機進行數據信息管理&#xff0c;對于手機的各種軟件也是備受用戶的喜愛&#xff0c;記賬微信小程序被用戶普遍使用&#xff0c;為方便用戶能夠…

算法題中常用的C++功能

文章目錄 集合優先隊列雙端隊列排序時自定義比較函數最大數值字符串追加&#xff1a;刪除&#xff1a;子串&#xff1a; 元組vector查找創建和初始化賦值&#xff1a; 字典map引入頭文件定義和初始化插入元素訪問元素更新元素刪除元素檢查元素存在遍歷元素int和string轉換 集合…

Ubuntu20.04更新GLIBC到2.35版本

目錄 1 背景2 增加源2.1 標準源2.2 鏡像源 3 更新 1 背景 Ubuntu20.04默認GLIBC庫版本是2.31.今天碰到一個軟件需要2.35版本的GLIBC。 升級GLIBC庫有兩種方式&#xff1a; 下載高版本庫源碼&#xff0c;編譯后替換系統中低版本庫。由于GLIBC庫是Linux系統中最基礎庫&#xff…

你想活出怎樣的人生?

hi~好久不見&#xff0c;距離上次發文隔了有段時間了&#xff0c;這段時間&#xff0c;我是裸辭去感受了一下前端市場的水深火熱&#xff0c;那么這次咱們不聊技術&#xff0c;就說一說最近這段時間的經歷和一些感觸吧。 先說一下自己的個人情況&#xff0c;目前做前端四年&am…

深圳技術大學oj C : 生成r子集

Description 輸出給定序列按字典序的 &#xfffd; 組合&#xff0c;按照所有 &#xfffd; 個元素出現與否的 01 標記串 &#xfffd;&#xfffd;&#xfffd;&#xfffd;?1,...,&#xfffd;1 的字典序輸出. 此處01串的字典序指&#xff1a;先輸入的數字對應低位&#x…

移動智能終端數據安全管理方案

隨著信息技術的飛速發展&#xff0c;移動設備已成為企業日常運營不可或缺的工具。特別是隨著智能手機和平板電腦等移動設備的普及&#xff0c;這些設備存儲了大量的個人和敏感數據&#xff0c;如銀行信息、電子郵件等。員工通過智能手機和平板電腦訪問企業資源&#xff0c;提高…

【HICE】web服務搭建3

端口號的不同進行監聽 1.下載httpd協議&#xff1a;dnf install httpd -y 2.編輯vhost.conf cd /etc/httpd cd /conf.d [rootlocalhost conf.d]# cat 1.conf listen 9090 listen 9091 listen 9092 <directory /www> allowoverride none require all granted </d…

【機器學習】Datawhale-AI夏令營分子性質AI預測挑戰賽

參賽鏈接&#xff1a;零基礎入門 Ai 數據挖掘競賽-速通 Baseline - 飛槳AI Studio星河社區 一、賽事背景 在當今科技日新月異的時代&#xff0c;人工智能&#xff08;AI&#xff09;技術正以前所未有的深度和廣度滲透到科研領域&#xff0c;特別是在化學及藥物研發中展現出了巨…

SpringBoot+Vue集成AOP系統日志

新建logs表 添加aop依賴 <!-- aop依賴--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency> 新建獲取ip地址工具類 import javax.servlet.http.H…

React 函數式組件里面有生命周期嗎?沒有怎么辦?

React 函數式組件沒有像類組件那樣傳統的生命周期方法&#xff0c;但是通過 React Hooks&#xff0c;可以在函數式組件中實現類似的生命周期行為。 useEffect: 可以看作是類組件里的 componentDidMount, componentDidUpdate 和 componentWillUnmount 的結合體。它允許你在函數組…

在Linux環境下使用sqlite3時,如果嘗試對一個空表進行操作(例如插入數據),可能會遇到表被鎖定的問題。

在Linux環境下使用sqlite3時&#xff0c;如果嘗試對一個空表進行操作&#xff08;例如插入數據&#xff09;&#xff0c;可能會遇到表被鎖定的問題。這通常是因為sqlite3在默認情況下會對空表進行“延遲創建”&#xff0c;即在實際需要寫入數據之前&#xff0c;表不會被真正創建…

React Native V0.74 — 穩定版已發布

嗨,React Native開發者們, React Native 世界中令人興奮的消息是,V0.74剛剛在幾天前發布,有超過 1600 次提交。亮點如下: Yoga 3.0New Architecture: Bridgeless by DefaultNew Architecture: Batched onLayout UpdatesYarn 3 for New Projects讓我們深入了解每一個新亮點…