GDB默认拦截信号导致注册的信号处理函数无法执行,需用handle命令配置pass;信号在任意未屏蔽线程上下文中异步执行,info threads不显示信号处理线程。

直接说结论:GDB 默认会拦截信号(如 SIGINT),导致你注册的信号处理函数根本收不到信号;想让程序正常响应信号,必须显式配置 handle 命令,否则调试时信号行为和生产环境不一致。
为什么 info threads 看不到信号处理线程?
信号处理函数没有独立线程——它在「某个正在运行的线程上下文中异步执行」,而这个线程是不确定的。GDB 的 info threads 只列出用户创建的线程(std::thread、pthread_create 等),不会为信号处理函数新建或显示一个线程。你看到的卡死现象,往往就是信号被投递到正持有锁的线程上,而该线程又在信号处理函数里试图调用 pthread_mutex_lock 或 printf 这类非 async-signal-safe 函数。
- 信号不是“发给某个线程”,而是发给整个进程,内核随机选一个未屏蔽该信号的线程来执行 handler
-
info threads输出中绝不会出现 “Signal Handler Thread” 这种东西 - 若 handler 里调用了
write(它是 async-signal-safe 的)还能勉强工作,但std::cout、malloc、pthread_mutex_unlock全部禁止
gdb 中 signal 被拦住的典型表现
你在代码里用 sigaction 注册了 SIGUSR1 处理函数,然后在终端发 kill -USR1 $(pidof a.out),结果程序没反应——不是代码写错了,是 GDB 悄悄把信号吃掉了。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 执行
info handle查看当前所有信号的处理策略,默认SIGINT、SIGUSR1等多数信号都是Stop + Print,即 GDB 自己暂停并打印,不传给程序 - 真正想测试 handler 行为,得先执行:
handle SIGUSR1 stop print pass(最后的pass才关键) - 若只想让信号透传、不打断调试流,用:
handle SIGUSR1 nostop noprint pass - 注意:修改后需用
signal SIGUSR1命令手动向被调试进程发一次信号,不能依赖外部kill(GDB 仍可能拦截)
如何定位信号引发的死锁?
程序收到 SIGTERM 后卡住,gdb 附着后 info threads 显示某个线程停在 pthread_mutex_lock,另一线程栈顶是 __sighandler ——这就是经典信号死锁链。
立即学习“C++免费学习笔记(深入)”;
- 先执行
thread apply all bt,重点看带__sighandler或sigaction调用栈的线程 - 对疑似阻塞线程执行
thread <n>切换过去,再info registers确认是否真卡在锁指令上(如lock xchg) - 用
info proc mappings和x/20i $pc-10查看 handler 入口附近反汇编,确认有没有调用printf、std::string::append等高危函数 - 若 handler 只写了
volatile sig_atomic_t g_exit = 1;,那死锁大概率不在 handler 内,而在主逻辑轮询该变量时没加内存屏障或用了错误同步原语
最易被忽略的一点:即使你用 signalfd 避开了传统 signal handler,GDB 仍会拦截原始信号——所以 pthread_sigmask 屏蔽信号后,signalfd 对应的 read() 调用是否被阻塞,也得靠 thread apply all bt 看清楚,而不是只盯主线程。

















