通过ptrace捕获write后,readlink返回pipe标识,无法获取文件路径求助
捕获write系统调用时获取文件路径的问题解决方法
问题描述
需求为捕获write系统调用,统计文件的写入位置及字节数。已通过ptrace成功捕获该系统调用,但用readlink获取文件描述符对应路径时,返回类似pipe:[766279]的标识。测试场景中,通过Python脚本调用echo命令向/tmp目录下的临时文件写入内容,确认已捕获正确的write调用(写入字节数匹配),但无法获取期望的完整文件路径。
问题原因
- 访问错误进程的文件描述符:原代码中
get_file_path函数使用/proc/self/fd/%d,此处的self指向追踪进程(父进程),而非被追踪的子进程,导致获取的不是目标进程的文件路径。 - 未过滤特殊文件:捕获到的
write调用可能是向管道、套接字等特殊文件写入,这类文件的路径标识就是pipe:[xxx]或socket:[xxx],需要过滤掉非普通文件的场景。 - 变量传递逻辑错误:处理
write调用时,increment函数传入的是未初始化的filename变量,而非实际获取到的文件路径data。 - 系统调用阶段处理错误:原代码未区分
write系统调用的进入和退出阶段,可能导致参数读取时机不正确。
解决步骤
- 修改文件路径获取逻辑:访问被追踪子进程的
/proc目录,即/proc/<child_pid>/fd/%d,来获取子进程的文件描述符对应路径。 - 过滤非普通文件:获取路径后通过
stat检查文件类型,只处理普通文件。 - 修正变量传递:将正确获取的文件路径传入统计函数。
- 区分系统调用阶段:标记
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, ®s); 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
相关产品推荐
相关产品推荐

