编译过程
预编译处理
读取源码,对源码中文伪指令(以 # 开头)和特殊符号进行处理。生成一个没有宏定义、条件编译指令以及特殊符号的文件。
- 宏替换,对#define、#undef、#、##替换。
- 条件编译,如#ifdef、#ifndef、#else、#elif、#endif。预编译程序将根据相关指令,过滤掉不必要的代码。
- 头文件包含,该阶段会把#include引入的头文件插入到源文件中。
特殊符号,预编译程序可以识别一些特殊的符号,例如在源程序中出现的LINE和FILE。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。 - 注释,删除所有注释。
- 添加行号和文件名标识。
编译阶段
编译阶段通过词法分析、语法分析和语义分析确认所有指令是否符合语法规则,为当前编译单元中定义的数据分配地址空间,对中间代码进行代码优化,并将其翻译成等价的汇编代码。详细过程不做阐述。
编译阶段会生成三个表:未解决符号表、导出符号表和地址重定向表。 (链接阶段使用)
- 未解决符号表:编译单元引用但未在本编译单元定义的符号及地址。
- 导出符号表:编译单元定义,并能够提供给其他编译单元使用的符号及地址。
- 地址重定向表:本编译单元所有对自身地址的引用的记录。
汇编阶段
汇编阶段是把上一阶段生成的汇编代码翻译成机器指令并生成目标文件。
目标文件有三种形式:
- 可重定位目标文件。包含二进制代码和数据,其形式可以在链接时与其他可重定位目标文件和并起来,创建一个可执行目标文件。
- 可执行目标文件。包含二进制代码和数据,其刑事可以被直接复制到内存并执行。
- 共享目标文件。一种特殊类型的可重定位目标文件,可以再加载或者运行时被动态地址加载进内存并连接。
注:到该阶段可生成重定位目标文件和共享目标文件。
目标文件在Unix和x86_64 Linux上称为ELF文件。ELF提供了两种视角:
动态库中还会包含 .dynsym。对于ELF文件详细结构暂时不做解析总结。
相关命令
readelf 命令可以查看ELF文件的详细信息。
参数 | 描述 |
---|---|
-a | 显示全部信息 |
-h | 显示文件头信息 |
-s | 显示符号表信息 |
nm命令可以显示二进制文件中的动态符号表。
nm命令可能会出现no symbols的错误,这个原因是动态库被strip命令处理过,去掉了库中的.symtab,只能使用-D参数查看动态符号表。
参数 | 描述 |
---|---|
-A | 每个符号前显示文件名 |
-D | 显示动态符号表 |
-g | 仅显示外部符号 |
链接阶段
将相关目标文件彼此连接起来,生成最终期望的可执行程序或库文件。
静态链接
符号解析
编译期间绝大多数错误都发生在此阶段。首先链接器会进行符号解析阶段,从左到右将命令行上出现的顺序扫描可重定位目标文件和存档文件(即静态库文件),进行一系列符号匹配动作。大致流程绘制为下方流程图:
链接器ld将传入的目标文件以及库文件按从左至右按顺序读取,根据文件中的未定义符号表和解决符号表修改相应集合。全部遍历完后,根据为解决符号集中是否仍有内容判断链接是否成功。
动态链接
动态链接是将动态库链接到程序中,但是这里并未将动态库代码拷贝至程序中,大致与静态链接相似,对于符号的处理,只是将符号表补全,判断链接是否可以通过,生成一个不完全的的可执行程序。
程序启动后首先加载和运行动态链接器,在此阶段完成对动态库文本、数据以及符号引用的内存地址重定位操作。
重定位
符号解析完成后,链接器就能知道每个模块中代码和数据的确切大小。开始为每个符号分配运行时地址。主要分为两步:
- 将所有模块中相同类型的段合并到一起。然后,链接器将运行时内存地址给新的段以及每个定义的符号。这个过程结束后,所有函数和变量都会有唯一的运行时内存地址。
- 链接器对外部引用的符号进行重定位,根据地址重定向表以及第一阶段的结果,将外部引用符号的运行时地址确定。
运行
通过ldd命令可以发现程序都会去依赖一个文件ld-linux-x86-64.so.2,这个默认情况下是绝对路径,在gcc/g++的配置文件中写好的,这个也是有版本区分的,与glibc版本配套。
除了ldd命令也可以通过readelf –l查看程序头信息,也能够看到该文件路径。
ld-linux-x86-64.so.2这个东西看起来像是一个动态库,实际上是linux的动态加载器。当应用程序被加载到内存时,OS将控制权传递给ld-linux-x86-64.so.2,而不是程序的入口点。ld-linux-x86-64.so.2会去搜索并加载未解析的库,都结束后才将控制权传递给程序的入口点。
链接器连接时回去查找以下几个路径:
- 编译期间的指定的动态库搜索路径;
- 环境变量LD_LIBRARY_PATH指定的搜索路径;
- 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
- 默认的动态库搜索路径/lib 或 /lib64。