avatar

链接

关于链接的相关重点描述

链接的两个步骤

  • 符号解析:将每一个符号引用和符号定义联系起来
    • 程序中 定义和引用的符号(包括变量和函数)
    • 编译器(编译过程)将定义的符号存放在 符号表
    • 编译器(编译过程)将符号的引用存放在重定位节(.rel.text rel.data)中
  • 重定位
    • 将多个代码段与数据段合并
    • 计算定义的符号在虚拟地址空间中的绝对地址

符号解析(符号绑定)

符号类型

  • 全局符号(global symblos)
    • 非 static 函数及非 static 全局变量
    • g++编译器将未初始化全局变量初始化为0,将之变为强符号
  • 外部符号(external symbols)
    • 仅仅declear的函数,带有extern 声明的变量
    • 也就是从语法角度来看函数声明总是extern,变量不带extern的话g++就会将之视为全局符号
  • 局部符号(local symbols)
    • 带有static修饰的静态符号
    • 不是局部变量,局部变量编译时不会放到任何节中去

多重定义符号处理

  • 强符号不能多次定义
  • 若一个符号被定义为弱及强,以强为准
  • 存在多个弱定义则选择一个

概述

1.编译器会将静态局部变量解析为独一无二的符号,分配在.data/.bss中,因此链接器对于static修饰局部符号不做修改
2.对于外部符号,在符号表中表示为NDE,链接器会在其他输入单元中寻找,如果没有输出异常信息
3.对于全局符号来说,链接要判断的工作就是在其他输入单元中不能存在相同的符号名

  • 符号解析的最终结果
    • 将全局符号唯一化,也就是说最终的符号表中的全局符号都是唯一的

静态链接和符号解析

静态库(static library) .a(linux) .lib(window)
解析方式:链接器维护一个obj集合E,该集合最终被合并成可执行文件,未解析的符号(即外部符号)U,以及在前一个输入文件中定义的符号D

  • 对于命令行中个每一个输入文件f,若为obj则加入E,修改U和D来反应f的符号定义和引用,并输入下一个f
  • 若f为.a,则尝试匹配U中未定义的符号和.a中定义的符号.若a中存在obj文件m则将m加入,并且修改U和D来反映m的符号情况,依次对其他文件进行,知道U和D都不发生改变.
  • 当链接器完成工作后,若U为非空则输出一个错误.否则则进行和并及重定向E中的目标文件,构建可执行文件.

解释:我之前一直在想所谓的符号解析到底对elf文件本身做了那些改变,这么来看实际上就是:

  • 保证全局变量唯一性:即保证合并后的符号表每个符号唯一
  • 处理未定义符号:即去在符号表中去除未定义符号,并合并
  • 静态符号不改变

符号表就是给链接器进行符号解析处理的一个信息,它并不参与到程序代码的任意一处去,如此图中的代码段,对于汇编代码来说,这些 符号就是符号引用,也就是要在链接过程得到真正的地址,编译器会将这些引用作为所谓的重定向信息添加到.rel text / .rel data中

重定向

概述

  • 重定位节和符号定义:
    合并不同的节,如上图第二部分,当该聚合结构体构建后,那么在text,data段中的所有变量(data节) 函数(指令)都有了唯一的地址
  • 重定位节中的符号引用:
    即处理上图指令中的符号引用,通过重定位条目处理

重定位条目

汇编器在汇编时并不知道数据和代码最终在内存中的实际位置,因此当遇到对最终位置位置的引用时,就会产生可重定位条目.函数引用对应.rel.text 全局变量对应.rel.data.

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
Elf32_Sword r_addend; /* Addend */
} Elf32_Rela;

typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;

r_offset表示该条目据离其所在text节偏移
r_addend表示该条目到下一条指令的附加
可重定位条目类型有32种,这里仅仅研究2种

  • R_X86_64_PC32: 使用相对pc定位,具体参照深入计算机原理3.6.3节,关于跳转部分.
    原理:相对跳转方式 call xx xx=下一条指令地址 - 目标地址
