C++异常的幕后11:阅读CFI表

原文地址:https://monoinfinito.wordpress.com/2013/04/11/c-exceptions-under-the-hood-11-reading-a-cfi-table/

作者:nicolasbrailo

要从我们已经为我们的ABI实现的personality函数里正确处理异常,我们需要阅读LSDA(语言特定数据区)来了解哪个调用帧(即哪个函数)可以处理哪个异常,以及了解哪里可以找到着陆垫(catch块))。LSDA是CFI格式的,我们将在本章里学习如何读它。

读CFI数据是相当直截了当的,但有一些我们需要首先考虑的陷阱。实际上,两个:

  1. 关于.gcc_except_table格式的文档非常少(实际上,我仅找到关于它的几封邮件),因此我们将需要读大量的源代码,并反汇编来理解它。
  2. 虽然格式本身不是特别复杂,它使用一个使得读这个表不那么直截了当的LEB编码。

就我所知,大多数DWARF代价像这样编码,使用LEB格式,对一头雾水的程序员看起来不错,在编码任意长的整数时节省代码空间。幸运地,在这里我们可以耍个小花招:大多数时间里,可以uint8_t来读LEB编码的数字,因为我们不准备处理大的异常表或任何类似的东西。

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

让我们直接从汇编分析CFI数据,然后看我们是否能构建某些在我们的personality函数上读它的东西。我将重命名这些标记,使它们对我们更友好些。LSDA有三部分,试着在下面找出它们:

1

2

3

4

.local_frame_entry:

    .globl  __gxx_personality_v0

    .section    .gcc_except_table,"a",@progbits

    .align 4

这个非常简单:它只是声明我们将使用__gxx_personality_v0作为全局对象,并让链接器知道我们准备为.gcc_except_table节声明内容的头部。继续:

1

2

3

4

5

6

7

8

9

10

11

.local_lsda_1:

    # This declares the encoding type. We don't care.

    .byte   0xff

 

    # This specifies the landing pads start; if zero, the func's ptr is

    # assumed (_Unwind_GetRegionStart)

    .byte   0

 

    # Length of the LSDA area: check that LLSDATT1 and LLSDATTD1 point to the

    # end and the beginning of the LSDA, respectively

    .uleb128 .local_lsda_end - .local_lsda_call_site_table_header

现在这有更多一些信息。这些标签相当模糊,但确实遵循一个模式。LSDA表示语言特定数据区,前面的L表示本地,因此这是本地(对于编译单元,.o文件)语言特定数据区编号1。其他标记遵循类似的模式,但我还没有时间把它们算出来。不过,我们确实不需要。

1

2

3

4

5

6

.local_lsda_call_site_table_header:

    # Encoding of items in the landing pad table. Again, we don't care.

    .byte   0x1.

 

    # The length of the call site table (ie the landing pads)

    .uleb128 .local_lsda_call_site_table_end - .local_lsda_call_site_table

另一个单调的头。继续:

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:

这有趣得多,现在我们看到调用表本身。不管怎样,在所有这些项里,我们应该能够找到我们的着陆垫。根据一些网页,每个调用项的格式应该是:

1

2

3

4

5

6

7

8

9

10

11

12

13

struct lsda_call_site_entry {

    // Start of the IP range

    size_t cs_start;

 

    // Length of the IP range

    size_t cs_len;

 

    // Landing pad address

    size_t cs_lp;

 

    // Offset into action table

    size_t cs_action;

};

看起来了我们在正轨上,虽然我们还不知道为什么在我们仅定义了一个着陆垫时,有3个调用项。在任何情形里,我们可以耍点小花招:通过查看汇编,我们可以推断CFI上的所有值将小于128,这意味着在LEB编码中,它们可以作为uchar来读。这使得我们读CFI的代码大为容易,下次我们将看到如何在personality函数里使用它。

猜你喜欢

转载自blog.csdn.net/wuhui_gdnt/article/details/89540585