GDB对Linux信号的处理方式

前言

在软件开发过程中,调试工具是程序员不可或缺的助手。GDB(GNU Debugger)作为一个强大的调试器,广泛应用于Linux系统中的C/C++程序调试。然而,信号处理机制的复杂性常常给调试带来挑战。特别是在处理异步和同步信号时,不同的信号处理方式对程序执行流和调试工具的行为会产生显著影响。

本文旨在深入探讨GDB如何处理Linux信号,以及不同信号处理方式对调试的影响。通过具体示例和代码演示,我们将解析GDB处理信号的机制,探讨如何在信号处理函数中有效地进行调试,并提出在同步信号处理方式下使GDB能够捕获信号的解决方案。希望通过本文的学习,读者能更好地理解和掌握在实际开发中如何使用GDB调试带有复杂信号处理的程序,提高调试效率。

GDB中的信号处理方式

GDB(GNU Debugger)是一个功能强大的调试工具,能够捕获和处理被调试的程序收到的信号。当信号发送到正在被调试的程序时,GDB 可以选择不同的处理方式。

查看信号处理方式

通过在 GDB 中运行命令: info handle,可以查看 GDB 对各个信号的处理方式。例如:

(gdb) info handle
Signal        Stop      Print   Pass to program Description

SIGHUP        Yes       Yes     Yes             Hangup
SIGINT        Yes       Yes     No              Interrupt
SIGQUIT       Yes       Yes     Yes             Quit
SIGILL        Yes       Yes     Yes             Illegal instruction
SIGTRAP       Yes       Yes     No              Trace/breakpoint trap
SIGABRT       Yes       Yes     Yes             Aborted
SIGEMT        Yes       Yes     Yes             Emulation trap
SIGFPE        Yes       Yes     Yes             Arithmetic exception
SIGKILL       Yes       Yes     Yes             Killed
SIGBUS        Yes       Yes     Yes             Bus error
SIGSEGV       Yes       Yes     Yes             Segmentation fault
...省略

信号处理分析

通过info handle的结果可以看到,GDB 对信号的处理方式有三种:
(1)Stop: 暂停程序执行
(2)Print: 打印信号信息
(3)Pass to program: 传递给被调试的程序
以SIGINT(通常由 Ctrl+C 触发)为例,当收到这个信号后,GDB 会暂停程序执行并打印信号信息,但不会将 SIGINT 信号传递给程序的信号处理函数。但有特殊场景会使信号直接传给被调试程序,不被GDB截获,详见下文。

应用程序中捕获信号的方式

在应用程序中,可以通过多种方式捕获信号,包括Linux系统调用signal、sigaction、sigwait和signalfd相关系统调用。这些方法可以分为异步信号处理和同步信号处理。

异步信号处理

通过Linux系统调用signal 和 sigaction可以注册信号的处理函数,这种方式属于异步信号处理。

特点

(1)即时处理:当一个信号被发送到进程时,如果该信号没有被屏蔽,内核会很快调用相应的信号处理函数。这个过程不需要等待进程中的某个同步点或特定的代码段;
(2)不可预测性:信号处理函数可以在程序的任意位置被调用,程序无法预见信号何时会到来。

示例代码
#include <iostream>
#include <csignal>
#include <unistd.h>
void handle_signal(int sig) {
    std::cout << "Received signal " << sig << std::endl;
}
int main() {
    signal(SIGINT, handle_signal);
    while (true) {
        std::cout << "Running..." << std::endl;
        sleep(1);
    }
    return 0;
}
......省略
(gdb) r
Starting program: /root/work_dir/test_programs/test_signal/test
Running...
Running...
Running...
^C
  Program received signal SIGINT, Interrupt.
0x00007ffff71d0068 in nanosleep () from /lib64/libc.so.6
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-251.el8.x86_64 libgcc-8.5.0-21.el8.x86_64 libstdc++-8.5.0-21.el8.x86_64
(gdb) bt
#0  0x00007ffff71d0068 in nanosleep () from /lib64/libc.so.6
#1  0x00007ffff71cff9e in sleep () from /lib64/libc.so.6
#2  0x0000000000400989 in main () at test.cpp:13

