_chkstk()这是一个用来分配堆栈用的。源码如下
_chkstk:
push ecx
cmp eax,1000h
lea ecx,[esp+8]
jb lastpage
probepages:
sub ecx,1000h
sub eax,1000h
test dword ptr [ecx],eax
cmp eax,1000h
jae probepages (0040818c)
lastpage:
sub ecx,eax
mov eax,esp
test dword ptr [ecx],eax
mov esp,ecx
mov ecx,dword ptr [eax]
mov eax,dword ptr [eax+4]
push eax
ret
大概流程
1) “chkstk Routine is a helper routine for the C compiler. For x86
compilers, _chkstk Routine is called when the local variables exceed
4096 bytes; for x64 compilers it is 4K and 8K respectively.”
即, 当一个函数局部变量超过一个页面大小4k的时候, 编译器会自动插入这个函数。插入这个函数的位置在:
push ebp
mov ebp,esp
mov eax,1000D4h
call _chkstk()
2) 这个函数做什么?当函数为堆栈分配的页面不够时, (堆栈默认大小为1M), 堆栈需要
更多的页面时调用这个函数。
当堆栈使用大于分配的大小(默认1M)时,产生_XCPT_UNABLE_TO_GROW_STACK.
3) 当调用这个函数时, 首先外面对eax进行赋值(已经分配的堆栈大小 + 即将分配给函数局部变量的堆栈大小)
4) 调用 _chkstk() 首先,保持当期esp到eax中,然后开始判断:如果分配的大小大于一个页面, 到第5)步(大多都先第5)步);否则到第6)步。
5) 当需要分配的大小大于一个页面, 则增加一个页面。“sub eax 1000h” 表示堆栈栈顶下移1000h,[由于堆栈是高地址(栈底部)->低地址(栈顶部)分布],所以堆栈扩大了1000h; “sub ecx 1000h”表示分配了1000h(1个页面)之后还需要多少空间;
“test dword ptr [ecx],eax” 表示分配空间,这个时候之前只不过是分配虚存,内存没有 commit ,这个时候对这个内存地址进行读写操作都会引发一个 page fault 异常(_XCPT_GUARD_PAGE_VIOLATION), OS捕获这个异常,检查一定的条件,适合的时候就把这个内存页 commit 了,即分配了实际的物理内存。然后再次比较需要多少内存,如果还是超过1页(1000h), 则重复第5)步,否则到第6)步。
6) 还需要分配的堆栈空间小于1页的时候,“sub ecx,eax” 堆栈继续扩大(扩大了剩余大小的空间);然后“mov esp,ecx”,保存到原来的esp;并且通过“test”为堆栈分配空间。
7) 最后,esp的值不再是原来的值,堆栈的大小变成: 堆栈原来大小 + 局部变量需要的堆栈大小 + xx (push 用的一点堆栈的零头).
如果你的函数中需要一个很大的局部变量,那就要注意一点了。因为局部变量是在存放在堆栈中的,在分配的时候需要会调用上面的函数,并检测地址是否有效。在这个时刻,如果你是在驱动中,那么很可能会出现蓝屏。在应用层可能会崩溃。因此,如果需要使用比较大的数据时,最好是通过动态分配来得到内存。