_chkstk()函数的目的是什么?

41

我最近使用了 /FAsu Visual C++ 编译器选项 来输出一个特别长的成员函数定义的源代码和汇编信息。在汇编输出中,在堆栈帧设置之后,有一个调用神秘的 _chkstk() 函数。

MSDN 页面关于 _chkstk() 的文档没有解释为什么要调用这个函数。我还看到了 Stack Overflow 上的问题 分配大于一页大小的缓冲区到堆栈上会损坏内存?,但我不明白原帖和被接受的答案在说什么。

_chkstk() CRT 函数的目的是什么?它是做什么的?


我猜测(在阅读了您提供的链接后)该函数设置保护,以便函数栈外的写操作将被 CRT 捕获,并可以像在 C++ 中抛出异常或其他处理方式一样进行处理。 - Some programmer dude
相关内容:在使用“push”或“sub”x86指令时如何分配堆栈内存? (还涵盖了Linux,在这种情况下不需要堆栈探针:它具有用于确定是否增长堆栈或将其视为无效页面故障= >段错误的不同启发式方法)。 - Peter Cordes
2个回答

62

当线程使用额外的堆栈时,Windows在堆栈结尾映射一个无法访问的内存作为守护页 -- 如果程序尝试使用比当前映射的堆栈更多的堆栈,则会出现访问冲突。操作系统捕捉到这个错误后,在旧的守护页地址处映射另一页堆栈,创建新的守护页,并从导致错误的指令继续执行。

如果函数有多页本地变量,则其访问的第一个地址可能超出当前堆栈末尾超过一页。因此,它会跳过守护页并触发访问违规,而操作系统没有意识到需要更多的堆栈。如果所需的总堆栈特别巨大,甚至可能超出守护页、虚拟地址空间分配给堆栈的末尾,进入实际上已被用于其他用途的内存。

因此,_chkstk 确保有足够的空间来存储本地变量。可以想象它通过按页面大小的间隔依次触碰本地变量的内存(称为 "堆栈探测")来实现这一点,以确保不会错过守护页。我不知道它是否实际上这样做,可能采取更直接的方式并指示操作系统映射一定量的堆栈。无论哪种方式,如果所需的总堆栈大于虚拟地址空间可用于堆栈,则操作系统可以抱怨而不是执行未定义的操作。


9
阅读您的答案后,我偶然发现了KB100775:Windows NT应用程序中的堆栈检查描述。确实,"[_chkstk()]只是从当前堆栈指针位置到请求分配的每4K内存地址进行访问"。感谢您详细解释的答案。 - Daniel Trebbien
2
如果你遇到访问冲突问题,请检查是否有一个变量太大超出了堆栈的容量。巨大的数组或者嵌套数组往往是罪魁祸首。 - ubershmekel
@DanielTrebbien 看起来那个页面已经不存在了。这是它的存档链接:https://archive.is/J01oT - Alex Budovski

11

我查看了__chkstk的代码,它确实会在一页间隔中进行重复堆栈探测。这样一来,就不需要向操作系统发起任何调用。 rax参数是您要添加数据的大小。它确保目标地址(当前rsp - rax)是可访问的。如果rax > rsp,则为地址0执行此操作。有趣的是,它首先将地址与gs:[10h]比较,后者是当前映射的最低页;如果目标地址>=此值,则不执行任何操作。

顺便说一下,至少对于64位代码,它的拼写是两个下划线:__chkstk__


64位Windows只有一个gs:[10h]堆栈底部值。因此,__chkstk函数总是会触及堆栈下方的页面。 - Bonita Montero

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接