程序构建基础
1.gcc背后的操作
1.1 gcc命令
用于C/C++程序构建,主要完成以下工作:
- 预处理 Prepressing
- 编译 Compilation
- 汇编 Assembly
- 链接 Linking
流程图如下:
1.2 预编译
主要用于处理引入的头文件、宏定义等信息。
对应的gcc编译语句如下:
1 |
|
处理流程如下:
- 展开所有的宏定义,删除所有的 “#define” 指令
- 处理所有的条件编译指令,如 “#if”、”#ifdef”、”#elif”、”#else”、”#endif”
- 处理 “#include” 预编译指令,递归使用包含的文件内容填充
- 删除所有的注释 “//“ 和 “/* */“
- 添加调试信息,行号和文件名标识
- 保留所有的 #pragma 编译器指令
hello.i:
1.3 编译
1.3.1 基本概念
用于将源码翻译成汇编指令,其中包括词法分析、语法分析、语义分析等几个重要步骤。
对应的gcc编译语句如下:
1 |
|
hello.s:
1.3.2 编译流程图
1.3.3 词法分析
源代码被输入到扫描器,扫描器根据有限状态机将源码的字符序列分割成一系列的记号。产生的记号一般可以分为以下几类:关键字、标识符、字面量(数字、字符串等)和特殊符号(如加号、等号等),并将分析得到的词汇记号分类放入表格,以备后续使用。
1.3.4 语法分析
对上面得到的词汇记号进行上下文无关语法分析,生成语法树(以表达式为节点的二叉树),同时确定运算符号的优先级和含义。下图展示了基本语法树:
1.3.5 语义分析
语法分析只能检查表达式语法层面的正确性,并不能检查该表达式是否有意义,如将一个指针变量与浮点数做乘法。编译器只能进行静态语义分析,主要是对类型的分析与转换。经过语义分析后,语法树会被标识类型。如下图所示:
1.3.6 中间语言生成与优化
现代编译器会在生成目标代码前进行源码级的优化,而直接在语法树上做修改效率很差,所以编译器先生成中间代码(不包含数据的尺寸、变量地址和寄存器名字等)作为通用的解释语言(不存在arm、x86等指令集之间的差异),并在中间代码上执行优化策略。
1.3.7 目标代码生成与优化
目标代码生成器将上面产生的中间代码转换成目标平台的机器码,目标代码优化器做出指令序列优化,最终生成目标汇编代码。
1.4 汇编
用于将汇编代码翻译成机器码
对应的gcc编译语句如下:
1 |
|
hello.o:
1.5 静态链接
静态链接就是将多个模块化的已经编译好的依赖库代码按照一定的规则组织完整,主要过程包括地址和空间分配、符号决议和重定位等。
1.5.1 空间与地址分配
扫描所有的输入目标文件,获得各个Section的长度、属性和位置,并且将输入目标文件中的符号表中的所有符号定义和符号引用整理放到全局符号表中,并且将相同的Section合并生成新的具有映射关系的Segment,此时Segment和符号的虚拟地址都被确定了。
1.5.2 符号解析与重定位
根据上一步收集到的信息,解析目标文件的重定位表,解析符号获取类型,根据类型去修复重定位。比如符号解析后发现是外部符号,会去对应的.o文件中得到地址来进行指令地址修复,最终完成静态链接。