手把手带你了解链接全过程(一)

手把手带你了解链接全过程(一)

link

目标文件

可重定位目标文件格式

首先要知道

  • 目标文件就是纯粹的字节块的集合
  • 这些块中有: 包含程序代码,程序数据,链接器与加载器的数据结构
  1. .text已编译程序代码
  2. .rodata只读数据
  3. .data已经初始化的全局和静态变量
  4. .bss未初始化全局和静态变量
  5. .symtab 符号表,每一个可重定位目标文件中都有一个symtab
  6. .rel.text 一个.text节中位置列表
  7. .rel.data被模块引用或者定义的全局变量的重定位信息
  8. debug调试符号表 编译时候加入-g生成
  9. .line远吗行号与.text中机器指令的映射,编译-g得到
  10. .strtab字符串表

目标文件分类

我们知道,通过as(汇编器),可以将汇编文件.s翻译成目标文件

目标文件有以下几种

  • 可重定位目标文件,包含二进制代码,可以与其他可重定位目标文件合并起来生成可执行目标文件
  • 可执行目标文件,包含二进制代码个数据,可以直接将复制到内存并执行
  • 共享目标文件,一种特殊的可重定位目标文件,可以在加载或者运行的时候被动加载进内存并链接

编译器和汇编器会生成可重定位目标文件(包括共享目标文件).链接器生成可执行目标文件.

一个目标模块是一个字节序列,一个目标文件就是一个以文件形式存放在磁盘的目标模块

链接器的作用

  • 符号解析 , 目标文件定义和引用符号(函数,一个全局变量,一个静态变量).符号解析的是为了将每一个符号的引用和定义关联起来
  • 重定位, 编译器和汇编器生成地址从0开始的代码和数据节,链接器通过将每个符号的定义与一个内存位置关联起来,从而定位出这个节,这时候就可以修改所有对这些符号的引用,使得都指向这个内存位置

就是先关联,再绑定内存,之后修改指向

符号和符号表

我们知道,每一个可重定位目标模块都有一个symtab,里面有符号定义与引用的信息.

符号分为三种

  1. 由模块m定义并可以被其他模块引用的全局符号

非静态的C函数(默认是extern的)以及全局变量

  1. 外部符号, 由其他模块定义被模块m引用的全局符号

其他模块中定义的非静态的C函数(默认是extern的)以及全局变量

  1. 局部符号 只能被模块m定义和引用的局部符号

带有static属性的C函数和全局变量.这些在模块m的任意位置可见,但是不能被其他模块引用相当于C++中的私有private

任何带有static属性声明的函数以及全局变量都是私有的;任何不带有static属性声明的函数以及全局变量都是公共的可以被其他模块访问

.symtab中不包含的符号由栈管理(局部static变量除外)

符号解析

符号解析的目的是,将每一个引用与它的输入的可重定位目标文件中的符号表symtab中的定义关联起来

对于局部符号,编译器会确保它们有唯一的名字

对于全局符号,当遇到外部符号的时候,会假设该符号在其他的模块中有定义,会生成一个链接器符号条目表,并把它交给链接器处理.链接器会在所有的输入模块的全局符号中查找定义,如果没有查找到就会出错.一般是undefine reference to

所以声明可以声明多次,之后选择其中一个,因为声明是弱符号。

int a是定义

extern int a是声明,不分配空间,可以写多次

静态库

出现静态库的原因主要在于不方便将每一个实现都放在一个可重定位目标文件里面,如果这样的话,引用一个实现就需要在内存中加载整个.o文件,且有一点改动都需要完成的重新编译.

所有有了静态库的概念,静态库其实就是一组可重定位目标文件,在执行文件时,只会赋值静态库中被应用程序引用的目标模块

链接器如何引用静态库

**过程如下:

图片

所以才会有这个链接顺序问题

gcc ./libvector.a main.c

这时候如果mian.c中用到了存档文件中的函数或者全局变量就会报错

因为,当libvector.a取匹配的U中未引用符号的时候,会匹配不到,其中的可重定位目标文件就不会加入E最后导致加入main.o文件时U非空,链接出错

到这里符号解析就全部完成了

重定位

符号解析完成以后.每一个符号引用都会对应一个符号定义.这时候链接器就知道输入的目标模块中的代码节.text.data的大小(所以数据函数的大小在编译的时候就知道啦),就可以开始重定位

重定位有两个步骤一共

  • 重定位节和符号定义

将输入的目标文件的所有的相同类型的节合并成一个聚合节成为可执行文件的一个节,比如所有的.data聚合成可执行目标文件的.data节.

之后链接器将运行时内存地址赋予新的聚合节,这样所有的符号定义都有唯一的运行时候的内存地址了

  • 重定位节中的符号引用

修改代码节和数据节中的符号引用,使它们都指向正确的运行时候的地址

也就是运行时候的地址是唯一的,是在重定位的时候确定的

加载可执行目标文件

./prog

终端执行该命令的时候,因为prog不是shell内置的命令.所以.shell会认为prog是一个可执行的目标文件(目标文件类型的第二种).会通过execve函数来调用加载器,将可执行目标文件的代码和数据从磁盘复制的内存,并跳转到入口点来执行函数.这时候就会运行一个内存映像,也就是虚拟内存

动态链接库

首先得了解共享库的概念

共享库是一个目标模块(目标文件类型的第三种)运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来,这个过程就叫做动态链接

共享库的作用

  • 相比于静态库更新需要重新编译部署,动态库更新只需要更换动态库的内容就可以
  • C的标准I/O函数几乎都会用到,如果每一个进程都将这些函数的代码加载进内存,那么几百个进程就会造成内存资源的浪费,动态库只会加载一份,所有的程序都共同享有.共同享有的是代码和数据

Linux下动态库.so结尾.

不同于静态链接,

静态链接是在链接器的作用下会把所有重定位目标文件的代码和数据聚合到可执行目标文件,最终形成一个完全链接的可执行文件

动态链接的过程中,首先会静态执行一部分链接,在时候的程序的加载和运行的时候在动态完成链接的过程.这时候libvector.so的代码和内容并没有真正的复制到可执行文件prog里面,而是复制了一些重定位和符号表信息

当我们真正的记载运行可执行目标文件prog的时候,这时候prog中有一个.interp节含有动态链接器(ld-linux.so)的路径名.本身也是一个共享目标,程序会加载这个共享目标完成以下任务

  • 重定位libc.so的文本个数据到某一个内存段

  • 重定位libvector.so的文本个数据到某一个内存段

  • 重定位prog中所有由libc.solibvector.so定义的符号的引用

最后动态链接器将程序归还应用程序.这个时候,共享库的位置在内存中就固定了,并且在运行的过程中都不会改变

应用程序中加载和链接共享库

上面说的都是,在程序被加载后但是在执行之前动态加载和链接动态库的情景.

应用程序可以在运行的时候手动要求动态链接器加载和链接某个共享库,不需要在白编译的时候将库的链接到应用中

主要有两个作用

  • 分发软件 其实就是版本更新
  • 构建高性能Web服务器

工作方式类似于

涉及函数

dlopen

dlsym

dlclose

dlerror

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值