跳过正文

ELF 符号表和 DWARF 调试符号表

·
Elf Debuginfo Dwarf
目录
elf-debug - 这篇文章属于一个选集。
§ 5: 本文

介绍 Linux ELF 二进制文件的符号表和调试符号表(.debug_XX)生成、管理机制。

Linux 使用 ELF 格式类保存可执行二进制文件、共享库文件 和 debuginfo 文件。

符号表(.dynsym 和 .symtab 两个 Sections)和 gcc -g 生成的调试符号表(.debug_XX Sections,DWARF 格式)不是一回事。

  1. .symtab 表记录程序中符号(如变量名、函数名)和二进制地址间的关系,在进行 perf report/uprobe/kprobe 时使用;

  2. .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;
  • /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 默认并不会添加到二进制中。

参考:

  1. gdb 的 Separate-Debug-Files.html

安装 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

Go 和 Rust 程序调试符号
#

参考: ../manual/20250201-gnu-gdb-manual.md

elf-debug - 这篇文章属于一个选集。
§ 5: 本文

相关文章

objdump
·
Elf Debug Tool
objdump 是 ELF 文件查看和反汇编工具。
readelf
·
Elf Debug Tool
readelf 是显示 ELF 二进制文件(可执行程序或动态库等)中各 Section 内容的重要工具。 显示符号表 Sections,如 .dnysym 和 .symtab 中的符号名称和地址; 显示 DWARF 格式的 Sections,如各种 .debug_xx,.eh_frame 等; 显示查找 debuginfo 文件所需的 .gnu_debuglink 和 .note.gnu.build-id ;
向 ELF 二进制添加元数据信息
·
Elf
介绍向 ELF 文件中添加自定义数据的方法。