操作系统系列六 —— 详细解释【静态链接】

往期地址:

  • 操作系统系列一 —— 操作系统概述
  • 操作系统系列二 —— 进程
  • 操作系统系列三 —— 编译与链接关系
  • 操作系统系列四 —— 栈与函数调用关系
  • 操作系统系列五——目标文件详解

本期主题:
静态链接详解


静态链接详解

  • 0.前言
  • 1.空间和地址分配
  • 2.重定位表和符号解析
  • 3.强弱符号、COMMOM块
    • 3.1 强弱符号
    • 3.2 强弱引用
    • 3.3 COMMON块


0.前言

从往期的目标文件详解(链接在上面)我们已经大概了解ELF文件的结构以及一些相关信息,现在的问题是,这些ELF文件怎么链接在一起,变成一个可执行文件呢?
以下面的两个源码 “a.c” 和 “b.c” 为例:
操作系统系列六 —— 详细解释【静态链接】_第1张图片

gcc -c -fno-stack-protector a.c b.c

经过gcc -c 编译后,生成了a.o和b.o两个文件,模块 a.c 中引用了 b.c 模块中的 "shared"变量 以及 “swap函数” ,接下来,我们来看这两个目标文件怎么最终链接成一个可执行文件。

静态链接分为两步:
1.空间和地址的分配
2.符号解析与重定位

1.空间和地址分配

总结: 静态链接第一步,多个目标文件链接时,会进行相似段的合并,并且会计算输出文件中的各个段合并后的长度与位置,建立映射关系。例如,a.o的.data段 会与 b.o的.data段合并。

jason@ubuntu:~/WorkSpace/3.OS_study/2.static_link$ objdump -h a.o b.o ab

a.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000032  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000072  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000072  2**0
                  ALLOC
  ...

b.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000031  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000004  0000000000000000  0000000000000000  00000074  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000078  2**0
                  ALLOC
  ...

ab:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  ...	
  1 .text         00000063  0000000000401000  0000000000401000  00001000  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  2 .eh_frame     00000058  0000000000402000  0000000000402000  00002000  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .data         00000004  0000000000404000  0000000000404000  00003000  2**2
                  CONTENTS, ALLOC, LOAD, DATA

从最后生成的ab文件可以看出,.text、.data段的大小是把a.o和b.o的对应段合并相加了。
还可以由 a.o、b.o、ab 这三个文件来确认符号地址

原则就是:各个符号在段内的相对位置是固定的。例如 main函数符号在a.o中的偏移位置和ab文件中是一样的。

然后我们就可以通过 objdump -d 来确认各个符号的位置:
结果:
main函数的位置: 在a.o的起始位置,(0000位置)
shared变量的位置: 看指令偏移量为17的指令,其中 48 8d 35是lea的指令码,00 00 00 00 是shared变量的位置
swap函数的位置: 看指令偏移量为26的指令,callq是一条相对跳转指令,指的是目的地址相对于下一条指令的偏移,26的下一条是2b,所以就是swap函数位置在 2b
重点: 编译器把这两条指令(shared变量和swap函数)是暂时用地址’0x0000’和地址’0x2b’代替着,真正的地址计算在链接的时候做。(那么链接器怎么知道哪个符号需要重新计算地址,哪个符号不需要呢?
操作系统系列六 —— 详细解释【静态链接】_第2张图片
操作系统系列六 —— 详细解释【静态链接】_第3张图片

2.重定位表和符号解析

解答前面的疑问,链接器是通过重定位表来知道哪个符号需要重定位,重定位表会描述需要进行重定位的符号。
操作系统系列六 —— 详细解释【静态链接】_第4张图片
还有使用readelf读符号表
操作系统系列六 —— 详细解释【静态链接】_第5张图片

3.强弱符号、COMMOM块

3.1 强弱符号

对于c/c++语言来说,编译器默认函数和初始化了的全局变量是强符号,未初始化的全局变量为弱符号。

强弱符号的相关规则:

  1. 强符号不允许被多次定义,如果有多个强符号定义,则链接器报错,“multiple definition of xxx” ;
  2. 如果一个符号在某个目标文件中是强符号,在其他文件中是弱符号,则选择强符号;
  3. 如果一个符号在所有目标文件中都是弱符号,那么最终链接时选择占用空间最大的一个;

3.2 强弱引用

同样的也有强弱引用的说法:

  1. 在处理强引用时,若符号有定义,则引用决议,判断哪个是最终使用的符号,若该符号没有定义,则认为链接报错;
  2. 在处理弱引用时,与强引用基本相似,只是在处理符号没有定义的情况,弱引用并不直接报错,而是认为其是一个默认值;

弱引用的常见形式,使用" attribute((weakref)) "

__attribute__((weakref)) void test();

int main()
{
	test();
}

3.3 COMMON块

在讲解目标文件时,我们就发现 未初始化的全局变量在链接之前处于COMMON段,这里我们来详细讲解。

现在的编译器和链接器都支持一种COMM块的机制,当不同目标文件所需要的COMMON块大小不一致时,以最大块为准

看一个实际的例子:
a.c 和 b.c都定义未初始化的全局变量,c.c去引用,通过符号表看大小
操作系统系列六 —— 详细解释【静态链接】_第6张图片

$ gcc -c -fno-stack-protector a.c b.c c.c
$ ld a.o b.o c.o -e main -o abc
$ readelf -s a.o b.o abc

结果:最终使用了b.c中的 a_comm,占4个字节
操作系统系列六 —— 详细解释【静态链接】_第7张图片
总结

1.把未初始化的全局变量分配在COMM空间,而不直接放在bss段的原因是:未初始化的全局变量是弱符号,假如其他目标文件也有该弱符号,在链接之前不能确定该弱符号的大小,所以大小是未知的,只能先放在COMMON块;
2.总体来看,链接之后,未初始化的全局变量最终还是放在bss段;

你可能感兴趣的:(计算机操作系统,操作系统,编译器)