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

基于ptrace函数从结构体提取数据的技术求助

嘿,作为ptrace新手,想抓fstat返回的stat结构体里的st_mode和st_size确实容易踩坑,我来帮你捋捋怎么搞定这个问题~

先说说你之前代码可能失败的原因

你的buffer函数大概率踩了这几个坑:

  • ptrace读取内存是按**字(64位系统是8字节,32位是4字节)**来读的,不能直接按任意size字节读取,否则会出现内存对齐或者读取不完整的问题
  • 没有正确处理ptrace返回-1的情况:如果读取的内存内容本身就是-1,errno不会被设置,这时候不能直接判定为读取失败
  • 可能没等fstat系统调用完成就去读内存:进入系统调用时,内核还没把stat结构体的数据填充到用户空间,这时候读出来的是无效值
修正后的内存读取函数

先给你一个能可靠读取子进程内存的函数,解决上面的问题:

#include <sys/ptrace.h>
#include <sys/wait.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>

int read_child_memory(pid_t child, unsigned long long addr, void *buffer, size_t size) {
    size_t bytes_read = 0;
    char *buf = (char *)buffer;
    errno = 0;

    while (bytes_read < size) {
        // 每次读取一个字(适配64/32位系统)
        long data = ptrace(PTRACE_PEEKDATA, child, addr + bytes_read, NULL);
        if (data == -1 && errno != 0) {
            // 真的读取失败,返回错误
            return -1;
        }

        // 计算本次要复制的字节数:取剩余字节和字长的最小值
        size_t copy_size = (size - bytes_read) < sizeof(long) ? (size - bytes_read) : sizeof(long);
        memcpy(buf + bytes_read, &data, copy_size);
        bytes_read += copy_size;
    }
    return 0;
}

这个函数的关键细节:

  • 用PTRACE_PEEKDATA读取子进程内存,循环处理所有字节
  • 严格区分“读取到-1”和“读取失败”的情况,避免误判
  • 自动处理剩余不足一个字的字节,保证数据完整
完整获取st_mode和st_size的步骤

接下来结合拦截fstat系统调用的逻辑,实现你要的功能:

#include <sys/stat.h>
#include <sys/user.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

// 上面的read_child_memory函数放在这里

int main() {
    pid_t child = fork();
    if (child == 0) {
        // 子进程:开启跟踪,然后执行ls
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
        perror("execl failed");
        exit(EXIT_FAILURE);
    } else {
        int status;
        struct user_regs_struct regs;

        while (wait(&status) > 0) {
            if (WIFEXITED(status)) break;

            // 获取当前寄存器状态
            if (ptrace(PTRACE_GETREGS, child, NULL, &regs) == -1) {
                perror("PTRACE_GETREGS failed");
                continue;
            }

            // 检查是否是fstat系统调用(x86_64下的系统调用号是__NR_fstat)
            if (regs.orig_rax == __NR_fstat) {
                // 第一次触发是进入系统调用,我们要等系统调用完成后再读内存
                ptrace(PTRACE_SYSCALL, child, NULL, NULL);
                wait(&status);
                
                // 再次获取寄存器,确认系统调用成功返回
                ptrace(PTRACE_GETREGS, child, NULL, &regs);
                if (regs.rax == 0) {
                    // stat结构体的用户空间地址存在rsi寄存器里(x86_64参数传递规则)
                    unsigned long long stat_addr = regs.rsi;
                    struct stat stat_buf;

                    // 读取整个stat结构体到本地
                    if (read_child_memory(child, stat_addr, &stat_buf, sizeof(struct stat)) == 0) {
                        printf("抓到fstat的字段:\n");
                        printf("st_mode: %o\n", stat_buf.st_mode); // 八进制输出权限更直观
                        printf("st_size: %lld bytes\n", stat_buf.st_size);
                    } else {
                        perror("读取stat结构体失败");
                    }
                }
            }

            // 继续跟踪下一个系统调用
            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        }
    }
    return 0;
}

关键注意点:

  • 系统调用时机:必须等fstat系统调用完成(第二次触发PTRACE_SYSCALL)后再读内存,这时候内核已经把数据写入到用户空间的stat结构体里了
  • 寄存器架构适配:如果是32位系统,系统调用参数存在ebx、ecx等寄存器里,系统调用号也不一样,需要调整代码
  • 结构体兼容性:直接用系统自带的struct stat定义,不要自己手动写,避免因为系统差异导致字段偏移错误
  • 权限问题:运行程序时需要足够的权限,比如不能跟踪root进程除非你也是root
额外优化:只读取需要的字段

如果不想读取整个结构体,可以直接读取目标字段的偏移地址,更高效:

// 读取st_mode(mode_t类型,通常2字节)
mode_t st_mode;
read_child_memory(child, stat_addr + offsetof(struct stat, st_mode), &st_mode, sizeof(st_mode));

// 读取st_size(off_t类型,64位下8字节)
off_t st_size;
read_child_memory(child, stat_addr + offsetof(struct stat, st_size), &st_size, sizeof(st_size));

用offsetof宏可以安全获取字段的偏移量,不用硬编码数值,适配不同系统的结构体定义。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 07:52:50