execve系统调用user_regs_struct值为0的原因及修复,ptrace读取RDI异常问题
解决ptrace捕获execve时RDI寄存器值为0的问题
我来帮你拆解这个问题——你遇到的情况是ptrace跟踪execve系统调用时的典型陷阱,咱们从原因到解决方案一步步说:
为什么你读到的RDI总是0?
主要有两个核心原因:
- execve执行后的上下文重置
当execve系统调用成功完成后,内核会完全替换当前进程的地址空间和上下文,新程序会从入口点(_start)开始执行。此时原来的寄存器状态会被彻底清空,换成新程序的初始状态,你读到的0其实是新程序启动时RDI的初始值,而不是execve调用时的参数指针。 - 跟踪时机错误
默认情况下,ptrace在系统调用退出时触发进程停止信号。等你拿到停止信号去读寄存器时,execve已经执行完毕,上下文早就被替换了,自然读不到正确的参数。
正确的解决方法
要捕获execve的第一个参数,你需要在系统调用进入时就读取寄存器,并且用更可靠的方式获取寄存器状态:
步骤1:调整ptrace跟踪时机
使用PTRACE_SYSCALL让进程在系统调用的进入和退出两个阶段都停止。这样你就能在进入execve的第一时间读取寄存器。
步骤2:用PTRACE_GETREGS读取完整寄存器状态
直接用PTRACE_PEEKUSER读取寄存器偏移容易因为架构差异出错,建议用PTRACE_GETREGS获取整个user_regs_struct结构体,这样能准确拿到所有寄存器的值。
示例代码片段
#include <sys/ptrace.h> #include <sys/wait.h> #include <sys/user.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { pid_t child = fork(); if (child == 0) { // 子进程设置被跟踪,然后执行目标程序 ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/bin/ls", "ls", NULL); // 这里替换成你要跟踪的程序 exit(1); } else { int status; wait(&status); // 第一次触发:进入系统调用 ptrace(PTRACE_SYSCALL, child, NULL, NULL); wait(&status); struct user_regs_struct regs; ptrace(PTRACE_GETREGS, child, NULL, ®s); // x86_64上execve的系统调用号是59 if (regs.orig_rax == 59) { // RDI此时就是execve的第一个参数:可执行文件路径的指针 long path_ptr = regs.rdi; char path[256]; int i = 0; // 从子进程地址空间读取路径字符串 do { path[i] = (char)ptrace(PTRACE_PEEKDATA, child, path_ptr + i, NULL); } while (path[i++] != '\0'); printf("捕获到的可执行文件路径:%s\n", path); } // 继续让子进程执行 ptrace(PTRACE_CONT, child, NULL, NULL); wait(&status); return 0; } }
关键注意事项
- 区分系统调用的进入和退出:第一次
wait后的PTRACE_SYSCALL会让子进程在进入系统调用时停止,这时候读取的寄存器才是execve调用时的参数;第二次停止是系统调用退出,此时上下文已经被替换,不要在这个阶段读取参数。 - 架构差异:不同架构的系统调用号和寄存器用法不同(比如x86上execve的系统调用号是11,参数存在EBX、ECX、EDX),上面的代码针对x86_64,如果你用32位系统需要调整。
- 权限问题:跟踪进程需要足够的权限,比如不能跟踪root进程除非你也是root。
内容的提问来源于stack exchange,提问作者vp8
相关产品推荐
相关产品推荐

