• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2026-03-29 16:44 Aet 隐藏边栏 |   抢沙发  1 
文章评分 1 次,平均分 5.0

进程地址空间

概述

  1. Windows 进程地址空间和 Linux 一样采用虚拟地址空间,每个进程有独立的虚拟地址空间,由 Windows 内核和硬件 MMU 联合管理
  2. 核心区别在于:
    1. Windows 使用 PEPortable Executable)格式代替 ELF
    2. 使用 HeapAlloc / VirtualAlloc 代替 ptmalloc / brk,且 32 位和 64 位的地址空间布局差异显著

32-Windows4GB 总虚拟地址空间)

  1. 开启 /3GB 启动参数 + /LARGEADDRESSAWARE 链接选项后
    1. 用户空间可扩至 3GB0x00000000 ~ 0xBFFFFFFF),内核压缩到 1GB
区域 地址范围 大小 说明
NULL 禁区 0x00000000~0x0000FFFF 64KB 不可访问,捕捉空指针解引用
用户空间 0x00010000~0x7FFEFFFF 2GB 用户程序可用
64KB 隔离带 0x7FFF0000~0x7FFFFFFF 64KB 用户/内核之间缓冲区
内核空间 0x80000000~0xFFFFFFFF 2GB Ring 0,用户态不可访问

64-Windows(理论 256TB 可用虚拟地址空间)

  1. 64-Windows 用户空间大小(128TB)和 64-Linux 相同,都源于 x86-64 CPU48位虚拟地址限制
区域 地址范围 大小 说明
NULL 禁区 0x0000000000000000~0x000000000000FFFF 64KB 不可访问
用户空间 0x0000000000010000~0x00007FFFFFFFFFFF 128TB 用户程序可用
规范地址空洞 0x0000800000000000~0xFFFF7FFFFFFFFFFF 不可访问 x86-64 硬件限制,地址必须符合规范形式
内核空间 0xFFFF800000000000~0xFFFFFFFFFFFFFFFF 128TB Ring 0,用户态不可访问

内核空间

  1. 内核空间是什么
    1. Windows 内核空间是进程虚拟地址空间的高地址区域,用户态(Ring 3)无法直接访问。与 Linux 相同,隔离机制依赖页表中的 Supervisor 位:
    2. 内核页表项 Supervisor 位为 1,用户态访问触发访问违规(EXCEPTION_ACCESS_VIOLATION),最终表现为应用崩溃而非系统崩溃
  2. 里面住着什么
    1. Windows 内核(ntoskrnl.exe)的代码和数据:进程管理、内存管理器(Mm)、对象管理器、I/O 管理器
    2. HALHardware Abstraction Layerhal.dll):硬件抽象层,屏蔽不同硬件差异
    3. 内核驱动(*.sys):网卡、磁盘、文件系统驱动加载于此
    4. 每线程内核栈:线程进入内核态时使用,默认大小 12KB32位)/ 24KB64位),大幅高于 Linux8KB/16KB
    5. 非分页池(Non-Paged Pool):必须常驻物理内存的内核数据,不可换出到页文件
    6. 分页池(Paged Pool):可换出到页文件的内核数据,类似 Linuxvmalloc 区域
    7. 系统 PTE 区域:内核动态映射用
  3. 进入内核的唯一合法途径
    1. 用户代码不能直接跳入内核,只能通过三条受控的门:
    2. 系统调用(syscall 指令,Win32 层通过 ntdll.dll 封装,最终调用 NtXxx 系列函数)
    3. 硬件中断(IRQ,网卡、键盘、定时器等设备触发)
    4. 异常(缺页、访问违规、除零等 CPU 检测到的错误)
    5. Windows 特有:
    6. 用户态通过 ntdll.dllsyscall 进入内核,中间多一层 Win32 APIkernel32.dll)→ Native APIntdll.dll)的包装
  4. 内核崩溃 vs 用户崩溃
