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

execve系统调用user_regs_struct值为0的原因及修复,ptrace读取RDI异常问题

解决ptrace捕获execve时RDI寄存器值为0的问题

我来帮你拆解这个问题——你遇到的情况是ptrace跟踪execve系统调用时的典型陷阱,咱们从原因到解决方案一步步说:

为什么你读到的RDI总是0?

主要有两个核心原因:

  1. execve执行后的上下文重置
    当execve系统调用成功完成后,内核会完全替换当前进程的地址空间和上下文,新程序会从入口点(_start)开始执行。此时原来的寄存器状态会被彻底清空,换成新程序的初始状态,你读到的0其实是新程序启动时RDI的初始值,而不是execve调用时的参数指针。
  2. 跟踪时机错误
    默认情况下,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, &regs);
        
        // 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 07:53:53