很早之前,我遇到过几个与栈相关的问题,当时总结过几篇关于线程栈的文章,分别是 《栈大小可以怎么改?》、《栈局部变量优化探究,意外发现了 vs 的一个 bug ?》、《栈又溢出了》、《有趣的异常》。在这几篇总结中,简单的总结了栈溢出的原因,设置线程栈大小的方法。但是还有一点没弄清楚:操作系统是怎么知道一个线程的栈大小的?一定记录在某个位置了,否则就不能正确的在栈溢出的时候抛出异常了。不能根据 PE 头中的字段判断,因为在创建线程的时候可以指定线程栈大小。TEB 中的 StackLimit 是真正的栈底吗?带着这些疑问一起来刨根问底吧~
PE
TEB
StackLimit
友情提示:结论在文章末尾。
相信,很多小伙伴儿都知道,可以使用 !teb 查看线程相关的信息。
!teb
其中的 StackBase 和 StackLimit 分别指示了栈顶和当前栈使用情况。因为栈是从上向下增长的,所以 StackBase 的值比较大。
StackBase
我之前一直认为这两个字段分别指向了栈顶和栈底(线程栈可以到达的最低位置),可以通过这两个字段计算出线程栈大小。后来才发现 StackLimit 并没有指向栈底,而是指向了线程栈当前所到达的最低位置。
线程栈默认的大小是 1MB。如果计算一下 StackBase - StackLimit 的值即可知道,它们的差值是 256KB,而不是 1MB。
1MB
StackBase - StackLimit
256KB
那么当前线程栈的大小是不是 1MB 呢?该如何确认呢?可以通过 vmmap 确定。
vmmap
打开 vmmap.exe,并选择想要查看的进程,即可进行查看。
vmmap.exe
注意: 当选择的进程已经中断到调试器时,vmmap.exe 会一直等待,需要让目标进程运行起来。
可以看到,线程 9804 的线程栈大小确实是 1MB。
9804
根据以上信息,可以确定 StackLimit 并不是真正的线程栈栈底。那么,栈底位置到底记录在哪里了呢?
最近在重翻《软件调试》的时候,发现了一个关键函数。
在第 22 章 22.8.1 节 栈空间的自动增长(P617)中提到了一个关键函数 MiCheckForUserStackOverflow。该函数是判断栈空间能否增长的关键函数。如果知道该函数是如何实现的,就能找到栈底了。
22
22.8.1
P617
MiCheckForUserStackOverflow
脑子里很快有了三个选项:google 搜索,ReactOS 和 server03 源码。正好电脑上有源码,不用考虑其它两个选项了。
google
ReactOS
server03
知道了函数名,但是还不知道这个函数在哪个文件中实现的。这个简单,在 File Locator 中输入 MiCheckForUserStackOverflow,很快就找到了关键的文件。
File Locator
双击打开 accesschk.c,找到 MiCheckForUserStackOverflow。注释很清晰的解释了这个函数的作用。
accesschk.c
说明: 该函数的实现在 wrk 中也可以找到,地址是 e53K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6E0K9h3x3I4x3o6q4Q4x3V1k6%4K9h3&6V1L8%4N6K6i4K6u0r3j5X3I4G2j5W2)9J5c8X3#2S2M7%4c8W2M7W2)9J5c8W2N6d9d9#2)9J5k6s2j5I4i4K6u0W2x3W2)9J5c8X3u0S2M7$3g2Q4x3V1k6F1N6r3!0K6i4K6u0r3L8h3#2Q4x3V1k6S2j5$3y4W2M7$3y4Z5K9#2)9J5k6h3y4Q4c8e0y4Q4z5o6m8Q4z5o6t1`.
wrk
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
拉闸太子 感谢楼主 学废了
简单的简单 只记得老师曾经说过栈的大小是1024[em_78]
秋狝 感谢分享