链接、装载、库

目标文件

Linux系统的目标文件格式都遵循ELF格式,目标文件一共分为四类

ELF文件类型 文件类型
可重定位文件 Linux的.o
可执行文件 a.out
共享目标文件 Linux的 .so
核心转储文件 coredump文件

目标文件有什么

目标文件主要分为两个部分:代码段和数据段,这样划分的理由有3点

  1. 权限管理:代码段只读,可执行;数据段,可读可写,可执行
  2. 缓存友好:由于程序局部性原理,加快运行速度
  3. 节省空间,主要是代码段,在动态链接的情况下可以重复利用

代码段主要结构

  1. .text:代码指令
  2. .init:C++中main函数之前构造函数
  3. .fini:C++中main函数之后析构函数
  4. __libc_atexit:用户注册的退出函数
  5. .rodata:const变量,字符串常量,虚表,type_info

数据段主要结构

  1. .data:初始化数据段
  2. .bss:未初始化数据段(不占据磁盘空间)
  3. .tdata:线程局部存储初始化数据段
  4. .tbss:线程局部存储未初始化数据段(不占据磁盘空间)
  5. .got:全局偏移量表,用户动态链接生成地址无关代码
  6. .plt:实现动态链接的延迟绑定

其他段

这些段用于静态链接,动态链接或者保存一些调试信息

  1. 静态链接:字符串表,符号表,重定位表
  2. 动态链接:.dynamic,动态字符串表,动态符号表,动态重定位表
  3. 调试信息:.debug,.line

静态链接

静态库是什么

静态库是一系列.o文件压缩打包在一起,没有对.o文件的结构作任何改变。

静态链接过程

  1. 地址和空间分配
  2. 符号决议
  3. 重定位

地址和空间分配

静态链接中地址和空间分配有两层含义,一是分配磁盘存储空间,二是分配加载后的虚拟地址空间。.bss段需要分配磁盘存储空间

  1. 扫描各个输入目标文件,获取各个段的长度
  2. 将相似段合并到一个目标文件
  3. 将输入目标文件的符号表合并,生成全局符号表

符号决议和重定位

符号决议

  • 强符号和弱符号:初始化全局变量和函数是强符号,未初始化全局变量是弱符号(未初始化全局变量生成.o文件的时候并没有放入.bss,而是符号决议后,有了固定大小才放入.bss)
  • 强引用和弱引用:对于未定义的强引用,编译器直接报错;未定义的弱引用,编译器生成一个默认值(一般为空值)

符号决议有3条规则:

  1. 多个强符号:报错重复定义
  2. 一个强符号多个弱符号:选择强符号
  3. 多个弱符号:选择占用空间最大的弱符号(通过COMMON块)

重定位

修正引用符号的地址

相关问题

  1. 重复代码消除:C++ template,class,inline函数可以每个编译单元定义一次,需要消除重复代码;如果不消除,会有如下影响

    1. 空间浪费
    2. 地址出错:相同的函数指针会有不同的地址
    3. 降低cache命中率
  2. 函数级别链接:静态链接默认是以.o文件为基本单位进行段的合并,可以开启函数级别链接,降低无用空间消耗

  3. 全局构造和析构:.init保存了构造函数指令,.fini保存了析构函数指令

动态链接

动态链接库是什么

动态链接库 .so文件,是由多个.o文件链接生成的一种目标文件。生成动态链接库的过程如下:

  1. 对每个cpp文件生成地址无关代码,gcc -shared -fPIC
  2. 编译生成 .so文件, gcc -shared -fPIC *.o

动态链接过程

动态链接在编译的时候只是确定动态库中有需要的符号,并没有发生链接,具体的链接在程序运行中

地址无关代码

由于动态库的指令是多个程序共享的,所以我们不能修改指令中的地址。所以,我们抽象出一个新的段.got,用于保存指令中的地址相关信息。

  1. 模块间数据访问
  2. 模块间函数调用

他们的数据都需要通过.got进行访问。一般而言,如果确定不被其他模块访问的数据或者代码,最好设置为static;这样明确指定了才不会通过.got访问。不然本模块访问也要通过.got中转,降低速度。

延迟绑定

由于动态链接把链接的过程延迟到了程序运行时,这样肯定会降低程序执行速度。为了减少链接的开销,动态链接库采用延迟绑定的方式。在需要这条指令地址的时候,才链接这个地址。.plt段就是完成延迟绑定功能

动态链接相关的段

.interp

保存了ld.so的地址

.dynamic

动态链接相关信息:各个段位置,所依赖的共享对象,共享对象搜索路径

  1. 动态字符串表
  2. 动态符号表
  3. .hash:加快字符串查找速度
  4. 动态重定位表:用于重定位.got.got.plt

动态链接过程

  1. 动态链接器自举
  2. 装载共享库,生成全局符号表
  3. 执行

动态库和静态库中的符号冲突

如果我们链接多个库,这些库中有相同的名字,我们只选择先出现的那个符号加入全局符号表。

Linux系统装载

  1. 创建一个独立的虚拟地址空间
  2. 寻找.interp段,设置动态连接器路径
  3. 读取可执行文件头,建立虚拟地址空间与可执行文件的映射关系
  4. 初始化进程环境变量
  5. 将CPU指令寄存器设置为可执行文件入口,启动运行。静态链接是ELF中的入口地址,动态链接是动态链接器的地址

ELF映射

上面我们提到,需要见ELF文件中的数据段和代码段映射到虚拟地址空间。这里.bss段有点特殊,它和堆段合并,并没有在数据段里面。

posted on 2017-06-19 15:41  bitError  阅读(593)  评论(0编辑  收藏  举报