在学习了异常如何抛出后,现在我们正在学习如何捕捉它们的过程中。上次我们在我们的例子里添加了一组try/catch语句来它们做什么,果然我们得到了一组链接器错误,就像在我们尝试找出throw语句做什么时那样。在尝试处理throw.o时,链接器输出了这些:
|
|
备注:你可以从我的github repo下载完整的源代码。
当然,我们的理论是catch语句被编译器翻译为libstdc++里的一对__cxa_begin_catch/end_catch调用,加上我们现在一无所知的称为personality函数的东西。
我们从检查我们关于__cxa_begin_catch与__cxa_end_catch是否成立开始。我们使用-S编译throw.cpp并分析汇编代码。有很多可看,但如果我把它减到最小,这是我得到的:
|
|
目前还好:对raise()我们得到相同的旧定义,只是抛出一个异常:
|
|
Try_but_dont_catch()的定义,由编译器重整。不过有些新的东西:对__gxx_personality_v0以及称为LSDA对象的引用。这些看似无关的声明,但它们实际上相当重要:
- 链接器将根据CFI规范使用这些;CFI代表调用帧信息,这里是完整的规范。它主要用于栈展开。
- 另一方面LSDA表示语言特定数据区,它将由personality函数用来了解这个函数可以处理哪些异常。
在下一篇文章里我们将更多讨论CFI与LSDA;不要忘了它们,不过现在让我们继续:
|
|
另一件容易的事情:只是调用raise,然后跳转到L8;这个函数将正常返回到L8。如果raise不能正确执行,那么执行(不过,我们还不知道怎么做)不会在下一条指令继续,而是在异常处理句柄里继续(这在ABI术语里称为着陆垫,landing pads。稍后细说)。
|
|
这相当难理解,但它实际上相当直截了当。在这里大部分奇迹将会发生:首先我们检查这是否我们可以处理的异常,如果不能,我们通过调用_Unwind_Resume来表示,如果能,我们调用__cxa_begin_catch与__cxa_end_catch;在调用这些函数后,执行将正常继续,因此将执行L8(即,L8就在我们的catch块下面):
|
|
只是从我们函数的正常返回……带有一些CFI内容。
这就是异常捕捉,虽然我们尚不知道__cxa_begin/end_catch如何工作,我们有一个概念,这些对形成称为着陆垫的东西,函数里处理抛出异常的地方。我们尚不知道的是如何找到着陆垫。_Unwind_必须以某种方法经历栈上所有的调用,检查是否有一个调用(准确说,栈帧)带有可以捕捉异常、并在那里重新开始执行的着陆垫的有效try块。
这是一个不小的壮举,我们将在下一次看它是如何工作的。