1
2
3
4
5
6
7
int sum(int *a,int n);
int array[2]={1,2};
int main()
{
int val=sum(array,2);
return val;
}
objdump代码
1
2
3
4
5
6
7
8
9
10
11
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: be 02 00 00 00 mov $0x2,%esi
d: bf 00 00 00 00 mov $0x0,%edi
12: e8 00 00 00 00 callq 17 <main+0x17>
17: 89 45 fc mov %eax,-0x4(%rbp)
1a: 8b 45 fc mov -0x4(%rbp),%eax
1d: c9 leaveq
1e: c3 retq

12: e8 00 00 00 00 就是xx的占位符
在符号解析完毕后目标函数的地址时确定的,下一条指令=重定向地址+r_addend,两者之差就是xx所要填上的

  • R_X86_64_32:绝对定位
    如全局变量在符号定位后其位置就是绝对的,直接改变并调用就行了.

由于引用函数调用会改变pc数,才会采用相对pc跳转,否则也按照绝对就行了.
至此就完成了链接过程,在可执行文件中不会带有.rel节,并且生成了program header来描述每个section所占段位置.

与动态库链接

静态链接弊端:

  • 更新时要和要更新的库进行主动链接
  • 所有使用了静态链接的程序都会将重复的代码 数据加载到内存中,浪费了内存空间

共享库(shared library):

  • 在文件系统中,仅仅存在一个so文件,所有引用该库的可执行目标文件都共享该库的代码和数据,节省磁盘.
  • 在内存运行中,所有不同的进程都可以共享该库的.text代码,节省内存.

加载时链接

  1. 创建动态库 gcc -shared -fpic - o xx.so xx.c xx.c
  2. gcc -o program xx.c ./xx.so
    此时ld(链接器)执行了部分链接工作,复制了so中的重定位和符号表信息,并没有进行text和data的复制工作,处理符号解析工作,确定了唯一全局符号.
  3. 当加载器加载可执行文件时,将会调用动态链接器(在program header中声明的.interp节表示的位置),该链接器也是一个共享库文件,此时动态链接器完成一下工作:
    • 重定位.so文件中的文本及数据到某个内存段
    • 重定位program中的所有对动态库的引用
  4. 之后共享库的内存位置就固定了,并在程序执行过程中都不会改变

动态链接的情况下,不同的模块装载地址一样是不行的。对于一个单个程序,我们可以指定各个模块的地址,但是对于某个模块被多个程序使用,或者是多个模块被多个程序使用,那么就会产生冲突的情况,比如1个人指定A模块为0x1000-0x2000,另一个人不使用B模块,而且指定B模块地址为0x1000-0x2000,那么很明显,A与B两个模块无法同时存在,任何人不能再同一个程序内使用模块A与B。

运行时动态链接

位置无关代码(PIC)

  • 共享库代码在加载时位置是可以不确定的
  • 即使代码库长度改变,也不影响调用它的程序
  • 无需修改程序代码就可将共享库加载到任意地址运行
    引用情况: 程序对共享库的引用情况,以及共享库自身的引用情况
  1. 模块内部调用,采用pc相对偏移寻找
  • 当动态链接时,动态模块会被分配到确定的内存位置
  • 模块内部代码调用pc偏移量实际就是pc下一条位置和函数指令间距,所以即使该模块在任何地方,该偏移量固定.
  • 无需重定位
  1. 模块内部数据访问
  • 动态库文件是由编译器构成,因此对于动态库文件的汇编代码实现和一般的可重入文件是不同
  • 这里也说明动态库内部文件没有进行节合并
    3 模块外数据调用 (PIC)

4 模块外函数调用 (PIC)

此图中GOT[2]存放的是动态链接延迟绑定代码的地址,该代码做了重定位工作

文章作者: fancylight
文章链接: https://www.fancylight.top/2018/09/05/%E9%93%BE%E6%8E%A5/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 博客
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论