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

Windows系统下C++中克隆/复刻(fork)进程的正确方法?

Windows下C++实现类Unix fork进程克隆的原生方案

问题背景

在Windows 10系统+Visual Studio 2022环境下,需要实现类似Unix fork()的进程克隆功能:子进程从克隆函数调用处继续执行,而非启动独立的可执行文件。已尝试以下方案但未满足需求:

  • CreateProcess系列API:只能启动指定可执行文件,无法从当前执行点复刻进程
  • 基于ntdll.dll未公开函数RtlCloneUserProcess的自定义fork实现:子进程未按预期执行后续逻辑,仅父进程输出内容

尝试的CreateProcess示例代码

#include <iostream>
#include <codecvt>
#include <Windows.h>

bool RunChildProcess(std::string command)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> stringConverter;
    bool result = CreateProcess(NULL, stringConverter.from_bytes(command.c_str()).data(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

    if (result)
    {
        WaitForSingleObject(pi.hProcess, INFINITE);

        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }

    return result;
}

int main()
{
    std::string command = "test.exe";
    bool runResult = RunChildProcess(command);

    if (!runResult)
    {
        std::cout << "\n-- ERROR: Failed to run child command \"" + command + "\"! --\n";
        return 1;
    }

    system("pause");
    return 0;
}

尝试的RtlCloneUserProcess示例代码

#define _WIN32_WINNT 0x0600
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winnt.h>
#include <winternl.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <process.h>
#include <processthreadsapi.h>
#include <iostream>
#include <Windows.h>
#include <codecvt>

typedef struct _SECTION_IMAGE_INFORMATION {
    PVOID EntryPoint;
    ULONG StackZeroBits;
    ULONG StackReserved;
    ULONG StackCommit;
    ULONG ImageSubsystem;
    WORD SubSystemVersionLow;
    WORD SubSystemVersionHigh;
    ULONG Unknown1;
    ULONG ImageCharacteristics;
    ULONG ImageMachineType;
    ULONG Unknown2[3];
} SECTION_IMAGE_INFORMATION, * PSECTION_IMAGE_INFORMATION;

typedef struct _RTL_USER_PROCESS_INFORMATION {
    ULONG Size;
    HANDLE Process;
    HANDLE Thread;
    CLIENT_ID ClientId;
    SECTION_IMAGE_INFORMATION ImageInformation;
} RTL_USER_PROCESS_INFORMATION, * PRTL_USER_PROCESS_INFORMATION;

#define RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED    0x00000001
#define RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES     0x00000002
#define RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE      0x00000004

#define RTL_CLONE_PARENT                0
#define RTL_CLONE_CHILD                 297

typedef DWORD pid_t;

typedef NTSTATUS(*RtlCloneUserProcess_f)(ULONG ProcessFlags,
    PSECURITY_DESCRIPTOR ProcessSecurityDescriptor /* optional */,
    PSECURITY_DESCRIPTOR ThreadSecurityDescriptor /* optional */,
    HANDLE DebugPort /* optional */,
    PRTL_USER_PROCESS_INFORMATION ProcessInformation);


int fork()
{
    DWORD parent_pid = GetCurrentProcessId();

    HMODULE mod;
    RtlCloneUserProcess_f clone_p;
    RTL_USER_PROCESS_INFORMATION process_info;
    NTSTATUS result;

    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> stringConverter;
    mod = GetModuleHandle(stringConverter.from_bytes("ntdll.dll").data());
    if (!mod)
        return -ENOSYS;

    clone_p = (RtlCloneUserProcess_f)GetProcAddress(mod, "RtlCloneUserProcess");
    if (clone_p == NULL)
        return -ENOSYS;

    /* lets do this */
    result = clone_p(RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED | RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES, NULL, NULL, NULL, &process_info);

    if (result == RTL_CLONE_PARENT)
    {
        HANDLE me = 0, hp = 0, ht = 0, hcp = 0;
        DWORD pi, ti, mi;
        me = GetCurrentProcess();
        pi = (DWORD)process_info.ClientId.UniqueProcess;
        ti = (DWORD)process_info.ClientId.UniqueThread;

        assert(hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pi));
        assert(ht = OpenThread(THREAD_ALL_ACCESS, FALSE, ti));

        ResumeThread(ht);
        CloseHandle(ht);
        CloseHandle(hp);

        return (int)pi;
    }
    else if (result == RTL_CLONE_CHILD)
    {
        /* fix stdio */
        FreeConsole();
        AttachConsole(parent_pid);

        return 0;
    }
    else
        return -1;

    /* NOTREACHED */
    return -1;
}

int main()
{
    pid_t pid;
    pid = fork();

    switch (pid) {
    case 0: //child
    {
        printf("I am child.\n");
        break;
    }
    default: //parent
        printf("I am parent. Child process PID: %d\n", pid);
        break;
    }
    Sleep(1000);

    exit(0);
}

核心问题分析

RtlCloneUserProcess的示例失败原因:

  1. 该函数属于未公开的NTAPI,行为依赖Windows版本且无官方文档,克隆后的子进程线程上下文未正确指向fork()返回点,导致后续逻辑未执行
  2. AttachConsole存在竞争问题,子进程可能无法正确关联父进程控制台,导致输出丢失
  3. Windows与Unix进程模型差异极大:Unix采用写时复制的内存克隆,Windows则基于可执行文件创建进程,没有原生的"内存快照式克隆"API

原生可行方案

方案1:CreateProcess+内存注入+上下文恢复(近似fork行为)

