使用ptrace系统调用追踪时如何捕获SIGILL信号?
你遇到的核心问题是:PTRACE_SYSCALL会让被追踪进程(tracee)在进入和退出系统调用时停止,而你的tracee在恢复执行后,首先触发的是之前raise(SIGSTOP)系统调用的退出停止点,而非无效指令的SIGILL信号。
当tracee调用raise(SIGSTOP)时,这个函数最终会触发kill系统调用。由于tracee已设置PTRACE_TRACEME,内核会在处理SIGSTOP信号时让tracee停止,此时你的tracer通过第一个waitpid捕获到SIGSTOP。但此时kill系统调用并未完全执行完毕——当你调用ptrace(PTRACE_SYSCALL, pid, 0, 0)让tracee恢复执行时,内核会先完成kill系统调用的剩余流程,然后在系统调用的退出点触发停止,此时因为你设置了PTRACE_O_TRACESYSGOOD,停止信号会被标记为SIGTRAP|0x80。
而此时tracee还未执行到那条无效汇编指令,自然不会触发SIGILL。如果使用PTRACE_CONT,tracee会直接跳过系统调用的停止点,继续执行后续代码,直到触发SIGILL并停止。
要正确捕获SIGILL,你需要在处理完系统调用的停止点后,再次让tracee继续执行,此时它才会执行无效指令并触发SIGILL。修改你的tracer函数如下:
void tracer(pid_t pid) { int status; waitpid(pid, &status, 0); assert(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD); // 第一次PTRACE_SYSCALL:处理系统调用退出停止点 ptrace(PTRACE_SYSCALL, pid, 0, 0); waitpid(pid, &status, 0); printf("stop sig (syscall exit) = %d\n", WSTOPSIG(status)); // 输出SIGTRAP|0x80 // 第二次PTRACE_SYSCALL:让tracee继续执行,此时会触发SIGILL ptrace(PTRACE_SYSCALL, pid, 0, 0); waitpid(pid, &status, 0); printf("stop sig (SIGILL) = %d\n", WSTOPSIG(status)); // 输出SIGILL }
GDB能够区分这两种停止事件,是因为它会在每次捕获到停止后,检查停止的原因:
- 如果是
SIGTRAP|0x80,则判定为系统调用停止,继续执行等待下一个事件; - 如果是其他信号(如
SIGILL),则判定为信号触发的停止,通知用户。
你需要在自己的tracer中实现类似的逻辑:循环处理停止事件,根据WSTOPSIG(status)的值判断是系统调用停止还是信号停止,再进行相应处理。
内容的提问来源于stack exchange,提问作者GZaidman

