[译]C++异常的幕后12:C++里的突然反射

栏目: C++ · 发布时间: 5年前

内容简介:我们的小ABI项目(让我们回顾一下:我们弄明白了我们用于有我们希望运行捕捉的函数的LSDA具有下面的调用表(即,下面着陆垫【即,下面的catch块】):

作者: nicolasbrailo

我们的小ABI项目( 链接 )能够抛出异常了,现在我们致力于捕捉它们;上次我们实现了一个能够检测及处理异常的personality函数,但它仍然有点不完整:即使在应该停止时它能正确地通知栈回滚器,但我们版本的__gxx_personality_v0不能执行catch块里的代码。上次我们学会了如何读LSDA,因此现在仅有的问题是,把各部分拼起来,在我们的personality函数里读.gcc_except_table。

让我们回顾一下:我们弄明白了我们用于有我们希望运行捕捉的函数的LSDA具有下面的调用表(即,下面着陆垫【即,下面的catch块】):

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:

在我们函数的汇编代码里,所有这些表可以被映射到不同的位置,但对一篇博文这有点太混乱了(我建议你自己反汇编函数并尝试匹配每个标记,这样做可以学到很多东西)。同样,归功于某些网页,我们学到了这个表的格式。

让我们这样做来看我们是否在正轨上(小心读对齐问题,记住像这样定义CFI仅能对uint8工作,并可能不可移植):

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

70

71

72

73

74

struct LSDA_Header {

    uint8_t lsda_start_encoding;

    uint8_t lsda_type_encoding;

    uint8_t lsda_call_site_table_length;

};

 

struct LSDA_Call_Site_Header {

    uint8_t encoding;

    uint8_t length;

};

 

struct LSDA_Call_Site {

   

    LSDA_Call_Site(const uint8_t *ptr) {

        cs_start = ptr[0];

        cs_len = ptr[1];

        cs_lp = ptr[2];

        cs_action = ptr[3];

    }

 

    uint8_t cs_start;

    uint8_t cs_len;

    uint8_t cs_lp;

    uint8_t cs_action;

};

 

_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");

 

        const uint8_t* lsda = (const uint8_t*)

                                    _Unwind_GetLanguageSpecificData(context);

 

        LSDA_Header *header = (LSDA_Header*)(lsda);

        LSDA_Call_Site_Header *cs_header = (LSDA_Call_Site_Header*)

                                                (lsda + sizeof(LSDA_Header));

 

        size_t cs_in_table = cs_header->length / sizeof(LSDA_Call_Site);

 

        // We must declare cs_table_base as uint8, otherwise we risk an

        // unaligned access

        const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header)

                                            + sizeof(LSDA_Call_Site_Header);

 

        // Go through every entry on the call site table

        for (size_t i=0; i < cs_in_table; ++i)

        {

            const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)];

            LSDA_Call_Site cs(offset);

            printf("Found a CS:\n");

            printf("\tcs_start: %i\n", cs.cs_start);

            printf("\tcs_len: %i\n", cs.cs_len);

            printf("\tcs_lp: %i\n", cs.cs_lp);

            printf("\tcs_action: %i\n", cs.cs_action);

        }

 

        uintptr_t ip = _Unwind_GetIP(context);

        uintptr_t funcStart = _Unwind_GetRegionStart(context);

        uintptr_t ipOffset = ip - funcStart;

 

 

        return _URC_INSTALL_CONTEXT;

    } else {

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

        return _URC_FATAL_PHASE1_ERROR;

    }

}

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

正如你看到的,如果你运行这个代码,调用表里所有的项是相关的。与什么相关?函数的开头。这意味着如果我们希望得到特定着陆垫的EIP,我们要做的是_Unwind_GetRegionStart + LSDA_Call_Site.cs_lp!

现在我们应该能解决我们的异常问题:让我们尝试修改我们的personality函数来运行正确的着陆垫。现在我们需要恢复执行:_Unwind_SetIP。让我们再次修改personality函数来运行第一个可用的着陆垫,这通过查看汇编我们已经知道我们想要的那个:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

...

const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header)

                                    + sizeof(LSDA_Call_Site_Header);

for (size_t i=0; i < cs_in_table; ++i)

{

    const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)];

    LSDA_Call_Site cs(offset);

 

    if (cs.cs_lp)

    {

        uintptr_t func_start = _Unwind_GetRegionStart(context);

        _Unwind_SetIP(context, func_start + cs.cs_lp);

        break;

    }

}

 

return _URC_INSTALL_CONTEXT;

尝试运行它,看到一个漂亮的无限循环。你能猜到哪里错了?答案在下一篇。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

我的第一本编程书

我的第一本编程书

[日]平山尚 / 张沈宇 / 人民邮电出版社 / 2016-7 / 79.00元

写这本书之前,作者一直在摸索一种最有利于入门者学编程的方法,并应用到教学当中。经过两年的教学实践,他确信他的方法是有效的,于是便有了这本书。这本书面向的是完全没有接触过编程的读者。作者将门槛设置得非常低,读者不需要懂得变量、函数这些名词(这些名词在书中也不会出现),不需要会英语,完全不需要查阅其他书籍,只需要小学算术水平即可。这本书给初学者非常平缓的学习曲线,有利于为之后的进阶学习打下坚实的基础。一起来看看 《我的第一本编程书》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具