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

h7wcgrx3  于 2022-11-13  发布在  其他
关注(0)|答案(1)|浏览(256)

glibc提供了backtrace()backtrace_symbols()来获取运行程序的堆栈跟踪。但是要实现这一点,程序必须使用链接器的-rdynamic标志来构建。
传递给gcc的-g标志与链接器的-rdynamic标志有什么区别?对于一个示例代码,我确实阅读了自己来比较输出。-rdynamic似乎在Symbol table '.dynsym'下产生了更多的信息,但我不太确定额外的信息是什么。
即使我用-rdynamic构建了一个strip程序二进制,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呢?
1.比较.symtab-g的加法运算,以及.dynsym-rdynamic的加法运算,它们并不完全相同..两者是否提供了更好的调试信息?FWIW,生成的输出大小如下所示:使用-g〉使用-rdynamic〉不使用任何选项
1..dynsym的确切用法是什么?它是这个二进制文件导出的所有符号吗?在这种情况下,为什么foo会进入.dynsym,因为我们没有将代码编译为库。
1.如果我使用所有静态库链接代码,那么backtrace_symbols就不需要使用-rdynamic。

ux6nzvsh

ux6nzvsh1#

根据文件:
这会指示连接器将所有符号加入至动态符号表,而不只是使用过的符号。
这些不是调试符号,而是动态链接器符号。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可以工作--它现在有足够的信息(在大多数情况下)来Map代码地址和函数名。
剥离:

$ 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
    1.除了静态链接的二进制文件外,可执行文件需要在加载时解析外部依赖项。例如链接printf和一些来自C库的应用程序启动过程。这些外部符号必须在可执行文件中的某个地方指明:这就是.dynsym的用途,这也是为什么即使您没有指定-rdynamic,exe也会有一个.dynsym。当您指定它时,链接器会添加其他符号,这些符号对于进程的工作不是必需的,但可以被backtrace_symbols之类的东西使用。
    1.如果静态链接,backtrace_symbols将不会解析任何函数名。即使指定-rdynamic.dynsym节也不会发出到可执行文件。没有符号表会加载到可执行文件映像中,因此backtrace_symbols无法将代码地址Map到符号。

相关问题