C++异常的幕后15:找到正确的着陆垫

原文地址:https://monoinfinito.wordpress.com/2013/05/02/c-exceptions-under-the-hood-15-finding-the-right-landing-pad/

作者:nicolasbrailo

这是我在这个博客里所写的最长系列文章中的第15篇;目前我们已经了解到异常如何抛出,并且已经编写了通过某种反射,能够检测catch块(在异常术语里,着陆垫)在何处的personality函数。在上一篇文章里,我们编写了可以处理异常的personality函数,但它仅对栈上第一个调用帧的第一个着陆垫这样做。让我们稍作改进,使得personality函数能够在具有多个着陆垫的函数里选择正确的那个。

以一个TDD风格,我们首先为ABI构建一个测试。让我们修改我们的测试程序,throw.cpp,以拥有两个try/catch块:

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

31

32

33

34

35

36

37

38

39

40

41

42

#include <stdio.h>

#include "throw.h"

 

struct Fake_Exception {};

 

void raise() {

    throw Exception();

}

 

void try_but_dont_catch() {

    try {

        printf("Running a try which will never throw.\n");

    } catch(Fake_Exception&) {

        printf("Exception caught... with the wrong catch!\n");

    }

 

    try {

        raise();

    } catch(Fake_Exception&) {

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

    }

 

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

}

 

void catchit() {

    try {

        try_but_dont_catch();

    } catch(Fake_Exception&) {

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

    } catch(Exception&) {

        printf("Caught an Exception!\n");

    }

 

    printf("catchit handled the exception\n");

}

 

extern "C" {

    void seppuku() {

        catchit();

    }

}

在测试之前,尝试想一下运行这个测试时将会发生什么。关注在try_but_dont_catch函数:第一个try/catch块不抛出异常,而第二个块将抛出。因为我们的ABI相当蠢,第一个块将处理第二个块的异常。在第一个块处理完异常后将发生什么?执行将从catch/try结束的地方恢复,因而再次进入第二个try/catch块。无限循环!我们又重新发明了一个非常复杂的while(真的)。

让我们使用调用表(LSDA)中start/length域的知识来正确选择着陆垫。对此,我们需要知道在抛出异常是指令指针是什么,我们可以使用我们已经知道的_Unwind_函数:_Unwind_GetIP。要理解_Unwind_GetIP将返回什么,让我们看一个例子:

1

2

3

4

5

6

7

8

9

10

11

12

void f1() {}

void f2() { throw 1; }

void f3() {}

 

void foo() {

L1:

    try{ f1(); } catch(...) {}

L2:

    try{ f2(); } catch(...) {}

L3:

    try{ f3(); } catch(...) {}

}

在这个情形里,将对f2的catch块调用我们的personality函数,栈将像这样:

1

2

3

4

5

+---------------------------------+

|   IP: f2  stack frame: f2    |

+---------------------------------+

|   IP: L3 stack frame: foo   |

+---------------------------------+

注意IP将在L3,但异常将在L2抛出;这是因为IP将指向要执行的下一条指令。这也意味着我们需要减一,如果需要找出异常抛出处的IP,否则_Unwind_GetIP的结果将不在我们着陆垫的范围里。回到我们的personality函数:

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

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

_Unwind_Reason_Code __gxx_personality_v0 (

                             int version,

                             _Unwind_Action actions,

                             uint64_t exceptionClass,

                             _Unwind_Exception* unwind_exception,

                             _Unwind_Context* context)

{

    if (actions & _UA_SEARCH_PHASE)

    {

        printf("Personality function, lookup phase\n");

        return _URC_HANDLER_FOUND;

    } else if (actions & _UA_CLEANUP_PHASE) {

        printf("Personality function, cleanup\n");

 

        // Calculate what the instruction pointer was just before the

        // exception was thrown for this stack frame

        uintptr_t throw_ip = _Unwind_GetIP(context) - 1;

 

        // Pointer to the beginning of the raw LSDA

        LSDA_ptr lsda = (uint8_t*)_Unwind_GetLanguageSpecificData(context);

 

        // Read LSDA headerfor the LSDA

        LSDA_Header header(&lsda);

 

        // Read the LSDA CS header

        LSDA_CS_Header cs_header(&lsda);

 

        // Calculate where the end of the LSDA CS table is

        const LSDA_ptr lsda_cs_table_end = lsda + cs_header.length;

 

        // Loop through each entry in the CS table

        while (lsda < lsda_cs_table_end)

        {

            LSDA_CS cs(&lsda);

 

            // If there's no LP we can't handle this exception; move on

            if (not cs.lp) continue;

 

            uintptr_t func_start = _Unwind_GetRegionStart(context);

 

            // Calculate the range of the instruction pointer valid for this

            // landing pad; if this LP can handle the current exception then

            // the IP for this stack frame must be in this range

            uintptr_t try_start = func_start + cs.start;

            uintptr_t try_end = func_start + cs.start + cs.len;

 

            // Check if this is the correct LP for the current try block

            if (throw_ip < try_start) continue;

            if (throw_ip > try_end) continue;

 

            // We found a landing pad for this exception; resume execution

            int r0 = __builtin_eh_return_data_regno(0);

            int r1 = __builtin_eh_return_data_regno(1);

 

            _Unwind_SetGR(context, r0, (uintptr_t)(unwind_exception));

            // Note the following code hardcodes the exception type;

            // we'll fix that later on

            _Unwind_SetGR(context, r1, (uintptr_t)(1));

 

            _Unwind_SetIP(context, func_start + cs.lp);

            break;

        }

 

        return _URC_INSTALL_CONTEXT;

    } else {

        printf("Personality function, error\n");

        return _URC_FATAL_PHASE1_ERROR;

    }

}

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

再测试这个例子,没有无限循环了!这是一个简单改动,现在我们可以选择正确的着陆垫。下次我们将尝试使得我们的personality函数还能挑选正确的栈帧,而不是选择第一个。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值