静态链接过程

转自:博客1, http://chuquan.me/2018/06/03/linking-static-linking-dynamic-linking/

1.为什么需要重定位?

执行函数:

程序的运行过程就是CPU不断的从内存中取出指令然后执行的过程。

假设对g_num做++操作,那么汇编指令:

call 0x4004fd    #地址为函数add在内存中的地址,调用时会跳转到0x4004fd来执行指令函数add对应的机器指令
mov 0x400fda %eax   #将0x400fda地址的内容放到eax寄存器中,g_num的地址就是0x400fda
add $0x1 %eax   #将寄存器内容+1

那么地址从哪里获取?确定程序运行时地址的过程就是 重定位(Relocation)。

2.编译为 .o文件的过程

目标文件.o包括:

  1. 数据段:全局变量的定义
  2. 代码段:所有函数的定义
  3. 符号表:对外部变量的引用

编译过程可以确定:

  • 定义在该源文件中函数的内存地址
  • 定义在该源文件中全局变量的内存地址

//都是针对本文件内部的相对地址

而如果针对引用其他文件中定义的变量或函数,那么编译时就将调用的地址设置为空,并且生成一条记录,之后的任务交给链接器。

例如:

// 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;
}

 那么在编译为汇编文件.i时,调用其他文件中的函数,汇编指令为空地址,表示不确定:

call 0x000000

 并且生成一条记录放在.rel.text中,对外部定义的全局变量的使用,放在.rel.text中,链接器根据上述两者内容填写空白位置。

静态链接过程

.o文件内容

对外部函数的引用放在.rel.text中,对外部全局变量的引用放在.rel.data段中。

编译过程确定了该源文件中函数和变量的相对地址,以及引用其他文件的相对地址,需要链接时确定的信息放在.rel.data/.rel.text中,以修正地址。

3.链接器的工作

3.1 重定位第一个过程:链接合并段

链接器会将所有需要的目标文件.o合并,即将对应段合并,这样就能确定相对地址,以更新目标文件中的相对地址。

静态链接过程

上图中,蓝色部分表示为代码段,依次合并;

黄色是数据段,主要观察数据段的相对地址变化,目标文件2经过合并更新相对地址后是6,目标文件3经过合并更新相对地址后是8.

相对地址 + offset(偏移) = 最终内存地址

在合并时会产生偏移。段的偏移只有在链接的时候才能确定,因为连接时才会发生段的合并,确定最终地址。编译过程是无法确定的。

3.2 引用符号重定位

已经确定了相对地址和偏移,确定了变量和函数的最终内存地址,所以就可以更新.rel.data/.ref.text的最终值。

其中rel是Relocation Table的缩写,保存着需要重定位的符号,即重定位表。

4.#include的所有都会被包含?

可以认为在静态编译时,形成可执行文件时,会将头文件中的函数也一并有个副本,进行段合并,在代码段合并,共同生成可执行文件。

即每生成一个可执行文件,就会有一份函数的拷贝,在调用时,比如说scanf这样,直接call 最终内存地址即可。

上一篇:48-54 尝试每天两道题中 一道顺序 一道每日一题 遇到困难题可能是一道.


下一篇:第六题 Z字走法