通过手动序列化父进程执行上下文、注入子进程并修改线程上下文,实现子进程从克隆点继续执行的效果。

#define _WIN32_WINNT 0x0600
#include <windows.h>
#include <iostream>
#include <string>

// 保存执行上下文的结构体
struct ForkContext {
    CONTEXT ctx;
};

// 子进程恢复执行的入口函数
DWORD WINAPI ForkResume(LPVOID param) {
    ForkContext* remoteCtx = (ForkContext*)param;
    SetThreadContext(GetCurrentThread(), &remoteCtx->ctx);
    ResumeThread(GetCurrentThread());
    return 0;
}

int my_fork() {
    // 1. 获取当前进程可执行文件路径
    wchar_t exePath[MAX_PATH];
    GetModuleFileNameW(NULL, exePath, MAX_PATH);

    // 2. 创建挂起的子进程,传递子进程标记参数
    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    std::wstring cmdLine = L"\"" + std::wstring(exePath) + L"\" --child-mode";
    if (!CreateProcessW(exePath, &cmdLine[0], NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
        return -1;
    }

    // 3. 序列化父进程当前执行上下文
    ForkContext localCtx;
    memset(&localCtx.ctx, 0, sizeof(CONTEXT));
    localCtx.ctx.ContextFlags = CONTEXT_ALL;
    GetThreadContext(GetCurrentThread(), &localCtx.ctx);
    // 调整RIP到my_fork函数返回后的位置(需根据编译结果调试偏移)
    localCtx.ctx.Rip += 5;

    // 4. 将上下文注入子进程内存
    ForkContext* remoteCtx = (ForkContext*)VirtualAllocEx(pi.hProcess, NULL, sizeof(ForkContext), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    WriteProcessMemory(pi.hProcess, remoteCtx, &localCtx, sizeof(ForkContext), NULL);

    // 5. 在子进程创建远程线程,执行上下文恢复逻辑
    CreateRemoteThread(pi.hProcess, NULL, 0, ForkResume, remoteCtx, 0, NULL);

    // 6. 恢复子进程主线程
    ResumeThread(pi.hThread);

    // 关闭句柄
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    return pi.dwProcessId;
}

int main(int argc, char* argv[]) {
    // 判断是否为子进程
    bool isChild = false;
    for (int i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "--child-mode") == 0) {
            isChild = true;
            break;
        }
    }

    int pid;
    if (!isChild) {
        // 父进程执行克隆
        pid = my_fork();
        std::cout << "I am parent. Child process PID: " << pid << std::endl;
    } else {
        // 子进程从克隆点继续执行
        pid = 0;
        std::cout << "I am child." << std::endl;
    }

    Sleep(1000);
    return 0;
}

注意事项:

  • 需要调试调整localCtx.ctx.Rip的偏移量,不同编译器优化级别会影响指令位置
  • 若需复制堆或全局变量,需额外序列化这些数据并注入子进程
  • 该方案稳定性高,适合生产环境

方案2:CreateProcess+内存映射文件(简化版)

若无需完全克隆所有内存,仅需共享状态并让子进程执行指定逻辑,可采用此方案:

  1. 父进程创建内存映射文件,写入需传递的状态数据
  2. 创建挂起的子进程,通过命令行传递内存映射文件句柄
  3. 子进程打开内存映射文件,读取状态后执行目标逻辑

此方案实现简单,但无法完全复刻fork的内存克隆行为。

RtlCloneUserProcess方案修复(非生产环境)

若坚持使用未公开API,需修正线程上下文和控制台输出问题:

int fork() {
    DWORD parent_pid = GetCurrentProcessId();

    HMODULE mod = GetModuleHandleW(L"ntdll.dll");
    if (!mod) return -ENOSYS;

    auto clone_p = (RtlCloneUserProcess_f)GetProcAddress(mod, "RtlCloneUserProcess");
    if (!clone_p) return -ENOSYS;

    RTL_USER_PROCESS_INFORMATION process_info = { sizeof(process_info) };
    NTSTATUS result = clone_p(RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED | RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES, NULL, NULL, NULL, &process_info);

    if (result == RTL_CLONE_PARENT) {
        // 获取并修改子进程线程上下文,指向fork返回点
        CONTEXT ctx = { 0 };
        ctx.ContextFlags = CONTEXT_ALL;
        GetThreadContext(process_info.Thread, &ctx);
        // 调整RIP到fork函数返回后的位置(需调试确定偏移)
        ctx.Rip = (DWORD64)&&fork_return;

        SetThreadContext(process_info.Thread, &ctx);
        ResumeThread(process_info.Thread);

        CloseHandle(process_info.Process);
        CloseHandle(process_info.Thread);

        return (int)process_info.ClientId.UniqueProcess;
    } else if (result == RTL_CLONE_CHILD) {
        // 重定向输出到父进程控制台,避免输出丢失
        HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
        DuplicateHandle(GetCurrentProcess(), hOut, GetCurrentProcess(), &hOut, 0, FALSE, DUPLICATE_SAME_ACCESS);
        SetStdHandle(STD_OUTPUT_HANDLE, hOut);

        return 0;
    } else {
        return -1;
    }

fork_return:
    return 0; // 父进程不会执行到此处,仅作为占位
}

注意:未公开API兼容性无保障,仅适合测试场景。

总结

  • Windows原生无与Unix fork()完全等价的API,因两者进程模型差异极大
  • 生产环境优先选择CreateProcess+内存注入的方案,稳定性可控
  • 未公开NTAPI仅适合测试或特定场景,无法保证跨版本兼容性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 03:32:02