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

Linux fork场景下父进程调用ptrace无法恢复子进程运行问题

ptrace追踪exec流程统计fork调用次数问题修复

核心错误点说明

对应你提出的三个疑问,代码里的逻辑错误如下:

  • PTRACE_CONT的pid参数问题:该参数必须传入被追踪的子进程pid,你代码里的pid变量本身取值正确,参数传参没有问题,子进程无法恢复是其他逻辑错误导致的。
  • 子进程无法恢复的根因:
    1. 你调用的raise(SIGCONT)是给当前进程(父进程自身)发送SIGCONT信号,根本不会发送给子进程,完全不影响子进程状态。如果要手动给子进程发信号需要调用kill(pid, SIGCONT),但在ptrace追踪场景下,被追踪进程的所有信号都会被ptrace拦截,直接发信号无法绕过ptrace的事件等待逻辑。
    2. 你在子进程中调用ptrace(PTRACE_SETOPTIONS, ...)是无效操作:PTRACE_SETOPTIONS是仅允许追踪者(父进程)调用的接口,被追踪的子进程调用该接口会返回权限错误,你之前配置的PTRACE_O_TRACEEXEC根本没有生效,子进程调用execve时会触发SIGTRAP信号自动停止。
    3. 你第一次调用wait(&status)拿到子进程初始SIGSTOP的状态后,后续没有再次调用wait类接口等待子进程的新事件,status变量一直存储的是第一次拿到的停止状态,你后续判断WIFSTOPPED(status)永远为真,误以为子进程没有被唤醒,实际上是你根本没有读取子进程的最新状态。
  • 无法捕获fork事件的根因:
    1. 你在第一次唤醒子进程之前没有设置PTRACE_O_TRACEFORK选项,等子进程已经开始执行execve、bash启动后再设置选项,已经错过了fork事件的捕获时机。
    2. 你从未在PTRACE_CONT之后等待子进程事件,子进程产生的exec、fork事件都存在内核队列里没有被读取,status变量一直是旧值,自然匹配不到fork事件的状态码。
    3. 你用于判断fork事件的位运算写法错误:ptrace事件码存储在status的高8位,需要先通过WSTOPSIG(status)确认停止信号是SIGTRAP后,再通过(status >> 16) & 0xff获取事件码,直接对status右移8位无法得到正确的事件值。
    4. 你直接用status == 0、status ==1判断进程退出状态是错误的,wait返回的status必须通过WIFEXITED、WEXITSTATUS等标准宏解析,直接对比整数值没有意义。

正确实现逻辑

  1. 子进程仅需调用PTRACE_TRACEME标记自己为被追踪状态,随后主动发SIGSTOP暂停,等待父进程完成所有ptrace配置后再继续执行,不要在子进程内调用PTRACE_SETOPTIONS。
  2. 父进程第一次wait到子进程的初始暂停后,一次性配置所有需要的ptrace选项(包括PTRACE_O_TRACEEXEC、PTRACE_O_TRACEFORK、PTRACE_O_EXITKILL),不要等到后续循环中重复配置。
  3. 每次调用PTRACE_CONT唤醒子进程后,必须调用waitpid阻塞等待子进程的下一个事件,更新status变量后再做状态判断。
  4. 处理事件时严格按照宏规则解析status:先判断是否为停止事件,再判断停止信号,如果是SIGTRAP则进一步解析ptrace事件类型,区分exec、fork等事件。
  5. execve调用后仅在执行失败时才会返回,需要在子进程中加上错误退出逻辑,避免execve失败后子进程跑飞。

修正后的可运行代码

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

int main(int argc, char *argv[]) {
    int status;
    int fork_counter = 0;
    pid_t pid = fork();
    if (pid < 0) {
        perror("fork failed");
        exit(1);
    } else if (pid == 0) {
        // 子进程:仅标记被追踪,主动暂停等待父进程配置
        if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
            perror("ptrace traceme failed");
            exit(1);
        }
        raise(SIGSTOP);
        execve(argv[1], &argv[1], NULL);
        // execve只有失败才会走到这里
        perror("execve failed");
        exit(1);
    } else {
        // 等待子进程初始暂停
        waitpid(pid, &status, 0);
        if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP) {
            printf("child initial stopped, start tracing\n");
        }
        // 一次性配置所有ptrace选项
        if (ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC | PTRACE_O_TRACEFORK | PTRACE_O_EXITKILL) == -1) {
            perror("ptrace setoptions failed");
            exit(1);
        }
        // 第一次唤醒子进程
        ptrace(PTRACE_CONT, pid, NULL, NULL);

        while (1) {
            // 等待子进程事件
            waitpid(pid, &status, 0);
            // 处理子进程退出
            if (WIFEXITED(status)) {
                printf("child exited normally, fork count: %d\n", fork_counter);
                break;
            }
            if (WIFSIGNALED(status)) {
                printf("child killed by signal %d, fork count: %d\n", WTERMSIG(status), fork_counter);
                break;
            }
            // 处理停止事件
            if (WIFSTOPPED(status)) {
                int stopsig = WSTOPSIG(status);
                if (stopsig == SIGTRAP) {
                    int event = (status >> 16) & 0xff;
                    if (event == PTRACE_EVENT_EXEC) {
                        printf("captured exec event\n");
                    } else if (event == PTRACE_EVENT_FORK) {
                        fork_counter++;
                        printf("captured fork event, current count: %d\n", fork_counter);
                        // 这里可通过PTRACE_GETEVENTMSG拿到新fork的子进程pid,按需配置追踪
                    }
                    // 唤醒子进程继续执行,不需要传递额外信号
                    ptrace(PTRACE_CONT, pid, NULL, NULL);
                } else {
                    // 其他信号导致的停止,把信号传递给子进程避免丢失
                    ptrace(PTRACE_CONT, pid, NULL, (void*)(long)stopsig);
                }
            }
        }
    }
    return 0;
}

编译运行后传入测试参数/bin/bash -c "echo 'first test' | wc -c",可以正常捕获exec事件和两次fork(bash创建echo进程、创建wc进程的两次fork),最终统计到fork次数为2,子进程可以正常执行完成。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 17:54:26