18

有趣的异常

 3 years ago
source link: https://bianchengnan.gitee.io/articles/interesting-exception-0xc00000fd/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

最近,在项目中遇到一个有趣的异常。在没附加调试器的情况下会直接崩溃。附加调试器后,会中断到调试器中,但是按 F5 继续运行后,程序还能继续执行。 interesting !你能猜出这是个什么异常吗?

初遇错误

在测试程序功能的时候,意外的崩溃了。以为是偶发事件,于是重新执行,依然会崩溃。一般遇到这种问题可以先考虑在调试器中运行,看看是否能重现。

上调试器

附加 vs 到目标进程,再次执行相应的功能,果然中断到了调试器中。简单查看调用栈及出错的代码,并没有显著异常(其实,有相当明显的提示,只是当时没有注意)。偶然点了 F5 ,没想到居然可以继续运行,有点意思。由于项目比较紧,就一直将就着挂着调试器运行测试代码。前些日子有点时间,再次看了下这个问题。

再次检查

再次在调试器中运行程序,异常果然再次发生了。这次心态比较平和,不急不躁,仔细一看异常提示信息,差点被自己气吐血。具体是什么异常呢?请看下图:

V3YNRnJ.png!mobile

这不是栈溢出吗?这么明显的提示,当时怎么就给华丽丽的错过了呢?一定要仔细看提示啊!知道是栈溢出,后面的问题就不用查了。这里简单介绍一下 32 位程序的栈增长过程。

栈增长过程

默认情况下,每个线程有 1 MB 的栈空间,这 1 MB 的空间对应的虚拟内存(按页面组织的,页面大小一般是 4KB )并不是一开始就有对应的物理页面的,而是按需分配的。

开始时,最上方的两个虚拟页面(栈是向下增长的)有对应的物理页面,这两个页面有 PAGE_READWRITE 保护属性,其中第二个页面还额外包含 PAGE_GUARD 保护属性,是防护页面。当线程试图访问防护页面中的内存时,系统会得到通知 ,系统会去除第二个页面的 PAGE_GUARD 保护属性标志,为第三个页面调拨物理页面,然后为第三个页面设置 PAGE_GUARD 保护属性标志,以此类推。

1022 个页面是最后一个带 PAGE_GUARD 的页面,也就是最后一个保护页面。当线程访问第 1022 个页面的时候,系统会得到通知,系统会去除第 1022 个页面的 PAGE_GUARD 保护属性标志,为第 1023 个页面调拨物理页面,但是系统并不会为第 1023 个页面设置 PAGE_GUARD 保护属性标志,相反的,会抛出 EXCEPTION_STACK_OVERFLOW 异常,该异常对应的值是 0xC00000FD

系统永远不会为第 1024 个页面调拨物理页面。这样是为了保护进程的其它数据,使它们不会因为意外的内存写越界而遭到破坏。

现象解释

相信弄明白栈的增长过程,基本就明白了为什么在调试器中可以继续运行的原因了。当调试器收到栈溢出异常时,系统已经把保护页面的 PAGE_GUARD 保护属性标志去除了,并且为下一个页面调拨了物理页面。在调试器中按 F5 继续运行,再次访问相同的地址已经不会产生异常。所以就可以继续运行了。但并不是所有情况下都可以这么幸运,当使用内存过多,访问到第 1024 个页面时,还是会报页面访问异常(对应的异常码是 0xC0000005 )。

BbmERfI.png!mobile

总结

  • 一定要仔细看错误提示!

  • 遇到诡异的问题可以试试在调试器中运行程序。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK