bolin深入探索Linux系统下的符号解析

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系统下程序运行的重要过程之一,它将程序中的符号与其对应的地址或实际值联系起来,从而实现程序的正确运行。动态链接库是实现符号解析的核心技术,它可以减小程序的内存占用,提高代码重用性。符号解析的优化技术包括符号预取和符号隐藏,可以在一定程度上提高程序的性能。

操作系统标签