关于 _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。