跳过正文

Linux ELF 符号表(.symtab)和调试符号表(.debug_XX)

··7792 字
Linux Elf Debug Dwarf
目录
elf-debug - 这篇文章属于一个选集。
§ 2: 本文

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

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

ELF 使用两个 Sections 来保存符号表(symbol table):

  • .dynsym:动态符号, 不能被 strip, dynamic symbols, which can be used by another program;
  • .symtab:局部符号, local symbols used by the binary itself only;

符号表(.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 中的信息来完成这个转换。

  • 符号表对 kprobe/uprobe event tracing 至关重要,因为知道函数名字才能跟踪;

符号表 .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。

1 生成符号表和调试符号表
#

gcc 编译 C 程序时默认生成符号表。 加 -g 后,也会生成调试符号表:

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

file 命令可以显示 ELF 文件中是否包含这两类表:

  • 如果 ELF 有符号表,则显示 not stripped
  • 如果 ELF 有调试符号表 DEWARF,则显示 with debug_info

各调试符号表都以 .debug 开头,符合 DWARF 格式标准,如 :

  • .debug_abbrev: Abbreviations used in the .debug_info section
  • .debug_aranges: A mapping between memory address and compilation
  • .debug_frame: Call Frame Information, 和 FP 类似,记录函数调用栈信息。
  • .debug_info: The core DWARF data containing DIEs, 将 souce variable 映射到寄存器或保存它的 stack 地址;
  • .debug_line: Line Number Program, 将 code addres 映射到 source code 位置;
  • .debug_loc: Location descriptions
  • .debug_macinfo: Macro descriptions
  • .debug_pubnames: A lookup table for global objects and functions
  • .debug_pubtypes: A lookup table for global types
  • .debug_ranges: Address ranges referenced by DIEs
  • .debug_str: String table used by .debug_info
  • .debug_types: Type descriptions

比较重要的是 .debug_info 和 .debug_frame

  • .debug_info consists of debugging information entries (DIEs) for all of the variables, functions, types and other kinds of constructs found in a program’s source code. Debuggers can use this information to connect specific addresses within a running process’s memory with variables, functions, source files and line numbers.
  • .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。

2 查看符号表
#

查看 ELF 文件中的符号表:使用 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

3 查看调试符号表
#

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:
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
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     0000001c  0000000000400298  0000000000400298  00000298  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000078  00000000004002b8  00000000004002b8  000002b8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       00000043  0000000000400330  0000000000400330  00000330  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  0000000a  0000000000400374  0000000000400374  00000374  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000020  0000000000400380  0000000000400380  00000380  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn     00000018  00000000004003a0  00000000004003a0  000003a0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.plt     00000060  00000000004003b8  00000000004003b8  000003b8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         0000001a  0000000000400418  0000000000400418  00000418  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000050  0000000000400440  0000000000400440  00000440  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         00000192  0000000000400490  0000000000400490  00000490  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .fini         00000009  0000000000400624  0000000000400624  00000624  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .rodata       0000001d  0000000000400630  0000000000400630  00000630  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .eh_frame_hdr 0000003c  0000000000400650  0000000000400650  00000650  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame     00000114  0000000000400690  0000000000400690  00000690  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .init_array   00000008  0000000000600e10  0000000000600e10  00000e10  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 18 .fini_array   00000008  0000000000600e18  0000000000600e18  00000e18  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 19 .jcr          00000008  0000000000600e20  0000000000600e20  00000e20  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 20 .dynamic      000001d0  0000000000600e28  0000000000600e28  00000e28  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000008  0000000000600ff8  0000000000600ff8  00000ff8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got.plt      00000038  0000000000601000  0000000000601000  00001000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .data         00000004  0000000000601038  0000000000601038  00001038  2**0
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00000004  000000000060103c  000000000060103c  0000103c  2**0
                  ALLOC
 25 .comment      0000002d  0000000000000000  0000000000000000  0000103c  2**0
                  CONTENTS, READONLY

 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

4 创建和保存 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 也会自动查找使用。

  • debuglink 包含 32 位 CRC 校验码, GDB 可以用来确认找到的 debug 文件是匹配的.
  • 使用 readelf --string-dump=.gnu_debuglink hello 命令来查看 debuglink 的内容。
# 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。

5 删除 .symtab 和 .debug_xx
#

符号表和调试符号表不是程序运行所必须的,使用 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

#objdump -h hello

hello:     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
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     0000001c  0000000000400298  0000000000400298  00000298  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000078  00000000004002b8  00000000004002b8  000002b8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       00000043  0000000000400330  0000000000400330  00000330  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  0000000a  0000000000400374  0000000000400374  00000374  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000020  0000000000400380  0000000000400380  00000380  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn     00000018  00000000004003a0  00000000004003a0  000003a0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.plt     00000060  00000000004003b8  00000000004003b8  000003b8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         0000001a  0000000000400418  0000000000400418  00000418  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000050  0000000000400440  0000000000400440  00000440  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         00000192  0000000000400490  0000000000400490  00000490  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .fini         00000009  0000000000400624  0000000000400624  00000624  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .rodata       0000001d  0000000000400630  0000000000400630  00000630  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .eh_frame_hdr 0000003c  0000000000400650  0000000000400650  00000650  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame     00000114  0000000000400690  0000000000400690  00000690  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .init_array   00000008  0000000000600e10  0000000000600e10  00000e10  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 18 .fini_array   00000008  0000000000600e18  0000000000600e18  00000e18  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 19 .jcr          00000008  0000000000600e20  0000000000600e20  00000e20  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 20 .dynamic      000001d0  0000000000600e28  0000000000600e28  00000e28  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000008  0000000000600ff8  0000000000600ff8  00000ff8  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got.plt      00000038  0000000000601000  0000000000601000  00001000  2**3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .data         00000004  0000000000601038  0000000000601038  00001038  2**0
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00000004  000000000060103c  000000000060103c  0000103c  2**0
                  ALLOC
 25 .comment      0000002d  0000000000000000  0000000000000000  0000103c  2**0
                  CONTENTS, READONLY
 26 .gnu_debuglink 00000010  0000000000000000  0000000000000000  00001069  2**0
                  CONTENTS, READONLY

5.1 删除 .symtab 符号表后,=uprobe= 将失败
#

kprobe/uprobe 使用 .symtab 来查找符号名称(变量、函数、类型)和内存地址的关系:

#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

6 合并 debuginfo 到 ELF
#

和 strip 相反的工具是 elfutils 包提供的 eu-unstrip 工具,它可以将 exec binary 和 debug file 合并到一起,形成一个包含 debug 信息的 exec 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:~#

7 查找 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 设置查找 debug info 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

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

9 查看调试符号表
#

9.1 显示 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
...

9.2 objdump -h 显示 .debug_XX Sections
#

  • debug 符号表 Section 以 .debug 开头:
root@lima-ebpf-dev:~# objdump -h hello

hello:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000000318  0000000000000318  00000318  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.gnu.property 00000030  0000000000000338  0000000000000338  00000338  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000000368  0000000000000368  00000368  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .note.ABI-tag 00000020  000000000000038c  000000000000038c  0000038c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
...
 27 .debug_aranges 00000030  0000000000000000  0000000000000000  0000303b  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 28 .debug_info   000000a6  0000000000000000  0000000000000000  0000306b  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 29 .debug_abbrev 0000005e  0000000000000000  0000000000000000  00003111  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 30 .debug_line   0000005f  0000000000000000  0000000000000000  0000316f  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 31 .debug_str    000000df  0000000000000000  0000000000000000  000031ce  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
 32 .debug_line_str 00000011  0000000000000000  0000000000000000  000032ad  2**0
                  CONTENTS, READONLY, DEBUGGING, OCTETS
root@lima-ebpf-dev:~#

9.3 objdump -g/-W 显示 .debug_XX 详情
#

直接从文件中读或者根据 debuglink/build-id 从系统 /usr/lib/debug 读取 debug 文件。

root@lima-ebpf-dev:~# objdump -w -g hello
Contents of the .debug_aranges section (loaded from /usr/lib/debug/hello.debug):

  Length:                   44
  Version:                  2
  Offset into .debug_info:  0x0
  Pointer Size:             8
  Segment Size:             0

    Address            Length
    0000000000001149 000000000000002e
    0000000000000000 0000000000000000

Contents of the .debug_info section (loaded from /usr/lib/debug/hello.debug):

  Compilation Unit @ offset 0x0:
   Length:        0xa2 (32-bit)
   Version:       5
   Unit Type:     DW_UT_compile (1)
   Abbrev Offset: 0x0
   Pointer Size:  8
 <0><c>: Abbrev Number: 2 (DW_TAG_compile_unit)
    <d>   DW_AT_producer    : (strp) (offset: 0x35): GNU C17 11.3.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection
    <11>   DW_AT_language    : (data1) 29       (C11)
    <12>   DW_AT_name        : (line_strp) (offset: 0x0): test.c
    <16>   DW_AT_comp_dir    : (line_strp) (offset: 0x7): /root
    <1a>   DW_AT_low_pc      : (addr) 0x1149
    <22>   DW_AT_high_pc     : (data8) 0x2e
    <2a>   DW_AT_stmt_list   : (sec_offset) 0x0
 <1><2e>: Abbrev Number: 1 (DW_TAG_base_type)
    <2f>   DW_AT_byte_size   : (data1) 8
    <30>   DW_AT_encoding    : (data1) 7        (unsigned)
    <31>   DW_AT_name        : (strp) (offset: 0x0): long unsigned int
...
 <1><8b>: Abbrev Number: 5 (DW_TAG_subprogram)
    <8c>   DW_AT_external    : (flag_present) 1
    <8c>   DW_AT_name        : (strp) (offset: 0x2f): hello
    <90>   DW_AT_decl_file   : (data1) 1
    <91>   DW_AT_decl_line   : (data1) 3
    <92>   DW_AT_decl_column : (data1) 6
    <93>   DW_AT_prototyped  : (flag_present) 1
    <93>   DW_AT_low_pc      : (addr) 0x1149
    <9b>   DW_AT_high_pc     : (data8) 0x1a
    <a3>   DW_AT_frame_base  : (exprloc) 1 byte block: 9c       (DW_OP_call_frame_cfa)
    <a5>   DW_AT_call_all_tail_calls: (flag_present) 1
 <1><a5>: Abbrev Number: 0

Contents of the .debug_abbrev section (loaded from /usr/lib/debug/hello.debug):

  Number TAG (0x0)
   1      DW_TAG_base_type    [no children]
    DW_AT_byte_size    DW_FORM_data1
    DW_AT_encoding     DW_FORM_data1
    DW_AT_name         DW_FORM_strp
    DW_AT value: 0     DW_FORM value: 0
   2      DW_TAG_compile_unit    [has children]
...

Raw dump of debug contents of section .debug_line (loaded from /usr/lib/debug/hello.debug):

  Offset:                      0x0
  Length:                      87
  DWARF Version:               5
  Address size (bytes):        8
  Segment selector (bytes):    0
  Prologue Length:             42
  Minimum Instruction Length:  1
  Maximum Ops per Instruction: 1
  Initial value of 'is_stmt':  1
  Line Base:                   -5
  Line Range:                  14
  Opcode Base:                 13

 Opcodes:
  Opcode 1 has 0 args
  Opcode 2 has 1 arg
  Opcode 3 has 1 arg
  Opcode 4 has 1 arg
  Opcode 5 has 1 arg
  Opcode 6 has 0 args
  Opcode 7 has 0 args
  Opcode 8 has 0 args
  Opcode 9 has 1 arg
  Opcode 10 has 0 args
  Opcode 11 has 0 args
  Opcode 12 has 1 arg

 The Directory Table (offset 0x22, lines 1, columns 1):
  Entry Name
  0     (line_strp)     (offset: 0x7): /root

 The File Name Table (offset 0x2c, lines 2, columns 2):
  Entry Dir     Name
  0     (udata) 0       (line_strp)     (offset: 0x0): test.c
  1     (udata) 0       (line_strp)     (offset: 0x0): test.c

 Line Number Statements:
  [0x00000036]  Set column to 17
...

Contents of the .debug_str section (loaded from /usr/lib/debug/hello.debug):

  0x00000000 6c6f6e67 20756e73 69676e65 6420696e long unsigned in
  0x00000010 74007368 6f727420 756e7369 676e6564 t.short unsigned
  0x00000020 20696e74 0073686f 72742069 6e740068  int.short int.h
  0x00000030 656c6c6f 00474e55 20433137 2031312e ello.GNU C17 11.
  0x00000040 332e3020 2d6d7475 6e653d67 656e6572 3.0 -mtune=gener
...
Contents of the .debug_line_str section (loaded from /usr/lib/debug/hello.debug):

  0x00000000 74657374 2e63002f 726f6f74 00       test.c./root.


hello:     file format elf64-x86-64

Contents of the .eh_frame section (loaded from hello):

...

Contents of the .gnu_debuglink section (loaded from hello):

  Separate debug info file: hello.debug
  CRC value: 0x6a5bc5a3

root@lima-ebpf-dev:~#

objdump –dwarf 可以指定要打印的 DWARF 内容类型,支持如下参数:-W, –dwarf[a/=abbrev, A/=addr, r/=aranges, c/=cu_index, L/=decodedline, f/=frames, F/=frames-interp, g/=gdb_index, i/=info, o/=loc, m/=macro, p/=pubnames, t/=pubtypes, R/=Ranges, l/=rawline, s/=str, O/=str-offsets, u/=trace_abbrev, T/=trace_aranges, U/=trace_info]

例如打印内存地址和源码行之间的关系;

root@lima-ebpf-dev:~# objdump --dwarf=decodedline hello
Contents of the .debug_line section (loaded from /usr/lib/debug/.build-id/7e/31292c839740f24092f371f1e85cd9ad74a79b.debug):

test.c:
File name                            Line number    Starting address    View    Stmt
test.c                                         3              0x1149               x
test.c                                         4              0x1151               x
test.c                                         5              0x1160               x
test.c                                         6              0x1163               x
test.c                                         7              0x116b               x
test.c                                         8              0x1170               x
test.c                                         9              0x1175               x
test.c                                         -              0x1177

hello:     file format elf64-x86-64

root@lima-ebpf-dev:~#

9.4 readelf -w 显示 .debug_xx 详情
#

-w display all DWARF sections

-w[liaprmfFso] display specific sections

  • l line table
  • i debug info
  • a abbreviation table
  • p public names
  • r ranges
  • m macro table
  • f debug frame (encoded)
  • F debug frame (decoded)
  • s string table
  • o location lists

如果二进制被 strip,则本身不再包含调试符号表,这时 readelf 会根据 .gnu_debuglink Sections 中的 debug 文件名(需要单独添加该 Section),或根据 .note.gnu.build-id 在 /usr/lib/debug 下查找单独的 debuginfo file。

10 kernel-debuginfo
#

内核的调试符号表也被安装到 /usr/lib/debug 目录下,/usr/lib/debug/lib/modules 对应各种内核模块的符号文件:

# 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
/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

11 查看内核符号表
#

直接查看 /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

或者使用 vmlinux 文件 /usr/lib/debug/lib/modules/4.19.91-013.ali4000.os7.x86_64/vmlinux 中的符号表或 .debug_xx 表。

vmlinux 对应完整的内核 ELF 文件(未压缩):

  • 包含各种 .debug_XX 调试符号表;
  • 也包含未 strip 的 .symtab 符号表;
# 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
  1 .noinstr.text 00000097  ffffffff81c03000  0000000001c03000  00e03000  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  2 .notes        000001bc  ffffffff81c03098  0000000001c03098  00e03098  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  3 __ex_table    00001c44  ffffffff81c03260  0000000001c03260  00e03260  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  4 .rodata       0038ac62  ffffffff81e00000  0000000001e00000  01000000  2**7
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  5 .orc_unwind_ip 0015b6ac  ffffffff8218ac64  000000000218ac64  0138ac64  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  6 .orc_unwind   00209202  ffffffff822e6310  00000000022e6310  014e6310  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .orc_lookup   000300c4  ffffffff824ef514  00000000024ef514  016ef514  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .pci_fixup    00002c20  ffffffff8251f5e0  000000000251f5e0  0171f5e0  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  9 __ksymtab     00009a30  ffffffff82522200  0000000002522200  01722200  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
 10 __ksymtab_gpl 00009090  ffffffff8252bc30  000000000252bc30  0172bc30  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
 11 __kcrctab     00004d18  ffffffff82534cc0  0000000002534cc0  01734cc0  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
 12 __kcrctab_gpl 00004848  ffffffff825399d8  00000000025399d8  017399d8  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
 13 __ksymtab_strings 0002d743  ffffffff8253e220  000000000253e220  0173e220  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
...
 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

12 go 程序符号表和调试信息
#

go build 生成的二进制默认是包含符号表和调试信息的,除非使用链接参数 -ldflags="-s -w" 来去除它们(go doc cmd/link 查看帮助):

  • -s: 去掉符号表和调试信息(Omit the symbol table and debug information.)
  • -w:去掉 DEWAR 符号表调试信息;(Omit the DWARF symbol table.)

去除符号表和调试信息的影响:

  • 无法使用 gdb 调试
  • 堆栈跟踪信息会减少
  • pprof 等工具的功能可能受限

查看 go 符号表: go tool nm ./app

查看 ELF 可读写字符串:strings ./app

对于 ELF 中的符号表,可以使用 strip 命令来删除。

go run 默认使用 “–ldflags ‘-s -w’”, 故删除了 symbol table(-s) 和 DWARF debug info(-w),不能用于调试。

为了调试 go 程序,需要 The binary must be built with go build -gcflags=all="-N -l" to disable inlining and optimizations that can interfere with debugging.

13 Rust 程序符号表和调试信息
#

cargo 默认包含 4 个 profiles:dev、release、test 和 bench。

``` bash

cargo build

cargo build –release ```

参考:https://doc.rust-lang.org/cargo/reference/profiles.html

The default settings for the dev profile are:

cargo install –debug, cargo build –debug:

[profile.dev]
opt-level = 0  # 不开启优化,这样源码反汇编的更准确
debug = true   # 包含符号表和 DWARF
split-debuginfo = '...'  # Platform-specific.
strip = "none" # 不删除符号表
debug-assertions = true
overflow-checks = true
lto = false
panic = 'unwind'
incremental = true
codegen-units = 256
rpath = false

cargo install(默认使用 –release), cargo build –release:

[profile.release]
opt-level = 3  # 开启了代码优化,反汇编不准确
debug = false  # 不包含符号表和 DWARF 信息
split-debuginfo = '...'  # Platform-specific.
strip = "none"
debug-assertions = false
overflow-checks = false
lto = false
panic = 'unwind'
incremental = false
codegen-units = 16
rpath = false

opt-level: 设置 -C opt-level flag

  • 0: no optimizations
  • 1: basic optimizations
  • 2: some optimizations
  • 3: all optimizations
  • “s”: optimize for binary size
  • “z”: optimize for binary size, but also turn off loop vectorization.

debug: 设置 -C debuginfo flag,控制在 ELF 中包含的调试信息数量

  • 0, false, or “none”: no debug info at all, default for release
  • “line-directives-only”: line info directives only. For the nvptx* targets this enables profiling. For other use cases, line-tables-only is the better, more compatible choice.
  • “line-tables-only”: line tables only. Generates the minimal amount of debug info for backtraces with filename/line number info, but not anything else, i.e. no variable or function parameter info.
  • 1 or “limited”: debug info without type or variable-level information. Generates more detailed module-level info than line-tables-only.
  • 2, true, or “full”: full debug info, default for dev

split-debuginfo:The split-debuginfo setting controls the -C split-debuginfo flag which controls whether debug information, if generated, is either placed in the executable itself or adjacent to it.

strip:The strip option controls the -C strip flag, which directs rustc to strip either symbols or debuginfo from a binary. This can be enabled like so:

[package]
# ...

[profile.release]
strip = "debuginfo"

Possible string values of strip are:

  • “none” (缺省值,表示不删除 symbols 和 debuginfo)
  • “debuginfo”
  • “symbols”
elf-debug - 这篇文章属于一个选集。
§ 2: 本文

相关文章

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