在这个例子中,使用signal 捕获 SIGINT 信号。GDB先捕获到了这个信号,没有调用用户注册的信号处理函数。这时程序也没有退出,可以进行查看堆栈、打断点等调试操作,非常方便。

信号处理函数的调用时机

信号处理函数的调用时机,总结为以下几个要点:
(1)当一个信号(如 SIGINT)被发送到进程时,进程并不会立刻中断当前的执行,而是继续执行当前的指令;
(2)当进程因为系统调用、硬件中断或者其他原因进入内核态,并处理完内核态逻辑返回用户态之前,内核会检查是否有待处理的信号。检测到待处理的信号后,会查找该信号对应的处理函数,并将其上下文准备好;
(3)内核返回用户态时,执行用户注册的信号处理函数。在信号处理函数执行完毕后,进程恢复到之前的状态,继续执行被中断的代码。

当收到一个信号(如 SIGINT),处理流程示意图如下:
信号处理示意图

同步信号处理

sigwait 和 signalfd相关系统调用对信号的处理方式是同步的。

sigwait: 线程阻塞等待指定信号到达。
signalfd: 使用文件描述符同步接收信号。
特点

同步信号处理的特点主要体现在信号的处理机制和程序的控制流上。与异步信号处理不同,同步信号处理在程序的特定点等待和处理信号。以下是同步信号处理的主要特点:

  1. 明确的等待和处理信号的点
    在同步信号处理机制中,程序明确地调用函数来等待和处理信号。这些函数会阻塞执行,直到指定的信号到达。这使得信号处理更可控,程序可以在预定的、安全的地方处理信号。
  2. 避免了异步信号处理的不可预测性
    同步信号处理避免了异步信号处理的不确定性。异步信号处理函数可能在程序的任何位置被调用,可能会中断关键代码段。而同步信号处理只有在程序显式等待信号时才会进行处理,这减少了对程序流的干扰。
  3. 更好的线程和进程控制
    同步信号处理特别适用于多线程程序。线程可以独立地等待和处理信号,不会影响其他业务线程的执行。
示例代码
#include <iostream>
#include <csignal>
#include <pthread.h>
#include <unistd.h>

void* signal_handler(void* arg) {
    sigset_t* set = (sigset_t*)arg;
    int sig;
    while (true) {
        sigwait(set, &sig);
        std::cout << "Received signal " << sig << std::endl;
    }
    return nullptr;
}

int main() {
    sigset_t set;
    pthread_t thread;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    pthread_sigmask(SIG_BLOCK, &set, nullptr);
    pthread_create(&thread, nullptr, signal_handler, (void*)&set);
    while (true) {
        sleep(1);
    }
    return 0;
}
......省略
(gdb) r
Starting program: /root/work_dir/test_programs/test_signal/test1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff6ea7700 (LWP 4072812)]
^CReceived signal 2
^CReceived signal 2
^CReceived signal 2

在这个例子中,使用 sigwait 捕获 SIGINT 信号。GDB 无法捕获同步信号处理中的信号,信号直接被用户代码捕获处理了,这会导致无法通过 Ctrl+C 暂停程序执行,增加调试困难。

信号捕获分析

异步信号处理:GDB 能捕获信号,调试方便。
同步信号处理:GDB 不能捕获信号,调试不便,无法通过 Ctrl+C 暂停程序。

解决同步阻塞信号处理的调试问题

由于 GDB 无法捕获同步阻塞的信号,我们可以在信号处理函数中显式调用 INT 3 汇编指令,并检查当前是否被 GDB 追踪。如果被追踪,则暂停程序,否则执行程序本身的信号处理逻辑。

示例代码

#include <iostream>
#include <fstream>
#include <csignal>
#include <pthread.h>
#include <unistd.h>
#include <sys/ptrace.h>

bool is_debugged() {
    std::ifstream status_file("/proc/self/status");
    std::string line;
    while (std::getline(status_file, line)) {
        if (line.find("TracerPid:") == 0) {
            int tracer_pid = std::stoi(line.substr(10));
            return tracer_pid != 0;
        }
    }
    return false;
}

