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。
1条答案
按热度按时间ux6nzvsh1#
根据文件:
这会指示连接器将所有符号加入至动态符号表,而不只是使用过的符号。
这些不是调试符号,而是动态链接器符号。
strip
不会删除这些符号,因为它(在大多数情况下)会破坏可执行文件--它们被运行时链接器用来完成可执行文件的最后链接阶段。示例:
不使用
-rdynamic
进行编译和链接(显然也没有优化)因此,可执行文件中包含了
.symtab
。但是请注意,.dynsym
根本没有提到foo
--它只包含了基本信息。这还不足以让backtrace_symbols
工作。它依赖于该部分中的信息来匹配代码地址和函数名。现在使用
-rdynamic
进行编译:对于
.symtab
中的符号也是如此,但是现在foo
在动态符号部分有了一个符号(还有一堆其他符号),这使得backtrace_symbols
可以工作--它现在有足够的信息(在大多数情况下)来Map代码地址和函数名。剥离:
现在
.symtab
已经消失了,但是动态符号表还在,并且可执行文件还在运行,所以backtrace_symbols
仍然可以工作。去除动态符号表:
...您会得到一个损坏的可执行文件。
这里有一个有趣的关于
.symtab
和.dynsym
用途的阅读:Inside ELF Symbol Tables。需要注意的一点是,运行时不需要.symtab
,因此加载程序会将其丢弃。该部分不会保留在进程的内存中。另一方面,运行时 * 需要.dynsym
,所以它被保存在进程映像中,所以像backtrace_symbols
这样的东西可以从它自己内部收集关于当前进程的信息。所以简而言之:
strip
不会去除动态符号,因为这会使可执行文件不可加载backtrace_symbols
需要动态符号来确定代码属于哪个函数backtrace_symbols
不使用调试符号所以你才注意到这种行为。
对于您的具体问题:
gdb
是一个调试器。它使用可执行文件和库中的调试信息来显示相关信息。它比backtrace_symbols
* 复杂 * 得多,除了实时进程外,它还检查驱动器上的实际文件。backtrace_symbols
不这样做,它完全是进程内的-所以它不能访问没有加载到可执行映像中的部分。调试部分没有加载到运行时映像中,所以它不能使用它们。.dynsym
不是调试节。它是动态链接器使用的节。.symbtab
也不是调试节,但可以由有权访问可执行文件的调试器使用(和库)文件。-rdynamic
* 不会 * 生成调试部分,从-rdynamic
开始的可执行文件的增长完全取决于该可执行文件中的符号数(以及对齐/填充注意事项)。它应远小于-g
。1.除了静态链接的二进制文件外,可执行文件需要在加载时解析外部依赖项。例如链接
printf
和一些来自C库的应用程序启动过程。这些外部符号必须在可执行文件中的某个地方指明:这就是.dynsym
的用途,这也是为什么即使您没有指定-rdynamic
,exe也会有一个.dynsym
。当您指定它时,链接器会添加其他符号,这些符号对于进程的工作不是必需的,但可以被backtrace_symbols
之类的东西使用。1.如果静态链接,
backtrace_symbols
将不会解析任何函数名。即使指定-rdynamic
,.dynsym
节也不会发出到可执行文件。没有符号表会加载到可执行文件映像中,因此backtrace_symbols
无法将代码地址Map到符号。