静态链接2
前面回顾了elf目标文件的结构,这里来讲一下静态链接的具体工作。
空间与地址的分配
链接的目的就是把多个文件加工合并成一个文件输出。输出文件的内容由多个文件的内容组成,这里就涉及文件的地址如何分配的问题。
由于各个段存在着地址对齐的问题, 不能简单的进行段的叠加,否则会有很多空间浪费。主要通过相同段的合并。虚拟空间的重新分配。主要分2步:
空间与地址的分配
扫描各个文件的段的长度、属性及位置。收集符号表的所有符号和符号的引用,统一存放到全局的符号表。合并相同的段,计算出合并后的长度及位置,保存关系。符号的解析与重定位
读取输入文件的段的数据、重定位信息,进行符号的解析与重定位,调整代码中的地址。
1 | ld a.o b.o -e main -o ab |
ELF可执行文件默认从地址0x08048000开始分配。
符号地址的确定
经过上一步,各个段的虚拟地址已经确定。由于各个符号在段内的偏移是固定的,很容易就可以得出各个符号的虚拟地址。链接器就可以更新全局符号表中的符号地址了。
1 | objdum -t ab // 查看符号表 |
符号的解析与重定位
在编译成目标文件的时候,外部引用的地址或者函数在指令中使用了假的地址作了替换。
并且在对应段的重定位表中保存了哪些位置需要被重定位,当上一步的地址分配确定下来之后,根据重定位表的内容,一一替换段中的地址。
重定位表保存了段的偏移位置以及符号在符号表中的下标,从而可以找到符号的地址,替换对应段偏移位置的内容。
1 | Keep:programmer-up keep$ objdump -r a.o |
链接的过程中,涉及一个符号解析的过程。在引用外部符号的时候,外部符号在符号表中是UND的。链接的时候,会在全局符号表中查找是否存在对应的符号,如果不存在,就会报错。 undefine reference xxx。
1 |
|
其他
函数级别的链接
链接的时候一般会把所有的函数和变量都进行链接,实际很多函数和变量是没有被调用到的,这样有点浪费空间了。GCC提供了选项 -ffunction-sections, -fdata-sections, 将每个函数和变量存在独立的段中, 这样就使得目标文件的段很多。编译时,需要计算依赖关系,编译速度也降低了。
交叉编译链工具
在开发嵌入式程序时,由于目标机器的环境限制(内存、空间、cpu速度),无法在目标机器上对代码直接进行编译。需要在其他系统上进行编译,这就需要用到交叉编译工具。其实,就是目标机器程序的编译工具 ,类似gcc, g++,nm等一系列工具。 这些工具需要在编译系统上运行,也就是说, 他们需要是编译系统上的程序。通过调用这些编译工具编译的代码,不能在编译系统上运行,但是需要在嵌入式系统(目标机器)运行。
实际上,大部分嵌入式系统也是类unix的系统,其可执行文件也遵循elf文件格式。只要在编译系统上,可以编译出目标机器上运行的可执行文件就可以了。 不管是windows,还是linux上,只要编译工具链完整,都是可以拿来编译程序的。
现在,编译工具大多是开源了的,而且都支持编译出不同系统的程序,只需要在编译的时候指定目标机器,设置好相关的参数,就可以编译出对应的编译链工具。
1 | ./configuare --host xxxlinux --prefix=/build/bin --target=arm-linux |
具体的编译配置可以使用选项-v查看
1 | arm-linux-gcc -v |
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。