void* signal_handler(void* arg) {
    int sig;
    while (true) {
        sigwait((sigset_t*)arg, &sig);
        if (is_debugged()) {
            asm("int $0x3"); // 如果被GDB追踪,触发断点
        } else {
            std::cout << "Received signal " << sig << std::endl;
        }
    }
    return nullptr;
}

int main() {
    sigset_t set;
    pthread_t thread;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    pthread_sigmask(SIG_BLOCK, &set, nullptr);
    pthread_create(&thread, nullptr, signal_handler, (void*)&set);
    while (true) {
        sleep(1);
    }
    return 0;
}

代码中的is_debugged函数通过读取 /proc/self/status 文件,可以检测当前进程的 TracerPid 值。如果 TracerPid 不为零,则说明当前进程正在被调试。测试结果如下图所示:
问题解决可见,这次使用gdb进行调试时按 Ctrl+C后,程序停在了asm(“int $0x3”);这一行,此时就可以进行查看堆栈、打断点等调试操作了,问题得到解决。

结束语

本文详细介绍了GDB对Linux信号的处理方式,比较了异步和同步信号处理的机制,并提供了解决同步信号处理调试问题的方法。通过使用显式的 INT 3 指令,可以在调试同步信号处理的程序时使GDB能够捕获并暂停程序,提供更高效的调试体验。理解这些机制和技巧,可以显著提高程序开发和调试的效率。

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

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

相关文章

React中 将UI 视为树

当 React 应用程序逐渐成形时&#xff0c;许多组件会出现嵌套。那么 React 是如何跟踪应用程序组件结构的&#xff1f; React 以及许多其他 UI 库&#xff0c;将 UI 建模为树。将应用程序视为树对于理解组件之间的关系以及调试性能和状态管理等未来将会遇到的一些概念非常有用。…

加入MongoDB AI创新者计划,携手MongoDB共同开创AI新纪元

加入MongoDB AI创新者计划&#xff01; MongoDB对AI创新和初创企业的支持既全面又广泛&#xff01;无论您是领先的AI初创企业还是刚刚起步&#xff0c;MongoDB Atlas都是支持您愿景的最佳平台。 AI 初创者计划The AI Startup Track AI初创者计划为早期初创企业提供专属福利&…

CHI协议_1

作者&#xff1a;someone链接&#xff1a;https://www.zhihu.com/question/304259901/answer/3455648666来源。 1. AMBA CHI简介 一致性总线接口&#xff08;CHI&#xff09;是AXI一致性扩展&#xff08;ACE&#xff09;协议的演进。它是Arm的AMBA总线的一部分。AMBA是一种免…

鸿蒙ArkTS声明式开发:跨平台支持列表【触摸事件】

触摸事件 当手指在组件上按下、滑动、抬起时触发。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独…

WEB攻防【1】——ASP应用/HTTP.SYS/短文件/文件解析/Access注入/数据库泄漏

ASP&#xff1a;常见漏洞&#xff1a;本文所写这些 ASPX&#xff1a;未授权访问、报错爆路径、反编译 PHP&#xff1a;弱类型对比、mdb绕过、正则绕过&#xff08;CTF考得多&#xff09; JAVA&#xff1a;反序列化漏洞 Python&#xff1a;SSTI、字符串、序列化 Javascript&…

openEuler 22.03 LTS SP3源码编译部署OpenStack-Caracal

openEuler 22.03 LTS SP3源码编译部署OpenStack-Caracal 说明机器详情安装操作系统注意事项基础准备Controller节点 && Compute节点 && Block节点关闭防火墙关闭selinux设置静态IP更新安装前准备Controller节点 && Compute节点 && Block节点设…

Linux系统启动原理

Linux系统启动原理及故障排除 Centos6系统启动过程 修改系统启动级别 vim /etc/inittabCentos7启动流程 加载BIOS信息&#xff0c;进行硬件检测 根据BIOS设定读取设备中的MBR&#xff0c;加载Boot loader 加载内核&#xff0c;内核初始化以后以模块的形式动态加载硬件 并且加…

嵌入式进阶——蜂鸣器

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 蜂鸣器原理图测试发声乐理知识乐理应用PWM测试发声PWM驱动封装 蜂鸣器 蜂鸣器是一种能够产生固定频率的声音的电子元件。它通常由…

