【问题标题】:gcc debug symbols (-g flag) vs linker's -rdynamic optiongcc 调试符号(-g 标志)与链接器的 -rdynamic 选项
【发布时间】:2012-01-27 06:36:56
【问题描述】:

glibc 提供backtrace()backtrace_symbols() 来获取正在运行的程序的堆栈跟踪。但要使其正常工作,必须使用链接器的 -rdynamic 标志构建程序。

传递给 gcc 的 -g 标志与链接器的 -rdynamic 标志有什么区别?对于示例代码,我做了 readelf 来比较输出。 -rdynamic 似乎在Symbol table '.dynsym' 下产生了更多信息,但我不太确定附加信息是什么。

即使我strip 使用-rdynamic 构建的程序二进制文件,backtrace_symbols() 继续工作。

strip 从二进制文件中删除所有符号时,为什么它会留下-rdynamic 标志添加的任何符号?

编辑:基于下面 Mat 的回答的后续问题..

对于您使用的相同示例代码,这是我看到的 -g-rdynamic 的区别

没有任何选择..

    Symbol table '.dynsym' contains 4 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 0000000000000000   218 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
         2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
         3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

    Symbol table '.symtab' contains 70 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 0000000000400200     0 SECTION LOCAL  DEFAULT    1 
         2: 000000000040021c     0 SECTION LOCAL  DEFAULT    2 

-g 有更多部分,.symtab 表中有更多条目,但.dynsym 保持不变..

      [26] .debug_aranges    PROGBITS         0000000000000000  0000095c
           0000000000000030  0000000000000000           0     0     1
      [27] .debug_pubnames   PROGBITS         0000000000000000  0000098c
           0000000000000023  0000000000000000           0     0     1
      [28] .debug_info       PROGBITS         0000000000000000  000009af
           00000000000000a9  0000000000000000           0     0     1
      [29] .debug_abbrev     PROGBITS         0000000000000000  00000a58
           0000000000000047  0000000000000000           0     0     1
      [30] .debug_line       PROGBITS         0000000000000000  00000a9f
           0000000000000038  0000000000000000           0     0     1
      [31] .debug_frame      PROGBITS         0000000000000000  00000ad8
           0000000000000058  0000000000000000           0     0     8
      [32] .debug_loc        PROGBITS         0000000000000000  00000b30
           0000000000000098  0000000000000000           0     0     1

    Symbol table '.dynsym' contains 4 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 0000000000000000   218 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
         2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
         3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

    Symbol table '.symtab' contains 77 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000400200     0 SECTION LOCAL  DEFAULT    1 

-rdynamic 没有额外的调试部分,.symtab 条目为 70(与 vanilla gcc 调用相同),但更多 .dynsym 条目..

    Symbol table '.dynsym' contains 19 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
         1: 0000000000000000   218 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
         2: 00000000005008e8     0 OBJECT  GLOBAL DEFAULT  ABS _DYNAMIC
         3: 0000000000400750    57 FUNC    GLOBAL DEFAULT   12 __libc_csu_fini   
         4: 00000000004005e0     0 FUNC    GLOBAL DEFAULT   10 _init
         5: 0000000000400620     0 FUNC    GLOBAL DEFAULT   12 _start
         6: 00000000004006f0    86 FUNC    GLOBAL DEFAULT   12 __libc_csu_init   
         7: 0000000000500ab8     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
         8: 00000000004006de    16 FUNC    GLOBAL DEFAULT   12 main
         9: 0000000000500aa0     0 NOTYPE  WEAK   DEFAULT   23 data_start
        10: 00000000004007c8     0 FUNC    GLOBAL DEFAULT   13 _fini
        11: 00000000004006d8     6 FUNC    GLOBAL DEFAULT   12 foo
        12: 0000000000500ab8     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
        13: 0000000000500a80     0 OBJECT  GLOBAL DEFAULT  ABS _GLOBAL_OFFSET_TABLE_
        14: 0000000000500ac0     0 NOTYPE  GLOBAL DEFAULT  ABS _end
        15: 00000000004007d8     4 OBJECT  GLOBAL DEFAULT   14 _IO_stdin_used
        16: 0000000000500aa0     0 NOTYPE  GLOBAL DEFAULT   23 __data_start
        17: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
        18: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__    

    Symbol table '.symtab' contains 70 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000400200     0 SECTION LOCAL  DEFAULT    1 
         2: 000000000040021c     0 SECTION LOCAL  DEFAULT    2 

