You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

关于GetModuleInformation返回内存信息及重复读取差异的技术问询

关于GetModuleInformation与Windows应用加载流程的解答

一、GetModuleInformation返回的模块信息包含哪些内容?

嘿,这问题我之前也踩过坑——GetModuleInformation()返回的MODULEINFO结构体里,lpBaseOfDll是模块在内存中的基地址SizeOfImage是整个模块在内存中占据的总大小,这俩可不是只覆盖代码段,而是包含了模块的所有组成部分:

  • 代码段(.text):只读的可执行指令
  • 数据段(.data):已初始化的全局变量、静态变量(可写)
  • 只读数据段(.rdata):常量字符串、导入表/导出表信息
  • 未初始化数据段(.bss):未初始化的全局/静态变量(加载时会被清零)
  • 资源段(.rsrc):图标、对话框、字符串资源等
  • 其他辅助段:比如重定位表、TLS段等

至于你说多次调用ReadProcessMemory()结果有差异,大概率是因为读取范围包含了可写的数据段——代码段是只读的,内容不会变,但数据段里的全局变量、静态变量可能会被程序运行时动态修改(比如计数变量、状态标记),每次读取自然会有不同。如果你是同一进程内调用,排除ASLR(地址随机化)的影响,基本就是动态数据变化导致的差异。

二、Windows应用完整加载流程详解

从双击exe到程序真正开始执行,整个流程可以拆成这几步:

  1. 启动请求处理

    • 资源管理器(explorer.exe)调用CreateProcess() API,向内核发起进程创建请求。
    • 内核创建进程对象、主线程对象,并为进程分配独立的虚拟地址空间。
  2. 加载核心系统库

    • 内核将ntdll.dll加载到进程地址空间(这个模块是所有Windows进程的基础,负责内核态和用户态的交互)。
    • 主线程开始执行ntdll.dll中的LdrInitializeThunk函数,正式启动PE加载器。
  3. PE加载器初始化模块

    • 解析exe文件的PE头,确定模块的基地址、节表、导入表等关键信息。
    • 根据导入表递归加载所有依赖的DLL(比如kernel32.dlluser32.dll这些系统库,以及你引入的第三方DLL)。
    • 处理重定位:如果模块的实际加载基地址和PE头指定的“首选基地址”不一致(比如ASLR随机化、地址被占用),调整模块内所有依赖基地址的指针。
    • 初始化内存段:把.data段的初始化值从PE文件复制到内存,将.bss段清零。
    • 依次调用每个已加载DLL的DllMain函数,传递PROCESS_ATTACH通知,让DLL完成自身初始化。
  4. 进入程序入口点

    • 所有依赖加载完成后,PE加载器把主线程的执行流跳转到exe的入口点。注意这个入口点通常不是你写的main()WinMain(),而是C/C++运行时(CRT)提供的启动函数(比如mainCRTStartupWinMainCRTStartup)。
    • CRT启动函数会完成一系列前置初始化:初始化堆、stdio库、全局对象构造等,然后才调用你写的main()/WinMain()函数,程序正式开始运行。
  5. 程序退出清理

    • main()/WinMain()返回后,CRT启动函数会做收尾工作:调用全局对象的析构函数、关闭标准IO句柄等,然后调用ExitProcess()结束进程。
    • 内核回收进程的所有资源:虚拟地址空间、线程、句柄等。

内容的提问来源于stack exchange,提问作者EssexPAL

火山引擎 最新活动