You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用ptrace()监控进程直至其退出?求跨OSX/Linux实现方案

解决ptrace()跨平台监控进程直至退出的问题

你遇到的问题很常见——OSX和Linux的ptrace()行为差异不小,尤其是在attach后的流程处理上。你的程序立即退出,大概率是因为attach到目标进程后没有进入等待循环,也没有正确处理进程的状态变化。下面我给你一套跨平台的实现方案,能让你的监控程序直到目标进程退出才终止。

核心思路

不管是OSX还是Linux,要持续监控进程直到它退出,都需要这几个步骤:

  1. ptrace()附加到目标PID
  2. 处理附加后的进程暂停状态,让目标进程继续运行
  3. 循环调用waitpid()等待目标进程的状态变化
  4. 每次状态变化后,判断进程是否已经退出;如果没退出,就继续让进程运行,回到等待循环

跨平台实现代码

下面的代码用条件编译区分OSX和Linux,注释里标清楚了每个平台的差异点:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <target-pid>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    pid_t target_pid = atoi(argv[1]);
    int status;

    // Step 1: Attach to the target process
#ifdef __APPLE__
    if (ptrace(PT_ATTACH, target_pid, NULL, 0) == -1) {
        perror("ptrace PT_ATTACH failed");
        exit(EXIT_FAILURE);
    }
    // OSX: Attach后进程会暂停,需要先waitpid捕获暂停状态,再让它继续
    if (waitpid(target_pid, &status, WUNTRACED) == -1) {
        perror("waitpid after PT_ATTACH failed");
        exit(EXIT_FAILURE);
    }
    // 让目标进程继续运行
    if (ptrace(PT_CONTINUE, target_pid, (caddr_t)1, 0) == -1) {
        perror("ptrace PT_CONTINUE failed");
        exit(EXIT_FAILURE);
    }
#else // __linux__
    if (ptrace(PTRACE_ATTACH, target_pid, NULL, 0) == -1) {
        perror("ptrace PTRACE_ATTACH failed");
        exit(EXIT_FAILURE);
    }
    // Linux: Attach后进程会收到SIGSTOP,waitpid会捕获这个状态
    if (waitpid(target_pid, &status, 0) == -1) {
        perror("waitpid after PTRACE_ATTACH failed");
        exit(EXIT_FAILURE);
    }
    // 让目标进程继续运行,忽略之前的SIGSTOP
    if (ptrace(PTRACE_CONT, target_pid, NULL, 0) == -1) {
        perror("ptrace PTRACE_CONT failed");
        exit(EXIT_FAILURE);
    }
#endif

    // Step 2: 循环等待目标进程状态变化,直到它退出
    while (1) {
        // 等待进程状态变化
        if (waitpid(target_pid, &status, 0) == -1) {
            perror("waitpid in loop failed");
            exit(EXIT_FAILURE);
        }

        // 判断进程是否已经退出
        if (WIFEXITED(status)) {
            printf("Target process %d exited normally with code %d\n", target_pid, WEXITSTATUS(status));
            break;
        }
        if (WIFSIGNALED(status)) {
            printf("Target process %d exited due to signal %d\n", target_pid, WTERMSIG(status));
            break;
        }

        // 如果进程只是暂停(比如收到信号),让它继续运行
#ifdef __APPLE__
        if (ptrace(PT_CONTINUE, target_pid, (caddr_t)1, 0) == -1) {
            perror("ptrace PT_CONTINUE in loop failed");
            exit(EXIT_FAILURE);
        }
#else
        // Linux下,需要传递导致暂停的信号,让进程继续处理
        if (ptrace(PTRACE_CONT, target_pid, NULL, WSTOPSIG(status)) == -1) {
            perror("ptrace PTRACE_CONT in loop failed");
            exit(EXIT_FAILURE);
        }
#endif
    }

    // 最后 detach(可选,不过进程已经退出的话,detach可能没必要,但好习惯)
#ifdef __APPLE__
    ptrace(PT_DETACH, target_pid, NULL, 0);
#else
    ptrace(PTRACE_DETACH, target_pid, NULL, 0);
#endif

    return EXIT_SUCCESS;
}

关键细节说明

  1. 平台差异处理

    • OSX使用PT_ATTACH/PT_CONTINUE,Linux使用PTRACE_ATTACH/PTRACE_CONT,通过__APPLE____linux__宏区分
    • OSX在attach后必须先调用waitpid捕获暂停状态,再执行PT_CONTINUE;Linux则是attach后进程自动暂停,waitpid捕获后用PTRACE_CONT恢复
    • Linux下继续进程时,需要传递导致暂停的信号(WSTOPSIG(status)),让进程能正确处理这个信号
  2. 退出判断

    • WIFEXITED(status)判断进程是否正常退出
    • WIFSIGNALED(status)判断进程是否因信号终止(比如被kill)
  3. 权限问题

    • Linux下,普通用户只能attach到同用户的进程;如果要监控其他用户的进程,需要root权限
    • OSX下,现代版本有系统完整性保护(SIP),可能需要临时关闭SIP(csrutil disable重启后生效),才能让ptrace正常工作

编译与运行

  • Linux下编译:gcc monitor.c -o monitor,运行:sudo ./monitor <pid>(如果监控其他用户进程)
  • OSX下编译:clang monitor.c -o monitor,运行:sudo ./monitor <pid>(需要关闭SIP或者有足够权限)

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

火山引擎 最新活动