如何确定线程栈的基址?

缘起

很早之前,我遇到过几个与栈相关的问题,当时总结过几篇关于线程栈的文章,分别是 《栈大小可以怎么改?》《栈局部变量优化探究,意外发现了 vs 的一个 bug ?》《栈又溢出了》《有趣的异常》。在这几篇总结中,简单的总结了栈溢出的原因,设置线程栈大小的方法。但是还有一点没弄清楚:操作系统是怎么知道一个线程的栈大小的?一定记录在某个位置了,否则就不能正确的在栈溢出的时候抛出异常了。不能根据 PE 头中的字段判断,因为在创建线程的时候可以指定线程栈大小。TEB 中的 StackLimit 是真正的栈底吗?带着这些疑问一起来刨根问底吧~

友情提示:结论在文章末尾。

!teb 命令

相信,很多小伙伴儿都知道,可以使用 !teb 查看线程相关的信息。

view-thread-using-teb-command

其中的 StackBaseStackLimit 分别指示了栈顶和当前栈使用情况。因为栈是从上向下增长的,所以 StackBase 的值比较大。

我之前一直认为这两个字段分别指向了栈顶和栈底(线程栈可以到达的最低位置),可以通过这两个字段计算出线程栈大小。后来才发现 StackLimit 并没有指向栈底,而是指向了线程栈当前所到达的最低位置。

线程栈默认的大小是 1MB。如果计算一下 StackBase - StackLimit 的值即可知道,它们的差值是 256KB,而不是 1MB

calculate-current-thread-stack-size

那么当前线程栈的大小是不是 1MB 呢?该如何确认呢?可以通过 vmmap 确定。

vmmap

打开 vmmap.exe,并选择想要查看的进程,即可进行查看。

注意: 当选择的进程已经中断到调试器时,vmmap.exe 会一直等待,需要让目标进程运行起来。

view-stack-size-in-vmmap

可以看到,线程 9804 的线程栈大小确实是 1MB

根据以上信息,可以确定 StackLimit 并不是真正的线程栈栈底。那么,栈底位置到底记录在哪里了呢?

最近在重翻《软件调试》的时候,发现了一个关键函数。

栈空间自动增长的关键函数

在第 2222.8.1 节 栈空间的自动增长(P617)中提到了一个关键函数 MiCheckForUserStackOverflow。该函数是判断栈空间能否增长的关键函数。如果知道该函数是如何实现的,就能找到栈底了。

脑子里很快有了三个选项:google 搜索,ReactOSserver03 源码。正好电脑上有源码,不用考虑其它两个选项了。

参考源码

知道了函数名,但是还不知道这个函数在哪个文件中实现的。这个简单,在 File Locator 中输入 MiCheckForUserStackOverflow,很快就找到了关键的文件。

search-MiCheckForUserStackOverflow

双击打开 accesschk.c,找到 MiCheckForUserStackOverflow。注释很清晰的解释了这个函数的作用。

MiCheckForUserStackOverflow-description

说明: 该函数的实现在 wrk 中也可以找到,地址是 https://github.com/mic101/windows/blob/master/WRK-v1.2/base/ntos/mm/acceschk.c。

整个函数虽然行数很多,但是有大部分是注释,而且考虑了各种情况。我截取了最关键的部分,如下图:

MiCheckForUserStackOverflow-key-flow

看样子 teb->DeallocationStack 记录了栈底。 StackBase 减去 teb->DeallocationStack 的值应该是栈大小(默认是 1MB)。在 windbg 中验证一下。

验证

windbg 中输入如下命令 dt _teb 0043f000 -y DeallocationStack -y NtTib.StackBase,只查看 DeallocationStackNtTib.StackBase 的值。然后计算差值,发现正好是 1MB0x100000)。

view-stack-base-and-stack-size

总结

  • _TEB 结构体的 DeallocationStack 指向线程栈底,而 NtTib.StackLimit 指向的是线程栈当前所到达的最低位置。

  • 可以在 dt 命令中通过 -y 选项来显示特定字段。

  • vmmap.exe 可以非常详细的展示进程虚拟内存情况。

参考资料

  • 《软件调试》第一版 第 22
  • srv03rtm 源码
BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
0%