实现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.mzph.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让我们深入了解每一个新亮点…