我试图理解如何通过Visual C ++运行时在x64上实现C ++异常处理。
在http://www.nynaeve.net/?p=110阅读关于x64上SEH实现的Nynaeve博客时,似乎RtlUnwindEx调用RtlRestoreContext并将ExceptionCode设置为STATUS_UNWIND_CONSOLIDATE以进行框架合并展开。
对我来说不完全清楚的是RtlRestoreContext会做什么? MSDN在http://msdn.microsoft.com/en-us/site/ms680605处声明“RtlRestoreContext在调用回调函数之前合并其帧与上下文记录中指定的帧之间的调用帧。这隐藏了回调函数中可能发生的任何异常处理的帧。”
“合并其框架与上下文记录中指定的框架之间的调用框架”是什么意思?这是如何“隐藏回调函数中可能发生的任何异常处理的帧”? “框架合并”是什么意思,框架的确切位置在哪里?
让我们说一个C ++ catch处理程序是由RtlRestoreContext调用的,它会引发另一个异常 - 是由某种SEH块保护的(重新)抛出异常吗?或者,这个框架整合业务以某种方式照顾它?如果是,怎么样?
答案 0 :(得分:1)
如果你按照这个功能,你会看到你所指的情况,代码在堆栈上设置了一个虚假的机器框架(相对于[r8],只有RIP和RSP填充)来自原始文件上下文传递给RtlRestoreContext。然后,它将原始上下文复制到机器框架下方分配的堆栈空间中。
有关机器框架的更多信息,请参阅http://msdn.microsoft.com/en-us/library/ck9asaa9.aspx(在UWOP_PUSH_MACHFRAME下)。
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的角度来看,callstack是OriginalContext - > RtlRestoreContext - > [用户提供的回调],两者之间没有任何内容。
因此,如果堆栈被“展开”,则ContextRecord描述的帧与RtlRestoreContext中的当前上下文之间的所有中间帧都被“遗忘”。即,帧已合并为单个帧,该帧作为单个函数展开。 因此,如果在ExceptionRecord中传递的回调函数内发生异常,则隐藏这些中间帧中的任何异常处理程序。 正如博客所指出的,这个功能对于语言异常处理非常有用。
正如MS文档也指出的那样,在调用回调之前不会销毁中间堆栈帧的本地,如果在该函数的堆栈帧上分配了某种语言的异常对象,这将非常有用。
答案 1 :(得分:0)