用户程序崩溃 内核/驱动崩溃
表现 弹出"程序已停止工作",WER 收集崩溃转储 蓝屏死机(BSOD),系统完全停机
影响范围 只杀死当前进程 所有进程死亡,系统重启
转储文件 AppCrashView 可查 %SystemRoot%\MEMORY.DMP

  1. 栈和 Linux 机制相同,由 CPUrsp/rbp 寄存器管理,LIFO 结构,分配一条 sub rsp, N 指令完成,速度极快
  2. 关键区别:栈大小
    1. Linux 默认栈 8MB,进程启动时由 OS 保留
    2. Windows 默认栈 1MB(由 PE 头 SizeOfStackReserve 字段指定),可通过链接器 /STACK 选项或 CreateThread 参数修改
    3. Windows 栈采用按需提交机制:1MB 是"保留"大小,实际只提交少量页,通过 Guard Page 机制按需扩展;用完才触发 EXCEPTION_STACK_OVERFLOW
  3. Windows 特有的 Guard Page 机制:
    1. 栈底部维护一个 Guard Page
    2. 每次栈增长触碰 Guard Page 时,OS 自动将其提交为普通页,并在更低地址设置新的 Guard Page
    3. Guard Page 被耗尽且无法扩展时,抛出 STATUS_STACK_OVERFLOW 异常
  4. 同样的陷阱:
    1. 返回局部变量的地址,函数返回后栈帧逻辑销毁(rsp 移回),指针变成悬空指针

  1. Windows 堆由 堆管理器(ntdll.dll 中的 RtlHeap)管理,不存在 Linuxbrk/sbrk 概念,全部通过 VirtualAllocOS 申请内存
  2. Windows 内存的三种状态(Linux 没有这个区分):
    1. Free:未分配,不占物理内存,也不占页文件
    2. Reserved:已预留虚拟地址范围,但不占物理内存(VirtualAlloc + MEM_RESERVE
    3. Committed:已提交,建立了物理内存或页文件的对应关系(VirtualAlloc + MEM_COMMIT
  3. 分配路径:

  1. 对应关系
Linux Windows
malloc/free HeapAlloc/HeapFree
new/delete new / delete(内部调用 HeapAlloc
mmap(MAP_ANON) VirtualAlloc(MEM_COMMIT)
munmap VirtualFree(MEM_RELEASE)
brk/sbrk 无对应,Windows 不使用
  1. 同样的四种经典问题:
    1. 内存泄漏:new 没有对应 delete
    2. double freedelete 同一块两次
    3. 悬空指针:delete 后继续访问
    4. 堆溢出:越界写破坏相邻块头部,下次 HeapAlloc/HeapFree 时崩溃

bss

  1. Linux 相同:存放未初始化(或初始值为 0)的全局变量和静态变量
  2. Windows PE 格式的处理方式略有不同:
    1. PE.bss 节通常被 合并进 .data 节,通过节头的 SizeOfRawData(文件中实际大小)和 VirtualSize(内存中大小)之差来表示未初始化部分
    2. VirtualSize > SizeOfRawData 的差额由 PE 加载器自动填零,效果和 Linux.bss 完全相同
    3. 也可能单独存在 .bss 节,SizeOfRawData = 0,节头只记录 VirtualSize
  3. 同样的结论:
    1. int arr[1000000] 不会让可执行文件变大 4MB,运行时由加载器或 OS 填零

data

  1. 存放有初始值的全局变量和静态变量,和 Linux 完全一致
  2. 初始值存在 PE 文件中,加载时由 PE 加载器原样复制到内存
  3. 页权限:可读 + 可写,不可执行

  1. Linux 的唯一区别:
    1. PE 节名通常就叫 .data,与 Linux ELF 相同
    2. Windows 不区分 .data.bss 节的写保护,两者都是 RW 权限

rdata

  1. 等价于 Linux.rodata
    1. Windows PE 格式中只读数据存放在 .rdata 节(Read-only DATA),Linux ELF 中叫 .rodata,概念完全相同
  2. OS 在页表层面设置写保护,写入触发 EXCEPTION_ACCESS_VIOLATIONWindows)vs SIGSEGVLinux
  3. 主要内容:
    1. 字符串字面量:const char* p = "hello""hello".rdatap 本身在 .datap[0] = 'H' 直接崩溃
    2. vtable:和 Linux 相同,所有同类对象共享,存放在 .rdata 防止被篡改
    3. const 全局变量
    4. switch 跳转表
    5. 导入表(Import Address Table,IAT)也可能存于此段
  4. Windows 特有内容
    1. 调试目录(Debug Directory):指向 .pdb 调试符号文件路径
    2. 异常表(Exception Directory):__try / __except 结构化异常处理(SEH)的 unwind 信息,x64 下存放 RUNTIME_FUNCTION 数组

text

  1. 存放编译后的机器指令,和 Linux 完全相同,权限"可读 + 可执行,不可写"
  2. 主要内容:
    1. 每个函数的机器码顺序排列
    2. 普通函数调用:编译器将目标地址硬编码进 call 指令(或通过 IAT 间接调用 DLL 函数)
    3. 虚函数调用:和 Linux 相同,通过 vptrvtable.rdata)→ 函数地址(.text),多了两次内存读取
    4. 纯虚函数槽位指向 _purecall()MSVC 运行时),调用即 abort(),等价于 Linux__cxa_pure_virtual
  3. Windows 特有:DLL 调用的间接跳转
    1. 调用 MessageBoxADLL 函数时,.text 中生成 call [IAT_slot]
    2. IAT_slot.rdata.idata 中,存放 DLL 函数真实地址
    3. DLL 加载时由加载器填入实际地址,运行时通过这个间接表调用,这就是 Windows DLL 调用比直接调用多一次内存读取的原因

