美文网首页
编译链接总结

编译链接总结

作者: angry_zxy | 来源:发表于2019-12-06 18:44 被阅读0次

    本文是读<程序员的自我修养>的笔记和总结

    编译过程

    程序的编译可以分解为四个步骤, 预处理->编译->汇编->链接
    下面以main.c文件为例分析:

    预编译

    main.c -> main.i

    • 展开宏定义, 替换#define
    • 处理预编译指令, #if, #endif, #ifdef等
    • 递归处理#include, 将引入的文件插入到对应位置
    • 删除注释
    • 添加行号, 文件名标识
    • 保留#pragma指令

    编译

    main.i -> main.s
    词法分析: 源代码经过扫描器的处理, 运用类似有限状态机的算法将代码的字符分割成不同的记号
    语法分析: 根据不同的记号, 构建语法树
    语义分析: 分析语法树的静态语义是否正确, 主要是类型匹配和转换
    中间语言生成:将语法树转换成中间代码, 优化一些中间结果. 中间代码使得编译器分为前端和后端, 前端产生与机器无关的中间代码, 后端将中间代码转换成目标机器代码

    汇编

    main.s -> main.o
    根据汇编指令和机器指令的翻译一一对应

    链接

    修正指令对符号地址的引用
    主要包括:地址和空间分配, 符号决议, 重定位

    目标文件分析

    目标文件是指编译后未进行链接的中间文件, 格式和可执行文件几乎一样, 在Windows称为PE-COFF, linux下称为ELF

    目标文件按照节(section)或者段(segment)形式存储, 目标文件的开头是一个文件头, 描述了整个文件的属性, 静态链接还是动态链接, 入口地址等, 还包含一个段表, 段表用来描述各个段的属性和地址偏移.
    常见的段名有:

    • .text(代码段)
      程序指令编译后放在代码段
    • .data(数据段)
      保存初始化了的全局静态变量和局部静态变量, 有些编译器也会将const变量保存在.rodata段里
    • .bbs(为初始化数据段)
      存放未初始化的全局变量和局部静态变量
    • 其他段
      .plt存放动态链接的跳转表, .got存放全局入口表, .dynamic存放动态链接信息, .comment存放编译器版本信息等
      除此之外, 也可以自定义段名, 但不能使用"."为前缀, 使用__ attribute__(section("customName"))可自定义段名.

    注意:
    源代码编译后会把程序指令和程序数据分成两个段, 即.text和.data, 这样做的优点是:
    1.数据和指令分别映射到两个虚拟内存区域, 读写权限就可以分开, 防止指令被意外改写
    2.提高程序的局部性, 有利于CPU缓存命中率的提高
    3.对于只读数据和只读指令, 可以进程共享资源, 节省空间

    .bbs段记录了未初始化的变量, 只是预留了位置, 没有内容, 因此在文件中不占据空间, 但是在链接器装载后的虚拟地址中是要分配虚拟地址空间的. 其中区别在于, 未初始化的变量不会增加可执行文件的大小, 但是会增加程序运行时的空间.

    符号

    函数和变量称为符号, 函数名和变量名就是符号名.
    链接器的过程就是把多个不同的目标文件拼装在一起, 如果目标文件之间有引用关系, 那么就需要解析外部符号, 链接器主要通过符号表和重定位表实现.

    • 符号表
      记录了目标文件中用到的所有符号, 对应每个符号的地址
    • 重定位表
      在引用外部文件的符号时, 目标文件会先预置为undefined, 之后重定位. 重定位信息都记录在重定位表里.

    静态链接

    多个源代码文件编译后生成多个.o目标文件, 静态链接就是将多个目标文件链接在一起最终形成一个可执行文件.

    相似段合并

    将所有输入的目标文件的相同段合并在一起, 比如多个.text段合并为一个大的.text段. 链接器会扫描所有的输入文件, 读取段首信息, 将目标文件中的所有符号的定义和引用收集到一个全局符号表里. 简单来说, 链接器合并了多个目标文件, 并建立了全局符号映射关系

    符号解析和重定位

    链接之前, 目标文件引用的外部符号都标记为undefined, 链接时, 通过全局符号表替换所有undefined符号. 除了符号的重定位, 还有虚拟地址VMA(Virtual Memory Address)的分配, 即链接后, 各个段分配了虚拟地址. 各个段内的符号根据偏移量计算符号地址

    注意:
    虚拟地址并不是从0地址开始分配, 不同操作系统有不同的分配规则, 如Linux下默认分配的起始地址是0x08048000

    Common块

    Common块是编译器用来处理弱符号的
    强符号与弱符号
    编译器默认函数和初始化了的全局变量是强符号, 未初始化的全局变量为弱符号, 也可以使用__ attribute__(weak)定义一个强符号为弱符号, 编译器对强弱符号有以下规则:

    • 强符号不允许被定义多次
    • 一个符号在某个文件中为强符号, 其他文件只能为弱符号, 编译器最终选择强符号的类型
    • 一个符号在多个文件中只有弱符号, 没有强符号, 编译器会选择占用空间最大的弱符号, 比如double和int, 选择double

    注意: 强符号和弱符号的并集并不是全部的符号, 很多符号既不是强符号, 也不是弱符号

    直接导致需要Common机制的原因是编译器和链接器允许不同类型的弱符号存在, 但最本质的原因还是链接器不支持符号类型, 即链接器无法判断各个符号的类型是否一致

    链接过程控制

    链接过程可以使用ld链接脚本控制, 自定义链接过程

    相关文章

      网友评论

          本文标题:编译链接总结

          本文链接:https://www.haomeiwen.com/subject/gfyyictx.html