我最近使用/FAsu
Visual C++ compiler option输出特别长的成员函数定义的源+汇编。在汇编输出中,在设置堆栈帧之后,只需调用一个神秘的_chkstk()
函数。
_chkstk()
上的MSDN页面没有解释调用此函数的原因。我也看到了Stack Overflow问题Allocating a buffer of more a page size on stack will corrupt memory?,但我不明白OP和接受的答案是在讨论什么。
_chkstk()
CRT功能的目的是什么?它做了什么?
答案 0 :(得分:37)
使用时线程的额外堆栈中的Windows页面。在堆栈的末尾,有一个保护页面映射为无法访问的内存 - 如果程序访问它(因为它试图使用比当前映射更多的堆栈),则存在访问冲突。操作系统捕获故障,在与旧保护页面相同的地址处的另一个堆栈页面中映射,创建一个新旧保护页面,并从导致违规的指令中恢复。
如果一个函数有多个局部变量页面,那么它访问的第一个地址可能超出了当前堆栈的一个页面。因此,它将错过防护页面并触发操作系统未实现的访问冲突,因为需要更多堆栈。如果所需的总堆栈特别大,它甚至可能超出保护页面,超出分配给堆栈的虚拟地址空间的末尾,并进入实际用于其他内容的内存。
因此,_chkstk
确保有足够的空间用于局部变量。您可以想象它通过以页面大小的间隔触发局部变量的内存,按升序执行,以确保它不会错过保护页面(所谓的“堆栈探测”)。我不知道它是否真的这样做,但是,它可能需要一个更直接的路径并指示操作系统映射到一定量的堆栈。无论哪种方式,如果所需的总数大于可用于堆栈的虚拟地址空间,那么操作系统可以抱怨它而不是做一些未定义的事情。
答案 1 :(得分:6)
我查看了__chkstk
的代码,它确实以一页的间隔执行重复的堆栈探测。所以这样,它不需要对OS进行任何调用。 rax
中的参数是您要添加的数据的大小。它确保可以访问目标地址(当前rsp
- rax
)。如果rax
> rsp
,它为地址0执行此操作。作为一个有趣的快捷方式,它首先将地址与gs:[10h]
进行比较,__chkstk__
是映射的当前最低页面;如果目标地址> =这个,那么它什么都不做。
顺便说一下,至少对于64位代码,拼写有两个下划线:{{1}}。