欢迎光临
我们一直在努力

Reactos 第 5 章 进程与线程 — 5.6 Windows 的进程创建和映像装入

第 5 章 进程与线程 — 5.6 Windows 的进程创建和映像装入

概述

进程创建是操作系统最复杂的操作之一,它涉及内核多个子系统的协同工作。本节深入剖析 Windows 从用户态调用 CreateProcess 到新进程开始执行的完整流程,揭示进程创建和映像装入的核心机制。

本节内容概览

本节将系统地介绍以下内容:

1. 进程创建的完整流程

  • 用户态入口:CreateProcess 的参数解析和准备工作
  • 系统调用过渡:从 kernel32 到 ntdll 再到内核态的转换
  • 内核态核心:NtCreateProcess 和 PspCreateProcess 的执行逻辑
  • 主线程创建:进程创建后如何创建并启动第一个线程

2. 关键数据结构的创建与初始化

  • EPROCESS 对象的创建和初始化
  • 地址空间的建立(页表创建、内存映射)
  • PEB(进程环境块)的初始化
  • ETHREAD 和 TEB 的创建

3. 映像装入机制

  • PE 文件的解析和验证
  • Section 对象的创建和映射
  • 重定位的处理
  • 导入表的解析和 DLL 加载

4. 用户态初始化

  • LdrInitializeThunk 的作用
  • DLL 的加载顺序和依赖解析
  • TLS(线程本地存储)的初始化
  • DLL_PROCESS_ATTACH 的调用

学习目标

读完本节后,您将能够:

  • 理解从双击可执行文件到程序开始执行的完整过程
  • 掌握进程创建涉及的核心数据结构(EPROCESS、PEB、ETHREAD、TEB)
  • 理解 PE 映像如何被加载到内存并执行
  • 了解 DLL 加载和初始化的机制
  • 理解用户态和内核态在进程创建中的协作

涉及的内核子系统

进程创建是一个复杂的系统工程,涉及多个内核子系统的协作:

子系统职责
Process Manager (PS) 进程/线程对象的创建和管理
Memory Manager (MM) 地址空间创建、内存映射、PEB/TEB 分配
Object Manager (OB) 对象创建、句柄管理、对象目录
Security (SE) 安全令牌分配和权限检查
I/O Manager (IO) 文件打开、Section 对象创建
LDR (Loader) 用户态 DLL 加载和初始化

关键函数调用链

用户态

├─► CreateProcessInternalW() [kernel32]
│ │
│ ├─► CreateFile() // 打开可执行文件
│ ├─► NtCreateSection() // 创建 Section 对象
│ └─► NtCreateProcess() // 进入内核

▼ syscall

内核态

├─► NtCreateProcess() [ntoskrnl]
│ │
│ └─► PspCreateProcess() // 核心创建逻辑
│ │
│ ├─► ObCreateObject() // 创建 EPROCESS
│ ├─► MmCreateProcessAddressSpace() // 创建地址空间
│ ├─► SeAssignSecurity() // 设置安全令牌
│ └─► PspCreateThread() // 创建主线程
│ │
│ └─► KeInitializeThread()

▼ 线程调度

新进程用户态

├─► LdrInitializeThunk() [ntdll]
│ │
│ ├─► LdrpInitializeProcess()
│ ├─► LdrLoadDll() // 加载依赖 DLL
│ ├─► LdrpRunInitializeRoutines() // TLS 初始化
│ └─► LdrpCallInitRoutines() // DLL_PROCESS_ATTACH


└─► 用户程序入口点 (WinMain/main)

通过本节的学习,您将深入理解 Windows 进程创建的完整机制,为后续学习线程调度、内存管理等高级主题打下坚实基础。


5.6.0 框架图

┌──────────────────────────────────────────────────────────────────────────────────┐
│ Windows 进程创建与映像装入完整流程 │
│ │
│ 用户态 (Application) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 用户调用 CreateProcess("notepad.exe", …) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ kernel32!CreateProcessInternalW │ │
│ │ │ │ │
│ │ ├─► 解析命令行参数 │ │
│ │ ├─► 打开可执行文件 │ │
│ │ ├─► 创建 Section 对象 │ │
│ │ ├─► 初始化 RTL_USER_PROCESS_PARAMETERS │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ntdll!NtCreateProcess │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ syscall │
│ │
│ 内核态 (ntoskrnl) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ NtCreateProcess → PspCreateProcess │ │
│ │ │ │ │
│ │ ├─► 创建 EPROCESS 对象 │ │
│ │ ├─► 创建地址空间 │ │
│ │ ├─► 初始化 KPROCESS │ │
│ │ ├─► 设置安全令牌 │ │
│ │ ├─► 初始化句柄表 │ │
│ │ ├─► 映射 Section 到地址空间 │ │
│ │ ├─► 分配 PID │ │
│ │ ├─► 创建 PEB │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ PspCreateThread → 创建主线程 │ │
│ │ │ │ │
│ │ ├─► 创建 ETHREAD 对象 │ │
│ │ ├─► 创建内核栈/用户栈 │ │
│ │ ├─► 设置线程上下文 (EIP = 入口点) │ │
│ │ ├─► 创建 TEB │ │
│ │ ├─► 分配 TID │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 线程加入就绪队列 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ 调度器选择执行 │
│ │
│ 用户态 (新进程) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 线程开始执行 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ntdll!LdrInitializeThunk │ │
│ │ │ │ │
│ │ ├─► 初始化 PEB_LDR_DATA │ │
│ │ ├─► 加载 NTDLL.dll │ │
│ │ ├─► 加载其他依赖 DLL │ │
│ │ ├─► 处理 TLS 初始化 │ │
│ │ ├─► 调用 DLL_PROCESS_ATTACH │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 用户程序入口点 (WinMain/main) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────┘

5.6.0.1 设计意图

核心问题

Windows 如何将一个可执行文件变成一个运行中的进程?整个过程涉及哪些组件?

设计哲学:「分层的进程创建与加载」

