使用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继续执行,避免它被信号杀死。
核心步骤:
- 父进程fork出子进程(也就是你的inferior),子进程调用
ptrace(PTRACE_TRACEME)把自己设为可追踪状态,然后执行目标程序。 - 父进程通过
waitpid等待子进程的状态变化。 - 当子进程触发
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
相关产品推荐
相关产品推荐

