为啥 GCC 编译的 C 程序需要 .eh_frame 部分?

Posted

技术标签:

【中文标题】为啥 GCC 编译的 C 程序需要 .eh_frame 部分?【英文标题】:Why GCC compiled C program needs .eh_frame section?为什么 GCC 编译的 C 程序需要 .eh_frame 部分? 【发布时间】:2014-12-05 16:56:25 【问题描述】:

测试在 32 位 x86 Linux 上使用gcc 4.6.3

当使用gcc 编译C 程序并使用readelf 检查节信息时, 我可以看到里面的.eh_frame 部分和.eh_frame_hdr 部分。

例如这里是二进制程序Perlbench的section info。

readelf -S perlbench

There are 28 section headers, starting at offset 0x102e48:

Section Headers:
[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[ 0]                   NULL            00000000 000000 000000 00      0   0  0
[ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
[ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
[ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
[ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000044 04   A  5   0  4
[ 5] .dynsym           DYNSYM          080481f0 0001f0 0007b0 10   A  6   1  4
[ 6] .dynstr           STRTAB          080489a0 0009a0 0003d6 00   A  0   0  1
[ 7] .gnu.version      VERSYM          08048d76 000d76 0000f6 02   A  5   0  2
[ 8] .gnu.version_r    VERNEED         08048e6c 000e6c 0000a0 00   A  6   2  4
[ 9] .rel.dyn          REL             08048f0c 000f0c 000028 08   A  5   0  4
[10] .rel.plt          REL             08048f34 000f34 000388 08   A  5  12  4
[11] .init             PROGBITS        080492bc 0012bc 00002e 00  AX  0   0  4
[12] .plt              PROGBITS        080492f0 0012f0 000720 04  AX  0   0 16
[13] .text             PROGBITS        08049a10 001a10 0cf86c 00  AX  0   0 16
[14] .fini             PROGBITS        0811927c 0d127c 00001a 00  AX  0   0  4
[15] .rodata           PROGBITS        081192a0 0d12a0 017960 00   A  0   0 32
[16] .eh_frame_hdr     PROGBITS        08130c00 0e8c00 003604 00   A  0   0  4
[17] .eh_frame         PROGBITS        08134204 0ec204 01377c 00   A  0   0  4
[18] .ctors            PROGBITS        08148f0c 0fff0c 000008 00  WA  0   0  4
[19] .dtors            PROGBITS        08148f14 0fff14 000008 00  WA  0   0  4
[20] .jcr              PROGBITS        08148f1c 0fff1c 000004 00  WA  0   0  4
[21] .dynamic          DYNAMIC         08148f20 0fff20 0000d0 08  WA  6   0  4
[22] .got              PROGBITS        08148ff0 0ffff0 000004 04  WA  0   0  4
[23] .got.plt          PROGBITS        08148ff4 0ffff4 0001d0 04  WA  0   0  4
[24] .data             PROGBITS        081491e0 1001e0 002b50 00  WA  0   0 32
[25] .bss              NOBITS          0814bd40 102d30 002b60 00  WA  0   0 32
[26] .comment          PROGBITS        00000000 102d30 00002a 01  MS  0   0  1
[27] .shstrtab         STRTAB          00000000 102d5a 0000ec 00      0   0  1

据我了解,这两个部分用于处理异常,它生成描述如何展开堆栈的表格。

但是对于C++程序,他们使用eh_framegcc_exception_table部分来管理异常,那么为什么编译器将eh_frameeh_frame_hdr部分放在ELF中编译自C程序?

【问题讨论】:

x86-64 ABI 要求.eh frames 无处不在。添加它是为了允许在任何地方展开堆栈,因为这在某些情况下是系统范围内需要的,例如在分析工具中。因此,x86_64 上的 GCC 默认生成 EH 帧并尝试使其在任何地方都精确,而在大多数其他 ABI 上,它仅在需要 EH 帧时才默认生成 EH 帧,并且仅在可能触发 EH 和从而放松。 我怀疑gcc 也使用它们来实现__attribute__((cleanup(..))) What's the use of the .eh_frame section in C programs?的可能重复 “那个问题的参考?”我不明白你的意思。你是说cmets吗?为什么问题不一样?我可能错了。反过来关闭是另一种可能性:这个姿势稍微好一点,但另一个是更老、更艰难的选择。关于用户名,留下讨论元或推特。干杯。 【参考方案1】:

首先,这主要是出于政治原因——添加基于 DWARF 的展开 (.eh_frame) 的人希望它成为一个始终存在的功能,以便它可以用于实现其他各种东西不仅仅是 C++ 异常,包括:

backtrace() __attribute__((__cleanup__(f))) __builtin_return_address(n),为n>0 pthread_cleanup_push,按照__attribute__((__cleanup__(f)))实现 ...

但是,如果您不需要任何这些东西,.eh_frame 就像将.text 的大小增加 15-30% 一样,没有任何好处。您可以为单个翻译单元禁用-fno-asynchronous-unwind-tables 生成.eh_frame,这主要消除了大小成本,尽管您仍然有一些来自crtbegin.o 等的剩余部分。您不能稍后使用strip 命令剥离它们;因为.eh_frame 是一个存在于程序加载部分的部分(这是重点),所以剥离它会以在运行时破坏二进制文件的方式修改二进制文件。请参阅https://sourceware.org/bugzilla/show_bug.cgi?id=14037 了解如何破坏的示例。

请注意,DWARF 表也用于调试,但出于此目的,它们不需要位于程序的可加载部分。使用-fno-asynchronous-unwind-tables 不会中断调试,因为只要将-g 也传递给编译器,表格仍然会生成;它们只是存储在二进制文件的一个单独的、不可加载的、可剥离的部分.debug_frame

【讨论】:

一个小修正:crtbegin.o 不包含 CFI,至少在 RedHat 和 Ubuntu 上是这样。剩菜很可能来自__libc_csu_init__libc_csu_fini 的CFI。这些不在crtbegin.o 中,而是在 GLIBC libc_nonshared.a 的非共享部分中 嗨,我想知道eh_frame 的使用是否是GCC 指定的功能?我尝试使用clanggcc 编译一段C 代码。 GCC 生成的eh_frame 部分在0x5cfc 附近,而对于clang,它只有a4 @PeterCordes:那是 UB,即使不是,它也几乎肯定会违反 C 库的约定——大多数使用回调的 C 库代码都假定回调返回而不是 longjmp,并将异常传播出去相当于longjmp。如果使用异常的 C++ 代码作为 C 的回调调用,则回调需要捕获所有异常并将其转换为错误返回给 C 调用者。 @PeterCordes:好吧,“C++ 异常应该能够通过 C 传播,即使它是未定义的行为”政治立场(和 IMO由于我上面描述的原因,它是站不住脚的——即使它被定义了,它也只能用于非常纯的 C 函数,而无需清理错误)。 @PeterCordes:回复:文本段和实际与虚拟内存的使用情况,你是对的,在具有 mmu 的系统上,仅仅因为文本被映射并不意味着它必须被加载。在这种情况下,浪费的只是存储空间(这仍然很烦人,因为要上传的 Docker 映像要大 30%,但不会那么糟糕)。然而,在没有 mmu 的系统上,整个映射文本必须在内存中。甚至在带有 mmu 的 32 位系统上,虚拟地址空间也是相当宝贵的资源;用不需要的垃圾填满它意味着malloc 会更快失败。

以上是关于为啥 GCC 编译的 C 程序需要 .eh_frame 部分?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用gcc编译时总显示找不到文件

kali linux的gcc编译完的C语言小程序,为啥执行后显示段错误?求各位大神的说明或解决方法。

急!LINUX下,GCC编译,原程序包含<semaphore.h>头文件,为啥编译时说sem_wait,sem_post等未定义的引用

win10我安装了gcc编译器可以运行gcc命令,为啥不能使用g++命令呢?如图

mac 使用gcc 为啥编译错误是clang 提示

为啥只有注释更改的两个程序二进制文件在 gcc 中不完全匹配?