【问题标题】:What does RtlRestoreContext do when ExceptionCode is STATUS_UNWIND_CONSOLIDATE?当 ExceptionCode 为 STATUS_UNWIND_CONSOLIDATE 时,RtlRestoreContext 会做什么?
【发布时间】:2012-02-24 09:50:41
【问题描述】:

我试图了解 Visual C++ 运行时如何在 x64 上实现 C++ 异常处理。

在阅读关于在 x64 上实现 SEH 的 Nynaeve 博客http://www.nynaeve.net/?p=110 后,似乎 RtlUnwindEx 调用 RtlRestoreContext 并将 ExceptionCode 设置为 STATUS_UNWIND_CONSOLIDATE 以展开框架合并。

我不完全清楚的是 RtlRestoreContext 做了什么? MSDN 在http://msdn.microsoft.com/en-us/site/ms680605 声明“在调用回调函数之前,RtlRestoreContext 会在其帧和上下文记录中指定的帧之间合并调用帧。这会隐藏在回调函数中可能发生的任何异常处理的帧。”

“在其框架和上下文记录中指定的框架之间合并调用框架”是什么意思?这如何“隐藏回调函数中可能发生的任何异常处理的帧”? “框架整合”是什么意思,整合的框架具体在哪里?

假设一个 C++ catch 处理程序将由 RtlRestoreContext 调用,它会引发另一个异常 - (重新)引发的异常是否受到某种 SEH 块的保护?或者,这个框架整合业务以某种方式处理它?如果是,怎么做?

【问题讨论】:

    标签: c++ 64-bit seh


    【解决方案1】:

    如果你遵循这个函数,你会看到在你所指的情况下,代码在堆栈上设置了一个假机器框架(相对于 [r8],只有 RIP 和 RSP 填充)来自 original 上下文传递给 RtlRestoreContext。然后它将原始上下文复制到分配在机器框架下方的堆栈空间中。

    有关机器框架(在 UWOP_PUSH_MACHFRAME 下)的更多信息,请参阅 http://msdn.microsoft.com/en-us/library/ck9asaa9.aspx

    0:004> u ntdll!RtlRestoreContext+0x296
    00000000`771f0c05 83ec30          sub     esp,30h
    00000000`771f0c08 4c8bc4          mov     r8,rsp
    00000000`771f0c0b 4881ecd0040000  sub     rsp,4D0h
    00000000`771f0c12 488bf1          mov     rsi,rcx
    00000000`771f0c15 488bfc          mov     rdi,rsp
    00000000`771f0c18 b99a000000      mov     ecx,9Ah
    00000000`771f0c1d f348a5          rep movs qword ptr [rdi],qword ptr [rsi]
    00000000`771f0c20 488b842498000000 mov     rax,qword ptr [rsp+98h]
    0:004> u
    ntdll!RtlRestoreContext+0x2ba:
    00000000`771f0c28 49894018        mov     qword ptr [r8+18h],rax
    00000000`771f0c2c 488b8424f8000000 mov     rax,qword ptr [rsp+0F8h]
    00000000`771f0c34 498900          mov     qword ptr [r8],rax
    00000000`771f0c37 488bca          mov     rcx,rdx
    00000000`771f0c3a eb12            jmp     ntdll!RcFrameConsolidation (00000000`771f0c4e)
    

    代码跳转到博客所说的伪函数 NTDLL!RcFrameConsolidation。

    如果我们检查这个函数的函数表和 UNWIND 条目,我们会看到它包含对应于假堆栈帧设置的元数据:

    0:004>.fnent ntdll!rcframeconsolidation
    
    ...snip...
    
    Unwind info at 00000000`772c8e0c, 52 bytes
      version 1, flags 0, prolog 0, codes 27
      00: offs 0, unwind op 8, op info f    UWOP_SAVE_XMM128 FrameOffset: 290 reg: xmm15.
      02: offs 0, unwind op 8, op info e    UWOP_SAVE_XMM128 FrameOffset: 280 reg: xmm14.
      04: offs 0, unwind op 8, op info d    UWOP_SAVE_XMM128 FrameOffset: 270 reg: xmm13.
      06: offs 0, unwind op 8, op info c    UWOP_SAVE_XMM128 FrameOffset: 260 reg: xmm12.
      08: offs 0, unwind op 8, op info b    UWOP_SAVE_XMM128 FrameOffset: 250 reg: xmm11.
      0a: offs 0, unwind op 8, op info a    UWOP_SAVE_XMM128 FrameOffset: 240 reg: xmm10.
      0c: offs 0, unwind op 8, op info 9    UWOP_SAVE_XMM128 FrameOffset: 230 reg: xmm9.
      0e: offs 0, unwind op 8, op info 8    UWOP_SAVE_XMM128 FrameOffset: 220 reg: xmm8.
      10: offs 0, unwind op 8, op info 7    UWOP_SAVE_XMM128 FrameOffset: 210 reg: xmm7.
      12: offs 0, unwind op 8, op info 6    UWOP_SAVE_XMM128 FrameOffset: 200 reg: xmm6.
      14: offs 0, unwind op 4, op info f    UWOP_SAVE_NONVOL FrameOffset: f0 reg: r15.
      16: offs 0, unwind op 4, op info e    UWOP_SAVE_NONVOL FrameOffset: e8 reg: r14.
      18: offs 0, unwind op 4, op info d    UWOP_SAVE_NONVOL FrameOffset: e0 reg: r13.
      1a: offs 0, unwind op 4, op info c    UWOP_SAVE_NONVOL FrameOffset: d8 reg: r12.
      1c: offs 0, unwind op 4, op info 7    UWOP_SAVE_NONVOL FrameOffset: b0 reg: rdi.
      1e: offs 0, unwind op 4, op info 6    UWOP_SAVE_NONVOL FrameOffset: a8 reg: rsi.
      20: offs 0, unwind op 4, op info 5    UWOP_SAVE_NONVOL FrameOffset: a0 reg: rbp.
      22: offs 0, unwind op 4, op info 3    UWOP_SAVE_NONVOL FrameOffset: 90 reg: rbx.
      24: offs 0, unwind op 1, op info 0    UWOP_ALLOC_LARGE FrameOffset: 4d0.
      26: offs 0, unwind op a, op info 0    UWOP_PUSH_MACHFRAME.
    

    这样做的效果是“欺骗” VirtualUnwindEx/异常处理代码,使其认为 ContextRecord 描述的函数是 RtlRestoreContext 的直接调用者。

    从 VirtualUnwind 的角度来看,调用堆栈是 OriginalContext -> RtlRestoreContext -> [用户提供的回调],中间没有任何内容。

    因此,如果堆栈被“展开”,则 ContextRecord 描述的帧和 RtlRestoreContext 中的当前上下文之间的所有中间帧都被“忘记”。即框架已合并为一个框架,该框架作为单个功能展开。 因此,如果在 ExceptionRecord 中传递的回调函数内部发生异常,则这些中间帧中的任何异常处理程序都将被隐藏。 正如博客所指出的,此功能对于语言异常处理非常有用。

    正如 MS 文档还指出的那样,中间堆栈帧的局部变量在调用回调之前不会被销毁,如果在该函数的堆栈帧上分配了某种语言的异常对象,这很有用。

    【讨论】:

      【解决方案2】:

      图 6 从 http://www.microsoft.com/msj/0197/exception/exception.aspx 中的异常展开

      也许这就是“整合”的意思

      【讨论】:

        猜你喜欢
        • 2016-03-01
        • 1970-01-01
        • 2018-10-12
        • 2014-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-01
        相关资源
        最近更新 更多