0%

程序员自我修养读后感(3)-静态链接

静态链接2

前面回顾了elf目标文件的结构,这里来讲一下静态链接的具体工作。

空间与地址的分配

链接的目的就是把多个文件加工合并成一个文件输出。输出文件的内容由多个文件的内容组成,这里就涉及文件的地址如何分配的问题。

由于各个段存在着地址对齐的问题, 不能简单的进行段的叠加,否则会有很多空间浪费。主要通过相同段的合并。虚拟空间的重新分配。主要分2步:

  1. 空间与地址的分配
    扫描各个文件的段的长度、属性及位置。收集符号表的所有符号和符号的引用,统一存放到全局的符号表。合并相同的段,计算出合并后的长度及位置,保存关系。

  2. 符号的解析与重定位
    读取输入文件的段的数据、重定位信息,进行符号的解析与重定位,调整代码中的地址。

1
ld a.o b.o -e main -o ab

ELF可执行文件默认从地址0x08048000开始分配。

符号地址的确定

经过上一步,各个段的虚拟地址已经确定。由于各个符号在段内的偏移是固定的,很容易就可以得出各个符号的虚拟地址。链接器就可以更新全局符号表中的符号地址了。

1
objdum -t ab // 查看符号表

符号的解析与重定位

在编译成目标文件的时候,外部引用的地址或者函数在指令中使用了假的地址作了替换。

并且在对应段的重定位表中保存了哪些位置需要被重定位,当上一步的地址分配确定下来之后,根据重定位表的内容,一一替换段中的地址。

重定位表保存了段的偏移位置以及符号在符号表中的下标,从而可以找到符号的地址,替换对应段偏移位置的内容。

1
2
3
4
5
6
7
8
9
10
11
12
Keep:programmer-up keep$ objdump -r a.o

a.o: file format Mach-O 64-bit x86-64

RELOCATION RECORDS FOR [__text]:
0000000000000024 X86_64_RELOC_BRANCH _swap
000000000000000b X86_64_RELOC_GOT_LOAD _sharedInited@GOTPCREL

RELOCATION RECORDS FOR [__compact_unwind]:
0000000000000000 X86_64_RELOC_UNSIGNED __text

Keep:programmer-up keep$

链接的过程中,涉及一个符号解析的过程。在引用外部符号的时候,外部符号在符号表中是UND的。链接的时候,会在全局符号表中查找是否存在对应的符号,如果不存在,就会报错。 undefine reference xxx。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

a.o: file format Mach-O 64-bit x86-64

SYMBOL TABLE:
0000000000000000 g F __TEXT,__text _main
0000000000000000 *UND* _sharedInited //外部变量
0000000000000000 *UND* _swap // 外部函数



Keep:programmer-up keep$ objdump -t ab

ab: file format Mach-O 64-bit x86-64

SYMBOL TABLE:
0000000000001000 g F __TEXT,__text __mh_execute_header
0000000000001f60 g F __TEXT,__text _main
0000000000002000 g __DATA,__data _sharedInited // 变量
0000000000002004 g __DATA,__common _sharedUndef
0000000000001fa0 g F __TEXT,__text _swap // 函数
0000000000000000 *UND* dyld_stub_binder

其他

函数级别的链接

链接的时候一般会把所有的函数和变量都进行链接,实际很多函数和变量是没有被调用到的,这样有点浪费空间了。GCC提供了选项 -ffunction-sections, -fdata-sections, 将每个函数和变量存在独立的段中, 这样就使得目标文件的段很多。编译时,需要计算依赖关系,编译速度也降低了。

交叉编译链工具

在开发嵌入式程序时,由于目标机器的环境限制(内存、空间、cpu速度),无法在目标机器上对代码直接进行编译。需要在其他系统上进行编译,这就需要用到交叉编译工具。其实,就是目标机器程序的编译工具 ,类似gcc, g++,nm等一系列工具。 这些工具需要在编译系统上运行,也就是说, 他们需要是编译系统上的程序。通过调用这些编译工具编译的代码,不能在编译系统上运行,但是需要在嵌入式系统(目标机器)运行。

实际上,大部分嵌入式系统也是类unix的系统,其可执行文件也遵循elf文件格式。只要在编译系统上,可以编译出目标机器上运行的可执行文件就可以了。 不管是windows,还是linux上,只要编译工具链完整,都是可以拿来编译程序的。

现在,编译工具大多是开源了的,而且都支持编译出不同系统的程序,只需要在编译的时候指定目标机器,设置好相关的参数,就可以编译出对应的编译链工具。

1
2
./configuare --host xxxlinux --prefix=/build/bin --target=arm-linux
make && make install

具体的编译配置可以使用选项-v查看

1
arm-linux-gcc -v

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。