如何在 Win32 中定位进程的全局和堆栈区域?

Posted

技术标签:

【中文标题】如何在 Win32 中定位进程的全局和堆栈区域?【英文标题】:How can I locate a process' global and stack areas in Win32? 【发布时间】:2011-01-04 06:47:16 【问题描述】:

如何定位 Win32 进程的哪些内存区域包含每个线程的全局数据和堆栈数据?

【问题讨论】:

【参考方案1】:

获取分配在您感兴趣的内存区域中的变量的地址。拥有地址后如何处理这些地址完全是另一个问题。

你也可以objdump -h(我认为是-h,可能是-x)列出节地址,包括数据节。

【讨论】:

【参考方案2】:

没有 API(据我所知)可以做到这一点。但是如果您在进程中有一个 DLL,那么在创建每个线程时,您将在 DllMain 中收到 DLL_PROCESS_ATTACH/DLL_THREAD_ATTACH 通知。当您收到这些通知时,您可以记录该线程的线程 ID 和堆栈对象的地址,因为您将在新线程上被调用。因此,将线程 ID 和堆栈地址存储在您当时创建的某个表中。不要试图在 DllMain 中做很多工作,只需记录堆栈位置并返回即可。

然后您可以使用VirtualQuery 将每个线程堆栈上的变量地址转换为虚拟分配范围,这应该为您提供堆栈的基地址(请记住,堆栈从高地址增长到低地址) .堆栈的默认分配大小为 1Mb,但可以由链接器开关或线程创建者覆盖,但堆栈必须是连续的。所以你从VirtualQuery 得到的将是当时的完整堆栈

至于堆位置 - 堆可以有多个位置,但一般来说,如果您想假设一个连续的堆位置,则使用HeapAlloc 获取堆对象的地址,然后使用VirtualQuery 获取确定堆的该部分的页面范围。

您也可以在 hModule 上为 EXE 和每个 DLL 使用 VirtualQuery。然后您可以假设任何可读写且不是堆栈或模块的东西都是堆的一部分。请注意,这在大多数进程中都是正确的,但在某些进程中可能不是正确的,因为应用程序可以直接调用VirtualAllocCreateFileMapping,从而导致有效的数据指针既不是来自堆栈也不是来自堆。 使用EnumProcessModules 获取加载到进程中的模块列表。

VirtualQuery 基本上采用随机地址,并返回该地址所属的页面集合的基地址,以及页面保护。因此,从分配“类型”的特定指针开始是有好处的。

【讨论】:

【参考方案3】:

全球数据

“全局”我将假设您指的是所有未使用 new、malloc、HeapAlloc、VirtualAlloc 等动态分配的数据 - 您可以在源代码中声明的函数之外和外部的数据类定义。

您可以通过将每个 DLL 作为 PE 文件加载到 PE 文件阅读器中并确定 .data 和 .bss 部分的位置来定位这些部分(对于不同的编译器,它们可能具有不同的名称)。您需要为每个 DLL 执行此操作。这为您提供了每个 DLL 的此数据的一般位置。然后,如果您有调试信息,或者没有调试信息,MAP 文件,您可以将 DLL 地址映射到调试信息/映射文件信息,以获取每个变量的名称和确切位置。

您可能会发现PE Format DLL 帮助您执行此任务比自己编写代码查询 PE 文件要容易得多。

线程堆栈

使用 ToolHelp32(或 PSAPI 库,如果在 Windows NT 4 上)枚举应用程序中的线程。对于每个线程,获取线程上下文并读取 ESP 寄存器(RSP for x64)。现在对从每个上下文读取的 ESP/RSP 寄存器中的地址执行 VirtualQuery。该地址周围的 1MB(默认值)区域(从 mbi.AllocationBase 开始并向上工作 1MB)是堆栈位置。请注意,堆栈大小可能不是 1MB,如果您愿意,可以从启动线程的 DLL/EXE 的 PE 头中查询。

编辑,修正我交换了一些寄存器名称的错字,感谢@interjay

【讨论】:

您的意思是 ESP 而不是 EIP? @interjay。哎呀!是的,我做到了。 ESP/RSP 而不是 EIP/RIP。编辑文章进行修复。感谢您的保存。

以上是关于如何在 Win32 中定位进程的全局和堆栈区域?的主要内容,如果未能解决你的问题,请参考以下文章

线程局部存储的Win32实现

内存区域

进程间的数据共享

动态加载并执行Win32可执行程序

win32线程

是否在C中的堆栈或堆上创建激活记录?