• 链接、装载、库


    目标文件

    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段有点特殊,它和堆段合并,并没有在数据段里面。

  • 相关阅读:
    使用ZooKeeper实现Java跨JVM的分布式锁
    基于ZooKeeper的分布式锁和队列
    activiti数据库表结构剖析
    visualvm监控jvm及远程jvm监控方法
    使用visualvm 远程监控 JVM
    java jprofile
    Linux服务器上监控网络带宽的18个常用命令
    Redis-sentinel哨兵模式集群方案配置
    电容的去耦半径
    DC-DC BUCK电源芯片的基本原理和组成
  • 原文地址:https://www.cnblogs.com/biterror/p/7048984.html
Copyright © 2020-2023  润新知