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

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

概述

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

本节内容概览

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

1. 进程创建的完整流程

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

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

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

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)

相关推荐
泡^泡2 小时前
Python数据类型与运算符
开发语言·windows·python
caimouse13 小时前
Reactos 第 4 章 对象管理 — 4.5 几个常用的内核函数
c语言·windows·架构
caimouse14 小时前
Reactos 第 4 章 对象管理 — 4.3 句柄和句柄表(Handle & Handle Table)
c语言·windows·架构
Chase_______15 小时前
【Java基础 | 15】集合框架(中):Set、HashSet、TreeSet 与哈希表
java·windows·散列表
caimouse15 小时前
Windows NT 内核架构(主通用模型)流 NT 5.x/10+
windows·架构
caimouse16 小时前
Reactos 第 3 章 内存管理 — 【中篇】Hyperspace、系统空间、API 与异常
c语言·开发语言·windows·架构
caimouse17 小时前
Reactos 第 4 章 对象管理 — 4.1 对象与对象目录
服务器·c语言·开发语言·windows·架构
影寂ldy19 小时前
C# 索引器(Indexer)超全笔记【基础 + 重载 + 实战练习】
windows·microsoft
caimouse1 天前
Reactos 第 4 章 对象管理 — 4.2 对象类型(Object Type)
c语言·windows·架构