1. 什么是符号解析?
符号解析(symbol resolution)是指在计算机程序中将标识符(符号)与其对应的地址或实际值联系起来的过程。在Linux系统下,符号解析在程序的运行阶段进行,主要通过动态链接库实现。
动态链接库(Dynamic Linking Library,DLL)是一种特殊的共享库,可以在程序运行时动态加载和卸载。相比于静态链接库,动态链接库可以减小程序的内存占用,提高代码重用性。动态链接库中包含了程序需要调用的函数、变量等符号,程序在运行时通过符号解析将这些符号与相应的地址联系起来。
2. 符号解析的实现
2.1. 符号表
符号表是符号解析的核心数据结构,记录了程序中所有的符号以及其对应的地址或实际值。符号表的建立是在编译时进行的,在编译过程中将所有定义的符号加入符号表中。编译器会将符号表嵌入到目标文件中,供链接时使用。
在Linux系统下,可以通过命令`readelf -s
$ readelf -s /usr/bin/bash
Symbol table '.dynsym' contains 1252 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000400680 0 SECTION LOCAL DEFAULT 1
2: 0000000000400694 0 SECTION LOCAL DEFAULT 2
3: 00000000004006a8 0 SECTION LOCAL DEFAULT 3
4: 00000000004006e0 0 SECTION LOCAL DEFAULT 4
5: 0000000000400700 0 SECTION LOCAL DEFAULT 5
...
上述命令输出了`/usr/bin/bash`文件中的符号表。每一行表示一个符号,包括符号的名字、地址、大小、类型、绑定等信息。其中,`Ndx`字段表示符号所在的段(section)。
2.2. 符号重定位
符号重定位(symbol relocation)是指在程序运行时将符号与其对应的地址联系起来的过程。在Linux系统下,符号重定位是由动态链接器完成的。
动态链接器在程序运行时会在库载入时解析函数的符号,将函数地址存储在全局偏移表(Global Offset Table,GOT)中。当程序调用该函数时,会使用PLT(Procedure Linkage Table)进行跳转,PLT会检查该函数是否已经被解析,如果没有解析则进行符号解析,然后将符号地址写入GOT,供下一次调用使用。
可以通过`readelf -r
$ readelf -r /usr/bin/bash
Relocation section '.rela.dyn' at offset 0x1678 contains 186 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000601ff0 000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000602058 000500000007 R_X86_64_COPY 00000000006dc9c0 environ@@GLIBC_2.2.5 + 0
...
上述命令输出了`/usr/bin/bash`文件中的重定位表。每一行表示一个需要进行重定位的位置,包括偏移量、类型、符号名等信息。
3. 符号解析的优化
3.1. 符号预取
符号预取(symbol preemption)是指在程序运行时,动态链接器会尝试使用预取技术(预先加载库)尽可能地减少符号解析的时间。如果一个函数或变量在程序运行过程中没有被调用,则使用预取技术可以避免对该函数或变量进行解析,从而提高程序的性能。
可以通过`readelf -d
$ readelf -d /usr/bin/bash
Dynamic section at offset 0x16d0 contains 28 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libtinfo.so.6]
0x0000000000000001 (NEEDED) Shared library: [libdl.so.2]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
...
上述命令输出了`/usr/bin/bash`文件中的动态节信息,包括需要加载的共享库。
3.2. 符号隐藏
符号隐藏(symbol hiding)是指将一些符号隐藏起来,使得它们只能被当前文件或库内的函数使用,从而避免全局作用域的符号冲突。在编译时,可以通过标识符前加上`static`关键字来实现符号隐藏。
可以通过`nm
$ nm /usr/lib/libc.so.6 | grep calloc
0000000000085a60 T __calloc_avx2_32
0000000000086e00 T calloc
0000000000085f10 T calloc@GLIBC_2.2.5
0000000000085f10 T __calloc_avx512
00000000000845f0 T __memalign_calloc_alignment
00000000000d99c0 T pvalloc
00000000000469f0 W __walcalloc
...
上述命令输出了`/usr/lib/libc.so.6`库中的符号信息。每一行包括符号的地址、类型、符号名等信息。其中,`T`表示该符号是可执行程序中的符号,`W`表示该符号是可写变量,`U`表示该符号未定义,需要在链接时解析。
4. 总结
符号解析是Linux系统下程序运行的重要过程之一,它将程序中的符号与其对应的地址或实际值联系起来,从而实现程序的正确运行。动态链接库是实现符号解析的核心技术,它可以减小程序的内存占用,提高代码重用性。符号解析的优化技术包括符号预取和符号隐藏,可以在一定程度上提高程序的性能。