gcc调试符号(-g标志)vs链接器的-rdynamic选项

时间:2011-12-24 10:07:29

标签: c linux gcc linker shared-libraries

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>没有选项

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

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

1 个答案:

答案 0 :(得分:39)

根据文件:

  

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

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