VC中捕捉StackOverflow异常

来源:互联网 发布:做微信发淘宝优惠券 编辑:程序博客网 时间:2024/05/29 03:02
  程序中栈溢出通常会导致进程直接关闭。本文分析Windows中栈增长和溢出过程,并提供一种捕捉栈溢出的方法,作为调试程序的参考。

栈大小

    VC中,栈大小默认为1M,但可以在编译的时候或在创建线程的时候指定其他值。

栈增长和溢出

   栈底地址在线程生命期内是常量,栈顶地址保存在SP(ESP、RSP)寄存器。栈地址从高向低增长,因此栈增长,对应着SP寄存器减小。

   1M空间只是预留进程地址空间(Reserve的过程),默认没有分配物理内存。随着程序对栈的使用,操作系统逐步为地址分配物理内存(commit的过程)。x86平台下,每次commit一个页面,也就是4K大小的物理内存。1M的地址空间可以对应256个页面。

   正常情况下,假设已经为栈分配了N个物理页面。只要N<254,第1到N个页面就是可读写的。第N+1个页面也会分配物理内存,但是其保护位是PAGE_GUARD,不可读写。现在栈继续增长,需要N+1个页面的内存,当程序试图读写第N+1个页面的时候,由于PAGE_GUARD属性,发生STATUS_GUARD_PAGE_VIOLATION。操作系统捕捉该异常,把第N+1个页面的保护属性修改为可读写。同时为第N+2个页面地址分配物理内存,设置保护属性为PAGE_GUARD。

   在N=254的时候,如果栈继续增长,则会触发第255个页面的STATUS_GUARD_PAGE_VIOLATION异常,操作系统捕捉该异常,把第255个页面设置为可读写,然后触发栈溢出异常(EXCEPTION_STACK_OVERFLOW)。

   N=254与N<254不同点是

  1. N=254时,不为第256个页面分配物理内存。实际上系统永远不会为第256个页面地址分配物理内存,第256个页面永远只是Reserve状态。
  2. N=254时,触发EXCEPTION_STACK_OVERFLOW。

栈溢出捕捉和恢复

   EXCEPTION_STACK_OVERFLOW不是C++异常,只能用操作系统提供的结构化异常(Structured Exception Handling,SEH)措施来处理。如下所示:

[cpp] view plaincopyprint?
  1. __try{  
  2.         // 导致栈溢出的操作   
  3.     }__except((GetExceptionCode() == EXCEPTION_STACK_OVERFLOW) ?   
  4.                 EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)  
  5.     {  
  6.         int reset = _resetstkoflw();  
  7.         if(reset)  
  8.             std::cout << "栈恢复成功。递归次数=" << count << std::endl;  
  9.         else  
  10.             std::cout << "栈恢复失败。" << count << std::endl;  
  11.     }  

   注意到代码中,在捕捉到栈溢出异常后,调用了_resetstkoflw函数。_resetstkoflw是VC运行库提供的函数,作用是恢复栈内存中相应页的PAGE_GUARD属性。如果不执行_resetstkoflw,本次溢出异常看似捕捉到了,程序也可以继续运行。但是由于栈中的页面不存在PAGE_GUARD属性,如果栈下一次溢出,访问第256个页面的时候,由于第256个页面没有分配物理内存, 直接抛出ACCESS_VIOLATION异常,而不是EXCEPTION_STACK_OVERFLOW。

   _resetstkoflw是有返回值的。如果_resetstkoflw执行的时候,栈又不够用,则返回0。此时无法恢复栈到正常状态。SetThreadStackGuarantee函数可以要求系统在抛出EXCEPTION_STACK_OVERFLOW异常的时候,保证仍然有指定大小的空闲区可以用。SetThreadStackGuarantee实际上减少了程序实际可用栈的大小。

 

完整代码

[cpp] view plaincopyprint?
  1. #include"stdafx.h"   
  2. #include<iostream>   
  3. #include<Windows.h>   
  4.   
  5. int count = 0;  
  6.   
  7. void recursive(){  
  8.      char dummy[1024];  
  9.      ++count;  
  10.      recursive();  
  11. }  
  12.   
  13.    
  14.   
  15. void testRecursive(){  
  16.   
  17.      __try{  
  18.          recursive();  
  19.      }__except((GetExceptionCode() == EXCEPTION_STACK_OVERFLOW) ?  
  20.                    EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)  
  21.      {  
  22.          int reset = _resetstkoflw();  
  23.          if(reset)  
  24.               std::cout << "栈恢复成功。递归次数=" << count << std::endl;  
  25.          else  
  26.               std::cout << "栈恢复失败。" << count << std::endl;  
  27.      }  
  28.   
  29. }  
  30.   
  31.    
  32. int_tmain(intargc, _TCHAR* argv[])  
  33. {  
  34.      std::cout << GetProcessId(GetCurrentProcess()) << std::endl;  
  35.    
  36.      ULONG stkLimit = 1024*4;// 4K  
  37.      if(!SetThreadStackGuarantee(&stkLimit)){  
  38.          std::cout << "SetThreadStackGuarantee失败" << std::endl;  
  39.          return -1;  
  40.      }  
  41.   
  42.   
  43.      // 第一次栈溢出   
  44.      testRecursive();  
  45.   
  46.      // 第二次栈溢出   
  47.      testRecursive();  
  48.   
  49.      std::cout << "测试结束" << std::endl;  
  50.   
  51.      bool tmp = std::cin >> tmp;  
  52.      return 0;  
  53. }  
原创粉丝点击