介绍 Linux elf 二进制文件的符号表(symtab)生成和管理机制。
Linux 使用 ELF 格式保存可执行二进制文件、共享库文件和 debuginfo 文件。
ELF 使用两个 Sections 来表示 symbol table:
- .dynsym:动态符号, dynamic symbols, which can be used by another program;
- .symtab:局部符号, local symbols used by the binary itself only;
.symtab 保存了程序中 标识符和内存地址的对应关系
,对于 uprobe/kprobe/stack unwinding
等至关重要,因为只有知道函数名才能追踪(tracing)。
注:symbol table 和 gcc -g 生成的调试符号表不是一回事。
1 生成符号表 #
gcc 编译时默认会生成这两个符号表, file
命令显示 not stripped
-g
表示同时生成调试符号表(以.debug
开头的 Sections,DWARF
格式),file 会显示with debug_info
;
root@lima-ebpf-dev:~# gcc -g test.c -o hello
root@lima-ebpf-dev:~# file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a513ea9e31dc7b8ffbf423f56a04fbff6c0e99a9, for GNU/Linux 3.2.0, with debug_info, not stripped
2 查看符号表 #
查看可执行程序/共享库/debuginfo 文件中的符号表:
- 使用
readelf -s/objdump -t
命令。
root@lima-ebpf-dev:~# readelf -S hello |grep sym
[ 6] .dynsym DYNSYM 00000000000003d8 000003d8
[35] .symtab SYMTAB 0000000000000000 000032d0
root@lima-ebpf-dev:~# readelf -s hello|grep Symbo
Symbol table '.dynsym' contains 7 entries:
Symbol table '.symtab' contains 37 entries:
# 符号表中有 hello 函数的地址
# readelf -s hello|grep hello
52: 000000000040057d 26 FUNC GLOBAL DEFAULT 13 hello
# readelf -s hello
Symbol table '.dynsym' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@GLIBC_2.2.5 (2)
Symbol table '.symtab' contains 65 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400238 0 SECTION LOCAL DEFAULT 1
2: 0000000000400254 0 SECTION LOCAL DEFAULT 2
3: 0000000000400274 0 SECTION LOCAL DEFAULT 3
4: 0000000000400298 0 SECTION LOCAL DEFAULT 4
5: 00000000004002b8 0 SECTION LOCAL DEFAULT 5
6: 0000000000400330 0 SECTION LOCAL DEFAULT 6
7: 0000000000400374 0 SECTION LOCAL DEFAULT 7
8: 0000000000400380 0 SECTION LOCAL DEFAULT 8
9: 00000000004003a0 0 SECTION LOCAL DEFAULT 9
10: 00000000004003b8 0 SECTION LOCAL DEFAULT 10
11: 0000000000400418 0 SECTION LOCAL DEFAULT 11
12: 0000000000400440 0 SECTION LOCAL DEFAULT 12
13: 0000000000400490 0 SECTION LOCAL DEFAULT 13
14: 0000000000400624 0 SECTION LOCAL DEFAULT 14
15: 0000000000400630 0 SECTION LOCAL DEFAULT 15
16: 0000000000400650 0 SECTION LOCAL DEFAULT 16
17: 0000000000400690 0 SECTION LOCAL DEFAULT 17
18: 0000000000600e10 0 SECTION LOCAL DEFAULT 18
19: 0000000000600e18 0 SECTION LOCAL DEFAULT 19
20: 0000000000600e20 0 SECTION LOCAL DEFAULT 20
21: 0000000000600e28 0 SECTION LOCAL DEFAULT 21
22: 0000000000600ff8 0 SECTION LOCAL DEFAULT 22
23: 0000000000601000 0 SECTION LOCAL DEFAULT 23
24: 0000000000601038 0 SECTION LOCAL DEFAULT 24
25: 000000000060103c 0 SECTION LOCAL DEFAULT 25
26: 0000000000000000 0 SECTION LOCAL DEFAULT 26
27: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
28: 0000000000600e20 0 OBJECT LOCAL DEFAULT 20 __JCR_LIST__
29: 00000000004004c0 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
30: 00000000004004f0 0 FUNC LOCAL DEFAULT 13 register_tm_clones
31: 0000000000400530 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
32: 000000000060103c 1 OBJECT LOCAL DEFAULT 25 completed.6355
33: 0000000000600e18 0 OBJECT LOCAL DEFAULT 19 __do_global_dtors_aux_fin
34: 0000000000400550 0 FUNC LOCAL DEFAULT 13 frame_dummy
35: 0000000000600e10 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_init_array_
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS test.c
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
38: 00000000004007a0 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
39: 0000000000600e20 0 OBJECT LOCAL DEFAULT 20 __JCR_END__
40: 0000000000000000 0 FILE LOCAL DEFAULT ABS
41: 0000000000600e18 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
42: 0000000000600e28 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
43: 0000000000600e10 0 NOTYPE LOCAL DEFAULT 18 __init_array_start
44: 0000000000400650 0 NOTYPE LOCAL DEFAULT 16 __GNU_EH_FRAME_HDR
45: 0000000000601000 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_
46: 0000000000400620 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
47: 0000000000601038 0 NOTYPE WEAK DEFAULT 24 data_start
48: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
49: 000000000060103c 0 NOTYPE GLOBAL DEFAULT 24 _edata
50: 0000000000400624 0 FUNC GLOBAL DEFAULT 14 _fini
51: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
52: 000000000040057d 26 FUNC GLOBAL DEFAULT 13 hello
53: 0000000000601038 0 NOTYPE GLOBAL DEFAULT 24 __data_start
54: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
55: 0000000000400638 0 OBJECT GLOBAL HIDDEN 15 __dso_handle
56: 0000000000400630 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
57: 00000000004005b0 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init
58: 0000000000601040 0 NOTYPE GLOBAL DEFAULT 25 _end
59: 0000000000400490 0 FUNC GLOBAL DEFAULT 13 _start
60: 000000000060103c 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
61: 0000000000400597 16 FUNC GLOBAL DEFAULT 13 main
62: 0000000000601040 0 OBJECT GLOBAL HIDDEN 24 __TMC_END__
63: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@@GLIBC_2.2.5
64: 0000000000400418 0 FUNC GLOBAL DEFAULT 11 _init
查看内核符号表:
#grep -E ' (t|T) ' /proc/kallsyms |head
ffffffffa0000000 T startup_64
ffffffffa0000000 T _stext
ffffffffa0000000 T _text
ffffffffa0000030 T secondary_startup_64
ffffffffa00000e0 T verify_cpu
ffffffffa00001e0 T start_cpu0
ffffffffa00001f0 T __startup_64
ffffffffa0001000 T hypercall_page
ffffffffa0001000 t xen_hypercall_set_trap_table
ffffffffa0001020 t xen_hypercall_mmu_update
3 保存和删除符号表 #
一般情况下, 在生成可执行文件后,会将局部符号表(.symtab) 和调试符号表(.debug_XX) 保存到单独的 debuginfo 文件中,按需安装,从而减少二进制文件大小。
可以使用 objcopy --only-keep-debug hello hello.debug
来创建单独的 debuginfo 文件 hello.debug,该文件 同时包含
.symtab
和各种 .debug_XX
Sections:
# du -sh hello
12K hello
# objcopy --only-keep-debug hello hello.debug
# du -sh hello.debug
8.0K hello.debug
使用 strip -s/--strip-all
命令同时删除二进制文件中的 .symtab
和 .debug_XX
调试符号表内容,file 命令显示
stripped
;
.dynsym
是二进制给外界的符号表,不能删除;
# strip -s hello
# file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=86296aac1aa4149c55d40ced40c4ca8fbe3b17a9, stripped
# readelf -s hello|grep Symbo
Symbol table '.dynsym' contains 5 entries:
# readelf -s hello|grep hello
删除 .symtab
符号表后, uprobe
将失败:
- kprobe/uprobe 使用 .symtab 来查找符号名称(变量、函数、类型)和内存地址的关系,不依赖 debug symbol table;
#bpftrace -e 'uprobe:./hello:hello {printf("%s",ustack)}' -c ./hello
Attaching 1 probe...
hello world
hello+0
__libc_start_main+245
#strip -s hello
#bpftrace -e 'uprobe:./hello:hello {printf("%s",ustack)}' -c ./hello
No probes to attach
4 从 debuginfo 文件合并符号表 #
和 strip 相反的工具是 elfutils 包提供的 eu-unstrip
工具,它可以将 exec binary 和 debuginfo 文件合并到一起,形成一个包含符号表和调试符号表的 exec binary 文件:
root@lima-ebpf-dev:~# eu-unstrip -f /usr/bin/bash /usr/lib/debug/.build-id/33/a5554034feb2af38e8c75872058883b2988bc5.debug -o /tmp/bash
root@lima-ebpf-dev:~#
root@lima-ebpf-dev:~# readelf -S /tmp/bash |grep sym
[ 6] .dynsym DYNSYM 0000000000004f68 00004f68
[37] .symtab SYMTAB 0000000000000000 002fb538
root@lima-ebpf-dev:~# readelf -S /tmp/bash |grep -E 'sym|\.debug_'
[ 6] .dynsym DYNSYM 0000000000004f68 00004f68
[29] .debug_aranges PROGBITS 0000000000000000 00154678
[30] .debug_info PROGBITS 0000000000000000 00154820
[31] .debug_abbrev PROGBITS 0000000000000000 00215af0
[32] .debug_line PROGBITS 0000000000000000 0021ad30
[33] .debug_str PROGBITS 0000000000000000 0027ad40
[34] .debug_line_str PROGBITS 0000000000000000 002840b0
[35] .debug_loclists PROGBITS 0000000000000000 002847a0
[36] .debug_rnglists PROGBITS 0000000000000000 002ec800
[37] .symtab SYMTAB 0000000000000000 002fb538
root@lima-ebpf-dev:~#