静态链接

静态链接

    • 一、编译:.c 变 .o
      • 1.1 源文件:a.c、b.c
      • 1.2 .c 变 .o
    • 二、链接
      • 2.1 链接过程
      • 2.2 链接器 ld 链接文件
      • 2.3 段地址分配
      • 2.4 符号地址分配
      • 2.5 符号解析
      • 2.6 重定位
      • 2.7 指令修正

一、编译:.c 变 .o

1.1 源文件:a.c、b.c

/* a.c */
extern int shared;

int main ()
{
    int a = 100;
    swap(&a, &shared);
}
/* b.c */
int shared = 1;

void swap(int* a, int* b)
{
	*a ^= *b ^= *a ^= *b;
}

1.2 .c 变 .o

gcc -c a.c b.c

通过 gcc 将源文件 a.c、b.c 编译成目标文件 a.o、b.o
静态链接_第1张图片

二、链接

2.1 链接过程

(1)空间与地址分配

  • 扫描所有输入目标文件,获取各段长度、属性和位置
  • 收集输入目标文件中的符号定义和符号引用,放到全局符号表
  • 将输入目标文件具有相同性质的段合并到一起,计算出输出文件中各个段的长度与位置

(2)符号解析与重定位

  • 读取输入目标文件中的段的数据、重定位信息
  • 根据 (1)中收集的信息,进行符号解析与重定位

重定位的过程伴随着符号解析的过程,当链接器需要对某个符号的引用进行重定位时,链接器会查找全局符号表中是否有该符号,不存在则报符号未定义错误;存在则开始对符号进行重定位

2.2 链接器 ld 链接文件

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

将输入目标文件 a.o、b.o 加工合并成一个输出文件 ab
静态链接_第2张图片

2.3 段地址分配

objdump -h 查看链接前 a.o、b.o 各段属性

objdump -h a.o

静态链接_第3张图片

objdump -h b.o

静态链接_第4张图片

objdump -h 查看链接后 ab 各段属性
静态链接_第5张图片

text(ab)= .text(a.o)+ .text(b.o)= 0x51 + 0x4b = 0x9c
.data(ab)= .data(a.o)+ .data(b.o)= 0x00 + 0x04 = 0x04

  • 上图中的 VMA 列,表示的是虚拟地址
  • 链接前后使用的正是虚拟地址,只是链接前虚拟空间还未分配,VMA 值为 0(输入目标文件 a.o、b.o 的 VMA 列值全为 0);链接后输出文件 ab 各段都分配了虚拟地址(输出文件 ab 的 VMA 列都有了相应的虚拟地址)

2.4 符号地址分配

当上面输出文件 ab 各段虚拟地址分配完成后,链接器开始计算各个符号的虚拟地址(各个符号在段内的相对位置是固定的,因此段虚拟地址确定后,就可以确定符号的虚拟地址)

readelf -s 查看输入目标文件 a.o、b.o 符号表
静态链接_第6张图片

readelf -h 查看输入目标文件 a.o、b.o 文件头
静态链接_第7张图片

由图可知,a.o、b.o 的文件头大小都为 0x40,在段地址分配中,通过 objdump -h 看到 a.o、b.o 的 .text 段的偏移量都为 0x40,则:
静态链接_第8张图片

readelf -s 查看输出文件 ab 符号表
Value:符号值
Type:符号类型
符号类型(Type)为函数(FUNC)或变量(OBJECT)时,符号值(Value)就是函数或变量的地址
静态链接_第9张图片

由图可得,对应符号 main、swap、shared 的符号地址

符号 类型 虚拟地址
main 函数(FUNC) 0x4002d0
swap 函数(FUNC) 0x400321
shared 变量(OBJECT) 0x601020

2.5 符号解析

全局符号表:由所有输入目标文件的符号表组成
链接器在扫描完所有的输入目标文件后,所有未定义的符号(符号表中 Ndx 列显示 UND 的符号)都应该能够在全局符号表中找到,否则链接器会报符号未定义错误

readelf -s 可以查看输入目标文件 a.o、b.o 符号表
静态链接_第10张图片

接着开始重定位

2.6 重定位

objdump -d 反汇编输入目标文件 a.o
静态链接_第11张图片

由图可知

  • 在链接(空间与地址分配 + 符号解析与重定位)前,目标文件中代码段(.text 段)起始地址从 0x00000000 开始
  • 编译器在编译目标文件时,无法确定符号引用 shared 和 swap 的地址,会暂时把地址置为 0

疑问:怎么知道无法确定的符号引用是 shared 和 swap?
解:看重定位表

objdump -r 查看输入目标文件 a.o 重定位表
OFFSET:段中偏移
shared 出现在 .text 段偏移 0x25 的位置
swap 出现在 .text 段偏移 0x32 的位置
静态链接_第12张图片

再次查看输入目标文件 a.o 反汇编结果
静态链接_第13张图片

查看偏移

  • 22: 48 8d 35 00 00 00 00
    48 偏移为 0x22,那 00 00 00 00 第一个字节偏移 0x22 + 3 = 0x25,而重定位表中,在 .text 段偏移 0x25 的是 shared
  • 31: e8 00 00 00 00
    e8 偏移为 0x31,那 00 00 00 00 第一个字节偏移 0x31 + 1 = 0x32,而重定位表中在 .text 段中偏移 0x32 的是 swap

2.7 指令修正

静态链接_第14张图片

ELF 重定位类型

  • R_X86_64_PC32
    重定位一个使用 32 位 PC 相对地址(PC 相对地址:距程序计数器 PC 的当前运行时值的偏移量)的引用。当 CPU 执行一条使用 PC 相对寻址的指令时,将指令中编码的 32 位值加上 PC 的当前运行时值(下一条指令在内存中的地址),从而得到有效地址(call 指令的目标)
  • R_X86_64_PLT32
    暂时不清楚宏 R_X86_64_PLT32 的重定位修正方法,只能猜一下修正后的指令的计算过程(符号的虚拟地址 - 下一条指令虚拟地址),如下图
    静态链接_第15张图片

你可能感兴趣的:(读书笔记,静态链接)