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 如何将一个可执行文件变成一个运行中的进程?整个过程涉及哪些组件?

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

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

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

本节定位

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

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

5.6.1 用户态的准备工作

CreateProcess:一站式进程创建服务

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

CreateProcess 的完整签名:

c 复制代码
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:

c 复制代码
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. 返回进程句柄                                                   │       │
│   └─────────────────────────────────────────────────────────────────────┘       │
└──────────────────────────────────────────────────────────────────────────────────┘

地址空间初始化的细节:

c 复制代码
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 将线程加入就绪队列                                   │       │
│   │ 调度器可以选择该线程执行                                           │       │
│   └─────────────────────────────────────────────────────────────────────┘       │
└──────────────────────────────────────────────────────────────────────────────────┘

线程上下文的设置:

c 复制代码
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:

c 复制代码
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 的位置:

c 复制代码
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

读取所有节区头信息:

c 复制代码
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 内存映射与节区加载

地址空间预留

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

c 复制代码
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;
}

节区映射

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

c 复制代码
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;
}

节区属性转换

c 复制代码
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 文件无法加载到期望的基地址时,需要调整所有硬编码的地址引用:

c 复制代码
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 加载

导入表结构

c 复制代码
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;

导入表解析流程

c 复制代码
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:

c 复制代码
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 调用与返回机制

函数调用过程

c 复制代码
// 用户代码调用 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

返回机制

c 复制代码
// 典型的函数返回流程
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 结构:

c 复制代码
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 设计原则

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

5.6.7.3 常见陷阱

  1. DLL 依赖缺失:程序无法启动,提示找不到 DLL
  2. 基地址冲突:DLL 无法加载到首选基地址
  3. 权限不足:创建进程或线程需要足够的权限
  4. 栈溢出:线程栈大小不足

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)

相关推荐
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
rockmelodies3 天前
# Windows Server2008 R2 Standard(SP1镜像U盘)重置本地管理员密码【完整详细步骤】
windows·密码破解
kakakahahahaha3 天前
Windows C盘空间不足的安全清理与迁移流程
windows·电脑·笔记本电脑·软件需求·c盘清理
做咩啊~3 天前
远程连接提示:身份验证错误,要求的函数不受支持
windows
百事牛科技3 天前
PPT只读怎么取消?四种情况分别处理
windows·powerpoint
熊明才4 天前
WSL2 网络突然瘫痪(Network is unreachable / 没有 eth0)最短修复指南(2026年9月20日亲测可用)
linux·windows·wsl2
xbzb4 天前
Windows 启动故障修复知识整理:蓝屏 / 无限重启 / 盘符 X / 无 PE 盘
windows
我命由我123454 天前
Mac 操作系统 - 一些使用记录
运维·windows·学习·系统安全·运维开发·mac·学习方法
kakakahahahaha5 天前
Windows Steam 游戏存档在哪里?定位和恢复排查流程
windows·其他·电脑·笔记本电脑·内容运营·软件需求
Xy-unu5 天前
Windows 开启外接显示器 HDR 后外接显示器无信号、无法扩展模式故障记录
windows