You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ptrace时阻止信号传播至inferior,如何避免SIGTRAP使其终止?

如何在C中阻止SIGTRAP杀死进程(模拟GDB的handle SIGSTOP nopass)

我来帮你理清这个问题并给出具体的实现方案~首先得搞懂你看到的现象为啥会发生:当你在inferior进程里植入trap指令触发SIGTRAP时,默认情况下这个信号是会直接终止进程的,所以父进程收到的SIGCHLD信号里si_code是CLD_KILLED,但si_status会明确告诉你,真正导致进程终止的信号是SIGTRAP。

GDB之所以能让进程触发断点后继续跑,核心是靠ptrace系统调用接管了进程的信号处理逻辑。要在C语言里实现类似效果,有两种常用思路:


方案1:直接在inferior进程里捕获SIGTRAP

如果你能修改inferior的代码,最直接的办法就是给SIGTRAP注册一个自定义信号处理函数,这样进程收到信号后不会被杀死,而是执行你定义的逻辑。

示例代码:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void trap_handler(int signum) {
    printf("Inferior捕获到SIGTRAP,继续执行...\n");
    // 这里可以加断点后的调试逻辑,比如打印寄存器、保存状态等
}

int main() {
    struct sigaction sa;
    // 配置信号处理函数
    sa.sa_handler = trap_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART; // 可选:让被中断的系统调用自动重启

    if (sigaction(SIGTRAP, &sa, NULL) == -1) {
        perror("sigaction调用失败");
        return 1;
    }

    printf("Inferior运行中,触发断点...\n");
    // x86架构下触发trap指令
    __asm__ __volatile__ ("int $3");

    printf("Inferior从断点恢复运行!\n");
    return 0;
}

这个方案简单粗暴,但有个前提:你必须能修改inferior进程的代码。如果是第三方程序,那得用下面的方法。


方案2:父进程通过ptrace追踪并控制inferior

这就是GDB用的思路,父进程通过ptrace系统调用attach到inferior进程,当inferior触发SIGTRAP时,父进程会收到SIGCHLD信号,然后可以手动让inferior继续执行,避免它被信号杀死。

核心步骤:

  1. 父进程fork出子进程(也就是你的inferior),子进程调用ptrace(PTRACE_TRACEME)把自己设为可追踪状态,然后执行目标程序。
  2. 父进程通过waitpid等待子进程的状态变化。
  3. 当子进程触发SIGTRAP停止时,父进程检查到WIFSTOPPED(status)且WSTOPSIG(status) == SIGTRAP,就调用ptrace(PTRACE_CONT)让子进程继续跑。

示例代码(父进程逻辑):

#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t child_pid = fork();
    if (child_pid == 0) {
        // 子进程:设置为可追踪状态,然后触发断点
        if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
            perror("ptrace TRACEME失败");
            return 1;
        }
        // 触发trap指令
        __asm__ __volatile__ ("int $3");
        printf("子进程从断点恢复运行!\n");
        return 0;
    } else if (child_pid > 0) {
        int status;
        // 等待子进程因信号停止
        waitpid(child_pid, &status, 0);
        
        if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
            printf("父进程检测到子进程触发SIGTRAP,恢复其运行...\n");
            // 让子进程继续执行,第四个参数传0表示不把SIGTRAP传递给子进程
            if (ptrace(PTRACE_CONT, child_pid, NULL, 0) == -1) {
                perror("ptrace CONT失败");
                return 1;
            }
            // 等待子进程正常退出
            waitpid(child_pid, &status, 0);
            printf("子进程退出状态:%d\n", WEXITSTATUS(status));
        }
    } else {
        perror("fork失败");
        return 1;
    }
    return 0;
}

关键细节:

  • 子进程调用PTRACE_TRACEME后,所有信号都会被转发给父进程处理,父进程拥有绝对控制权。
  • 调用ptrace(PTRACE_CONT, pid, NULL, 0)时,传0作为第四个参数,意味着子进程不会收到之前的SIGTRAP信号,直接从断点处继续执行。
  • 如果需要做更复杂的调试(比如读寄存器、改内存),可以在收到SIGTRAP后用ptrace的PTRACE_PEEKUSER、PTRACE_POKEDATA等命令操作。

再回到你的疑问:为啥是CLD_KILLED?

默认情况下,进程没有注册SIGTRAP的处理函数时,内核会直接终止进程,所以父进程收到的SIGCHLD里si_code是CLD_KILLED,但si_status会记录下真正的终止信号是SIGTRAP。上面的两种方案,本质都是改变了SIGTRAP的默认处理行为,从而避免进程被杀死。

内容的提问来源于stack exchange,提问作者Daniele Bestiale

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:44:03