参考书目:《嵌入式C语言自我修养》,王利涛编著。

(一)gcc编译器编译选项

常用的gcc编译选项如下:

-E 对.c程序预处理,生成.i文件
-S 对.c程序进行汇编,生成.s文件
-c 只生成目标文件(.o),不进行链接
-o 生成可执行文件(.out)

-g 生成带调试信息的debug文件
-O2/O3/O1/Ofast 开始编译优化选项,设置对应的编译优化等级(一般使用O2,Ofast包含O3全部的优化选项,并且不严格遵守语言标准)

-I(大写的i) 指定头文件路径
-l(小写的L) 指定使用的函数库
-L 指定函数库的路径

(二)可执行文件的内部结构

一个可执行文件通常由不同的段(section)构成:代码段、数据段、BSS段、只读数据段等。

  • 代码段:函数翻译成二进制指令放在代码段中。

  • 数据段:初始化的全局变量经过编译后放在数据段中。

  • BSS段:一般来讲,未初始化的全局变量和静态变量会放置在BSS段中。(由于未初始化,默认全为0,没必须单独开辟存储空间,因此在可执行文件中BSS段是不占用空间的。)

(三)程序编译和链接流程

gcc对一个程序的编译链接流程如下图所示:

预处理阶段

预处理主要包括以下操作:

  • 头文件展开:将#include包含的头文件内容展开到当前位置

  • 宏展开:展开所有宏定义,并删除#define

  • 条件编译:根据宏定义的条件,选择要参与编译的分支代码,其余的分支丢弃

  • 删除注释

  • 添加行号和文件名标识:编译过程中根据需要可以显示这些信息

    printf("line number : %d   file: %s\n\r", __LINE__, __FILE__);
  • 保留#pragma命令:该命令会在程序编译时指示编译器执行一些特定行为,如自己在程序中添加编译提示信息,则编译器在编译此文件时会在窗口输出编译提示信息。

    #pragma message("build helloworld.c...\n\r")

编译阶段

编译阶段主要分为两步:

  • 第一步,编译器调用一系列解析工具,将C源文件编译为汇编文件;
  • 第二步,通过汇编器将汇编文件汇编成可重定位的目标文件

编译过程

编译过程可以分为6步:

  • 词法分析

    主要对c语言语句进行分析,通过词法扫描器从左到右,按字符读入源程序,将源程序分解为不能分解的记号单元——token。常见的token如下:

    • C语言关键字,如:int、float等
    • 用户定义的各种标识符,如函数名、变量名等
    • 字面量:数字、字符串等
    • 运算符:C语言标准定义的40多个运算符。
    • 分隔符:分号、逗号等。
  • 语法分析

    对上一阶段的token序列进行解析,分解为一个语法上正确的语法树。例如语句sum = a + b / c 可以分解成如下图的语法树。语法分析仅对程序做语法检查,对程序、语句的真正意义并不了解。

  • 语义分析

    语义分析主要对语法分析输出的各种表达式、语句进行检查。

  • 生成中间代码

    在语法分析阶段输出的表达式或程序语句,还是以语法树的形式存储,需要将其转换为中间代码。

  • 汇编代码生成

    中间代码一般和平台无关。如果在X86平台或ARM平台上运行,则根据不同的指令集,编译器将中间代码翻译成不同平台的汇编程序。

  • 目标代码生成

汇编过程

汇编过程是使用汇编器将前一阶段生成的汇编文件翻译成目标文件(.o)。

*.o文件是不可执行的,属于可重定位的目标文件,即以零地址为链接起始地址进行链接的。可以使用 readelf -S 命令来观察可重定位目标文件的地址。在每个可重定位目标文件中,函数或变量的地址就是它们在文件中相对于零地址的偏移。

链接器将各个目标文件组装在一起后,重新修改各个目标文件中的变量或函数的地址,这个过程一般称为重定位

链接过程中会生成符号表和重定位表,可以使用readelf -s命令来查看某个.o文件的符号表信息。重定位表使用readelf -S查看,某个.o文件中.rel.text部分就是重定位表。

链接阶段

链接阶段主要分为三个过程:分段组装、符号决议和重定位。

分段组装

将各个可重定位目标文件重新分解组装:将各个目标文件的代码段放在一起,数据段放在一起,其他section组装方法同样。

同时收集各个目标文件符号表中的符号,创建全局符号表。

符号决议

为了解决位于不同模块或不同文件中函数名、全局变量名可能重名冲突的情况。

强符号:函数名、初始化的全局变量。

弱符号:未初始化的全局变量。

决议规则:

  • 不允许强符号多次定义。
  • 强符号和弱符号可以共存,共存时强符号会覆盖弱符号。
  • 允许多个弱符号共存。

重定位

各个目标文件中有一个重定位表,专门记录各个文件中需要重定位的符号。重定位的核心工作就是修正指令中的符号地址,是链接过程中最后、最核心、最重要的一步。

链接器读取各个目标文件中的重定位表,根据这些符号在可执行文件中的新地址,进行符号重定位,修改指令代码中引用这些符号的地址。

至此,整个编译链接流程结束,最终生成可执行目标文件(.out)。

说点什么
请文明发言!
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...