实现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 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 函数是在程序接收到 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 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/768697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OceanBase Meetup北京站|跨行业应用场景中的一体化分布式数据库:AI赋能下的探索与实践

随着业务规模的不断扩张和数据处理需求的日益复杂化&#xff0c;传统数据库架构逐渐暴露出业务稳定性波动、扩展性受限、处理效率降低以及运营成本高等一系列问题。众多行业及其业务场景纷纷踏上了数据库现代化升级之路。 为应对这些挑战&#xff0c;7月6日&#xff0c;OceanB…

搭建个人博客及错误记录

搭建个人博客及错误记录 文章目录 搭建个人博客及错误记录需要用到的网址2.推荐两个参考教学视频3.发布一篇博客个人主题配置的提醒localhost拒绝连接问题解决办法ssh -T gitgithub.com失败问题解决Deployer not found:git解决 可以根据目录解决遇到的相同问题 需要用到的网址 …

德国威步的技术演进之路(下):从云端许可管理到硬件加密狗的创新

从单机用户许可证到WkNET网络浮点授权的推出&#xff0c;再到引入使用次数和丰富的时间许可证管理&#xff0c;德国威步产品不断满足市场对灵活性和可扩展性的需求。TCP/IP浮动网络许可证进一步展示了威步技术在网络时代的创新应用。借助于2009年推出的借用许可证以及2015年推出…

如何选择适合自己的笔记本电脑

在现代社会中&#xff0c;笔记本电脑已经成为人们工作、学习和娱乐的重要工具。然而&#xff0c;面对市场上琳琅满目的笔记本电脑产品&#xff0c;如何选择一款适合自己的笔记本电脑呢&#xff1f;本文将为您提供一些有用的建议。 首先&#xff0c;确定您的使用需求。不同的用户…

新手教学系列——慎用Flask-SQLAlchemy慢日志记录

在使用 Flask-SQLAlchemy 开发应用时,了解和避免潜在的问题是非常重要的。特别是在常驻进程和循环执行任务的场景下,慢查询记录功能(SQLALCHEMYRECORDQUERIES)可能会引发严重的内存泄漏问题。本文将详细介绍这个问题,并提供解决方案,帮助你在开发过程中避免掉入这些陷阱。…

为RK3568或RK3288开发板创建交叉编译环境{采用amd64的ubuntu系统配置交叉编译arm64开发环境}(保姆级包括安装QT)超详细记录版

为RK3568或RK3288开发板创建交叉编译环境{采用amd64的ubuntu系统配置交叉编译arm64开发环境}【保姆级包括安装QT】超详细记录版 Chapter1 为RK3568或RK3288开发板创建交叉编译环境{采用amd64的ubuntu系统配置交叉编译arm64开发环境}(保姆级包括安装QT)超详细记录版一. 安装QT程…

深入了解自动化:聊聊什么项目适合做自动化测试?

自动化测试 什么是自动化测 什么是自动化测试&#xff1f; 随着软件产业的不断发展&#xff0c;市场对软件周期的要求越来越高&#xff0c;于是催生了各种开发模式&#xff0c;如大家熟知的敏捷开发&#xff0c;从而对测试提出了更高的要求。此时&#xff0c;产生了自动化测试…

2024年港澳台联考考生成绩数据分析来啦

分数线 出炉 2024年的港澳台联考正式出分&#xff01;根据考生成绩&#xff0c;全国联招划档线如下&#xff1a; 一、本科批次 &#xff08;一&#xff09;普通类院校&#xff08;专业&#xff09;&#xff1a;文史类365分、理工类390分&#xff08;部分院校执行高分线&#…

算法基础入门 - 2.栈、队列、链表

文章目录 算法基础入门第二章 栈、队列、链表2.1 队列2.2 栈2.3 纸牌游戏2.4 链表如何建立链表?1.我们需要一个头指针(head)指向链表的初始。链表还没建立时头指针head为空2.建立第一个结点3.设置刚创建的这个结点的数据域(左半)和指针域(右半)4.设置头指针,头指针可方便…

10 - matlab m_map地学绘图工具基础函数 - 绘制多边形区域、流线图、散点图和添加注释的有关函数

