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
现在这些是我的问题..
在gdb中你可以做bt来获得bactrace。如果仅适用于-g
,为什么我们需要-rdynamic
才能使backtrace_symbols正常工作?
将.symtab
的添加内容与-g
&进行比较添加.dynsym
与-rdynamic
它们不完全相同..是否有一个提供比另一个更好的调试信息?
FWIW,产生的输出大小如下:with -g> with -rdynamic>没有选项
.dynsym的用途究竟是什么?是这个二进制文件导出的所有符号吗?在那种情况下,为什么foo会进入.dynsym,因为我们没有将代码编译为库。
如果我使用所有静态库链接我的代码,那么backtrace_symbols不需要-rdynamic吗?
答案 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
不使用调试符号因此你注意到的行为。
针对您的具体问题:
gdb
是一个调试器。它使用可执行文件和库中的调试信息来显示相关信息。它比<{1}}更复杂 ,并检查驱动器上的实际文件以及实时进程。 backtrace_symbols
没有,它完全处于进程中 - 因此它无法访问未加载到可执行映像中的部分。调试部分未加载到运行时映像中,因此无法使用它们。backtrace_symbols
不是调试部分。它是动态链接器使用的部分。 .dynsym
也不是调试部分,但可以由可以访问可执行(和库)文件的调试器使用。 .symbtab
不会生成调试部分,只会生成扩展的动态符号表。 -rdynamic
的可执行增长完全取决于该可执行文件中的符号数(以及对齐/填充注意事项)。它应该远远小于-rdynamic
。-g
和C库中的一些应用程序启动过程一样。必须在可执行文件的某处指示这些外部符号:这是printf
的用法,即使您未指定.dynsym
,这也是exe .dynsym
的原因。当您指定它时,链接器会添加其他符号,这些符号对于该过程不起作用,但可以由-rdynamic
之类的内容使用。backtrace_symbols
将无法解析任何函数名称。即使您指定backtrace_symbols
,也不会将-rdynamic
部分发送到可执行文件。没有符号表被加载到可执行映像中,因此.dynsym
无法将代码地址映射到符号。