使用 Visual Studio 确定堆栈空间

Posted

技术标签:

【中文标题】使用 Visual Studio 确定堆栈空间【英文标题】:Determining Stack Space with Visual Studio 【发布时间】:2010-12-16 23:42:17 【问题描述】:

我在 Visual Studio 2005 中使用 C 语言进行编程。我有一个多线程程序,但这在这里并不是特别重要。

如何确定(大约)我的线程使用了多少堆栈空间?

我计划使用的技术是将堆栈内存设置为某个预定值,例如 0xDEADBEEF,运行程序很长时间,暂停程序并调查堆栈。

如何使用 Visual Studio 读写堆栈内存?

编辑:例如,请参阅"How to determine maximum stack usage." 该问题涉及嵌入式系统,但在这里我试图在普通 PC 上确定答案。

【问题讨论】:

【参考方案1】:

Windows 不会立即提交堆栈内存;相反,它为其保留地址空间,并在访问时逐页提交。阅读this page了解更多信息。

因此,堆栈地址空间由三个连续的区域组成:

保留但未提交的内存,可用于堆栈增长(但从未访问过); 保护页面,也从未被访问过,访问时触发堆栈增长; 已提交的内存,即线程曾经访问过的堆栈内存。

这允许我们构造一个获取堆栈大小(以页面大小为粒度)的函数:

static size_t GetStackUsage()

    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery(&mbi, &mbi, sizeof(mbi));
    // now mbi.AllocationBase = reserved stack memory base address

    VirtualQuery(mbi.AllocationBase, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe reserved (uncommitted) portion of the stack
    // skip it

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe the guard page
    // skip it

    VirtualQuery((char*)mbi.BaseAddress + mbi.RegionSize, &mbi, sizeof(mbi));
    // now (mbi.BaseAddress, mbi.RegionSize) describe the committed (i.e. accessed) portion of the stack

    return mbi.RegionSize;

要考虑的一件事:CreateThread 允许指定初始堆栈提交大小(通过dwStackSize 参数,当STACK_SIZE_PARAM_IS_A_RESERVATION 标志未设置时)。如果此参数不为零,则只有当堆栈使用量大于dwStackSize 值时,我们的函数才会返回正确的值。

【讨论】:

堆栈不会增长吗?为什么要将 RegionSize 添加到基地址而不是减去它? @Philip - 堆栈确实变小了(至少在 x86 上)。我之所以添加是因为VirtualQuery 返回内存分配区域的基地址——向下增长的堆栈的最后一个(理论上)可用字节的地址。在堆栈向上增长的平台上,第一个 VirtualQuery 调用会给出所需的结果。我想我可以用图片来说明它;等我有更多时间的时候,我什至可能会这样做。 @atzz 我对这个解决方案有点担心(这很有帮助)。我们如何知道在执行此函数或它进行的 VirtualQuery 调用之一时,我们没有遇到保护页面并因此导致实际堆栈状态在我们下面发生变化?保护页不能移动吗? @acm 不能(如果你愿意接受一些关于VirtualQuery 内部和编译器代码生成的合理假设,堆栈增长应该在第一次VirtualQuery 调用时完成)。尽管您可以将其调用 fn 次(或 n 次)并采用最后一个结果来更加确定。 (但这也不是 100%;例如,另一个进程可能会对我们造成WriteProcessMemory,我们会被搞砸的 :))。堆栈使用的概念无论如何只对健康监控或调试有意义,所以 fn 应该是可以的。【参考方案2】:

您可以利用Win32 Thread Information Block中的信息

当你想在一个线程中找出它使用了多少堆栈空间时,你可以这样做:

#include <windows.h>
#include <winnt.h>
#include <intrin.h>

inline NT_TIB* getTib()

    return (NT_TIB*)__readfsdword( 0x18 );

inline size_t get_allocated_stack_size()

    return (size_t)getTib()->StackBase - (size_t)getTib()->StackLimit;


void somewhere_in_your_thread()

    // ...
    size_t sp_value = 0;
    _asm  mov [sp_value], esp 
    size_t used_stack_size = (size_t)getTib()->StackBase - sp_value;

    printf("Number of bytes on stack used by this thread: %u\n", 
           used_stack_size);
    printf("Number of allocated bytes on stack for this thread : %u\n",
           get_allocated_stack_size());    
    // ...

【讨论】:

【参考方案3】:

堆栈也不会按您期望的方式工作。堆栈是页面的线性序列,其中最后一个(顶部)用页面保护位标记。当这个页面被触摸时,保护位被移除,并且该页面可以被使用。为了进一步增长,分配了一个新的保护页。

因此,您想要的答案是 gaurd 页面的分配位置。但是您提出的技术会触及相关页面,因此它会使您尝试测量的内容无效。

确定(堆栈)页面是否具有保护位的非侵入性方法是通过VirtualQuery()

【讨论】:

您的评论并不完全正确。触摸有问题的页面是可以的,真的。该技术是用特定值写入所有相关内存,然后在长时间操作后,查看有多少内存不再具有该值。 Qupting Microsoft:“尝试读取或写入保护页面会导致系统引发 STATUS_ACCESS_VIOLATION 异常并关闭保护页面状态。保护页面因此充当一次性访问警报."。不,阅读也不例外。 我认为我们正在互相交谈。 但如果我理解正确,您的解决方案只有页面分辨率。您的回答很有帮助,但并没有像我希望的那样给我一个具体的答案。 其实是正确的答案,因为分配给栈的页是独占分配给那个栈和线程的。因此,堆栈大小始终为页数。另请参阅 MSVC 编译器选项 - 诸如“初始堆栈空间”之类的选项以页面大小的倍数指定。【参考方案4】:

您可以使用 GetThreadContext() 函数来确定线程的当前堆栈指针。然后使用 VirtualQuery() 查找此指针的堆栈基数。减去这两个指针将为您提供给定线程的堆栈大小。

【讨论】:

以上是关于使用 Visual Studio 确定堆栈空间的主要内容,如果未能解决你的问题,请参考以下文章

visual studio 编译器的堆空间不足问题的解决(cmake版本)

BOOST_FOREACH宏强制方法退出Visual Studio命名空间

如何仅使用键盘在 Visual Studio 中导航调用堆栈?

如何在 Visual Studio 中找到堆栈跟踪?

在 Visual Studio 2013 上显示堆栈内容(不是堆栈调用)

Windbg可以看到Visual Studio中看不到的有效函数调用堆栈