gcc完成编译链接的过程:
一 编译阶段
预编译:gcc -E main.c -o main.i (main.c是源码文件,main.i是生成的预编译后的文件)
编译:gcc -S main.i(默认生成汇编指令文件main.s)
汇编:gcc -C main.s(默认生成main.o文件)
预编译:
- #include 的展开,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件
- 宏替换
- 删除注释
- 处理预编译指令如:#if,#endif,#ifdef,#elif,#else
- 添加行号和文件名标识,比如#2 "hello.c" 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能产生行号
- 保留所有的#pragma编译器指令,因为编译器需要使用他们
编译:
- 进行语法,词法的解析
- 代码优化
- 汇总符号(数据(全局,静态)和函数会生成符号)
- 将源码转成汇编指令
汇编:
- 将汇编指令翻译成二进制
- 生成各个section
- 生成符号表
汇编过后生成的main.o文件是可重定位的二进制文件
编译阶段每个源文件都是单独编译的
objdump -h main.o 用来查看main.o文件的section header信息
可重定位文件的ELF文件格式:
二 链接阶段
链接: gcc 所有生成的中间文件 -----》a.out
或 gcc 所有生成的中间文件 -o 自己命名的文件(可执行文件名)
链接:
链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。链接可以分为静态链接和动态链接。相应的也有静态库和动态库来供链接使用。
静态链接:源代码文件经编译器编译成目标文件(.o或.obj),目标文件和静态库一起链接形成最终的可执行文件。
- 合并所有文件的各个section,调整段大小的起始位置,合并符号表,进行符号解析,并给符号分配一个虚拟地址
- 符号重定位
1、段的合并是根据什么来合并的?
段的合并是根据各个段的属性来进行合并,比如data段合并到data段,text合并到 text 段等。
2、什么是符号解析?
符号解析:所有对符号的引用,都有找到该符号定义的地方。常见错误:符号重定义,符号未定义。
3、什么是重定位?
《程序员自我修养》这本书中给重定位的定义是:在编译多个目标文件时,无法确定变量/函数地址的情况下,会将其目标地址置为0,等到链接时将目标地址进行修改。这个地址修正的过程就被叫做重定位。就是给程序中的每个绝对地址引用的位置打补丁,使它们指向正确的位置。
因为在编译过程中符号是不分配虚拟地址的,但是编译过程指令已经生成。所以先把符号的地址都置为0,等到链接时符号解析成功以后,给所有的符号分配虚拟地址,写入指令中置0的地方,称之为符号的重定向。
可以发现目标文件和最终的可执行文件格式上来说是很像的,但是目标文件不能执行的原因之一就是因为其还不确定符号的地址。必须得等到链接时对其地址进行重定位。
参考资料:https://blog.csdn.net/Disremembrance/article/details/93385383
可执行文件的ELF格式:
参考资料:《程序员的自我修养》 俞甲子,石凡,潘爱民著,电子工业出版社 第二部分:静态链接 第二章:编译和链接