c++ - (.eh) 在 nm 输出中意味着什么?

标签 c++ nm symbol-table

当我查看我的库 nm mylib.a 中的符号时,我看到一些重复的条目,如下所示:

000000000002d130 S __ZN7quadmat11SpAddLeavesC1EPNS_14BlockContainerEPy
00000000000628a8 S __ZN7quadmat11SpAddLeavesC1EPNS_14BlockContainerEPy.eh

当通过 c++filt 进行管道传输时:

000000000002d130 S quadmat::SpAddLeaves::SpAddLeaves(quadmat::BlockContainer*, unsigned long long*)
00000000000628a8 S quadmat::SpAddLeaves::SpAddLeaves(quadmat::BlockContainer*, unsigned long long*) (.eh)

.eh 是什么意思,这个额外的符号有什么用?

我看到它与异常处理有关。但是为什么要使用额外的符号呢?

(我用 clang 注意到了这一点)

最佳答案

下面是一些简单的代码:

bool extenrnal_variable;

int f(...)
{
    if (extenrnal_variable)
        throw 0;

    return 42;
}

int g()
{
    return f(1, 2, 3);
}

我添加了 extenrnal_variable 以防止编译器优化所有分支。 f... 来防止内联。

编译时:

$ clang++ -S -O3 -m32 -o - eh.cpp | c++filt

它为 g() 发出以下代码(其余部分省略):

g():                                 ## @_Z1gv
    .cfi_startproc
## BB#0:
    pushl   %ebp
Ltmp9:
    .cfi_def_cfa_offset 8
Ltmp10:
    .cfi_offset %ebp, -8
    movl    %esp, %ebp
Ltmp11:
    .cfi_def_cfa_register %ebp
    subl    $24, %esp
    movl    $3, 8(%esp)
    movl    $2, 4(%esp)
    movl    $1, (%esp)
    calll   f(...)
    movl    $42, %eax
    addl    $24, %esp
    popl    %ebp
    ret
    .cfi_endproc

所有这些 .cfi_* 指令都用于在抛出异常时展开堆栈。它们都编译成一个FDE(Frame Description Entry) block 并保存在g().eh(__Z1gv.eh mangled)名称下。这些指令指定 CPU 寄存器在堆栈中的保存位置。当抛出异常并且正在展开堆栈时,不应执行函数中的代码(局部变量的析构函数除外),但应恢复先前保存的寄存器。这些表准确地存储了该信息。

这些表可以通过 dwarfdump 工具转储:

$ dwarfdump --eh-frame --english eh.o | c++filt

输出:

0x00000018: FDE
        length: 0x00000018
   CIE_pointer: 0x00000000
    start_addr: 0x00000000 f(...)
    range_size: 0x0000004d (end_addr = 0x0000004d)
  Instructions: 0x00000000: CFA=esp+4     eip=[esp]
                0x00000001: CFA=esp+8     ebp=[esp]  eip=[esp+4]
                0x00000003: CFA=ebp+8     ebp=[ebp]  eip=[ebp+4]
                0x00000007: CFA=ebp+8     ebp=[ebp]  esi=[ebp-4]  eip=[ebp+4]

0x00000034: FDE
        length: 0x00000018
   CIE_pointer: 0x00000000
    start_addr: 0x00000050 g()
    range_size: 0x0000002c (end_addr = 0x0000007c)
  Instructions: 0x00000050: CFA=esp+4     eip=[esp]
                0x00000051: CFA=esp+8     ebp=[esp]  eip=[esp+4]
                0x00000053: CFA=ebp+8     ebp=[ebp]  eip=[ebp+4]

Here你可以找到这个 block 的格式。 Here更多和一些替代的更紧凑的方式来表示相同的信息。基本上这个 block 描述了哪些寄存器以及在堆栈展开期间从堆栈中弹出的位置。

要查看这些符号的原始内容,您可以列出所有符号及其偏移量:

$ nm -n eh.o

00000000 T __Z1fz
         U __ZTIi
         U ___cxa_allocate_exception
         U ___cxa_throw
00000050 T __Z1gv
000000a8 s EH_frame0
000000c0 S __Z1fz.eh
000000dc S __Z1gv.eh
000000f8 S _extenrnal_variable

然后转储 (__TEXT,__eh_frame) 部分:

$ otool -s __TEXT __eh_frame eh.o

eh.o:
Contents of (__TEXT,__eh_frame) section
000000a8    14 00 00 00 00 00 00 00 01 7a 52 00 01 7c 08 01
000000b8    10 0c 05 04 88 01 00 00 18 00 00 00 1c 00 00 00
000000c8    38 ff ff ff 4d 00 00 00 00 41 0e 08 84 02 42 0d
000000d8    04 44 86 03 18 00 00 00 38 00 00 00 6c ff ff ff
000000e8    2c 00 00 00 00 41 0e 08 84 02 42 0d 04 00 00 00

通过匹配偏移量,您可以看到每个符号是如何编码的。

当存在局部变量时,它们必须在堆栈展开期间被销毁。为此,函数本身通常嵌入了更多代码,并创建了一些额外的更大的表。您可以通过将具有非平凡析构函数的局部变量添加到 g、编译并查看程序集输出来自行探索。

进一步阅读

关于c++ - (.eh) 在 nm 输出中意味着什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19376458/

相关文章:

c++ - 如何在 _T 包装器中使用变量?

c++ - 每个 undefined symbol 是否都与它来自的库名称相关联?

c - 如何在 C 中用 nm 或 readelf 输出区分静态函数

elf - 来自 elf 可执行文件的结构中元素的地址

C/symbol 中的 const 数组在使用 nm 编译的 *.o 文件中不可见

java - 在 Java 中创建符号表时在编译时考虑未知变量值

ruby - Ruby 中的符号表设计与实现

c++ - OpenCV idft() 和 MATLAB ifft2 的结果不匹配

c++ - 如何避免多态和 vector 的内存泄漏?

c++ - 将 vector<unsigned char> 转换为 vector<unsigned short>