原文地址:https://monoinfinito.wordpress.com/2013/04/23/c-exceptions-under-the-hood-14-multiple-landing-pads-the-teachings-of-the-guru/
作者:nicolasbrailo
在大量困难的工作后,上次我们最终得到了可工作的personality函数,它无需libstdc++就能处理异常。它将不加区分地处理所有异常,但它能工作。我们尚有一个大问题还没回答:如果我们回到LSDA(语言特定数据区),我们将看到像这样的东西:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | .local_lsda_call_site_table: .uleb128 .LEHB0-.LFB1 .uleb128 .LEHE0-.LEHB0 .uleb128 .L8-.LFB1 .uleb128 0x1 .uleb128 .LEHB1-.LFB1 .uleb128 .LEHE1-.LEHB1 .uleb128 0 .uleb128 0 .uleb128 .LEHB2-.LFB1 .uleb128 .LEHE2-.LEHB2 .uleb128 .L9-.LFB1 .uleb128 0 .local_lsda_call_site_table_end: |
这里定义了3个着陆垫,即使我们仅写了一条try/catch语句。这里发生了什么?
如果你仔细阅读这个话题的上一篇文章,你可能注意到我向struct LSDA_CS的定义增加了一些注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | struct LSDA_CS { // Note start, len and lp would be void*'s, but they are actually relative // addresses: start and lp are relative to the start of the function, len // is relative to start // Offset into function from which we could handle a throw uint8_t start; // Length of the block that might throw uint8_t len; // Landing pad uint8_t lp; // Offset into action table + 1 (0 means no action) // Used to run destructors uint8_t action; }; |
备注:你可以从我的github repo下载完整的源代码。
这里发生了一些有趣的事情,但让我们首先通过下面的例子按域分析这个结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void foo() { L0: try { do_something(); L1: } catch (const Exception1& ex) { ... } catch (const Exception2& ex) { ... } catch (const ExceptionN& ex) { ... } catch (...) { } L2: } |
- lp:从函数开头到着陆垫开头的偏移。下面例子的lp的值将是L1 – addr_of(foo)
- action:到action表的偏移。这用于在回滚栈的同时执行清理活动。我们还没研究到这一点,目前可以忽略它。
- start:从函数开头到try块开始的偏移:在这个例子里,这是L0 – addr_of(foo)
- len:try块的长度。在这个例子里这将是L1 – L0
现在感兴趣的域是start与len:在有多个try/catch块的函数里,通过检查当前帧的指令指针是否在start与start + len之间,我们可以知道是否应该处理一个异常。
这解决了带有多个try/catch块的函数如何可以处理异常的谜案,但我们仍然有另一个问题:为什么在我们仅声明一个着陆垫时,有三个调用点?另外三个是可能抛出异常的地方,因此它们被添加为清理活动或着陆垫可能的地方。如果我们从GOTW学到了什么,那就是异常会在我们最意想不到的地方抛出。在调用表中对我们的抛出有一项,因为它是可能抛出的块;编译器也检测到了另外三个。
现在我们知道了start与field域作何用处,让我们修改personality函数,使得正确的着陆垫可以处理抛出的异常。前进吧。我的实现在下一篇文章里。