C++异常的幕后5:围绕__cxa_begin_catch与__cxa_end_catch的魔术

原文地址:https://monoinfinito.wordpress.com/2013/03/05/c-exceptions-under-the-hood-5-magic-around-__cxa_begin_catch-and-__cxa_end_catch/

作者:nicolasbrailo 

在学习了异常如何抛出后,现在我们正在学习如何捕捉它们的过程中。上次我们在我们的例子里添加了一组try/catch语句来它们做什么,果然我们得到了一组链接器错误,就像在我们尝试找出throw语句做什么时那样。在尝试处理throw.o时,链接器输出了这些:

1

2

3

4

5

6

7

8

9

10

11

12

13

> g++ -c -o throw.o -O0 -ggdb throw.cpp

> gcc main.o throw.o mycppabi.o -O0 -ggdb -o app

throw.o: In function `try_but_dont_catch()':

throw.cpp:12: undefined reference to `__cxa_begin_catch'

throw.cpp:12: undefined reference to `__cxa_end_catch'

 

throw.o: In function `catchit()':

throw.cpp:20: undefined reference to `__cxa_begin_catch'

throw.cpp:20: undefined reference to `__cxa_end_catch'

 

throw.o:(.eh_frame+0x47): undefined reference to `__gxx_personality_v0'

 

collect2: ld returned 1 exit status

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

当然,我们的理论是catch语句被编译器翻译为libstdc++里的一对__cxa_begin_catch/end_catch调用,加上我们现在一无所知的称为personality函数的东西。

我们从检查我们关于__cxa_begin_catch__cxa_end_catch是否成立开始。我们使用-S编译throw.cpp并分析汇编代码。有很多可看,但如果我把它减到最小,这是我得到的:

1

2

3

_Z5raisev:

    call    __cxa_allocate_exception

    call    __cxa_throw

目前还好:对raise()我们得到相同的旧定义,只是抛出一个异常:

1

2

3

4

_Z18try_but_dont_catchv:

    .cfi_startproc

    .cfi_personality 0,__gxx_personality_v0

    .cfi_lsda 0,.LLSDA1

Try_but_dont_catch()的定义,由编译器重整。不过有些新的东西:对__gxx_personality_v0以及称为LSDA对象的引用。这些看似无关的声明,但它们实际上相当重要:

  • 链接器将根据CFI规范使用这些;CFI代表调用帧信息,这里是完整的规范。它主要用于栈展开。
  • 另一方面LSDA表示语言特定数据区,它将由personality函数用来了解这个函数可以处理哪些异常。

在下一篇文章里我们将更多讨论CFI与LSDA;不要忘了它们,不过现在让我们继续:

1

2

3

[...]

call    _Z5raisev

jmp .L8

另一件容易的事情:只是调用raise,然后跳转到L8;这个函数将正常返回到L8。如果raise不能正确执行,那么执行(不过,我们还不知道怎么做)不会在下一条指令继续,而是在异常处理句柄里继续(这在ABI术语里称为着陆垫,landing pads。稍后细说)。

1

2

3

4

5

6

7

8

9

10

    cmpl    $1, %edx

    je  .L5

 

.LEHB1:

    call    _Unwind_Resume

.LEHE1:

 

.L5:

    call    __cxa_begin_catch

    call    __cxa_end_catch

这相当难理解,但它实际上相当直截了当。在这里大部分奇迹将会发生:首先我们检查这是否我们可以处理的异常,如果不能,我们通过调用_Unwind_Resume来表示,如果能,我们调用__cxa_begin_catch与__cxa_end_catch;在调用这些函数后,执行将正常继续,因此将执行L8(即,L8就在我们的catch块下面):

1

2

3

4

5

6

.L8:

    leave

    .cfi_restore 5

    .cfi_def_cfa 4, 4

    ret

    .cfi_endproc

只是从我们函数的正常返回……带有一些CFI内容。

这就是异常捕捉,虽然我们尚不知道__cxa_begin/end_catch如何工作,我们有一个概念,这些对形成称为着陆垫的东西,函数里处理抛出异常的地方。我们尚不知道的是如何找到着陆垫。_Unwind_必须以某种方法经历栈上所有的调用,检查是否有一个调用(准确说,栈帧)带有可以捕捉异常、并在那里重新开始执行的着陆垫的有效try块。

这是一个不小的壮举,我们将在下一次看它是如何工作的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值