1.程序的编译、链接、运行
1.1从源程序到可执行文件
首先,通过readelf命令可以查看可执行文件的信息,了解可执行文件的结构
-h可以查看文件头部信息,包括该文件运行的平台、软件版本、程序入口地址等信息,section header的个数,
-S可以查看节头表section header table,包括不同段头section header(用于描述section相关信息)的相关信息,section header table 自身也是以一个 section 的形式进行存储的,
一个可执行文件通常由代码段、数据段、BSS段、只读段等构成。
ELF header:文件头用来描述文件类型、要运行处理器平台、入口地址等。 |
Program header table |
.init 初始化c程序运行的环境依赖,堆栈的初始化等 |
.text 代码段: 函数会被翻译成二进制指令放入其中、 |
.rodata 只读数据段:存放程序中定义的一些字符串,printf打印的字符串常量 |
.data 数据段:初始化的全局变量、静态局部变量(在静态存储区分配存储单元,在整个程序运行期间都不释放,但是其他函数都无法使用) |
.bss BSS段:未初始化的全局变量和静态变量,默认为0,因此该段是不占用空间的,但是该段的大小和起始地址是存在与节头表中的。各个变量的地址信息在symtab符号表中存储,加载器会紧挨着数据段的后面为BSS开辟一段内存空间。 |
.symtab |
.debug 如果程序处于debug模式,则产生该段记录每一条二进制指令的对应的源码位置。 |
.line,.strtab |
section header table :包括不同段头section header的相关信息 |
表1-1 可执行文件的段结构
1.2预处理、编译、汇编、链接
对于c程序到可执行文件的编译过程,可分为:预处理、编译、汇编、链接四个阶段
对应这四个编译阶段的工具分别是预处理器、编译器、汇编器、链接器。
目标文件一般分为:可重定位的、可执行的、可共享的,汇编生成的是可重定位的,链接生成的是可执行的目标文件,而可共享的文件一般以共享库的形式存在,在程序运行时需要动态加载如内存中。
1.2.1预处理(处理掉预处理命令,编译器不认识的)
通常会为开发者提供一些预处理的命令,使用#进行标识,例如#include模块化编程,#define定义常量提高程序可读性,#if,#else,#endif令代码兼容不同处理器的架构和平台,编译控制#pragma可以制定编译器的状态,让编译器完成一些特定的动作如有选择的改变编译器的警告信息等。
那么,预处理过程到底做了些什么哪?主要包括下面这些操作:
头文件展开:将#include包含的内容展开到当前位置
宏展开:展开所有的宏定义,并删除#define
条件编译:选择要参与编译的分支,将其余的分支丢弃
删除注释。
添加行号和文件名标识:编译时要使用
保留编译控制命令,编译时也有用
1.2.2编译(从c文件变为汇编文件)
编译过程将高级语言转换为低级语言,汇编文件是以段为单位的,包括代码段、数据段、bss等,各个段之间 彼此独立,与二进制目标文件已经很接近了。
c源文件到汇编文件的转换,是将c文件中的程序代码块、函数转换为代码段,全局变量、静态变量、常量转换到数据段、只读数据段,这是基本逻辑,实际分为五个步骤:
词义分析:对token进行检查看最小单元是否正确。token是字符流解析中有意义的最小记号单位,常见的token有c语言的关键字,各种标识符如函数名、变量名等,运算符、分隔符等,
语法分析:对上一阶段产生的token进行解析,看是否能够构成一个语法上正确的语法短句。如果少了个分号什么的,就是简单的看是否能构成一个句子,主谓宾一样,不考虑实际效用,就会报syntax error的字眼
语义分析:如果生成的合适的语法短句,那就要结合程序语义进行分析了,看语法短句中的实参形参是否匹配,使用的变量是否声明(无声明则判定程序员无使用该变量的意思,不符合实际语义,就会报错),又或者是除数为0则说明该语句无实际意义,break在循环或switch语句之外出现了。
中间代码生成:将语法分析输出的程序语句(仍以语法树形式存储)转换为中间代码(临时代码,三地址码,P-代码),
汇编代码
中间代码一般和平台是无关的,如果要在X86平台下,就根据X86指令集生成X86汇编程序,ARM平台生成ARM汇编程序、
1.2.3汇编(将汇编文件转换为目标文件)
汇编器的主要工作是参考ISA指令集,将汇编代码翻译成对应的二进制指令。
通过编译与汇编,生成了可重定位的目标文件,在链接过程中,将各个起始地址为零的目标文件组装了起来,则该文件中的参考起始地址不再为0,我们的如果通过函数名对该函数进行调用也会受到影响,因此在链接过程中需要重新修改变量和函数地址,这个过程称为重定位,而我们汇编生成的文件是可重定位的文件,那么这个文件有什么特别的哪?在该文件中,我们将需要重定位的文件收集起来,生成一个重定位表,以section的形式进行保存就可以啦。
汇编过程中,汇编器会分析汇编语言中各个section的信息,收集各种符号,生成符号表(保存源程序中各种符号的信息,可以辅助编译器进行语法语义检查,也可以辅助地址空间的分配,重定位等,其本质上是一个结构体数组)。
我们调用一个符号和函数,如果在调用前声明了,在编译阶段不会报错,到链接时如果找不到才会报错。
编译器在给目标文件生成符号表的时候,如果没有找到符号的定义,会将该符号搜集到一个单独的符号便中,这个表就是重定位符号表。
1.2.4链接
链接主要分为三个过程,分段组装、符号决议、重定位。
分段组装:将各个目标文件分段组装,将相同的section组装到一起。另外要注意,链接器会在可执行文件中创造一个全局符号表,将各个文件符号表中的符号放入。
符号决议:_attribute可以声明符号的属性,例如强符号和弱符号
(1)强符号只能定义一次
(2)强符号与弱符号可以共存,当共存时强符号会覆盖掉若符号,未初始化的全局变量是弱符号
(3)多个弱符号共存时选择空间较大的
重定位:在符号决议后,每个符号的地址还是原来的地址,需要根据重定位表进行修正各个符号的偏移offset,加上新的端基地址即可,生成新的符号表。