简短回答:堆、堆栈和其他PE 加载器相关任务不是 PE 标准或定义的一部分。
一般信息
PE 文件没有描述可执行文件的整个内存空间。它只包含执行程序所需的数据,操作系统保留在用户不知情的情况下添加额外区域的权利。进程运行和操作所需的堆、堆栈和其他内部存储器区域之类的东西不是 PE 文件(或任何可执行文件)的责任。
PE 不定义堆,它请求从操作系统为它分配一个堆(这AllocateHeap
是一个 Windows API,这样做)。没有必要为 PE 文件中的堆“占位符”实际占用空间。进程拥有的堆栈、PEB 和其他内存对象也是如此。
此外,用户(即程序员)通常甚至不需要调用AllocateHeap
它的进程来拥有一个堆。操作系统通常在加载进程时为其分配一个默认堆(通过加载器本身或通过操作系统在将控制权交给 PE 的入口点之前运行的启动代码)。其他时候,编译器使用分配堆的代码作为代码的前缀。
类似地,堆栈是作为进程创建的一部分分配的,而不是 PE 的一部分或由它定义。这是强制性的,因为没有堆栈就不能存在进程(尽管 PE 可以设置分配堆栈的大小)。
如果您有兴趣了解 NT Loader(和 PE 文件格式),建议您查看以下文章和资源:
您可能已经阅读了其中的一些内容,但我将其中的大部分内容包括在内以供将来参考。
有关堆和其他类型内存的大量信息是 windows 内存管理(相对较大)主题的一部分,您可能还想深入了解,这里有一些关于堆和内存管理的文章:
技术问题
- 为程序分配堆的方式和位置?
堆是在创建(堆)时保留和提交的内存页。操作系统分配指定的页面,这些页面位于实际 RAM 和/或页面文件上。
正如我之前提到的,一个进程可以有多个堆(但总是至少有一个,默认为堆)。进程在运行时分配和释放额外的堆,根据运行时逻辑,进程可以具有不同数量的堆。
有关如何创建默认堆(或更准确地说是第一个堆,因为进程可能会将其默认堆更改为稍后分配的堆)的简短描述,请参见上文。
- 如果我想扫描堆中的数据,我将如何获得内存地址空间?
这在一定程度上取决于您要扫描的数据的目的和具体原因以及类型。如果您想扫描进程内存中任何位置的数据,您应该使用VirtualQuery
或VirtualQueryEx
所有提交的内存页面。这不仅可以让您扫描所有可用的堆,还可以让您扫描堆栈、PE 部分和程序使用的其他内存(例如,直接使用 分配的页面VirtualAlloc
)。
如果要获取特定(或多个堆)的地址范围,则需要使用一些Heap 内存函数,例如msdnGetProcessHeap
GetProcessHeaps