MySQL数据库中的多表查询/连接查询操作

类型&#xff1a;内连接 &#xff0c;外连接{左外连接&#xff0c;右外连接} 之所以要使用连接查询的意义就是为了&#xff0c;借助数据库可以避免大量的数据重复。 进行连接查询的前提是要求多张表之间存在相关联的字段。 这里指的相关联的字段就是表与表之间存在着关系&am…

linux 常用命令:find grep ps netstat sudo df du rm

rm 命令 删除 -r 是递归参数&#xff08;recursive&#xff09;&#xff0c;用于删除目录及其内容。如果不加这个参数&#xff0c;rm 命令无法删除非空目录。-f 是强制参数&#xff08;force&#xff09;&#xff0c;用于强制删除文件或目录&#xff0c;不会进行任何确认提示…

windows 搭建 go开发环境

go语言&#xff08;或 Golang&#xff09;是Google开发的开源编程语言&#xff0c;诞生于2006年1月2日下午15点4分5秒&#xff0c;于2009年11月开源&#xff0c;2012年发布go稳定版。Go语言在多核并发上拥有原生的设计优势&#xff0c;Go语言从底层原生支持并发&#xff0c;无须…

Android network — 进程指定网络发包

Android network — 进程指定网络发包 0. 前言1. 进程绑定网络1.1 App进程绑定网络1.2 Native进程绑定网络 2. 源码原理分析2.1 申请网络requestNetwork2.2 绑定网络 BindProcessToNetwork 3. 总结 0. 前言 在android 中&#xff0c;一个app使用网络&#xff0c;需要在manifest…

从零开始搭建Springboot项目脚手架4:保存操作日志

目的&#xff1a;通过AOP切面&#xff0c;统一记录接口的访问日志 1、加maven依赖 2、 增加日志类RequestLog 3、 配置AOP切面&#xff0c;把请求前的request、返回的response一起记录 package com.template.common.config;import cn.hutool.core.util.ArrayUtil; import cn.hu…

【C/C++】Makefile文件的介绍与基本用法

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

烟囱ERP系统

一、烟囱系统定义 “烟囱式”系统&#xff0c;来自维基百科的解释是&#xff1a;一种不能与其他系统进行有效协调工作的信息系统&#xff0c;又称为孤岛系统。 二、烟囱系统的案例 比如&#xff1a;就像以下一样&#xff0c;各个系统之间是独立的&#xff0c;所有对接是通过…

揭秘Markdown:轻松掌握基础语法,让你的写作更高效、优雅!

文章目录 前言1.标题1.1 使用 和 - 标记一级和二级标题1.2 使用 # 号标记 2.段落格式2.1 字体2.2 分割线2.3 删除线2.4 下划线2.5 脚注 3.列表3.1 无序列表3.2 有序列表3.3 列表嵌套 4.区块4.1 区块中使用列表4.2 列表中使用区块 5.代码代码区块 6.链接7.图片8.表格9.高级技巧…

访问元组元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中&#xff0c;如果想将元组的内容输出也比较简单&#xff0c;可以直接使用print()函数即可。例如&#xff0c;要想打印上面元组中的untitle…

嵌入式进阶——震动马达

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 原理图控制分析功能设计 原理图 控制分析 S8050 NPN三极管特性 NPN型三极管的工作原理是基于PN结和PNP型晶体管的工作原理。 当外…

【大数据面试题】32 Flink 怎么重复读 Kafka?

一步一个脚印&#xff0c;一天一道面试题 首先&#xff0c;为什么要读过的 Kafka 数据重写读一次呢&#xff1f;什么场景下要怎么做呢&#xff1f; 答&#xff1a;当任务失败&#xff0c;从检查点Checkpoint 开始重启时&#xff0c;检查点的数据是之前的了&#xff0c;就需要…

扩散模型学习1

DDPM 总体训练原理 https://www.bilibili.com/video/BV1nB4y1h7CN/?spm_id_from333.337.search-card.all.click&vd_sourcef745c116402814185ab0e8636c993d8f 讲得很好&#xff1a;每次都是输入t和noise-x的图像&#xff0c;预测noise之后得到和加入的noise比较&#xff1b…