C++异常的幕后10:_Unwind_与调用帧信息

原文地址:https://monoinfinito.wordpress.com/2013/04/09/c-exceptions-under-the-hood-10-_unwind_-and-call-frame-info/

作者:nicolasbrailo

我们让我们的小ABI项目(链接)能够抛出异常了,现在我们着力捕捉它们;上次我们实现了一个能够检测并处理异常的personality函数,不过它仍然有点不完整:即使它能正确地通知栈回滚器它何时应该停止,但我们版本的__gxx_personality_v0不能运行catch块里的代码。有人会说这总比coredump要好,但要成为有用的异常处理ABI,仍然有长的路要走。我们能改进它吗?

我们如何能告诉_Unwind_我们的着陆垫在哪里,使得我们可以执行catch语句里的代码?回到ABI规范,有一些上下文管理函数可能对我们有用:

  • _Unwind_GetLanguageSpecificData,为这个栈帧获取LSDA。使用它,我们应该能够找到要运行的着陆垫与析构函数。
  • _Unwind_GetRegionStart,为当前被personality函数分析的栈帧,获取函数开头的指令指针(即,当前栈帧的函数指针)。
  • _Unwind_GetIP,获取当前栈帧里的指令指针(指向对下一个栈帧函数调用完成处的指针。下面的例子应该会更清楚)。

备注:你可以从我的github repo下载完整的源代码。

让我们通过gdb检查这些函数。在我的机器上:

Breakpoint 1, __gxx_personality_v0 (version=1, actions=6, exceptionClass=134515400, unwind_exception=0x804a060,

      1. context=0xbffff0f0) at mycppabi.cpp:77
      2. const uint8_t* lsda = (const uint8_t*)_Unwind_GetLanguageSpecificData(context);
      3. uintptr_t ip = _Unwind_GetIP(context) - 1;
      4. uintptr_t funcStart = _Unwind_GetRegionStart(context);
      5. uintptr_t ipOffset = ip - funcStart;

检查这些变量,看到_Unwind_GetRegionStart确实指向当前栈帧(try_but_dont_catch),_Unwind_GetIP是下一个栈帧调用完成位置的IP。_Unwind_GetRegionStart把我们指向异常第一次被抛出的地方;解释起来有点复杂,我们后面会用到它,不是现在。同样,这里我们没有看到LSDA,但我们可以推断它在函数代码后,因为_Unwind_GetLanguageSpecificData直接指向函数结尾:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

_Unwind_GetIP = (void *) 0x804861d

_Unwind_GetRegionStart = (void *) 0x8048612

_Unwind_GetLanguageSpecificData = (void *) 0x8048e3c

function pointer to try_but_dont_catch = 0x8048612 <try_but_dont_catch()>

 

(gdb) disassemble /m try_but_dont_catch

Dump of assembler code for function try_but_dont_catch():

10  void try_but_dont_catch() {

        [...]

11      try {

12          raise();

   0x08048619 <+7>:   call   0x80485e8 <raise()>

 

13      } catch(Fake_Exception&) {

   0x08048651 <+63>:  call   0x804874a <__cxa_begin_catch()>

   0x08048665 <+83>:  call   0x804875e <__cxa_end_catch()>

   0x0804866a <+88>:  jmp    0x804861e <try_but_dont_catch()+12>

 

14          printf("Caught a Fake_Exception!\n");

   0x08048659 <+71>:  movl   $0x8048971,(%esp)

   0x08048660 <+78>:  call   0x80484c0 <puts@plt>

 

15      }

16 

17      printf("try_but_dont_catch handled the exception\n");

   0x0804861e <+12>:  movl   $0x8048948,(%esp)

   0x08048625 <+19>:  call   0x80484c0 <puts@plt>

 

18  }

   0x0804862a <+24>:  add    $0x24,%esp

在_Unwind_的帮助下,现在我们能够获得关于当前栈帧的足够信息来决定是否可以处理异常,以及我们应该如何处理它。在我们可以检测我们希望的着陆垫前,需要更多一步:我们需要解析在函数末尾的CFI(调用帧信息)。这是DWARF规范的部分,gdb也用于调试目的,这不是容易实现的规范。就像对我们的ABI那样,我们把它维持在最小程度。

猜你喜欢

转载自blog.csdn.net/wuhui_gdnt/article/details/89230822