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

求助:基于PTrace实现C++包装器替代Strace追踪进程打开文件

Building a Lightweight Ptrace Tool to Track File Opens

Got it, let's tackle this problem head-on. You’re right that strace can be heavy because it logs every syscall and generates verbose output, but building a custom ptrace-based tool to only track file opens will cut down that overhead significantly. Here’s a practical, step-by-step guide to make this happen:

Core Idea

Strace’s performance hit comes from intercepting all syscalls and generating verbose output. We’ll focus only on syscalls that open files (open, openat, creat, etc.), which means we can skip most of the overhead strace incurs.

Step-by-Step Implementation

1. Attach to the Target Process

First, we need to attach to the process we want to monitor. This pauses the process, letting us take control via ptrace.

2. Intercept Syscall Entries

On x86_64 systems, when a process executes a syscall, it triggers a stop that we can catch with waitpid. We’ll check the syscall number (stored in the orig_rax register) to see if it’s one of the file-opening syscalls we care about.

3. Extract the File Path

For syscalls like open or openat, the filename is passed as a pointer in the target process’s user space. We use ptrace(PTRACE_PEEKDATA) to read this string from the target’s memory.

4. Resume the Process

After logging the file path, we let the syscall complete and wait for the next stop. We repeat this loop until the target process exits.

Full C++ Example Code

#include <iostream>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <cstring>
#include <unistd.h>
#include <stdexcept>

void read_filename(pid_t pid, unsigned long addr, char* buf, size_t buf_size) {
    memset(buf, 0, buf_size);
    size_t bytes_read = 0;
    // Read 8 bytes at a time (x86_64 long) to minimize ptrace calls
    while (bytes_read < buf_size - 1) {
        long data = ptrace(PTRACE_PEEKDATA, pid, addr + bytes_read, nullptr);
        if (data == -1) {
            perror("Failed to read memory");
            break;
        }
        memcpy(buf + bytes_read, &data, sizeof(long));
        // Stop early if we hit a null terminator
        if (memchr(buf + bytes_read, '\0', sizeof(long)) != nullptr) {
            break;
        }
        bytes_read += sizeof(long);
    }
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <target-pid>\n";
        return EXIT_FAILURE;
    }

    pid_t target_pid = std::stoi(argv[1]);

    // Attach to target process
    if (ptrace(PTRACE_ATTACH, target_pid, nullptr, nullptr) == -1) {
        throw std::runtime_error("Failed to attach to process: " + std::string(strerror(errno)));
    }

    int status;
    waitpid(target_pid, &status, 0);
    if (!WIFSTOPPED(status)) {
        std::cerr << "Process didn't stop after attachment\n";
        ptrace(PTRACE_DETACH, target_pid, nullptr, nullptr);
        return EXIT_FAILURE;
    }

    struct user_regs_struct regs;
    while (true) {
        // Wait for syscall entry
        if (ptrace(PTRACE_SYSCALL, target_pid, nullptr, nullptr) == -1) {
            perror("ptrace syscall entry failed");
            break;
        }
        waitpid(target_pid, &status, 0);
        if (WIFEXITED(status) || WIFSIGNALED(status)) {
            std::cout << "Target process exited with status: " << status << "\n";
            break;
        }

        // Get register state at syscall entry
        if (ptrace(PTRACE_GETREGS, target_pid, nullptr, &regs) == -1) {
            perror("Failed to get registers");
            break;
        }

        // Check for file-opening syscalls (x86_64 syscall numbers)
        long syscall_num = regs.orig_rax;
        if (syscall_num == 2 || syscall_num == 257 || syscall_num == 85) {
            // syscall 2 = open, 257 = openat, 85 = creat
            char filename[512];
            unsigned long filename_addr;

            if (syscall_num == 257) {
                // openat uses rdi as dirfd, rsi as filename
                filename_addr = regs.rsi;
            } else {
                // open/creat use rdi as filename
                filename_addr = regs.rdi;
            }

            read_filename(target_pid, filename_addr, filename, sizeof(filename));
            std::cout << "[PID " << target_pid << "] Opened file: " << filename << "\n";
        }

        // Wait for syscall exit
        if (ptrace(PTRACE_SYSCALL, target_pid, nullptr, nullptr) == -1) {
            perror("ptrace syscall exit failed");
            break;
        }
        waitpid(target_pid, &status, 0);
        if (WIFEXITED(status) || WIFSIGNALED(status)) {
            std::cout << "Target process exited with status: " << status << "\n";
            break;
        }
    }

    // Clean up: detach from the process
    ptrace(PTRACE_DETACH, target_pid, nullptr, nullptr);
    return EXIT_SUCCESS;
}

Key Notes & Optimizations

  • Architecture Compatibility: This example is for x86_64. For 32-bit x86, use orig_eax for syscall numbers, and ebx/ecx for parameters instead of rdi/rsi.
  • Performance: We read memory in 8-byte chunks instead of single bytes to reduce the number of ptrace calls (each call has overhead). This is way faster than strace’s default mode.
  • Permissions: You’ll need root privileges or the same UID as the target process to attach.
  • Tracking Child Processes: If your target forks children, add ptrace(PTRACE_SETOPTIONS, pid, nullptr, PTRACE_O_TRACEFORK | PTRACE_O_TRACECLONE) after attaching to track child processes.
  • Error Handling: The example has basic error handling; in production code, you’ll want to handle signals like SIGINT to cleanly detach from the target process.

Why Your Previous Guides Didn’t Help

Most general ptrace guides focus on attaching, modifying registers, or debugging execution—but your use case requires syscall interception and user-space memory reading, which are more specialized tasks. This guide zeroes in on exactly that workflow.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:54:51