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

Linux fork/execve子进程执行异常 基于ptrace统计clone调用次数

代码逻辑错误排查

原有实现存在6个核心逻辑错误,是导致execve调用后程序异常、计数不准的根本原因:

  • 调用顺序错误:fork完成后立刻在父子进程公共逻辑里调用wait,既没有先判断fork返回值(fork失败时wait会传入无效pid触发异常),也没有给子进程留出执行ptrace(PTRACE_TRACEME)、发送SIGSTOP的时机,wait等待目标完全错误。
  • 父子进程逻辑混写:父进程的ptrace配置、事件计数、进程恢复逻辑全部写在child_pid == 0的子进程分支中,子进程调用execve成功后会直接替换整个进程地址空间,这些代码永远不会被执行,同时父进程拿不到任何trace逻辑,自然无法跟踪后续事件。
  • 对execve的行为理解错误:execve调用成功后不会返回,原有代码把ptrace配置、事件判断逻辑全写在execve调用之后,子进程跑到execve就直接加载bash程序,后续代码全部被覆写丢弃。
  • ptrace调用主体错误:PTRACE_TRACEME确实由子进程调用,但PTRACE_SETOPTIONS、PTRACE_CONT必须由作为tracer的父进程调用,子进程自行调用这些接口完全不符合ptrace的设计,且PTRACE_TRACEME的第一个参数应传0,不需要传入自身pid。
  • wait循环判断逻辑完全失效:原有判断条件while(status != child_pid)把存储进程状态的status变量和pid做比较,二者语义、数据范围完全不匹配,循环根本不会按预期执行;且原有逻辑只等待初始fork的子进程,bash执行管道命令时clone出的echo、wc等后代进程的事件完全不会被捕获。
  • 事件判断位置错误:ptrace的clone事件是发送给tracer(父进程)的,原有逻辑把事件判断写在子进程分支,子进程永远收不到自身的clone事件,计数逻辑永远不会触发。
正确实现方案

严格限定仅使用ptrace、fork、wait/waitpid三类接口的前提下,实现逻辑如下:

  1. 彻底拆分父子进程逻辑,子进程仅负责标记自身被trace、主动停住等待父进程配置、调用execve加载目标程序,execve之后不写任何业务逻辑。
  2. 父进程首先等待初始子进程因SIGSTOP暂停,待子进程停稳后配置ptrace选项,除PTRACE_O_TRACECLONE外,同时开启PTRACE_O_TRACEFORK、PTRACE_O_TRACEVFORK,确保子进程通过任意clone flag创建的后代进程都会被自动纳入trace,无需手动attach。
  3. 父进程进入事件循环,调用waitpid(-1, &status, 0)等待所有被trace进程(含初始子进程和所有后代)的状态变化,不限制等待的pid范围。
  4. 每次wait返回后先判断事件类型:如果是进程退出/被信号杀死,就减少存活被trace进程计数,等所有被trace进程退出后跳出循环;如果是ptrace触发的SIGTRAP停住事件,判断事件类型为clone/fork/vfork时计数器加1,同时增加存活被trace进程计数。
  5. 所有停住的进程处理完事件后,统一调用ptrace(PTRACE_CONT)恢复运行。
修正后可运行代码
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "usage: %s <cmd> [args...]\n", argv[0]);
        exit(1);
    }
    pid_t child_pid = fork();
    if (child_pid == -1) {
        perror("fork failed");
        exit(1);
    }

    if (child_pid == 0) {
        // 子进程:被trace端
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        raise(SIGSTOP);
        execve(argv[1], &argv[1], NULL);
        perror("execve failed"); // execve成功则永远走不到这里
        exit(1);
    } else {
        // 父进程:tracer端
        int status;
        int clone_counter = 0;
        int alive_tracee = 1; // 初始仅1个被trace的子进程

        // 等待初始子进程触发SIGSTOP
        waitpid(child_pid, &status, 0);
        // 配置ptrace选项,跟踪所有新创建的后代进程
        ptrace(PTRACE_SETOPTIONS, child_pid, 0,
               PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK);
        // 恢复初始子进程运行
        ptrace(PTRACE_CONT, child_pid, 0, 0);

        while (alive_tracee > 0) {
            pid_t cur_pid = waitpid(-1, &status, 0);
            if (cur_pid == -1) break;

            // 处理进程退出事件
            if (WIFEXITED(status) || WIFSIGNALED(status)) {
                alive_tracee--;
                continue;
            }

            // 处理ptrace事件
            if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
                unsigned long event_code = (status >> 16) & 0xffff;
                if (event_code == PTRACE_EVENT_CLONE ||
                    event_code == PTRACE_EVENT_FORK ||
                    event_code == PTRACE_EVENT_VFORK) {
                    clone_counter++;
                    alive_tracee++;
                }
            }

            // 恢复当前停住的进程运行
            ptrace(PTRACE_CONT, cur_pid, 0, 0);
        }

        printf("# of clone executions: %d\n", clone_counter);
    }
    return 0;
}
测试说明

传入测试参数/bin/bash -c "echo 'first test' | wc -c"运行时,程序不会出现execve后整体终止的问题——父进程逻辑运行在独立地址空间,不会被子进程的execve覆写。计数结果包含bash启动流程、创建echo进程、创建wc进程的所有clone类系统调用,符合统计要求。

内容的提问来源于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 14:18:13