关于 _resetstkoflw

   当从 stack overflow exception(c00000fd) 恢复的时候需要调用 _resetstkoflw. 
    如果发生了 stack overflow (c00000fd) 异常, 而这个函数没有被调用, 那么就没有 guard page (PAGE_GUARD) 了; 
    下一次 stack overflow 时, 进程不会产生 stack overflow 异常了, 而很可能因为访问堆栈外的地址而产生AV异常, 或者更严重的错误.

    
    当 esp 访问 guard page 地址的时候, 会产生一个异常, 这时系统做三件事:
    1) 移除 guard page 上的 PAGE_GUARD 保护, 所以那个页面可以被读写.
    2) 在更低地址处 alloc 新的 guard page.
    3) 返回产生异常的指令, 继续执行.
    这样, 系统能够自动增加 stack 的 size.

    
    当超过 stack 的最大 size, 系统会:
    1) 移除 guard page 上的 PAGE_GUARD 标志.
    2) 试图在更低地址 alloc 新的 guard page, 但是这会失败, 因为超过了 stack 的最大 size.
    3) 产生异常, 这样线程就能处理这个异常了.


    注意: 这时, stack 已经没有 guard page 了, 下次栈溢出将一直到底, (本应该有 guard page 的), 
    程序会访问 stack 之外的地址并产生 AV 异常.

    当 stack overflow 异常被处理之后, 应该调用 _resetstkoflw 来恢复 guard page.
    _resetstkoflw 不能在以下场合使用:
    1) A filter expression. (SEH)
    2) A filter function. (SEH)
    3) A function called from a filter function. (SEH)
    4) A catch block. (C++)
    5) A __finally block. (SEH)
    因为这时 stack 还没有 完全 unwind.

    SEH 在 __except { ... } 中处理.
    C++ 在 catch {}  之后处理.

    即使在正确的位置调用 _resetstkoflw 也可能产生错误: 比如 unwinding stack 之后, 留下的 stack 空间还是非常少, 
    不足以执行 _resetstkoflw 来写 PAGE_GUARD 到 stack 的最后一页.
    _resetstkoflw 就会失败, 然后返回0. 所以, 安全使用这个函数 需要检查返回值, 而不是假设 stack 可以被安全使用.

 

  最后 PAGE_GUARD 的触发是因为: 尽管这个页面提交了, 但是因为该页面不在物理内存中, 通过!pte查看该页面对应的页表项, 会发现页表项的最后一位是0, 说明该页面不在物理内存中, 不在物理内存中的页面被访问会触发缺页中断, 而系统(windows)正是利用这个中断知道一个页面被访问, 再结合属性知道守护页面被访问, 最后系统来扩展stack。

posted @ 2015-05-13 19:15  嗨皮龙  阅读(703)  评论(0编辑  收藏  举报