关于GetModuleInformation返回内存信息及重复读取差异的技术问询
关于GetModuleInformation与Windows应用加载流程的解答
一、GetModuleInformation返回的模块信息包含哪些内容?
嘿,这问题我之前也踩过坑——GetModuleInformation()返回的MODULEINFO结构体里,lpBaseOfDll是模块在内存中的基地址,SizeOfImage是整个模块在内存中占据的总大小,这俩可不是只覆盖代码段,而是包含了模块的所有组成部分:
- 代码段(
.text):只读的可执行指令 - 数据段(
.data):已初始化的全局变量、静态变量(可写) - 只读数据段(
.rdata):常量字符串、导入表/导出表信息 - 未初始化数据段(
.bss):未初始化的全局/静态变量(加载时会被清零) - 资源段(
.rsrc):图标、对话框、字符串资源等 - 其他辅助段:比如重定位表、TLS段等
至于你说多次调用ReadProcessMemory()结果有差异,大概率是因为读取范围包含了可写的数据段——代码段是只读的,内容不会变,但数据段里的全局变量、静态变量可能会被程序运行时动态修改(比如计数变量、状态标记),每次读取自然会有不同。如果你是同一进程内调用,排除ASLR(地址随机化)的影响,基本就是动态数据变化导致的差异。
二、Windows应用完整加载流程详解
从双击exe到程序真正开始执行,整个流程可以拆成这几步:
启动请求处理
- 资源管理器(explorer.exe)调用
CreateProcess()API,向内核发起进程创建请求。 - 内核创建进程对象、主线程对象,并为进程分配独立的虚拟地址空间。
- 资源管理器(explorer.exe)调用
加载核心系统库
- 内核将
ntdll.dll加载到进程地址空间(这个模块是所有Windows进程的基础,负责内核态和用户态的交互)。 - 主线程开始执行
ntdll.dll中的LdrInitializeThunk函数,正式启动PE加载器。
- 内核将
PE加载器初始化模块
- 解析exe文件的PE头,确定模块的基地址、节表、导入表等关键信息。
- 根据导入表递归加载所有依赖的DLL(比如
kernel32.dll、user32.dll这些系统库,以及你引入的第三方DLL)。 - 处理重定位:如果模块的实际加载基地址和PE头指定的“首选基地址”不一致(比如ASLR随机化、地址被占用),调整模块内所有依赖基地址的指针。
- 初始化内存段:把
.data段的初始化值从PE文件复制到内存,将.bss段清零。 - 依次调用每个已加载DLL的
DllMain函数,传递PROCESS_ATTACH通知,让DLL完成自身初始化。
进入程序入口点
- 所有依赖加载完成后,PE加载器把主线程的执行流跳转到exe的入口点。注意这个入口点通常不是你写的
main()或WinMain(),而是C/C++运行时(CRT)提供的启动函数(比如mainCRTStartup或WinMainCRTStartup)。 - CRT启动函数会完成一系列前置初始化:初始化堆、stdio库、全局对象构造等,然后才调用你写的
main()/WinMain()函数,程序正式开始运行。
- 所有依赖加载完成后,PE加载器把主线程的执行流跳转到exe的入口点。注意这个入口点通常不是你写的
程序退出清理
- 当
main()/WinMain()返回后,CRT启动函数会做收尾工作:调用全局对象的析构函数、关闭标准IO句柄等,然后调用ExitProcess()结束进程。 - 内核回收进程的所有资源:虚拟地址空间、线程、句柄等。
- 当
内容的提问来源于stack exchange,提问作者EssexPAL