10 - matlab m_map地学绘图工具基础函数 - 绘制多边形区域、流线图、散点图和添加注释的有关函数 0. 引言1. 关于m_patch2. 关于m_streamline3. 关于m_scatter4. 关于m_annotation5. 结语 0. 引言 本篇介绍下m_map中绘制多边形区域函数&#xff08;m_patch&#xff09;、绘制流…

数据库组成及原理

属性&#xff1a; 把数据库中的一个表类比成一个公司&#xff0c;那么公司里的每个人都是一个“属性”&#xff08;表中的一个字段视为一个属性&#xff09;&#xff0c;不管老板还是员工&#xff0c;只要是公司里的人&#xff0c;就都是一个属性。 主键&#xff1a; 老板就是“…

Docker安装PostgreSQL详细教程

本章教程,使用Docker安装PostgreSQL具体步骤。 一、拉取镜像 docker pull postgres二、启动容器 docker run -it --name postgres --restart always -e POSTGRES_PASSWORD=123456 -e

配置windows环境下独立浏览器爬虫方案【不依赖系统环境与chrome】

引言 由于部署浏览器爬虫的机器浏览器版本不同&#xff0c;同时也不想因为部署了爬虫导致影响系统浏览器数据&#xff0c;以及避免爬虫过程中遇到的chrome与webdriver版本冲突。我决定将特定版本的chrome浏览器与webdriver下载到项目目录内&#xff0c;同时chrome_driver在初始…

客户端渗透

1.一键可执行程序 2.给程序加壳 3.宏病毒感染文档 4.Android apk 利用 1.一键可执行程序 介绍&#xff1a;我们要进行客户端渗透&#xff0c;我们生成一个可执行程序&#xff0c;也是简单粗暴&#xff0c;MSF建立监听&#xff0c;把它发给受害者&#xff0c;只要受害者点击…

使用U盘重装系统

目录 一、 制作启动盘 1. 准备一个U盘和一台电脑 2. 下载win10安装包 二、安装操作系统 1. 插入系统安装盘 2. 通过进入BIOS界面进入到我们自己制作的启动盘上 三、安装成功后进行常规设置 一、 制作启动盘 1. 准备一个U盘和一台电脑 注意&#xff1a;提前备份好U盘内的…

15- 22题聚合函数 - 高频 SQL 50 题基础版

目录 1. 相关知识点2. 例子2.15 - 有趣的电影2.16 - 平均售价2.17 - 项目员工 I2.18 - 各赛事的用户注册率2.19 - 查询结果的质量和占比2.20 - 每月交易 I2.21 - 即时食物配送 II2.22 - 游戏玩法分析 IV 1. 相关知识点 函数 函数含义order by排序group by分组between 小值 an…

272. 最长公共上升子序列

Powered by:NEFU AB-IN Link 文章目录 272. 最长公共上升子序列题意思路代码 272. 最长公共上升子序列 题意 如题 思路 若按这个思路的话&#xff0c;代码为 O ( n 3 ) O(n^3) O(n3) for (int i 1; i < n; i ) {for (int j 1; j < n; j ){f[i][j] f[i - 1][j];…

如何使用ECharts和Java接口实现可视化的数据挖掘

如何使用ECharts和Java接口实现可视化的数据挖掘 【引言】 随着大数据时代的到来&#xff0c;数据挖掘成为了一项重要的技术&#xff0c;在企业决策、市场分析等领域发挥着重要作用。数据挖掘需要将大量的数据进行分析和展示&#xff0c;而可视化是一种直观、形象的展示方式。…

wasm的逆向之旅一

目录 概要 技术名词解释 1、WebAssembly 指令集概览 1)基本结构 2)数据类型 3)模块和函数 4)指令概览 1.i32 整数运算 2.i32 浮点数运算&#xff08;用法同整数运算&#xff09; 3.逻辑运算和位移(用法同整数运算) 4.内存访问指令 6.控制流指令 7.模块和导出指令 8.其他常…

Landsat数据从Collection1更改为Collection2

目录 问题解决 问题 需要注意!您使用的是废弃的陆地卫星数据集。为确保功能持续&#xff0c;请在2024年7月1日前更新。 在使用一些以前的代码时会遇到报错&#xff0c;因为代码里面用的是老的数据集 解决 对于地表反射率SR&#xff0c;需要在name中&#xff0c;将C01换为C02&…