![50b922e03d8b2e45ebafba7014b12184.png](https://i-blog.csdnimg.cn/blog_migrate/6211f8a7c5351a7ec90e5f83dd7e5ca4.jpeg)
这个星期已经把b站中,袁春风老师讲解的计算机系统基础(一)看完了。写这个专栏已经差不多一个月了。也就是说,花费了一个月的时间把计算机系统基础(一)视频看完了。但是,最近我又陷进了“看这些基础知识有什么用?”、“这个知识,了解一下就够了,脑海里知道有这个知识”。就是这些想法,让我对一些知识浅尝即止,没法深入的去学习。学习后面链接的过程的视频,也有点囫囵吞枣。所以我还是下定决心,创建Linux的虚拟机,踏踏实实敲好每条指令。回想起建立专栏的初衷,不就是为了记录知识,将知识系统化。其实,在写专栏的时候,我也发现写文章,其实就是一个思考的过程。因为在这个过程中,你需要把知识分享给其他人,你就必须详细去理解知识。这时候,自己就会产生一些问题,就会去思考为什么会这样?还是要不断砥砺自己。不忘初心,继续前行。
在这篇中,链接是主菜。在上主菜之前,我们还是先来尝一下开胃菜——编译的过程。在Linux中,gcc命令实际上是具体程序的包装命令,用户通过gcc命令来使用具体的 预处理程序cpp、编译程序cc1和汇编程序as等。
预处理(生成.i预处理文本文件)
命令-$gcc -E hello.c -o hello.i -$cpp helloc.c > hello.i
处理源文件中以“#”开头的预编译指令,包括:
-展开定义的宏
-处理所有条件预编译指令,如#if,#ifdef,#endif等
-插入头文件到#include处
-删除所有的注释"//"和"/**/"
-添加行号和文件标识,以便编译时编译器产生调试用的行号信息
-保留所有#pragma编译指令(编译器需要使用)
编译(生成.s文本文件)
命令-$gcc -s hello.i -o hello.s
进行词法分析、语法分析、语义分析并优化,生成汇编代码文件。
汇编(生成.o可重定向目标文件,不可读二进制代码)
命令-$gcc -c hello.s -o hello.o -$as -c hello.s -o hello.o
![0a442c6babfdca69be1baf8d3bdc1c52.png](https://i-blog.csdnimg.cn/blog_migrate/9de5e303dc4e29d4d2d24c6ecb648565.jpeg)
![819addb68135a99e2da3511c36771f33.png](https://i-blog.csdnimg.cn/blog_migrate/3953f23904db84bf18d98a579b1899a3.jpeg)
![6b58bd747732bf58f400767522c1b1e5.png](https://i-blog.csdnimg.cn/blog_migrate/be4039f5d38812a226eda6305dcd34f7.jpeg)
- ELF头:文件类型、机器类型、节头表的表项大小以及表项个数。
- .text节:编译后的汇编代码
- .rodata节:只读数据,如printf格式串、switch跳转表等。
- .data节:已初始化的全局变量,局部静态变量。
- .bss节:未初始化的全局变量,局部静态变量。仅是占位符,不占据任务实际磁盘空间。
- .symtab节:符号表,存放函数名和全局变量信息(不包括局部变量)。
- .rel.text节:代码段的重定位信息
- .rel.data节:数据段的重定位信息
- .debug节:调试用的符号表
- .strtab节:包含symtab和debug节中符号及节名。?
- .line节:?
- Section Header table(节头表):每个节的节名、偏移和大小
看完,还是不知道.strtab 和 .line 有什么具体作用?先留一个疑问。后续再回来看看。
链接(将多个目标文件,生成一个可执行目标文件)
命令-$gcc -static -o myproc main.o test.o -$ld -static ...
可执行目标文件,与可重定位目标文件一样,是一个不可读的二进制代码文件。可通过工具逆向转化为文本文件objdump -d test.o。以下是两个目标文件的逆向转化后的比较。
![df401d9b68b2dfe0cee3f79e7b5a76a4.png](https://i-blog.csdnimg.cn/blog_migrate/8d906ee2f97f2cde569365ccfa39a110.jpeg)
![073919e5692c1071665e4d218d04141a.png](https://i-blog.csdnimg.cn/blog_migrate/afb6303102e7f0fbc79f6c981da4fc79.jpeg)
链接操作的步骤:
1.符号解析
程序中有定义和引用的符号(包括变量名,函数名),存放在符号表(.symtab)。符号表是一个结构数组,包含符号名、长度和位置等信息。编译器将符号的引用存放在重定位节(.rel.text和.rel.data)。链接器将每个符号的引用都与一个确定的符号定义建立关联。
![705af57972f45e96cf7cddeb16d77640.png](https://i-blog.csdnimg.cn/blog_migrate/be0feb5a36b0ae5b8bc5163590aa3dc0.jpeg)
2.重定位
将多个代码段和数据段分别合并为一个单独的代码段和数据段,计算每个定义的符号在虚拟地址空间的绝对地址。将可执行文件中的符号引用处修改为重定位后的地址信息。
符号类型:
- 全局符号(Global symbols):由模块m定义并能被其他模块引用的符号。(指不带static的全局变量)
- 外部符号(External symbols):由其他模块定义,并被模型m引用的全局符号。
- 局部符号(Local symbols):仅由模块m定义和引用的本地符号。例如,在模型m中定义的static函数和变量。
![294f1647936b42468e6e0d357e378930.png](https://i-blog.csdnimg.cn/blog_migrate/4174c774e79069334ac7aa8e86719be6.jpeg)
在开发过程中,容易遇到的符号重复定义的问题。比如:
//a.cpp文件
//b.cpp文件
两个文件在合并数据段时,就会发生链接错误。因为两个文件重复定义了变量名,在链接过程中,无法用同一符号,表示两个变量。如果,是一个强定义,一个弱定义编译通过。所谓,强定义就是定义的同时赋值,而弱定义只定义。所以,养成良好的编程习惯。
- 尽量使用本地变量(static),模块内引用不太会出错
- 全局变量要赋初值,使成为强符号,易查出链接错误
- 外部全局变量使用extern,以示其引用其他模块
![817d3a0d414ddee429331dc439152675.png](https://i-blog.csdnimg.cn/blog_migrate/279b89cd0f94acb00ab5688c7f9fb26d.jpeg)
![d4838fd498ff7835301f9efec1d8faa2.png](https://i-blog.csdnimg.cn/blog_migrate/5425c94111dcd915f589eba02aba1eee.jpeg)