程序的编译和链接过程

当我们写完一段C语言的代码,再将其在编译器中运行的时候,很少去研究其内部执行了怎样的一些操作,这其中发生了一些什么样的变化呢?最近看了一些相关的内容,做一个记录。

首先一段代码从编写到生成可执行文件大概要经历以下的一些流程,分别是:预处理、编译和链接。其中编译又包括两部分第一是将C源文件编译成汇编文件,第二部分是将汇编文件会变成可重定位的目标文件。

  • 预编译(gcc -E hello.c hello.i )
    预编译会将我们的 *.c 文件进过处理变成对应的 .i 文件,主要完成的是对程序中一些以 # 号表示的代码段进行处理,一般会有
    (1)头文件的包括 #include
    (2)宏定义 #define
    (3)条件编译 #ifdef # ifndef #endif
    (4)编译控制 #pragma
    预处理主要包括以下操作:
    (1)头文件的展开:将#include包含的头文件的内容展开到当前位置
    (2)宏展开:展开所有宏定义并删除 #define
    (3)条件编译:根据条件选择参与编译的内容其余的丢弃
    (4)删除注释
    (5)保留 #pragma 命令,因为在后续的编译中可能会对编译产生一定的行为约束
    (6)添加行号和文件标识

  • 编译
    前面已经提及编译主要分两部分进行,第一部分就是C源文件编译成汇编文件。(gcc -S hello.c hello.s )主要完成内容包括:**词法分析、语法分析、语义分析、生成中间代码、生成汇编代码。**第二部分是将汇编文件会变成可重定位的目标文件。(gcc -c hello.c hello.o)
    (1)在经过预处理过后的C代码中,首先进行词法分析,词法分析就是将源程序分解成一些的记号单元(token),常见的token有像C语言的关键字、运算法、用户定义的标识符、分隔符等等,例如:sum = a + b -c; 会被分解成8个token:“sum”、“=”、“a”、“+”、“b”、“-”、“c”、“;” 如果我们在编写代码的时候不小心将英文的分隔符输入成中文,那么这个时候就能检查到错误。
    (2)接着进行语法分析,语法分析就是对前一阶段产生的token序列进行解析,检查是否能组成一个语法上正确的语句或者表达式。我们在编程中如果一行代码结尾少输入分隔符就会在这个阶段被检测出。
    (3)如果没有语法错误,接着会进行语义分析,语义分析就是对代码进行语法检查是否有错,例如使用了一个未定义的变量或者函数传递的参数类型不匹配等等。
    (4)语义分析通过后会生成中间代码,中间代码是编译过程中的一种临时代码,常见的有三地址码和P-代码。
    (5)如果是要生成在X86上运行的可执行文件,那就需要根据x86指令集,将中间代码翻译成X86汇编文件。如果是运行在ARM上的可执行文件,那就需要根据ARM指令集,分配寄存器,将中间代码翻译成ARM汇编文件。
    (6)接着就是编译的第二阶段,将汇编文件编程可重定位的目标文件。汇编器的主要工作就是参考指令集将汇编代码翻译成对应的二进制指令,同时生成一些后续链接过程需要的重要信息,以section的形式组装到目标文件中去。汇编阶段有两个重要的表就是符号表和重定位表。汇编器会分析各个section 中的信息,收集各种符号,生成符号表,将各符号在section的编译地址也填充到符号表内。由于编译生成的目标文件都是由首地址为起始地址进行链接的,那么在后续的链接中,要将多个目标文件进行链接时,就会出现问题,如何解决呢?就是修改各个目标文件中的变量或者函数的地址(重定位),将每个需要重定位的符号收集起来就生成了一个重定位表。

  • 链接
    链接主要分三个阶段,分别是分段组装、符号决议和重定位
    (1)分段组装阶段,就是将各目标文件中的代码段和数据段分别组装在一起作为可执行文件的代码段和数据段。同时创建一个空的符号表来统一存储各目标文件符号表中的符号。
    此时就会一些问题:各段的组装顺序是怎样的?如果各目标文件符号表中的符号有重复怎么办?
    对于第一个问题的答案应该交给链接脚本,在链接脚本中定义了关于每个段的组装顺序、起始地址和位置对齐。
    第二个问题我们接着说
    (2)符号决议
    符号决议就是为了解决各目标文件符号表中的符号有重复的问题
    形象的概括为三句话:不允许出现同名的强符号、同名强弱符号可以共存、多弱符号选用内存大的
    首先介绍两个概念:强符号和弱符号。强符号是指那些函数名和已经初始化的全局变量。弱符号是指未初始化的全局变量。
    如果两个源文件中同时定义了全局变量 i 并初始为1,那么就会报错,同样的如果定义了同名函数也会报错,但是如果,一个源文件中定义了全局变量 i 并初始为1,而另一个源文件中定义了全局变量 i 但没有初始化,是不会报错的。在编译期间, 当出现多个弱符号时,编译器并不知道该弱符号是被采用或是丢弃,因此未定义的全局变量不会放入BSS段而是保存在一个叫 COMMON 的临时块中。等到链接阶段就会选择占用空间最大的弱符号放到可执行文件的BSS段中。
    (3)重定位
    重定位就是修正重新组合后的各段的地址并将其更新到符号表中,当我们调用函数或者访问变量的时候就知道在内存中的真正位置了。重定位的核心工作就是修正指令中的符号地址,是链接的最后且最核心一步。

补充一些知识
ELF文件
本部分转载自:https://blog.csdn.net/daide2012/article/details/73065204
在这里插入图片描述
左边是从汇编器和链接器的视角来看这个文件,开头的ELF Header描述了体系结构和操作系统等基本信息,并指出Section Header Table和Program Header Table在文件中的什么位置,Program Header Table在汇编和链接过程中没有用到,所以是可有可无的,Section Header Table中保存了所有Section的描述信息。右边是从加载器的视角来看这个文件,开头是ELF Header,Program Header Table中保存了所有Segment的描述信息,Section Header Table在加载过程中没有用到,所以是可有可无的。注意Section Header Table和Program Header Table并不是一定要位于文件开头和结尾的,其位置由ELF Header指出,上图这么画只是为了清晰。

 .text           
     存放程序源代码编译后的机器指令        
 .data 
     存放全局变量和局部静态变量
 .bss
     为未初始化的全局变量和局部静态变量预留位置,目标文件中,未初始化变量不需要占据任何实际的磁盘空间               
.rel.text 
    存放调用外部函数或者引用全局变量等相关的重定位信息
.rel.data 
    一个全局变量被初始化为其他文件中的全局变量地址或者外部定义函数的地址。     
.symtab
    存放函数和全局变量的信息,重定位时根据.rel.text和.rel.data来修正相关地址       
.debug
    -g选项之后才有, 调试符号表     
.line  
    -g选项之后才有,记录源代码行号和.text中机器指令的映射关系        
.strtab     
    以null结尾的字符串序列   
ELF头(elf header)------readelf -h filename
    包含系统相关、类型相关、加载相关、链接相关的信息
节头部表(section header table)------readelf -S filename
    描述程序节,为汇编器和链接器服务。它把elf文件分成了许多 

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值