从.dynamic到.debug_info:一次搞懂Linux下ELF文件的‘隐藏’数据段(readelf/objdump实战)

发布时间:2026/6/2 2:24:18
从.dynamic到.debug_info:一次搞懂Linux下ELF文件的‘隐藏’数据段(readelf/objdump实战)
从.dynamic到.debug_info揭秘ELF文件中那些不为人知的关键数据段当你第一次用readelf -S查看一个Linux可执行文件时可能会被那一长串以点开头的段名搞得晕头转向。除了熟悉的.text、.data和.bss之外还有.dynamic、.dynsym、.debug_info等数十个神秘段。这些段就像程序的隐藏器官虽然不常被提及却支撑着程序的动态链接、符号解析和调试等关键功能。1. ELF文件结构快速回顾在深入这些特殊段之前让我们先快速回顾ELF(Executable and Linkable Format)的基本结构。ELF文件由以下几部分组成ELF头(ELF Header)包含文件的魔数、架构、入口点等信息程序头表(Program Header Table)描述段(Segment)信息用于程序加载节头表(Section Header Table)描述节(Section)信息用于链接和调试实际节数据包含代码、数据等实际内容提示使用readelf -h查看ELF头readelf -l查看程序头readelf -S查看节头表下面是一个简单的C程序编译后的主要段分布$ readelf -S hello There are 31 section headers, starting at offset 0x19d8: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000000318 00000318 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.gnu.propert NOTE 0000000000000338 00000338 0000000000000020 0000000000000000 A 0 0 8 [ 3] .note.ABI-tag NOTE 0000000000000358 00000358 0000000000000020 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000000378 00000378 0000000000000024 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000000003a0 000003a0 00000000000000a8 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 0000000000000448 00000448 0000000000000082 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 00000000000004ca 000004ca 000000000000000e 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 00000000000004d8 000004d8 0000000000000020 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 00000000000004f8 000004f8 00000000000000c0 0000000000000018 A 5 0 8 [10] .rela.plt RELA 00000000000005b8 000005b8 0000000000000018 0000000000000018 AI 5 24 8 [11] .init PROGBITS 0000000000001000 00001000 000000000000001b 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 0000000000001020 00001020 0000000000000020 0000000000000010 AX 0 0 16 [13] .plt.got PROGBITS 0000000000001040 00001040 0000000000000010 0000000000000010 AX 0 0 16 [14] .text PROGBITS 0000000000001050 00001050 0000000000000185 0000000000000000 AX 0 0 16 [15] .fini PROGBITS 00000000000011d8 000011d8 000000000000000d 0000000000000000 AX 0 0 4 [16] .rodata PROGBITS 0000000000002000 00002000 000000000000000f 0000000000000000 A 0 0 4 [17] .eh_frame_hdr PROGBITS 0000000000002010 00002010 0000000000000044 0000000000000000 A 0 0 4 [18] .eh_frame PROGBITS 0000000000002058 00002058 0000000000000110 0000000000000000 A 0 0 8 [19] .init_array INIT_ARRAY 0000000000003db8 00002db8 0000000000000008 0000000000000008 WA 0 0 8 [20] .fini_array FINI_ARRAY 0000000000003dc0 00002dc0 0000000000000008 0000000000000008 WA 0 0 8 [21] .dynamic DYNAMIC 0000000000003dc8 00002dc8 00000000000001f0 0000000000000010 WA 6 0 8 [22] .got PROGBITS 0000000000003fb8 00002fb8 0000000000000048 0000000000000008 WA 0 0 8 [23] .data PROGBITS 0000000000004000 00003000 0000000000000010 0000000000000000 WA 0 0 8 [24] .bss NOBITS 0000000000004010 00003010 0000000000000008 0000000000000000 WA 0 0 1 [25] .comment PROGBITS 0000000000000000 00003010 000000000000002b 0000000000000001 MS 0 0 1 [26] .symtab SYMTAB 0000000000000000 00003040 0000000000000618 0000000000000018 27 45 8 [27] .strtab STRTAB 0000000000000000 00003658 0000000000000201 0000000000000000 0 0 1 [28] .shstrtab STRTAB 0000000000000000 00003859 000000000000011a 0000000000000000 0 0 1 [29] .debug_aranges PROGBITS 0000000000000000 00003973 0000000000000030 0000000000000000 0 0 1 [30] .debug_info PROGBITS 0000000000000000 000039a3 0000000000000033 0000000000000000 0 0 12. 动态链接相关段解析动态链接是现代Linux程序的重要组成部分它使得多个程序可以共享相同的库代码节省内存和磁盘空间。动态链接过程依赖于几个关键段2.1 .interp段 - 动态链接器的位置.interp段非常简单它只包含一个字符串指定了动态链接器的路径。例如$ readelf -p .interp /bin/ls String dump of section .interp: [ 0] /lib64/ld-linux-x86-64.so.2这个路径通常是/lib64/ld-linux-x86-64.so.264位系统或/lib/ld-linux.so.232位系统。内核在加载程序时会先加载这个动态链接器然后由它负责加载程序依赖的所有共享库。2.2 .dynamic段 - 动态链接信息中心.dynamic段是动态链接的核心它包含了动态链接器所需的所有信息。使用readelf -d可以查看其内容$ readelf -d /bin/ls Dynamic section at offset 0x2dc8 contains 24 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libselinux.so.1] 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000c (INIT) 0x1000 0x000000000000000d (FINI) 0x11d8 0x0000000000000019 (INIT_ARRAY) 0x3db8 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) 0x000000000000001a (FINI_ARRAY) 0x3dc0 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) 0x000000006ffffef5 (GNU_HASH) 0x378 0x0000000000000005 (STRTAB) 0x448 0x0000000000000006 (SYMTAB) 0x3a0 0x0000000000000007 (STRSZ) 130 (bytes) 0x0000000000000008 (SYMENT) 24 (bytes) 0x0000000000000009 (SYMENT) 24 (bytes) 0x0000000000000015 (DEBUG) 0x0 0x0000000000000003 (PLTGOT) 0x3fb8 0x0000000000000002 (PLTRELSZ) 24 (bytes) 0x0000000000000014 (PLTREL) RELA 0x0000000000000017 (JMPREL) 0x5b8 0x0000000000000007 (RELA) 0x4f8 0x0000000000000008 (RELASZ) 192 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes) 0x000000006ffffff9 (RELACOUNT) 3 0x0000000000000000 (NULL) 0x0.dynamic段中的每个条目都是一个键值对常见的重要条目包括NEEDED程序依赖的共享库INIT/FINI程序初始化和结束时的代码地址INIT_ARRAY/FINI_ARRAY初始化和结束函数数组STRTAB/SYMTAB字符串表和符号表位置PLTGOT全局偏移表(GOT)的位置JMPRELPLT重定位表位置2.3 动态符号相关段动态链接还依赖于几个符号相关的段.dynsym动态符号表包含动态链接所需的符号.dynstr动态字符串表包含符号名称等字符串.gnu.hash和**.hash**符号哈希表加速符号查找.rela.dyn和**.rela.plt**重定位表查看动态符号表的命令$ readelf -sD /bin/ls Symbol table for image: Num Buc: Value Size Type Bind Vis Ndx Name 0 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_toupper_locGLIBC_2.3 (2) 2 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getenvGLIBC_2.2.5 (3) 3 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sigprocmaskGLIBC_2.2.5 (3) ...3. 调试信息段(DWARF格式)DWARF是一种广泛使用的调试信息格式它包含了一系列以.debug_开头的段。这些段使得调试器能够将机器指令映射回源代码设置断点查看变量等。3.1 DWARF主要段介绍DWARF调试信息分布在多个段中段名描述.debug_info核心调试信息包含DIEs(调试信息条目).debug_abbrev缩写表用于压缩.debug_info.debug_line行号信息映射机器指令到源代码行.debug_str字符串表存储.debug_info中引用的字符串.debug_loc位置描述描述变量和参数的位置.debug_ranges地址范围描述非连续代码范围.debug_frame调用帧信息用于栈回溯3.2 查看DWARF信息使用readelf -w可以查看DWARF调试信息$ readelf -wi a.out Contents of the .debug_info section: Compilation Unit offset 0x0: Length: 0x33 (32-bit) Version: 4 Abbrev Offset: 0x0 Pointer Size: 8 0b: Abbrev Number: 1 (DW_TAG_compile_unit) c DW_AT_producer : (indirect string, offset: 0x0): GNU C17 9.3.0 -mtunegeneric -marchx86-64 -g 10 DW_AT_language : 12 (ANSI C99) 11 DW_AT_name : (indirect string, offset: 0x2a): hello.c 15 DW_AT_comp_dir : (indirect string, offset: 0x32): /home/user 19 DW_AT_low_pc : 0x1050 21 DW_AT_high_pc : 0x11d5 29 DW_AT_stmt_list : 0x0 12d: Abbrev Number: 2 (DW_TAG_subprogram) 2e DW_AT_external : 1 2e DW_AT_name : (indirect string, offset: 0x3d): main 32 DW_AT_decl_file : 1 33 DW_AT_decl_line : 1 34 DW_AT_type : 0x4e 38 DW_AT_low_pc : 0x1050 40 DW_AT_high_pc : 0x11d5 48 DW_AT_frame_base : 1 byte block: 9c (DW_OP_call_frame_cfa) 4a DW_AT_GNU_all_tail_call_sites: 1 24b: Abbrev Number: 3 (DW_TAG_base_type) 4c DW_AT_byte_size : 4 4d DW_AT_encoding : 5 (signed) 4e DW_AT_name : int对于大型程序DWARF信息可能非常庞大建议将输出重定向到文件$ readelf -wi large_program debug_info.txt $ less debug_info.txt3.3 行号信息.debug_line段将机器指令映射回源代码行号这对于调试至关重要$ readelf -wl a.out Decoded dump of debug contents of section .debug_line: CU: hello.c: File name Line number Starting address hello.c 1 0x1050 hello.c 2 0x1057 hello.c 3 0x1060 hello.c 5 0x1069 hello.c 6 0x1072 hello.c 7 0x107b hello.c 5 0x1084 hello.c 8 0x108d4. 实战解决常见问题理解了这些隐藏段后我们可以利用它们解决实际问题。4.1 诊断动态链接错误当遇到undefined symbol错误时可以按以下步骤诊断检查程序依赖的库$ readelf -d program | grep NEEDED查看缺失的符号是否在动态符号表中$ readelf -sD program | grep missing_symbol检查共享库是否导出该符号$ readelf -s /path/to/library.so | grep missing_symbol4.2 调试信息缺失问题如果GDB无法显示源代码或变量可能是调试信息有问题检查是否存在DWARF段$ readelf -S program | grep debug确认.debug_info是否包含你的源文件$ readelf -wi program | grep -A5 DW_TAG_compile_unit检查行号信息是否正确$ readelf -wl program4.3 优化调试体验通过理解DWARF格式可以优化调试体验使用-g3选项编译包含宏定义信息使用-fdebug-types-section将类型信息放在单独段减少重复使用-fvar-tracking-assignments增强变量跟踪5. 高级工具与技巧除了readelf和objdump还有其他工具可以帮助分析ELF文件5.1 eu-readelf (elfutils)elfutils套件中的eu-readelf提供了更友好的DWARF展示方式$ eu-readelf -win program5.2 dwarfdump专门用于分析DWARF信息的工具$ dwarfdump -a program5.3 自定义脚本分析对于复杂问题可以编写脚本解析ELF文件。Python的pyelftools库是一个不错的选择from elftools.elf.elffile import ELFFile with open(program, rb) as f: elffile ELFFile(f) if not elffile.has_dwarf_info(): print(No DWARF info found) else: dwarfinfo elffile.get_dwarf_info() for CU in dwarfinfo.iter_CUs(): print(Found CU at offset %s, length %s % (CU.cu_offset, CU[unit_length]))5.4 性能分析提示某些段对性能分析很有帮助.eh_frame用于异常处理和栈展开.gnu_debugdata包含压缩的调试信息可用于性能分析.note.gnu.build-id唯一构建ID用于精确匹配可执行文件和调试信息查看构建ID$ readelf -n /bin/ls Displaying notes found in: .note.gnu.build-id Owner Data size Description GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: 3e6f3159144281f709c3c5ffd41e376f53b47952理解ELF文件的这些隐藏段就像获得了程序的内部蓝图。无论是解决链接问题、优化调试体验还是进行底层性能分析这些知识都能让你事半功倍。