Qt PDF查看器集成终极指南:5分钟实现专业PDF浏览功能
2026/6/9 20:33:47
本文档基于 Linux 5.x 内核和 Glibc 2.3x 环境,深入解析 ELF 文件中的全局符号表技术。通过理论分析、可视化图表和实战案例,帮助开发者全面掌握符号解析与动态链接的核心机制。
在 ELF (Executable and Linkable Format) 文件中,符号表是链接器(Linker)和动态加载器(Loader)进行符号解析和重定位的关键数据结构。主要的符号表节区(Section)包括:
strip移除。ld.so必须依赖的信息,不可移除。符号表本质上是一个结构体数组,每个元素对应一个Elf64_Sym(64位系统)结构。
数据结构定义 (引用自<elf.h>):
typedefstruct{uint32_tst_name;/* 符号名称(字符串表索引) */unsignedcharst_info;/* 符号类型和绑定属性 */unsignedcharst_other;/* 符号可见性 */uint16_tst_shndx;/* 关联的节区索引 */Elf64_Addr st_value;/* 符号值(地址或偏移量) */uint64_tst_size;/* 符号大小(字节) */}Elf64_Sym;字段详解:
.strtab或.dynstr)的索引,表示符号的名称字符串。int变量为 4,对于函数则为指令序列的长度。STB_LOCAL(0),STB_GLOBAL(1),STB_WEAK(2)STT_NOTYPE(0),STT_OBJECT(1, 变量),STT_FUNC(2, 函数),STT_SECTION(3)STV_DEFAULT(0): 默认可见性,可被抢占。STV_HIDDEN(2): 隐藏符号,仅本模块内部可见,不导出到.dynsym。STV_PROTECTED(3): 外部可见,但不能被抢占。SHN_UNDEF。符号解析是链接器将每个符号引用(Reference)与唯一的符号定义(Definition)关联起来的过程。
静态链接期:ld扫描所有输入的可重定位目标文件(.o)和归档文件(.a)。它维护三个集合:
动态链接期:ld.so在程序启动或dlopen时工作。
当程序启动时,内核将控制权交给ld.so,其核心步骤如下:
R_X86_64_GLOB_DAT)和函数引用的重定位(如R_X86_64_JUMP_SLOT)。.hash或.gnu.hash)加速查找。为了解决 “DLL Hell” 问题,Glibc 引入了符号版本机制。
puts@GLIBC_2.2.5。.gnu.version节区包含每个动态符号的版本索引。.gnu.version_d定义本模块提供的版本定义。.gnu.version_r定义本模块依赖的外部版本需求。classDiagram class ELF_File { +ELF_Header +Program_Headers +Section_Headers +.text +.data +".symtab (Symbol Table)" +".strtab (String Table)" } class Elf64_Sym { +uint32_t st_name +unsigned char st_info +unsigned char st_other +uint16_t st_shndx +Elf64_Addr st_value +uint64_t st_size } class String_Table { +char[] strings } ELF_File *-- Elf64_Sym : "Contains List of" Elf64_Sym --> String_Table : "st_name (Index)" note for Elf64_Sym "st_info:\nHigh 4 bits: Binding (Global/Weak)\nLow 4 bits: Type (Func/Object)"我们将使用一个简单的 C 语言示例来演示。
代码准备:
libmath.c(共享库):
#include<stdio.h>intglobal_var=42;// 强符号intadd(inta,intb){returna+b;}// 弱符号__attribute__((weak))intsubtract(inta,intb){returna-b;}// 隐藏符号__attribute__((visibility("hidden")))voidinternal_helper(){printf("Internal\n");}voidpublic_api(){internal_helper();}main.c(主程序):
#include<stdio.h>externintglobal_var;externintadd(int,int);externintsubtract(int,int);intmain(){printf("Val: %d\n",global_var);returnadd(10,20);}编译命令:
gcc -shared -fPIC -o libmath.so libmath.c gcc -o demo_app main.c -L. -lmath -Wl,-rpath,.readelf -s查看符号表命令:readelf -s libmath.so
输出解析(截取):
Symbol table '.dynsym' contains 10 entries: Num: Value Size Type Bind Vis Ndx Name 6: 0000000000001161 21 FUNC GLOBAL DEFAULT 14 public_api 7: 0000000000001119 24 FUNC GLOBAL DEFAULT 14 add 8: 0000000000001131 22 FUNC WEAK DEFAULT 14 subtract 9: 0000000000004028 4 OBJECT GLOBAL DEFAULT 24 global_var14表示符号定义在第 14 号节区(通常是.text)。add是GLOBAL,subtract是WEAK。DEFAULT,表示可见且可被抢占。注意internal_helper不在.dynsym中,因为它被标记为hidden。nm解析符号命令:nm -D libmath.so(-D 查看动态符号表)
输出示例:
0000000000001119 T add 0000000000004028 D global_var 0000000000001161 T public_api 0000000000001131 W subtract U puts@GLIBC_2.2.5objdump定位符号引用命令:objdump -d demo_app | grep -A 10 "<main>:"
输出示例:
0000000000001189 <main>: ... 1191: 8b 05 79 2e 00 00 mov 0x2e79(%rip),%eax # 4010 <global_var@@Base> ... 11a8: e8 d3 fe ff ff call 1080 <printf@plt>mov 0x2e79(%rip), %eax: 这里使用了 RIP 相对寻址访问 GOT 表中的global_var地址。call 1080 <printf@plt>: 调用了 PLT 表项,实现了延迟绑定。static符号。可以通过-fvisibility=hidden改变默认行为。-fvisibility=hidden的影响default的符号的st_other字段设为STV_HIDDEN。.dynsym和.dynstr的大小。LD_PRELOAD预加载自定义库。由于动态链接器的全局查找顺序,预加载库中的符号会覆盖后续库的同名符号。malloc,open,write等系统调用,监控或篡改程序行为。static或隐藏可见性),或在链接时使用-Bsymbolic强制优先绑定库内符号。.symtab仅提供地址和名称。.debug_*sections)提供了丰富的信息:文件名、行号、变量类型、结构体布局等。