介绍 Linux ELF 二进制文件的符号表和调试符号表(.debug_XX)生成、管理机制。
Linux 使用 ELF 格式类保存可执行二进制文件、共享库文件 和 debuginfo 文件。
符号表(.dynsym 和 .symtab 两个 Sections)和 gcc -g 生成的调试符号表(.debug_XX Sections,DWARF 格式)不是一回事。
-
.symtab 表记录程序中符号(如变量名、函数名)和二进制地址间的关系,在进行 perf report/uprobe/kprobe 时使用;
-
.debug_XX 表记录源文件、行号等与二进制内存地址的关系,用于支持单步调试:
- 功能 1:.debug_line 将内存地址映射到具体某行源代码:objdump –dwarf=decodedline hello-world
- 功能 2:.debug_frame 用于 unwind stack: perf record -e probe_stack:func_c -aR -g –call-graph dwarf ./stack-unwind
符号表 .symtab 主要用于将内存地址转换为名称(称为 Symbolization),也可以使用 .debug_info 中的信息来完成这个转换。
符号表 .symtab 和调试符号表 .debug_xx 可以位于二进制程序 ELF 中,也可以被 strip 并保存到单独的 debuginfo 包中。
Rust 可以使用 add2line crate 来解析 .debug_xxx 等 DWARF 格式的信息,它实际使用 glimi 来解析 DWARF 内容, https://docs.rs/gimli/0.31.1/gimli/index.html
.eh_frame/.debug_frame 主要用于 stack unwinding。
生成符号表和调试符号表 #
gcc 编译 C 程序时默认生成符号表,加 -g 后,也会生成调试符号表:
root@lima-ebpf-dev:~# gcc -g test.c -o hello
file 命令可以显示 ELF 文件中是否包含这两类表:
- 如果 ELF 有符号表,则显示
not stripped
- 如果 ELF 有调试符号表 DEWARF,则显示
with debug_info
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
各调试符号表都以 .debug 开头,符合 DWARF
格式标准,如 :
- .debug_abbrev: .debug_info section 中使用的简写
- .debug_aranges: 内存地址和编译位置的映射
- .debug_frame: Call Frame Information, 和 FP 类似,记录函数调用栈信息
- .debug_info: 核心的 DWARF 数据,包含 DIEs, 将变量名映射到寄存器或保存它的 stack 地址
- .debug_line: 将 code addres 映射到 source code 位置
- .debug_loc: 地址描述信息
- .debug_macinfo: Macro 描述信息
- .debug_pubnames: 全局对象或函数的查找表
- .debug_pubtypes: 全局类型查找表
- .debug_ranges: DIE 使用的地址区间
- .debug_str: .debug_info 使用的字符串表
- .debug_types: 类型描述
比较重要的是 .debug_info 和 .debug_frame:
- .debug_info:包含 debugging information entries (DIEs),用于描述程序源码中的变量、函数、类型和其它结构。 Debuggers 使用这些信息来将程序地址和变量、函数、源文件和行号联系起来;
- .debug_frame: 用于在 call frame unwinding 时使用。在编译的二进制不支持 stack frame pointer 时,gdb/perf 使用 debuginfo 中的 .debug_frame 来进行 stack unwinding。
不管是否加 -g 参数,gcc/llvm 默认都生成 .eh_frame/.eh_frame_hdr Section,它们的内容和格式和 .debug_frame 类似,但是不能被 strip,也不能被移动到单独的 debuginfo 文件中。
.eh_frame 的主要功能是用户态函数 unwinding,用于替换传统的 FP unwinding 机制。由于 .eh_frame 一定位于 ELF 中,所以是 gdb/perf/lldb 等默认使用的用户态函数 unwinding 机制。
- go 编译器不生成 .eh_frame,而是使用 FP 和生成 .debug_frame。
查看符号表 #
ELF 使用两个 Sections 来保存符号表(symbol table):
- .dynsym:动态符号表, 不能被 strip,保存该二进制依赖的动态库函数信息(函数名、动态库名称和版本等);
- .symtab:局部符号, 可以被 strip,只被本二进制使用;
使用 readelf -s 或 objdump -t
命令查看符号表:
# readelf -S hello |grep sym
[ 6] .dynsym DYNSYM 00000000000003d8 000003d8
[35] .symtab SYMTAB 0000000000000000 000032d0
# 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
...
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
查看调试符号表 #
- objdump -h 显示 .debug_XX Sections
- objdump -g/-W 显示 .debug_XX 详情
- readelf -w 显示 .debug_xx 详情
使用 readelf -w file 或 objdump -g
命令查看调试符号表,使用 -g3 可以包含 macro definitions(默认 -g2)。
# gcc -g test.c -o hello # -g 指定生成调试符号表
# objdump -h hello
hello: file format elf64-x86-64
Sections:
...
# 以下为各种调试符号表
26 .debug_aranges 00000030 0000000000000000 0000000000000000 00001069 2**0
CONTENTS, READONLY, DEBUGGING
27 .debug_info 000000aa 0000000000000000 0000000000000000 00001099 2**0
CONTENTS, READONLY, DEBUGGING
28 .debug_abbrev 00000058 0000000000000000 0000000000000000 00001143 2**0
CONTENTS, READONLY, DEBUGGING
29 .debug_line 0000003e 0000000000000000 0000000000000000 0000119b 2**0
CONTENTS, READONLY, DEBUGGING
30 .debug_str 000000ba 0000000000000000 0000000000000000 000011d9 2**0
CONTENTS, READONLY, DEBUGGING
创建 debuginfo 文件 #
.symtab 符号表和调试符号表(.debug_XX) 都不是程序运行过程中所必须的,它们会增加 binary 文件大小和 load 到内存时也会占用大量内存,所以一般情况下使用 strip 将它们从 ELF 中删除,然后将符号表(.symtab) 和调试符号表(.debug_XX) 保存到单独的 debuginfo 文件中(例如 Ubunut 系统一般是 xx-dbg/xx-dbgsym 包),按需安装,从而减少二进制文件大小。
.eh_frame、.eh_frame_hdr 和 .dynsym 不能被 strip,也不能被保存到单独的 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
根据 hello 中 .note.gnu.build-id
值,将 hello.debug 文件放到系统标准 debug 文件目录 /usr/lib/debug/.build-id/xx/XXX.debug
中,这样后续 gdb/objdump/readelf
等都会自动读取。
使用 readelf -n XX
命令查看 build-id 的内容。
root@lima-ebpf-dev:~# readelf -n hello
Displaying notes found in: .note.gnu.property
Owner Data size Description
GNU 0x00000020 NT_GNU_PROPERTY_TYPE_0
Properties: x86 feature: IBT, SHSTK
x86 ISA needed: x86-64-baseline
Displaying notes found in: .note.gnu.build-id
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 7e31292c839740f24092f371f1e85cd9ad74a79b
Displaying notes found in: .note.ABI-tag
Owner Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 3.2.0
# 根据 build-id 前两位,创建一个目录;
root@lima-ebpf-dev:~# mkdir /usr/lib/debug/.build-id/7e
# 文件名为 build-id 去掉前两位后的内容 + .debug
root@lima-ebpf-dev:~# mv hello.debug /usr/lib/debug/.build-id/7e/31292c839740f24092f371f1e85cd9ad74a79b.debug
也可以在生成 hello.debug 文件后,使用命令 objcopy --add-gnu-debuglink=hello.debug hello
在 hello 中添加一个 .gnu_debuglink Section ,然后将 hello.debug 文件放置到 /usr/lib/debug/hello.debug
位置,这样后续 gdb/objdump/readelf
也会自动查找使用。
使用 readelf --string-dump=.gnu_debuglink hello
命令来查看 debuglink 的内容。debuglink 包含 32 位 CRC 校验码, GDB 可以用来确认找到的 debug 文件是匹配的:
# objcopy --add-gnu-debuglink=hello.debug hello
# objdump -h hello |grep .gnu_debug
31 .gnu_debuglink 00000010 0000000000000000 0000000000000000 00001293 2**0
# readelf --string-dump=.gnu_debuglink hello
String dump of section '.gnu_debuglink':
[ 0] hello.debug
由于编译时自动生成 build-id 且不会重复,所以建议使用 build-id 而非 debuglink 机制。另外,objdump -S 只认 build-id。
删除符号表和调试符号表 #
符号表和调试符号表不是程序运行所必须的,使用 strip -g hello
命令将 hello 中调试符号表删除,从而减轻二进制文件体积:
strip -g
:删除各种 .debug_XX 开头的调试符号表,这些表中包含源文件、行号等与内存地址的关系,用于支持单步调试。
strip -s/--strip-all
:同时删除 .symtab 和各种 .debug_XX 调试符号表;
删除符号表后,file 命令显示 stripped
。如果文件带调试符号表,file 命令显示 with_debuginfo。
strip 不会删除以下 section:
- .eh_frame/.eh_frame_hdr: 是 C++ 异常处理所必须的,不能从 ELF 中删除;
- .dynsym 是二进制给外界的符号表,不会被 strip 删除;
#strip -g hello
#du -sh hello
12K hello
删除符号表和调试信息的影响:
- 无法使用 gdb 调试
- 堆栈跟踪信息会减少
- pprof 等工具的功能可能受限
kprobe/uprobe 使用 .symtab 来查找符号名称(变量、函数、类型)和内存地址的关系,删除后 uprobe 等功能不可使用:
# 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
合并 debuginfo 到 ELF #
和 strip 相反的工具是 elfutils
包提供的 eu-unstrip
工具,它可以将 exec binary 和 debug file 合并到一起,形成一个包含 debug 信息的 binary 文件:
# 格式
eu-unstrip -f <executablefilename> <symbolefilename.debug> -o <newoutputfilename>
# 示例
root@lima-ebpf-dev:/sys/kernel/debug/tracing# readelf -n /usr/bin/bash |grep Build
Build ID: 33a5554034feb2af38e8c75872058883b2988bc5
root@lima-ebpf-dev:/sys/kernel/debug/tracing# eu-unstrip -f /usr/bin/bash /usr/lib/debug/.build-id/33/a5554034feb2af38e8c75872058883b2988bc5.debug -o /tmp/bash
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:~#
查找 debuginfo 文件 #
readelf/objdump/gdb/bpftrace
等工具从系统 /usr/lib/debug/
查找二进制对应的 debuginfo 文件,一般使用两种机制:
.note.gnu.build-id
Section, 使用命令readelf -n hello
查看该 Section 内容;.gnu_debuglink
Section,使用命令readelf --string-dump=.gnu_debuglink hello
来查看内容,debug link 包含 32 位 CRC 校验码, gdb 可以用来确认找到的 debug 文件是匹配的;
例如 gdb 要 debug 的 /usr/bin/ls
的 debuglink 是 ls.debug, 而且 build-id 是 hex abcdef1234,全局 debug 目录是 /usr/include/debug, 则 gdb 查找 debug info 文件的顺序如下:
/usr/lib/debug/.build-id/ab/cdef1234.debug
- 注意:.build-id 下的目录是 build ID hex 值的
前两位
,目录下的文件名是去掉前两位后的内容+.debug;
- 注意:.build-id 下的目录是 build ID hex 值的
/usr/bin/ls.debug
/usr/bin/.debug/ls.debug
/usr/lib/debug/usr/bin/ls.debug
gdb 可以通过参数 --with-separate-debug-dir
设置查找 debuginfo dir:
(gdb) show debug-file-directory
The directory where separate debug symbols are searched for is "/usr/lib/debug".
(gdb)
readelf 查找 hello.debug 文件的路径,它不会查找 /usr/lib/debug 目录下的各种 bin 目录。
root@lima-ebpf-dev:~# readelf -n hello
Displaying notes found in: .note.gnu.property
Owner Data size Description
GNU 0x00000020 NT_GNU_PROPERTY_TYPE_0
Properties: x86 feature: IBT, SHSTK
x86 ISA needed: x86-64-baseline
Displaying notes found in: .note.gnu.build-id
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 7e31292c839740f24092f371f1e85cd9ad74a79b
Displaying notes found in: .note.ABI-tag
Owner Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 3.2.0
root@lima-ebpf-dev:~#
root@lima-ebpf-dev:~# strace -e openat readelf -P -wk hello |&grep /usr/lib/debug
readelf: Warning: tried: /usr/lib/debug/usr/hello.debug
readelf: Warning: tried: /usr/lib/debug//root//hello.debug
readelf: Warning: tried: /usr/lib/debug/hello.debug
openat(AT_FDCWD, "/usr/lib/debug/.build-id/7e/31292c839740f24092f371f1e85cd9ad74a79b.debug", O_RDONLY) = 4
bpftrace 使用 bcc 的 libclang 来编译和查找二进制的 debug file,也会查找系统缺省 debug 目录 /usr/lib/debug
- 支持 find_debug_via_debuglink/find_debug_via_buildid。
- 也可以通过 BCC_DEBUGINFO_ROOT 设置 debug 目录。
- bpftrace 查找 debuginfo 示例:
root@lima-ebpf-dev:~# strace -e openat bpftrace -l 'uprobe:/bin/bash:readline' -v |& grep debug
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdebuginfod.so.1", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/sys/kernel/debug/tracing/available_filter_functions", O_RDONLY) = 3
openat(AT_FDCWD, "/sys/kernel/debug/kprobes/blacklist", O_RDONLY) = 4
openat(AT_FDCWD, "/usr/lib/debug/.build-id/33/a5554034feb2af38e8c75872058883b2988bc5.debug", O_RDONLY) = 4
openat(AT_FDCWD, "/usr/lib/debug/.build-id/33/a5554034feb2af38e8c75872058883b2988bc5.debug", O_RDONLY) = 3
root@lima-ebpf-dev:~#
如果未找到,但是 debuginfod 被开启,则 gdb/bpftrace 尝试从 debuginfod 服务器下载 debuginfo 文件。
以 CentsOS7 /bin/bash 为例:
- 二进制的被 strip 过,所以只有 .dynsym 符号表,而没有 .symtab 符号表。
- .dynsym ( dynamic symbols, which can be used by another program);
- .symtab ( “local” symbols used by the binary itself only );
- 二进制缺少 .symtab 表且也没有 .debug 表时,不能使用 uprobe 来插桩函数。
- uprobe 插桩只依赖 .symtab 表,.debug 表不是必须的,前者是记录程序中符号(如变量名、函数名)和二进制地址间的关系,而后者是记录源码行号和二进制地址间的关系(用于单步调试)。
# file /usr/bin/bash
/usr/bin/bash: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=03eac9b3562001644b8685d22f867151b359ecb9, stripped
# readelf -s /usr/bin/bash |grep Symb
Symbol table '.dynsym' contains 2170 entries:
#objdump -h /usr/bin/bash
/usr/bin/bash: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 .note.ABI-tag 00000020 0000000000400254 0000000000400254 00000254 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000400274 0000000000400274 00000274 2**2 # 记录了 build id,后续也用于查找 debug 文件
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash 000036d4 0000000000400298 0000000000400298 00000298 2**3
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .dynsym 0000cb70 0000000000403970 0000000000403970 00003970 2**3 # .dynsym 符号表
CONTENTS, ALLOC, LOAD, READONLY, DATA
5 .dynstr 00008423 00000000004104e0 00000000004104e0 000104e0 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
...
25 .gnu_debuglink 00000010 0000000000000000 0000000000000000 000e6a90 2**2 # 记录了 debug 文件的名称和 CRC 校验值
CONTENTS, READONLY
26 .gnu_debugdata 000044c8 0000000000000000 0000000000000000 000e6aa0 2**0
CONTENTS, READONLY
安装 bash 的 debuginfo 包,提供 debug 文件:
- 安装到系统缺省
/usr/lib/debug
目录下; /usr/lib/debug/.build-id
下根据 build-id 来匹配;/usr/lib/debug/usr/bin/bash.debug
对应 .gnu_debuglink 中的文件名;
# rpm -ql bash-debuginfo |head
/usr/lib/debug
/usr/lib/debug/.build-id
/usr/lib/debug/.build-id/03
/usr/lib/debug/.build-id/03/eac9b3562001644b8685d22f867151b359ecb9
/usr/lib/debug/.build-id/03/eac9b3562001644b8685d22f867151b359ecb9.debug
/usr/lib/debug/usr
/usr/lib/debug/usr/bin
/usr/lib/debug/usr/bin/bash.debug
/usr/lib/debug/usr/bin/sh.debug
/usr/src/debug/bash-4.2
bash.debug 文件的 build-id 和 bash 文件一致:
- bash.debug 文件中包含各种以 .debug_XX 开头的调试符号表。
# file /usr/lib/debug/usr/bin/bash.debug
/usr/lib/debug/usr/bin/bash.debug: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=03eac9b3562001644b8685d22f867151b359ecb9, not stripped
# objdump -h /usr/lib/debug/bin/bash.debug
/usr/lib/debug/bin/bash.debug: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .interp 0000001c 0000000000400238 0000000000400238 00000238 2**0
ALLOC, READONLY
1 .note.ABI-tag 00000020 0000000000400254 0000000000400238 00000238 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .note.gnu.build-id 00000024 0000000000400274 0000000000400258 00000258 2**2 # build ID
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .gnu.hash 000036d4 0000000000400298 0000000000400298 00000280 2**3
ALLOC, READONLY
4 .dynsym 0000cb70 0000000000403970 0000000000403970 00000280 2**3
ALLOC, READONLY
5 .dynstr 00008423 00000000004104e0 00000000004104e0 00000280 2**0
ALLOC, READONLY
...
26 .debug_aranges 00001e30 0000000000000000 0000000000000000 000002ad 2**0
CONTENTS, READONLY, DEBUGGING
27 .debug_info 000acd9c 0000000000000000 0000000000000000 000020dd 2**0
CONTENTS, READONLY, DEBUGGING
28 .debug_abbrev 0000abd2 0000000000000000 0000000000000000 000aee79 2**0
CONTENTS, READONLY, DEBUGGING
29 .debug_line 000313e2 0000000000000000 0000000000000000 000b9a4b 2**0
CONTENTS, READONLY, DEBUGGING
30 .debug_str 00015045 0000000000000000 0000000000000000 000eae2d 2**0
CONTENTS, READONLY, DEBUGGING
31 .debug_loc 0010ef66 0000000000000000 0000000000000000 000ffe72 2**0
CONTENTS, READONLY, DEBUGGING
32 .debug_ranges 00014870 0000000000000000 0000000000000000 0020edd8 2**0
CONTENTS, READONLY, DEBUGGING
33 .gdb_index 00025413 0000000000000000 0000000000000000 00223648 2**0
CONTENTS, READONLY, DEBUGGING
无论是 gdb/readelf/objdump/bpftrace 都会根据 .note.gnu.build-id 来查找 /usr/lib/debug/.build-id
目录,所以这是一种标准的放置 debuginfo file 的机制。
- .gnu_debuglink section 默认并不会添加到二进制中。
参考:
安装 debuginfo 文件 #
CentOS/Redhat:
- 6-7版本:只有 debuginfo 包,同时包含 DWARF 调试符号表和源代码;
- 8 开始:debuginfo 和 debugsource 是分开的两个 package,前者包含 DWARF 格式的 .debug_XX Sections, 后者包含对应的源代码,两者都被安装到
/usr/lib/debug
目录下; - 需要启用 debuginfo 和 debugsource YUM REPO;
- Centos 在用 gcc 编译时,默认开启了 -O2 级别优化,所以可能有写源码中的变量不可见,被表示为 <optimized out>;
debuginfo、debugsource 和 binary package 三者的名称、版本、架构等必须一致:
- Binary package: packagename-version-release.architecture.rpm
- Debuginfo package: packagename-debuginfo-version-release.architecture.rpm
- Debugsource package: packagename-debugsource-version-release.architecture.rpm
配置 debuginfo repo:
[debug]
name=CentOS-$releasever-debug
enabled=1
failovermethod=priority
baseurl=http://mirrors.cloud.aliyuncs.com/centos-debuginfo/$releasever/$basearch/
gpgcheck=0
[epel-debug]
name=epel-debug
enabled=1
failovermethod=priority
baseurl=http://mirrors.cloud.aliyuncs.com/epel/7/$basearch/debug
gpgcheck=0
安装 kernel 和系统常用工具的 debuginfo 包, 将进程的函数/参数地址以转换为 Symbol 名称:
yum install glibc-debuginfo -y
yum install coreutils-debuginfo -y
yum install kernel-debuginfo -y
gdb 会自动提示有没有找到 debug symbol,同时会提示安装命令:
$ gdb -q /bin/ls
Reading symbols from /bin/ls...Reading symbols from .gnu_debugdata for /usr/bin/ls...(no debugging symbols found)...done.
(no debugging symbols found)...done.
Missing separate debuginfos, use: dnf debuginfo-install coreutils-8.30-6.el8.x86_64
(gdb)
可以使用 debuginfo-install 来根据 binary package 来安装对应的 debuginfo 包:
# debuginfo-install zlib-1.2.11-10.el8.x86_64
Ubuntu:
- 一般 debug symbols 包以 -dbgsym 结尾,如 bash-dbgsym;
- 需要启用 dbgsym Package Source Repo,然后才能查询和安装;
root@lima-ebpf-dev:~# apt search bash |grep -B 1 debug
bash-builtins-dbgsym/jammy 5.1-6ubuntu1 amd64
debug symbols for bash-builtins
--
bash-dbgsym/jammy 5.1-6ubuntu1 amd64
debug symbols for bash
--
bash-static-dbgsym/jammy 5.1-6ubuntu1 amd64
debug symbols for bash-static
--
ddd/jammy 1:3.3.12-5.3build1 amd64
Data Display Debugger, a graphical debugger frontend
--
ondir-dbgsym/jammy 0.2.3+git0.55279f03-1 amd64
debug symbols for package ondir
root@lima-ebpf-dev:~# apt install bash-dbgsym
root@lima-ebpf-dev:~# gdb /usr/bin/bash
。。。
Reading symbols from /usr/bin/bash...
Reading symbols from /usr/lib/debug/.build-id/33/a5554034feb2af38e8c75872058883b2988bc5.debug...
(gdb)
显示 build-id 和 debuglink #
- 显示 build-id: readelf -n hello
- 显示 debuglink:readelf –string-dump=.gnu_debuglink hello
root@lima-ebpf-dev:~# objcopy --add-gnu-debuglink=hello.debug hello
root@lima-ebpf-dev:~#
root@lima-ebpf-dev:~# readelf --string-dump=.gnu_debuglink hello
String dump of section '.gnu_debuglink':
[ 0] hello.debug
[ e] [j
root@lima-ebpf-dev:~# readelf -n hello
...
Displaying notes found in: .note.gnu.build-id
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: ced7aad9174d074c704867b703c014fec94527df
...
内核 debuginfo 和 vmlinux #
内核的 debuginfo 包包含内核和内核模块的调试符号表文件,被安装到 /usr/lib/debug
目录下:
# rpm -ql kernel-debuginfo-4.19.91-013.ali4000.os7.x86_64|head
/usr/lib/debug
/usr/lib/debug/.build-id
/usr/lib/debug/.build-id/00
/usr/lib/debug/.build-id/00/0fc26ae19e5ae8e6ecdd110b0dbd24741726d0
/usr/lib/debug/.build-id/00/0fc26ae19e5ae8e6ecdd110b0dbd24741726d0.debug
。。。
/usr/lib/debug/lib
/usr/lib/debug/lib/modules
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/arch
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/arch/x86
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/arch/x86/crypto
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/arch/x86/crypto/aesni-intel.ko.debug
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/arch/x86/crypto/blowfish-x86_64.ko.debug
。。。
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/net/wireless/lib80211.ko.debug
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/net/xfrm
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/net/xfrm/xfrm_ipcomp.ko.debug
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/virt
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/virt/lib
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/kernel/virt/lib/irqbypass.ko.debug
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vdso
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vdso/.build-id
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vdso/.build-id/81
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vdso/.build-id/81/2688c147f9806c2f13de03a3d7593deccd4c91.debug.debug
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vdso/.build-id/9c
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vdso/.build-id/9c/855176d6cfba59c6ae81c0b5d7bf01cc34c385.debug.debug
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vdso/vdso32.so.debug
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vdso/vdso64.so.debug
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vmlinux # 包含内核符号表和 .debug_xx 调试符号表。
/usr/lib/debug/usr
/usr/lib/debug/usr/src
/usr/lib/debug/usr/src/kernels
/usr/lib/debug/usr/src/kernels/4.19.91-013.ali4000.os7.x86_64
/usr/lib/debug/usr/src/kernels/4.19.91-013.ali4000.os7.x86_64/scripts
/usr/lib/debug/usr/src/kernels/4.19.91-013.ali4000.os7.x86_64/scripts/asn1_compiler.debug
/usr/lib/debug/usr/src/kernels/4.19.91-013.ali4000.os7.x86_64/scripts/basic
/usr/lib/debug/usr/src/kernels/4.19.91-013.ali4000.os7.x86_64/scripts/basic/fixdep.debug
/usr/lib/debug/usr/src/kernels/4.19.91-013.ali4000.os7.x86_64/scripts/bin2c.debug
/usr/lib/debug/usr/src/kernels/4.19.91-013.ali4000.os7.x86_64/scripts/conmakehash.debug
其中 /usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vmlinux 文件是非压缩的、包含内核符号表、调试符号表的 ELF 文件:
# file /usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vmlinux
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=854b5a499ad2fe963e37462d882866a60cf5b35a, not stripped
# objdump -h /usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vmlinux
/usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vmlinux: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00c03000 ffffffff81000000 0000000001000000 00200000 2**12
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
...
35 .debug_aranges 00027cb0 0000000000000000 0000000000000000 01d75030 2**4
CONTENTS, RELOC, READONLY, DEBUGGING
36 .debug_info 0b5eb3dd 0000000000000000 0000000000000000 01d9cce0 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
37 .debug_abbrev 003b1205 0000000000000000 0000000000000000 0d3880bd 2**0
CONTENTS, READONLY, DEBUGGING
38 .debug_line 00a90e2e 0000000000000000 0000000000000000 0d7392c2 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
39 .debug_frame 0024f7a8 0000000000000000 0000000000000000 0e1ca0f0 2**3
CONTENTS, RELOC, READONLY, DEBUGGING
40 .debug_str 002f3d57 0000000000000000 0000000000000000 0e419898 2**0
CONTENTS, READONLY, DEBUGGING
41 .debug_loc 00c29a65 0000000000000000 0000000000000000 0e70d5ef 2**0
CONTENTS, RELOC, READONLY, DEBUGGING
42 .debug_ranges 0050db50 0000000000000000 0000000000000000 0f337060 2**4
CONTENTS, RELOC, READONLY, DEBUGGING
43 .gdb_index 004f73ea 0000000000000000 0000000000000000 0f844bb0 2**0
CONTENTS, READONLY, DEBUGGING
在使用 perf probe 时可以使用–vmlinux 参数来指定 vmlinux 文件路径,这样可以获得内核函数的参数详情。
/proc/kallsyms 文件也包含内核符号表:
#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