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

通过ptrace捕获write后,readlink返回pipe标识,无法获取文件路径求助

捕获write系统调用时获取文件路径的问题解决方法

问题描述

需求为捕获write系统调用,统计文件的写入位置及字节数。已通过ptrace成功捕获该系统调用,但用readlink获取文件描述符对应路径时,返回类似pipe:[766279]的标识。测试场景中,通过Python脚本调用echo命令向/tmp目录下的临时文件写入内容,确认已捕获正确的write调用(写入字节数匹配),但无法获取期望的完整文件路径。

问题原因

  1. 访问错误进程的文件描述符:原代码中get_file_path函数使用/proc/self/fd/%d,此处的self指向追踪进程(父进程),而非被追踪的子进程,导致获取的不是目标进程的文件路径。
  2. 未过滤特殊文件:捕获到的write调用可能是向管道、套接字等特殊文件写入,这类文件的路径标识就是pipe:[xxx]或socket:[xxx],需要过滤掉非普通文件的场景。
  3. 变量传递逻辑错误:处理write调用时,increment函数传入的是未初始化的filename变量,而非实际获取到的文件路径data。
  4. 系统调用阶段处理错误:原代码未区分write系统调用的进入和退出阶段,可能导致参数读取时机不正确。

解决步骤

  1. 修改文件路径获取逻辑:访问被追踪子进程的/proc目录,即/proc/<child_pid>/fd/%d,来获取子进程的文件描述符对应路径。
  2. 过滤非普通文件:获取路径后通过stat检查文件类型,只处理普通文件。
  3. 修正变量传递:将正确获取的文件路径传入统计函数。
  4. 区分系统调用阶段:标记write系统调用的进入和退出,确保在调用完成后处理写入数据。

修改后的完整代码

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/limits.h>
#include <sys/user.h>
#include <sys/stat.h>

typedef struct Counter {
    char filename[PATH_MAX];
    int counter;
    struct Counter* next;
} Counter;

typedef struct Counters {
    struct Counter* head;
} Counters;

void increment(Counters* counters, const char* filename, int value) {
    Counter* current = counters->head;
    while (current != NULL) {
        if (strcmp(current->filename, filename) == 0) {
            current->counter += value;
            return;
        }
        current = current->next;
    }

    Counter* new_entry = malloc(sizeof(Counter));
    new_entry->next = counters->head;
    new_entry->counter = value;
    strncpy(new_entry->filename, filename, PATH_MAX - 1);
    new_entry->filename[PATH_MAX - 1] = '\0';
    counters->head = new_entry;
}

void print(Counters* counters) {
    Counter* current = counters->head;
    while (current != NULL) {
        printf("%s:%d\n", current->filename, current->counter);
        current = current->next;
    }
}

// 获取子进程文件描述符对应的普通文件路径
int get_file_path(pid_t child_pid, int fd, char* path, size_t path_size) {
    char fd_path[PATH_MAX];
    snprintf(fd_path, sizeof(fd_path), "/proc/%d/fd/%d", child_pid, fd);
    int size = readlink(fd_path, path, path_size - 1);
    if (size < 0) {
        return -1;
    }
    path[size] = '\0';

    // 检查是否为普通文件
    struct stat st;
    if (stat(path, &st) == -1 || !S_ISREG(st.st_mode)) {
        return -1;
    }
    return 0;
}

int main(int argc, char *argv[]) {
    Counters counters;
    counters.head = NULL;

    pid_t child_pid = fork();

    if (child_pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }
    if (child_pid == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execvp(argv[1], &argv[1]);
        perror("execvp");
        exit(EXIT_FAILURE);
    } else {
        int status;
        waitpid(child_pid, &status, 0);
        int is_write_entry = 0; // 标记是否进入write系统调用

        while (WIFSTOPPED(status)) {
            struct user_regs_struct regs;
            ptrace(PTRACE_GETREGS, child_pid, NULL, &regs);

            if (regs.orig_rax == 1) { // write系统调用编号(x86_64)
                if (!is_write_entry) {
                    // 进入write调用,标记状态
                    is_write_entry = 1;
                } else {
                    // 退出write调用,处理写入数据
                    char data[PATH_MAX];
                    if (get_file_path(child_pid, regs.rdi, data, PATH_MAX) == 0) {
                        increment(&counters, data, regs.rdx);
                    }
                    is_write_entry = 0;
                }
            }

            ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL);
            waitpid(child_pid, &status, 0);
        }
        print(&counters);
        // 释放内存
        Counter* current = counters.head;
        while (current != NULL) {
            Counter* next = current->next;
            free(current);
            current = next;
        }
    }
    return 0;
}

关键修改说明

  • get_file_path函数:新增child_pid参数,访问子进程的/proc目录获取文件路径;添加stat检查,仅保留普通文件的路径。
  • 系统调用阶段处理:通过is_write_entry变量区分write调用的进入和退出,确保在调用完成后读取写入字节数。
  • 变量逻辑修正:将获取到的有效文件路径data传入increment函数,替换原代码中未初始化的filename。
  • 错误处理优化:get_file_path返回错误码而非直接退出,让主逻辑决定是否忽略无效的文件描述符。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 22:57:51