现在这些是我的问题..

  1. 在 gdb 中,您可以执行 bt 来获取 bactrace。如果这仅适用于 -g 为什么我们需要 -rdynamic 才能使 backtrace_symbols 工作?

  2. 比较.symtab-g 的添加以及.dynsym-rdynamic 的添加它们并不完全相同。与另一个相比,是否提供更好的调试信息? FWIW,产生的输出大小是这样的:with -g > with -rdynamic > with no option

  3. .dynsym 的具体用途是什么?是这个二进制文件导出的所有符号吗?在那种情况下,为什么 foo 进入 .dynsym 因为我们没有将代码编译为库。

  4. 如果我使用所有静态库链接我的代码,那么 backtrace_symbols 工作不需要 -rdynamic 吗?

【问题讨论】:

  • 不幸的是,回溯并不像大多数其他工具(包括 gdb、valgrind 等)那样使用调试符号 [如果可用]。

标签: c linux gcc linker shared-libraries


【解决方案1】:

根据文档:

这指示链接器将所有符号(不仅是使用的符号)添加到动态符号表中。

那些不是调试符号,它们是动态链接器符号。 strip 不会删除这些,因为它(在大多数情况下)会破坏可执行文件 - 运行时链接器使用它们来执行可执行文件的最后链接阶段。

例子:

$ cat t.c
void foo() {}
int main() { foo(); return 0; }

在没有-rdynamic 的情况下编译和链接(显然没有优化)

$ gcc -O0 -o t t.c
$ readelf -s t

Symbol table '.dynsym' contains 3 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

Symbol table '.symtab' contains 50 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000400270     0 SECTION LOCAL  DEFAULT    1 
....
    27: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS t.c
    28: 0000000000600e14     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_end
    29: 0000000000600e40     0 OBJECT  LOCAL  DEFAULT   21 _DYNAMIC

所以可执行文件的所有内容都有一个.symtab。但请注意,.dynsym 根本没有提及foo - 它包含基本要素。这不足以让backtrace_symbols 工作。它依赖于该部分中的信息来匹配代码地址和函数名称。

现在用-rdynamic编译:

$ gcc -O0 -o t t.c -rdynamic
$ readelf -s t

Symbol table '.dynsym' contains 17 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
     5: 0000000000601008     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
     6: 0000000000400734     6 FUNC    GLOBAL DEFAULT   13 foo
     7: 0000000000601028     0 NOTYPE  GLOBAL DEFAULT  ABS _end
     8: 0000000000601008     0 NOTYPE  WEAK   DEFAULT   24 data_start
     9: 0000000000400838     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    10: 0000000000400750   136 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    11: 0000000000400650     0 FUNC    GLOBAL DEFAULT   13 _start
    12: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    13: 000000000040073a    16 FUNC    GLOBAL DEFAULT   13 main
    14: 0000000000400618     0 FUNC    GLOBAL DEFAULT   11 _init
    15: 00000000004007e0     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    16: 0000000000400828     0 FUNC    GLOBAL DEFAULT   14 _fini

Symbol table '.symtab' contains 50 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000400270     0 SECTION LOCAL  DEFAULT    1 
....
    27: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS t.c
    28: 0000000000600e14     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_end
    29: 0000000000600e40     0 OBJECT  LOCAL  DEFAULT   21 _DYNAMIC

.symtab 中的符号也是如此,但现在 foo 在动态符号部分有一个符号(现在还有一堆其他符号)。这使得backtrace_symbols 工作 - 它现在有足够的信息(在大多数情况下)来映射代码地址和函数名称。

剥离:

$ strip --strip-all t
$ readelf -s t