想象一下,启动一个程序就像开一家新公司:

  • 申请营业执照(创建进程对象):向系统申请创建进程,获得进程句柄
  • 租用办公场地(创建地址空间):为进程分配独立的虚拟地址空间
  • 装修办公场地(映射可执行文件):将可执行文件映射到地址空间
  • 招聘员工(创建主线程):创建第一个线程作为进程的执行入口
  • 配备办公用品(创建栈和TEB):为线程分配栈空间和环境块
  • 员工到岗(线程开始执行):线程开始运行,执行程序代码
  • 正式营业(调用入口点):程序开始执行用户代码
  • 本节定位

    本节综合前面几节的内容,详细描述从用户调用 CreateProcess 到程序入口点执行的完整流程。读完本节后,读者应当能够:

    • 理解完整的进程创建流程
    • 掌握 PE 文件如何被加载到内存
    • 理解 DLL 加载顺序
    • 理解进程初始化的各个阶段

    5.6.1 用户态的准备工作

    CreateProcess:一站式进程创建服务

    CreateProcess 是用户态创建进程的主要 API,它提供了一站式的进程创建服务。

    CreateProcess 的完整签名:

    BOOL WINAPI CreateProcess(
    LPCWSTR lpApplicationName,
    LPWSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCWSTR lpCurrentDirectory,
    LPSTARTUPINFO lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation);

    参数详解:

    参数说明比喻
    lpApplicationName 可执行文件名 公司名称
    lpCommandLine 命令行参数 公司业务范围
    lpProcessAttributes 进程安全属性 公司注册信息
    lpThreadAttributes 线程安全属性 员工入职信息
    bInheritHandles 是否继承句柄 是否继承母公司资源
    dwCreationFlags 创建标志 特殊要求(如挂起创建)
    lpEnvironment 环境变量 公司运营环境
    lpCurrentDirectory 当前目录 公司办公地址
    lpStartupInfo 启动信息(窗口位置等) 公司初始配置
    lpProcessInformation 返回进程/线程信息 公司注册成功后的信息

    CreateProcess 的内部流程:

    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │ CreateProcess 内部流程 │
    │ │
    │ 阶段 1: 参数解析 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 1. 解析 lpApplicationName 和 lpCommandLine │ │
    │ │ 2. 确定可执行文件路径 │ │
    │ │ 3. 解析环境变量 │ │
    │ │ 4. 验证参数合法性 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 阶段 2: 文件操作 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 1. CreateFile 打开可执行文件 │ │
    │ │ 2. CreateSection 创建 Section 对象 │ │
    │ │ 3. CloseHandle 关闭文件句柄 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 阶段 3: 参数准备 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 1. 初始化 RTL_USER_PROCESS_PARAMETERS │ │
    │ │ – CommandLine │ │
    │ │ – Environment │ │
    │ │ – CurrentDirectory │ │
    │ │ – ImagePathName │ │
    │ │ 2. 初始化 STARTUPINFO 相关数据 │ │
    │ │ 3. 准备线程上下文 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 阶段 4: 系统调用 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 1. NtCreateProcess 创建进程 │ │
    │ │ 2. NtCreateThread 创建主线程 │ │
    │ │ 3. NtResumeThread 恢复线程(如果不是挂起创建) │ │
    │ │ 4. 返回 PROCESS_INFORMATION │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    └──────────────────────────────────────────────────────────────────────────────────┘

    RTL_USER_PROCESS_PARAMETERS:

    typedef struct _RTL_USER_PROCESS_PARAMETERS {
    BYTE Reserved1[16];
    PVOID Reserved2[10];
    UNICODE_STRING ImagePathName; // 可执行文件路径
    UNICODE_STRING CommandLine; // 命令行参数
    PWSTR CurrentDirectory; // 当前目录
    UNICODE_STRING DllPath; // DLL 搜索路径
    PWSTR Environment; // 环境变量块
    // … 其他字段 …
    } RTL_USER_PROCESS_PARAMETERS;


    5.6.2 内核态的进程创建

    PspCreateProcess:进程创建的核心

    PspCreateProcess 是内核态创建进程的核心函数,负责完成进程对象的创建和初始化。

    完整流程回顾:

    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │ PspCreateProcess 完整流程 │
    │ │
    │ 步骤 1-4: 参数校验与对象解析 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 1. 校验 CreateFlags │ │
    │ │ 2. 解析父进程句柄 → 获取 EPROCESS │ │
    │ │ 3. 解析 SectionHandle → 获取 SECTION 对象 │ │
    │ │ 4. 解析 DebugPort/ExceptionPort │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 步骤 5-7: 进程结构创建 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 5. ObCreateObject 创建 EPROCESS 对象 │ │
    │ │ 6. 初始化 EPROCESS 字段 │ │
    │ │ 7. MmCreateProcessAddressSpace 创建地址空间 │ │
    │ │ – 分配页目录 │ │
    │ │ – 设置 DirectoryTableBase │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 步骤 8-11: 进程初始化 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 8. KeInitializeProcess 初始化 KPROCESS │ │
    │ │ 9. PspInitializeProcessSecurity 复制安全令牌 │ │
    │ │ 10. ObInitializeObjectAttributes 初始化句柄表 │ │
    │ │ 11. MmInitializeProcessAddressSpace 映射 Section │ │
    │ │ – 解析 PE Header │ │
    │ │ – 映射各个节区到内存 │ │
    │ │ – 设置 SectionBaseAddress │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 步骤 12-15: 进程注册 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 12. ExCreateHandle 在 PspCidTable 分配 PID │ │
    │ │ 13. InsertTailList 插入 PsActiveProcessHead │ │
    │ │ 14. 设置调度参数 (Priority/Quantum) │ │
    │ │ 15. MmCreatePeb 创建用户态 PEB │ │
    │ │ – 初始化 PEB 字段 │ │
    │ │ – 设置 ProcessParameters │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 步骤 16-18: 完成创建 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 16. ObInsertObject 插入对象目录 │ │
    │ │ 17. 访问检查 │ │
    │ │ 18. 返回进程句柄 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    └──────────────────────────────────────────────────────────────────────────────────┘

    地址空间初始化的细节:

    NTSTATUS MmInitializeProcessAddressSpace(PEPROCESS Process, PSECTION Section) {
    // 1. 获取 PE Header
    PIMAGE_NT_HEADERS NtHeaders = MmGetImageHeader(Section);

    // 2. 计算映像大小和基地址
    PVOID ImageBase = NtHeaders->OptionalHeader.ImageBase;
    SIZE_T ImageSize = NtHeaders->OptionalHeader.SizeOfImage;

    // 3. 检查基地址是否可用
    if (!MmIsAddressAvailable(Process, ImageBase, ImageSize)) {
    // 如果基地址被占用,需要重定位
    ImageBase = MmFindAvailableAddress(Process, ImageSize);
    Process->Flags |= PROCEXP_RELOCATED;
    }

    // 4. 映射各个节区
    PIMAGE_SECTION_HEADER SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);
    for (int i = 0; i < NtHeaders->FileHeader.NumberOfSections; i++) {
    PVOID VirtualAddress = (PVOID)((ULONG_PTR)ImageBase + SectionHeader->VirtualAddress);
    SIZE_T Size = max(SectionHeader->SizeOfRawData, SectionHeader->Misc.VirtualSize);

    // 创建内存区域并映射文件内容
    MmCreateVirtualMemory(Process, &VirtualAddress, Size,
    PAGE_READWRITE, SEC_COMMIT | SEC_IMAGE);

    // 设置正确的保护属性
    ULONG Protect = MmSectionFlagsToProtection(SectionHeader->Characteristics);
    MmProtectVirtualMemory(Process, &VirtualAddress, &Size, Protect);

    SectionHeader++;
    }

    // 5. 设置进程的映像基地址
    Process->SectionBaseAddress = ImageBase;

    return STATUS_SUCCESS;
    }


    5.6.3 主线程的创建

    PspCreateThread:线程创建的核心

    创建完进程后,需要创建主线程才能让进程真正开始执行。

    线程创建流程:

    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │ PspCreateThread 完整流程 │
    │ │
    │ 步骤 1-4: 线程结构创建 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 1. 参数校验 │ │
    │ │ 2. 解析进程句柄 → 获取 EPROCESS │ │
    │ │ 3. ObCreateObject 创建 ETHREAD 对象 │ │
    │ │ 4. 初始化 ETHREAD 字段 │ │
    │ │ – ThreadsProcess = Process │ │
    │ │ – 初始化线程链表 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 步骤 5-8: 栈和上下文初始化 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 5. KeAllocateStack 创建内核栈 │ │
    │ │ 6. KeInitializeThread 初始化 KTHREAD │ │
    │ │ 7. MmCreateThreadStack 创建用户栈 │ │
    │ │ 8. 设置线程上下文 │ │
    │ │ – EIP = ImageBase + AddressOfEntryPoint │ │
    │ │ – ESP = 用户栈顶 │ │
    │ │ – 设置其他寄存器 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 步骤 9-14: 线程注册与完成 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 9. ExCreateHandle 在 PspCidTable 分配 TID │ │
    │ │ 10. InsertTailList 插入 Process->ThreadListHead │ │
    │ │ 11. 设置调度参数 (Priority/Quantum) │ │
    │ │ 12. MmCreateTeb 创建用户态 TEB │ │
    │ │ 13. ObInsertObject 插入对象目录 │ │
    │ │ 14. 返回线程句柄 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 线程加入就绪队列 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ KeInsertQueueApc (如果有初始化 APC) │ │
    │ │ KeReadyThread 将线程加入就绪队列 │ │
    │ │ 调度器可以选择该线程执行 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    └──────────────────────────────────────────────────────────────────────────────────┘

    线程上下文的设置:

    VOID KeContextToKframes(PKTHREAD Thread, PCONTEXT Context) {
    // 将用户态上下文复制到内核栈
    PKEXCEPTION_FRAME ExceptionFrame = (PKEXCEPTION_FRAME)(Thread->InitialStack sizeof(KEXCEPTION_FRAME));
    PKTRAP_FRAME TrapFrame = (PKTRAP_FRAME)(ExceptionFrame sizeof(KTRAP_FRAME));

    // 复制通用寄存器
    TrapFrame->Eax = Context->Eax;
    TrapFrame->Ecx = Context->Ecx;
    TrapFrame->Edx = Context->Edx;
    TrapFrame->Ebx = Context->Ebx;

    // 设置栈指针和指令指针
    TrapFrame->Esp = Context->Esp;
    TrapFrame->Eip = Context->Eip;

    // 设置标志寄存器
    TrapFrame->Eflags = Context->EFlags;

    // 设置段寄存器
    TrapFrame->SegCs = KGDT_R0_CODE;
    TrapFrame->SegDs = KGDT_R3_DATA;
    TrapFrame->SegEs = KGDT_R3_DATA;
    TrapFrame->SegFs = KGDT_R3_TEB;

    // 保存指向帧的指针
    Thread->InitialStack = (PVOID)TrapFrame;
    Thread->TrapFrame = TrapFrame;
    }


    5.6.4 PE 映像的加载与重定位

    核心流程概述:从磁盘读取 EXE 到内存执行的完整过程

    当操作系统加载一个可执行文件时,需要完成以下关键步骤:读取文件头、验证格式、映射节区到内存、处理重定位、解析导入表、加载依赖 DLL。本节详细描述这个过程。

    5.6.4.1 PE 文件头的解析过程

    第一步:读取 DOS Header

    加载器首先读取文件开头的 IMAGE_DOS_HEADER:

    NTSTATUS ReadDosHeader(HANDLE FileHandle, PIMAGE_DOS_HEADER *OutDosHeader) {
    // 1. 读取前 64 字节(DOS Header 的最小大小)
    *OutDosHeader = AllocatePool(NonPagedPool, sizeof(IMAGE_DOS_HEADER));
    DWORD BytesRead;

    // 2. 从文件偏移 0 开始读取
    if (!ReadFile(FileHandle, *OutDosHeader, sizeof(IMAGE_DOS_HEADER), &BytesRead, NULL)) {
    return STATUS_INVALID_IMAGE_FORMAT;
    }

    // 3. 验证 DOS 签名 "MZ"
    if ((*OutDosHeader)->e_magic != IMAGE_DOS_SIGNATURE) {
    return STATUS_INVALID_IMAGE_FORMAT;
    }

    return STATUS_SUCCESS;
    }

    第二步:定位 PE Header

    通过 DOS Header 的 e_lfanew 字段找到 PE Header 的位置:

    NTSTATUS ReadNtHeaders(HANDLE FileHandle, PIMAGE_DOS_HEADER DosHeader, PIMAGE_NT_HEADERS *OutNtHeaders) {
    // 1. e_lfanew 指向 PE Header 在文件中的偏移
    DWORD NtHeadersOffset = DosHeader->e_lfanew;

    // 2. 移动文件指针到 PE Header 位置
    SetFilePointer(FileHandle, NtHeadersOffset, NULL, FILE_BEGIN);

    // 3. 读取 PE Header
    *OutNtHeaders = AllocatePool(NonPagedPool, sizeof(IMAGE_NT_HEADERS));
    DWORD BytesRead;

    if (!ReadFile(FileHandle, *OutNtHeaders, sizeof(IMAGE_NT_HEADERS), &BytesRead, NULL)) {
    return STATUS_INVALID_IMAGE_FORMAT;
    }

    // 4. 验证 PE 签名 "PE\\0\\0"
    if ((*OutNtHeaders)->Signature != IMAGE_NT_SIGNATURE) {
    return STATUS_INVALID_IMAGE_FORMAT;
    }

    return STATUS_SUCCESS;
    }

    第三步:解析 Section Headers

    读取所有节区头信息:

    NTSTATUS ReadSectionHeaders(HANDLE FileHandle, PIMAGE_NT_HEADERS NtHeaders, PIMAGE_SECTION_HEADER *OutSections) {
    // 1. Section Headers 紧跟在 Optional Header 之后
    DWORD SectionsOffset = NtHeaders->e_lfanew +
    sizeof(IMAGE_FILE_HEADER) +
    NtHeaders->FileHeader.SizeOfOptionalHeader;

    // 2. 计算 Section Headers 的总大小
    DWORD NumSections = NtHeaders->FileHeader.NumberOfSections;
    DWORD SectionsSize = NumSections * sizeof(IMAGE_SECTION_HEADER);

    // 3. 读取所有 Section Headers
    *OutSections = AllocatePool(NonPagedPool, SectionsSize);
    SetFilePointer(FileHandle, SectionsOffset, NULL, FILE_BEGIN);

    DWORD BytesRead;
    if (!ReadFile(FileHandle, *OutSections, SectionsSize, &BytesRead, NULL)) {
    return STATUS_INVALID_IMAGE_FORMAT;
    }

    return STATUS_SUCCESS;
    }

    5.6.4.2 内存映射与节区加载

    地址空间预留

    加载器首先在进程地址空间中预留一块区域:

    NTSTATUS ReserveImageSpace(PEPROCESS Process, PIMAGE_NT_HEADERS NtHeaders, PVOID *OutImageBase) {
    // 1. 获取期望的基地址和映像大小
    PVOID DesiredBase = (PVOID)NtHeaders->OptionalHeader.ImageBase;
    SIZE_T ImageSize = NtHeaders->OptionalHeader.SizeOfImage;

    // 2. 尝试在期望地址预留空间
    NTSTATUS Status = MmCreateVirtualMemory(
    Process,
    &DesiredBase,
    ImageSize,
    MEM_RESERVE,
    PAGE_NOACCESS
    );

    if (!NT_SUCCESS(Status)) {
    // 3. 如果期望地址不可用,找一个可用地址
    *OutImageBase = MmFindAvailableAddress(Process, ImageSize);
    Status = MmCreateVirtualMemory(
    Process,
    OutImageBase,
    ImageSize,
    MEM_RESERVE,
    PAGE_NOACCESS
    );
    } else {
    *OutImageBase = DesiredBase;
    }

    return Status;
    }

    节区映射

    逐节区将文件内容映射到内存:

    NTSTATUS MapSections(HANDLE FileHandle, PEPROCESS Process, PIMAGE_NT_HEADERS NtHeaders,
    PIMAGE_SECTION_HEADER Sections, PVOID ImageBase) {
    DWORD NumSections = NtHeaders->FileHeader.NumberOfSections;

    for (DWORD i = 0; i < NumSections; i++) {
    PIMAGE_SECTION_HEADER Section = &Sections[i];

    // 1. 计算节区在内存中的地址
    PVOID SectionBase = (PVOID)((ULONG_PTR)ImageBase + Section->VirtualAddress);

    // 2. 计算需要提交的大小(取 VirtualSize 和 SizeOfRawData 的较大值)
    SIZE_T CommitSize = max(Section->Misc.VirtualSize, Section->SizeOfRawData);

    // 3. 提交物理内存
    NTSTATUS Status = MmCreateVirtualMemory(
    Process,
    &SectionBase,
    CommitSize,
    MEM_COMMIT,
    PAGE_READWRITE
    );

    if (!NT_SUCCESS(Status)) return Status;

    // 4. 如果节区在文件中有数据,读取并复制到内存
    if (Section->SizeOfRawData > 0) {
    DWORD BytesRead;
    SetFilePointer(FileHandle, Section->PointerToRawData, NULL, FILE_BEGIN);
    ReadFile(FileHandle, SectionBase, Section->SizeOfRawData, &BytesRead, NULL);
    }

    // 5. 根据节区属性设置内存保护
    ULONG Protection = SectionCharacteristicsToProtection(Section->Characteristics);
    MmProtectVirtualMemory(Process, &SectionBase, &CommitSize, Protection);
    }

    return STATUS_SUCCESS;
    }

    节区属性转换

    ULONG SectionCharacteristicsToProtection(ULONG Characteristics) {
    ULONG Protection = 0;

    if (Characteristics & IMAGE_SCN_CNT_CODE) {
    Protection |= PAGE_EXECUTE;
    }
    if (Characteristics & IMAGE_SCN_MEM_READ) {
    Protection |= PAGE_READONLY;
    }
    if (Characteristics & IMAGE_SCN_MEM_WRITE) {
    Protection &= ~PAGE_READONLY;
    Protection |= PAGE_READWRITE;
    }
    if (Characteristics & IMAGE_SCN_MEM_EXECUTE) {
    Protection |= PAGE_EXECUTE;
    }

    return Protection;
    }

    5.6.4.3 重定位处理

    为什么需要重定位

    当 PE 文件无法加载到期望的基地址时,需要调整所有硬编码的地址引用:

    NTSTATUS ApplyRelocations(PEPROCESS Process, PIMAGE_NT_HEADERS NtHeaders, PVOID ImageBase) {
    // 1. 检查是否需要重定位
    if (NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == 0) {
    // 没有重定位信息,直接返回
    return STATUS_SUCCESS;
    }

    // 2. 获取重定位表的位置和大小
    PIMAGE_BASE_RELOCATION RelocTable = (PIMAGE_BASE_RELOCATION)(
    (ULONG_PTR)ImageBase +
    NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress
    );
    SIZE_T RelocSize = NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;

    // 3. 计算地址偏移量(Delta)
    ULONG_PTR Delta = (ULONG_PTR)ImageBase NtHeaders->OptionalHeader.ImageBase;

    // 4. 遍历重定位块
    while (RelocTable->VirtualAddress != 0) {
    // 计算该重定位块的大小
    DWORD BlockSize = RelocTable->SizeOfBlock;
    DWORD NumEntries = (BlockSize sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

    // 获取重定位条目数组
    PWORD RelocEntries = (PWORD)(RelocTable + 1);

    // 处理每个重定位条目
    for (DWORD i = 0; i < NumEntries; i++) {
    WORD Entry = RelocEntries[i];

    // 低 12 位是偏移量,高 4 位是类型
    WORD Type = Entry >> 12;
    WORD Offset = Entry & 0x0FFF;

    // 计算需要修改的地址
    PULONG_PTR AddressToFix = (PULONG_PTR)(
    (ULONG_PTR)ImageBase + RelocTable->VirtualAddress + Offset
    );

    // 根据类型进行修复
    switch (Type) {
    case IMAGE_REL_BASED_ABSOLUTE:
    // 不需要修改
    break;
    case IMAGE_REL_BASED_HIGHLOW:
    // 完整的 32 位地址
    *AddressToFix += Delta;
    break;
    case IMAGE_REL_BASED_DIR64:
    // 64 位地址(仅 PE32+)
    *(PULONG64)AddressToFix += (ULONG64)Delta;
    break;
    // 其他类型省略…
    }
    }

    // 移动到下一个重定位块
    RelocTable = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)RelocTable + BlockSize);
    }

    return STATUS_SUCCESS;
    }

    5.6.4.4 导入表解析与 DLL 加载

    导入表结构

    typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
    DWORD Characteristics; // 0 表示结束
    DWORD OriginalFirstThunk; // 指向原始名称表(INT)
    };
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name; // DLL 名称的 RVA
    DWORD FirstThunk; // 指向导入地址表(IAT)
    } IMAGE_IMPORT_DESCRIPTOR;

    导入表解析流程

    NTSTATUS ProcessImportTable(PEPROCESS Process, PIMAGE_NT_HEADERS NtHeaders, PVOID ImageBase) {
    // 1. 获取导入表的位置
    PIMAGE_IMPORT_DESCRIPTOR ImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(
    (ULONG_PTR)ImageBase +
    NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
    );

    // 2. 遍历每个导入描述符
    while (ImportDesc->Name != 0) {
    // 获取 DLL 名称
    PCHAR DllName = (PCHAR)((ULONG_PTR)ImageBase + ImportDesc->Name);

    // 加载 DLL
    HMODULE hModule = LdrLoadDll(NULL, NULL, (PWSTR)DllName);

    if (hModule == NULL) {
    return STATUS_DLL_NOT_FOUND;
    }

    // 填充 IAT(导入地址表)
    PIMAGE_THUNK_DATA OriginalFirstThunk = (PIMAGE_THUNK_DATA)(
    (ULONG_PTR)ImageBase + ImportDesc->OriginalFirstThunk
    );
    PIMAGE_THUNK_DATA FirstThunk = (PIMAGE_THUNK_DATA)(
    (ULONG_PTR)ImageBase + ImportDesc->FirstThunk
    );

    // 遍历导入函数
    while (OriginalFirstThunk->u1.AddressOfData != 0) {
    PVOID FunctionAddress;

    if (IMAGE_SNAP_BY_ORDINAL(OriginalFirstThunk->u1.Ordinal)) {
    // 按序号导入
    WORD Ordinal = IMAGE_ORDINAL(OriginalFirstThunk->u1.Ordinal);
    FunctionAddress = GetProcAddress(hModule, (LPCSTR)Ordinal);
    } else {
    // 按名称导入
    PIMAGE_IMPORT_BY_NAME ImportByName = (PIMAGE_IMPORT_BY_NAME)(
    (ULONG_PTR)ImageBase + OriginalFirstThunk->u1.AddressOfData
    );
    FunctionAddress = GetProcAddress(hModule, (LPCSTR)ImportByName->Name);
    }

    // 将函数地址写入 IAT
    *FirstThunk = (IMAGE_THUNK_DATA)FunctionAddress;

    OriginalFirstThunk++;
    FirstThunk++;
    }

    ImportDesc++;
    }

    return STATUS_SUCCESS;
    }

    5.6.4.5 执行跳转与入口点调用

    设置线程上下文

    线程创建时,内核设置初始上下文,将入口点地址放入 EIP:

    VOID SetThreadEntryPoint(PETHREAD Thread, PIMAGE_NT_HEADERS NtHeaders, PVOID ImageBase) {
    PCONTEXT Context = &Thread->Tcb.Context;

    // 设置指令指针为入口点
    Context->Eip = (ULONG_PTR)ImageBase + NtHeaders->OptionalHeader.AddressOfEntryPoint;

    // 设置用户栈指针
    Context->Esp = (ULONG_PTR)Thread->Teb.Reserved1[1]; // TEB 中的栈顶指针

    // 设置标志寄存器(启用中断)
    Context->EFlags = 0x202; // IF = 1

    // 设置段寄存器
    Context->SegCs = KGDT_R3_CODE;
    Context->SegDs = KGDT_R3_DATA;
    Context->SegEs = KGDT_R3_DATA;
    Context->SegFs = KGDT_R3_TEB;

    // 将上下文复制到内核栈
    KeContextToKframes(Thread, Context);
    }

    执行流程

    ┌──────────────────────────────────────────────────────────────────────────┐
    │ EXE 加载与执行流程 │
    ├──────────────────────────────────────────────────────────────────────────┤
    │ │
    │ 1. 打开文件 │
    │ │ │
    │ ▼ │
    │ 2. 读取 DOS Header (offset 0) │
    │ │ – 验证 "MZ" 签名 │
    │ │ – 获取 e_lfanew │
    │ ▼ │
    │ 3. 读取 PE Header (offset = e_lfanew) │
    │ │ – 验证 "PE\\0\\0" 签名 │
    │ │ – 获取 FileHeader / OptionalHeader │
    │ ▼ │
    │ 4. 读取 Section Headers │
    │ │ – 遍历每个节区 │
    │ │ – 获取 VirtualAddress, SizeOfRawData, Characteristics │
    │ ▼ │
    │ 5. 预留地址空间 │
    │ │ – 尝试 ImageBase │
    │ │ – 如果失败,寻找可用地址 │
    │ ▼ │
    │ 6. 映射节区到内存 │
    │ │ – 提交物理页面 │
    │ │ – 读取文件内容 │
    │ │ – 设置保护属性 │
    │ ▼ │
    │ 7. 应用重定位(如果需要) │
    │ │ – 计算 Delta = 实际基地址 – 期望基地址 │
    │ │ – 遍历重定位表 │
    │ │ – 修改所有绝对地址引用 │
    │ ▼ │
    │ 8. 解析导入表 │
    │ │ – 遍历每个 IMAGE_IMPORT_DESCRIPTOR │
    │ │ – 加载依赖 DLL │
    │ │ – 填充 IAT(导入地址表) │
    │ ▼ │
    │ 9. 设置线程上下文 │
    │ │ – EIP = ImageBase + AddressOfEntryPoint │
    │ │ – ESP = 用户栈顶 │
    │ ▼ │
    │ 10. 线程调度执行 │
    │ │ – 调度器选择线程 │
    │ │ – 执行从 EIP 开始 │
    │ ▼ │
    │ 11. 用户态初始化 (LdrInitializeThunk) │
    │ │ – 加载剩余 DLL │
    │ │ – 调用 DLL_PROCESS_ATTACH │
    │ │ – 初始化 TLS │
    │ ▼ │
    │ 12. 调用入口点 │
    │ │ – WinMainCRTStartup / mainCRTStartup │
    │ │ – C 运行时初始化 │
    │ │ – 调用 WinMain / main │
    │ ▼ │
    │ 13. 程序执行结束 │
    │ │ – 返回值传递给 ExitProcess │
    │ │ – 清理资源 │
    │ ▼ │
    │ 14. 进程退出 │
    │ │
    └──────────────────────────────────────────────────────────────────────────┘

    5.6.4.6 DLL 调用与返回机制

    函数调用过程

    // 用户代码调用 DLL 函数
    int result = MessageBoxA(hwnd, "Hello", "Title", MB_OK);

    // 实际执行流程:
    // 1. 代码中的调用指令
    // call dword ptr [IAT_MessageBoxA]
    //
    // 2. IAT 中存储的是实际函数地址
    // IAT_MessageBoxA = 0x77D50000 (MessageBoxA 在 user32.dll 中的地址)
    //
    // 3. 执行 call 指令,跳转到 MessageBoxA
    // – 将返回地址压栈
    // – 跳转到 user32.dll 中的 MessageBoxA 入口
    //
    // 4. MessageBoxA 执行完毕后
    // – 清理栈帧
    // – 执行 ret 指令,弹出返回地址
    // – 返回到用户代码中 call 指令的下一条指令

    调用约定

    调用约定参数传递栈清理返回值
    __cdecl 栈(从右到左) 调用者清理 EAX
    __stdcall 栈(从右到左) 被调用者清理 EAX
    __fastcall ECX/EDX + 栈 被调用者清理 EAX
    __thiscall ECX(this) + 栈 被调用者清理 EAX

    返回机制

    // 典型的函数返回流程
    NTSTATUS SomeFunction(...) {
    // 函数执行…

    // 返回值放入 EAX
    return STATUS_SUCCESS;
    // 编译器生成:mov eax, 0
    // retn X (X = 参数大小,由被调用者清理)
    }

    // ret 指令执行过程:
    // 1. 从栈顶弹出返回地址
    // 2. 将指令指针设置为返回地址
    // 3. 如果是 retn X,还会调整 ESP(跳过参数)
    // 4. 继续执行调用者的代码

    源码位置:

    • [ntimage.h](file:///d:/reactos/sdk/include/ddk/ntimage.h) — PE 结构定义
    • [mm/section.c](file:///d:/reactos/ntoskrnl/mm/section.c) — Section 对象创建
    • [mm/memory.c](file:///d:/reactos/ntoskrnl/mm/memory.c) — 内存映射
    • [ldr/ldrinit.c](file:///d:/reactos/dll/ntdll/ldr/ldrinit.c) — 用户态加载器初始化

    5.6.5 用户态的初始化

    LdrInitializeThunk:用户态初始化入口

    当线程第一次在用户态执行时,会从 LdrInitializeThunk 开始,完成进程的用户态初始化。

    用户态初始化流程:

    ┌──────────────────────────────────────────────────────────────────────────────────┐
    │ 用户态初始化流程 │
    │ │
    │ 阶段 1: LDR 初始化 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 1. LdrInitializeThunk │ │
    │ │ – 设置 PEB->Ldr 指向 PEB_LDR_DATA │ │
    │ │ – 初始化 LDR 链表 │ │
    │ │ 2. LdrpInitialize │ │
    │ │ – 初始化 NTDLL 的加载信息 │ │
    │ │ – 设置加载顺序 │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 阶段 2: DLL 加载 │
    │ ┌─────────────────────────────────────────────────────────────────────┐ │
    │ │ 1. LdrLoadDll 加载依赖 DLL │ │
    │ │ – 遍历导入表 │ │
    │ │ – 对每个 DLL 调用 LoadLibrary │ │
    │ │ – 解析导入函数地址 │ │
    │ │ 2. LdrpMapDllIntoMemory │ │
    │ │ – 映射 DLL 到地址空间 │ │
    │ │ – 处理重定位 │ │
    │ │ – 填充 IAT │ │
    │ └─────────────────────────────────────────────────────────────────────┘ │
    │ │ │
    │ ▼ │
    │ 阶段 3: 初始化回调 │
    │ ┌─────────────────────────────────────────────────────────────┐ │
    │ │ 1. LdrpRunInitializeRoutines │ │ │
    │ │ – 调用每个 DLL 的 DLL_PROCESS_ATTACH 回调 │ │ │
    │ │ 2. LdrpInitializeTls │ │ │
    │ │ – 初始化 TLS 槽 │ │ │
    │ │ – 调用 TLS 初始化回调 │ │ │
    │ └─────────────────────────────────────────────────────────────┘ │ │
    │ │ │
    │ ▼ │
    │ 阶段 4: 入口点执行 │
    │ ┌─────────────────────────────────────────────────────────────┐ │
    │ │ 1. LdrpCallInitRoutine │ │ │
    │ │ – 调用程序的入口点 │ │ │
    │ │ – WinMainCRTStartup / mainCRTStartup │ │ │
    │ │ 2. C 运行时初始化 │ │ │
    │ │ – 初始化堆 │ │ │
    │ │ – 初始化全局变量 │ │ │
    │ │ – 设置命令行参数 │ │ │
    │ │ 3. 调用用户的 WinMain / main │ │ │
    │ └─────────────────────────────────────────────────────────────┘ │ │
    └──────────────────────────────────────────────────────────────────────────────────┘

    DLL 加载顺序:

    顺序DLL说明
    1 ntdll.dll 内核 API 的包装层,最先加载
    2 kernel32.dll 基础 Win32 API
    3 user32.dll 用户界面 API(如果是 GUI 程序)
    4 gdi32.dll 图形设备接口(如果需要)
    5 其他依赖 DLL 按依赖关系加载

    PEB_LDR_DATA 结构:

    typedef struct _PEB_LDR_DATA {
    ULONG Length;
    BOOLEAN Initialized;
    PVOID SsHandle;
    LIST_ENTRY InLoadOrderModuleList; // 按加载顺序排列
    LIST_ENTRY InMemoryOrderModuleList; // 按内存位置排列
    LIST_ENTRY InInitializationOrderModuleList; // 按初始化顺序排列
    } PEB_LDR_DATA;


    5.6.5 进程创建的完整时序图

    ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
    │ 用户程序 │ │ kernel32 │ │ ntdll │ │ ntoskrnl │
    └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
    │ │ │ │
    │ CreateProcess() │ │ │
    │────────────────────>│ │ │
    │ │ │ │
    │ │ 解析参数/打开文件 │ │
    │ │ │ │
    │ │ NtCreateProcess() │ │
    │ │────────────────────>│ │
    │ │ │ syscall │
    │ │ │────────────────────>│
    │ │ │ │
    │ │ │ │ PspCreateProcess
    │ │ │ │ 创建EPROCESS
    │ │ │ │ 创建地址空间
    │ │ │ │ 映射Section
    │ │ │ │ 创建PEB
    │ │ │ STATUS_SUCCESS │
    │ │ │<────────────────────│
    │ │ 返回进程句柄 │ │
    │ │<────────────────────│ │
    │ │ │ │
    │ │ NtCreateThread() │ │
    │ │────────────────────>│ │
    │ │ │ syscall │
    │ │ │────────────────────>│
    │ │ │ │
    │ │ │ │ PspCreateThread
    │ │ │ │ 创建ETHREAD
    │ │ │ │ 创建栈
    │ │ │ │ 设置上下文
    │ │ │ │ 创建TEB
    │ │ │ │ 加入就绪队列
    │ │ │ STATUS_SUCCESS │
    │ │ │<────────────────────│
    │ │ 返回线程句柄 │ │
    │ │<────────────────────│ │
    │ │ │ │
    │ │ NtResumeThread() │ │
    │ │────────────────────>│ │
    │ │ │ │
    │ PROCESS_INFORMATION│ │ │
    │<────────────────────│ │ │
    │ │ │ │
    │ │ │ │ 线程调度执行
    │ │ │ │
    │ │ │ LdrInitializeThunk│
    │ │ │<────────────────────│
    │ │ │ │
    │ │ │ 加载DLL/初始化 │
    │ │ │ │
    │ │ │ 调用入口点 │
    │ │ │────────────────────>│
    │ │ │ │ 用户程序开始执行


    5.6.6 关键设计决策

    5.6.6.1 为什么需要两个系统调用(NtCreateProcess + NtCreateThread)?

    答案:分离关注点和灵活性。

    • 分离关注点:进程创建和线程创建是两个独立的操作
    • 灵活性:允许创建没有线程的进程(如空壳进程)
    • 扩展性:可以在一个进程中创建多个线程
    • 安全性:分开权限检查,进程创建和线程创建需要不同的权限

    5.6.6.2 为什么用户态需要 LDR 初始化?

    答案:内核不了解用户态的 DLL 依赖关系。

    • 内核的职责:创建进程和线程,映射可执行文件
    • 用户态的职责:解析导入表,加载依赖 DLL
    • 模块化:将复杂的用户态初始化放在用户态执行
    • 灵活性:允许用户态自定义加载行为(如延迟加载)

    5.6.6.3 为什么入口点不是直接的 WinMain?

    答案:需要 C 运行时初始化。

    • C 运行时初始化:需要初始化堆、全局变量等
    • 参数处理:解析命令行参数
    • 异常处理:设置结构化异常处理链
    • 国际化:处理 Unicode/ANSI 转换

    5.6.7 小结

    5.6.7.1 关键知识点

    主题关键点
    CreateProcess 用户态创建进程的 API
    PspCreateProcess 内核态创建进程的核心函数
    PspCreateThread 内核态创建线程的核心函数
    LdrInitializeThunk 用户态初始化入口
    PEB_LDR_DATA 模块加载信息
    DLL_PROCESS_ATTACH DLL 初始化回调

    5.6.7.2 设计原则

  • 分层设计:用户态准备 → 内核态创建 → 用户态初始化
  • 分离关注点:进程创建和线程创建分开
  • 模块化:每个组件负责自己的部分
  • 灵活性:支持各种创建选项和标志
  • 5.6.7.3 常见陷阱

  • DLL 依赖缺失:程序无法启动,提示找不到 DLL
  • 基地址冲突:DLL 无法加载到首选基地址
  • 权限不足:创建进程或线程需要足够的权限
  • 栈溢出:线程栈大小不足
  • 5.6.7.4 后续学习路径

    • 进程终止流程
    • DLL 延迟加载
    • 进程调试机制
    • 进程间通信

    5.6.8 用户双击程序的完整调用流程

    当用户在资源管理器中双击一个可执行文件(如 notepad.exe)时,系统会经历以下完整的调用流程:

    ┌─────────────────────────────────────────────────────────────────────────────────────┐
    │ 用户双击 EXE 文件的完整调用流程图 │
    ├─────────────────────────────────────────────────────────────────────────────────────┤
    │ │
    │ 用户操作 │
    │ │ │
    │ ▼ │
    │ ┌──────────────┐ │
    │ │ Explorer.exe│ [资源管理器检测到双击事件] │
    │ │ (Shell32) │ │
    │ └──────┬───────┘ │
    │ │ │
    │ │ 解析文件路径,确认是可执行文件 │
    │ ▼ │
    │ ┌──────────────┐ │
    │ │ ShellExecute │ [shell32.dll] │
    │ │ ExW() │ – 检查文件类型关联 │
    │ └──────┬───────┘ – 获取可执行文件路径 │
    │ │ │
    │ │ 调用 CreateProcess 系列 API │
    │ ▼ │
    │ ┌──────────────┐ │
    │ │CreateProcess │ [kernel32.dll] │
    │ │ InternalW() │ │
    │ └──────┬───────┘ │
    │ │ │
    │ ├─► CreateFile() // 打开可执行文件 │
    │ │ │ │
    │ │ ▼ │
    │ │ ┌──────────────┐ │
    │ │ │ NtCreateFile │ [ntdll.dll] → syscall → [ntoskrnl] │
    │ │ └──────────────┘ │
    │ │ │
    │ ├─► NtCreateSection() // 创建 Section 对象 │
    │ │ │ │
    │ │ ▼ │
    │ │ ┌──────────────┐ │
    │ │ │ NtCreateSection│ [ntdll.dll] → syscall → [ntoskrnl/ob] │
    │ │ └──────────────┘ │
    │ │ │
    │ └─► NtCreateProcess() // 创建进程 │
    │ │ │
    │ ▼ syscall │
    │ ┌──────────────┐ │
    │ │ NtCreateProc │ [ntoskrnl/ps] │
    │ │ ess() │ │
    │ └──────┬───────┘ │
    │ │ │
    │ ▼ │
    │ ┌──────────────┐ │
    │ │ PspCreatePr │ 内核态进程创建核心 │
    │ │ ocess() │ – 创建 EPROCESS 对象 │
    │ └──────┬───────┘ – 创建地址空间 │
    │ │ – 映射 PE 映像 │
    │ ▼ – 创建 PEB │
    │ ┌──────────────┐ │
    │ │ NtCreateThr │ [ntoskrnl/ps] │
    │ │ ead() │ │
    │ └──────┬───────┘ │
    │ │ │
    │ ▼ │
    │ ┌──────────────┐ │
    │ │ PspCreateTh │ 内核态线程创建核心 │
    │ │ read() │ – 创建 ETHREAD 对象 │
    │ └──────┬───────┘ – 分配内核栈/用户栈 │
    │ │ – 设置线程上下文 (EIP=入口点) │
    │ │ – 创建 TEB │
    │ ▼ – 加入就绪队列 │
    │ ┌──────────────┐ │
    │ │ KeReadyThre │ [ntoskrnl/ke] │
    │ │ ad() │ – 线程加入调度队列 │
    │ └──────┬───────┘ │
    │ │ │
    │ ▼ 线程调度 │
    │ ┌──────────────┐ │
    │ │ LdrInitializ │ [ntdll.dll] 用户态初始化 │
    │ │ eThunk() │ – 初始化 LDR │
    │ └──────┬───────┘ – 加载依赖 DLL │
    │ │ – 调用 DLL_PROCESS_ATTACH │
    │ ▼ – 初始化 TLS │
    │ ┌──────────────┐ │
    │ │ WinMainCRTS │ [msvcrt.dll] C 运行时初始化 │
    │ │ tartup() │ – 初始化堆 │
    │ └──────┬───────┘ – 初始化全局变量 │
    │ │ – 设置命令行参数 │
    │ ▼ │
    │ ┌──────────────┐ │
    │ │ WinMain() │ 用户程序入口点 │
    │ │ / main() │ – 程序开始执行 │
    │ └──────────────┘ │
    │ │
    └─────────────────────────────────────────────────────────────────────────────────────┘

    5.6.8.1 详细阶段说明

    阶段 1:用户交互层

    • 用户双击文件图标
    • Explorer.exe 检测到鼠标事件
    • Shell32.dll 的 ShellExecuteExW 处理文件关联

    阶段 2:用户态准备层

    • kernel32.dll 的 CreateProcessInternalW
    • 打开可执行文件(CreateFile)
    • 创建 Section 对象(NtCreateSection)

    阶段 3:内核态创建层

    • NtCreateProcess → PspCreateProcess
      • 创建 EPROCESS 对象
      • 创建进程地址空间
      • 映射 PE 映像到内存
      • 创建 PEB
    • NtCreateThread → PspCreateThread
      • 创建 ETHREAD 对象
      • 分配内核栈和用户栈
      • 设置线程上下文
      • 创建 TEB
      • 将线程加入就绪队列

    阶段 4:用户态初始化层

    • LdrInitializeThunk(ntdll.dll)
      • 初始化 LDR 数据结构
      • 解析导入表,加载依赖 DLL
      • 调用各 DLL 的 DLL_PROCESS_ATTACH
      • 初始化 TLS 槽

    阶段 5:程序执行层

    • WinMainCRTStartup / mainCRTStartup(C 运行时)
      • 初始化堆(HeapInitialize)
      • 初始化全局变量
      • 设置命令行参数
    • 用户程序入口点(WinMain / main)
      • 程序正式开始执行

    5.6.8.2 关键数据结构传递

    阶段关键数据结构说明
    文件打开 HANDLE 可执行文件句柄
    Section 创建 SECTION_OBJECT 内存映射对象
    进程创建 EPROCESS 进程内核对象
    地址空间 PML4/EPT 页表结构
    PEB 创建 PEB 进程环境块
    线程创建 ETHREAD 线程内核对象
    TEB 创建 TEB 线程环境块
    上下文设置 CONTEXT 线程初始寄存器状态

    源码位置:[dll/kernel32/client/proc.c](file:///d:/reactos/dll/kernel32/client/proc.c)、[ntoskrnl/ps/process.c](file:///d:/reactos/ntoskrnl/ps/process.c)

    赞(0)
    未经允许不得转载:171主机测评 » Reactos 第 5 章 进程与线程 — 5.6 Windows 的进程创建和映像装入
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址