可执行文件(PE 格式)

概述

  1. Windows 使用 PE(Portable Executable) 格式,对应 LinuxELF 格式
  2. PE 文件包括 .exe.dll.sys(驱动)等

图示

PE Header

概述

  1. PE Header 是可执行文件最开头的元数据区域,是整个文件的"目录",Windows 加载器(ntdll!LdrLoadDll)靠它完成加载
  2. ELF header 的对应关系:
ELF PE 作用
Magic 7F 45 4C 46 Magic 4D 5A(MZ)+ 50 45 00 00PE 文件类型识别
e_machine MachineCOFF Header 目标架构
e_entry AddressOfEntryPointOptional Header 程序入口
e_phoff 节头表中 LOAD 段信息 加载信息
e_shoff 节头表偏移(PointerToSectionHeaders 节描述
ELF Class32/64位) Magic[4] = PE32 / PE32+ 位宽

DOS Header(历史遗留,20字节关键字段)

  1. OS 读取前 2 字节验证 MZ 魔数,然后跳到 e_lfanew 指向的位置读取 PE 签名

COFF Header(20字节)

字段 大小 含义
Machine 2字节 0x8664 = x86-64,0x014C = x86-32,0xAA64 = ARM64
NumberOfSections 2字节 节的数量
TimeDateStamp 4字节 编译时间戳(Unix 时间)
SizeOfOptionalHeader 2字节 Optional Header 大小
Characteristics 2字节 文件属性标志(是否 DLL、是否可重定位等)

Optional Header(最重要的导航字段)

字段 含义
Magic 0x010B = PE3232位),0x020B = PE32+64位)
AddressOfEntryPoint 程序入口点 RVA(相对虚拟地址),指向 mainCRTStartup
ImageBase 加载基址,默认 .exe = 0x400000.dll = 0x10000000ASLR 开启后运行时随机化
SectionAlignment 内存中节的对齐粒度,通常 0x10004KB,一页)
FileAlignment 文件中节的对齐粒度,通常 0x200512字节)
SizeOfImage 加载到内存后的总大小
SizeOfHeaders 所有头部(DOS + PE + 节头表)的总大小
SizeOfStackReserve 主线程栈预留大小,默认 1MB0x100000
SizeOfStackCommit 主线程栈初始提交大小,默认 4KB0x1000
SizeOfHeapReserve 默认堆预留大小,默认 1MB
DataDirectory[16] 16个数据目录,指向导入表、导出表、异常表、重定位表等

关键 DataDirectory 条目

索引 名称 对应 Linux ELF
0 Export Directory .dynsym(导出符号)
1 Import Directory NEEDED + PLT/GOT(导入依赖)
2 Resource Directory 无直接对应(嵌入资源)
3 Exception Directory .eh_frame(异常处理)
5 Relocation Directory .rel.text(重定位)
12 IAT Directory PLT/GOT(函数地址表)

两张表的职责分工

  1. 节头表(Section Table
    1. 描述每个节在文件中的偏移、在内存中的 RVA、大小、权限(类似 ELF 的节头表 SHT + 程序头表 PHT 的结合)
  2. DataDirectory
    1. 指向各种特殊数据(导入、导出、异常表等),是 PE 特有的机制,ELF 用专用节名(如 .plt.got)来承担类似职责

PE 加载过程(对应 ELF 加载)

  1. LinuxASLR 修正在动态链接器 ld.so 中完成,Windowsntdll!LdrLoadDll 中完成,流程相似

一些问题

声明:本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享