Symbol table '.dynsym' contains 17 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
     5: 0000000000601008     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
     6: 0000000000400734     6 FUNC    GLOBAL DEFAULT   13 foo
     7: 0000000000601028     0 NOTYPE  GLOBAL DEFAULT  ABS _end
     8: 0000000000601008     0 NOTYPE  WEAK   DEFAULT   24 data_start
     9: 0000000000400838     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    10: 0000000000400750   136 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    11: 0000000000400650     0 FUNC    GLOBAL DEFAULT   13 _start
    12: 0000000000601018     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    13: 000000000040073a    16 FUNC    GLOBAL DEFAULT   13 main
    14: 0000000000400618     0 FUNC    GLOBAL DEFAULT   11 _init
    15: 00000000004007e0     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    16: 0000000000400828     0 FUNC    GLOBAL DEFAULT   14 _fini
$ ./t
$

现在.symtab 不见了,但动态符号表还在,可执行文件运行。所以backtrace_symbols 仍然有效。

剥离动态符号表:

$ strip -R .dynsym t
$ ./t
./t: relocation error: ./t: symbol , version GLIBC_2.2.5 not defined in file libc.so.6 with link time reference

...你得到一个损坏的可执行文件。

关于.symtab.dynsym 的用途的有趣读物在这里:Inside ELF Symbol Tables。需要注意的一件事是 .symtab 在运行时不需要,因此它被加载程序丢弃。该部分不会保留在进程的内存中。 .dynsym,另一方面,在运行时需要的,所以它保存在进程映像中。因此,backtrace_symbols 之类的东西可以从其内部收集有关当前进程的信息。

简而言之:

  • 动态符号不会被strip 剥离,因为这会使可执行文件不可加载
  • backtrace_symbols 需要动态符号来判断哪些代码属于哪个函数
  • backtrace_symbols 不使用调试符号

因此你注意到了这种行为。


对于您的具体问题:

  1. gdb 是一个调试器。它使用可执行文件和库中的调试信息来显示相关信息。它backtrace_symbols 复杂得多,除了实时进程之外,它还会检查驱动器上的实际文件。 backtrace_symbols 没有,它完全在进程中 - 因此它无法访问未加载到可执行映像中的部分。调试部分未加载到运行时映像中,因此无法使用它们。
  2. .dynsym 不是调试部分。它是动态链接器使用的部分。 .symbtab 也不是调试部分,但可以由有权访问可执行(和库)文件的调试器使用。 -rdynamic 生成调试部分,只生成扩展的动态符号表。 -rdynamic 的可执行文件增长完全取决于该可执行文件中的符号数量(以及对齐/填充注意事项)。它应该比-g 少很多。
  3. 除了静态链接的二进制文件,可执行文件需要在加载时解决外部依赖关系。就像链接printf 和C 库中的一些应用程序启动过程一样。这些外部符号必须在可执行文件的某处指明:这就是.dynsym 的用途,这就是为什么即使您没有指定-rdynamic,exe 也有.dynsym 的原因。当您指定它时,链接器会添加其他符号,这些符号对于进程工作来说不是必需的,但可以被 backtrace_symbols 之类的东西使用。
  4. 如果您静态链接,backtrace_symbols 将不会解析任何函数名称。即使您指定-rdynamic.dynsym 部分也不会发送到可执行文件。没有符号表被加载到可执行映像中,因此backtrace_symbols 无法将代码地址映射到符号。

【讨论】:

  • 感谢您的回复..但我仍然很困惑..让我将我的问题作为单独的回复输入,以便更好地格式化。
  • 不要发布答案作为后续,这不是本网站的工作方式。编辑您的原始问题(如果您的新问题足够不同,则发布一个新问题)。
  • @Santhosh:最后添加了一些信息,并试图澄清整个事情。 HTH。
  • 谢谢,马特。不久前,我用其他信息编辑了我的问题。
  • @Santhosh:已更新。另请阅读该链接,不要将调试信息与程序链接/加载和运行所需的信息混淆。
猜你喜欢
  • 1970-01-01
  • 2019-02-01
  • 2020-09-07
  • 2017-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多