从源代码到可执行文件大概可分为四步:
预处理(
gcc -E
),产物为.i
文件(翻译单元)#include
替换,#define
宏展开,#ifdef
条件编译等等编译(
gcc -S
),产物为.s
汇编文件汇编(
gcc -c
),产物为.o
目标文件(可重定位目标文件)链接(
gcc
),产物为可执行文件(可执行目标文件)函数库一般分为两种:
- 静态(
.a
):运行时不需要链接操作,节省时间(目标文件打包) - 动态(
.so
):运行时链接,可执行文件本身不包含库文件的代码,节省空间(共享目标文件)
- 静态(
汇编
汇编器的任务是将汇编文件翻译成目标文件,而不仅仅是将汇编代码翻译成机器码(事实上翻译而成的机器码大多只在 .text 节中,只占了目标文件很少的一部分)
Linux 中目标文件采用 ELF 格式。该格式的目标文件可分为四个部分:
- ELF 头(ELF Header,
readelf -h
):其中包含段头部表和节头部表的偏移、大小、条目数等等。 - 段头部表(Program Header Table,
readelf -l
):表中每个条目对应一个段,包含段的偏移、地址、大小等等。 - 节(Sections,
readelf -x <name>
):可以理解成目标文件的 payload,每个节都是一串字节序列。 - 节头部表(Section Header Table,
readelf -S
):表中每个条目对应一个节,包含节的地址、偏移、大小等等。
段是节的集合,其意义在于将连续的节映射到连续的内存段,以便于加载。例如只读内存段(代码段)包括 ELF Header,Program Header Table,.text 节,.rodata 节等等,而读写内存段(数据段)包括 .data 节,.bss 节。
段的设计是为了加载方便,所以常常出现在可执行目标文件中,在可重定位目标文件中可以没有 Program Header Table。
图片来自维基百科
链接
静态链接主要分为两步:
符号解析(symbol resolution):将对每个符号的引用和其定义关联起来。
符号表(
readelf -s
)在目标文件中有对应的节,其中保存了该目标模块对符号引用和定义的信息。重定位(relocation):首先重定位输入目标文件的节,使每个符号定义有确定的运行时地址;然后重定位符号引用,使得它们指向正确的符号定义的位置。
重定位条目(
readelf -r
)在目标文件中也有对应的节,每个条目即对应一处本目标模块对一个外部符号的引用,其中保存了重定位时需要用到的信息。
Reference
- https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
- Chapter 7, CSAPP