菜鸟来亲戚家之后,不知道为什么,就是睡不早,而且发现时间也奇怪的变少了一样,根本没有什么时间写博客,这都是我舍弃睡觉时间写的博客,难受 (≧ ﹏ ≦)
这次依旧一样,还是不会包含很多单片机或者其它奇奇怪怪的知识
前面我们说过,写好的 C 语言代码,可以通过编译器编译成汇编代码,然后汇编代码再通过汇编器变成 CPU 可以理解的机器码,于是 CPU 就可以执行这些机器码了。你现在对这个过程应该不陌生了,但是这个描述把过程大大简化了。
实际上,“ C语言代码 - 汇编代码 - 机器码 ” 这个过程,在我们的计算机上进行的时候是由两部分组成的。
第一个部分由编译(Compile)、汇编(Assemble)以及链接(Link)三个阶段组成。在这三个阶段完成之后,我们就生成了一个可执行文件。
第二部分,我们通过装载器(Loader)把可执行文件装载(Load)到内存中。CPU 从内存中读取指令和数据,来开始真正执行程序。
如图:
读者们可以自己在linux上写一个简单的代码,然后通过这些命令来看看(xxxx表示由读者名命):
$ gcc -g -c xxxx.c
$ objdump -d -M intel -S xxxx.o
既然代码已经被我们“编译”成了指令,我们不妨尝试运行一下 ./xxxx.o。
不幸的是,文件没有执行权限,我们遇到一个 Permission denied 错误。即使通过chmod 命令赋予 xxxx.o 文件可执行的权限,运行./xxxx.o 仍然只会得到一条 cannot execute binary file: Exec format error 的错误。
因为xxxx.o 并不是一个可执行文件(Executable Program),而是目标文件(Object File)。只有通过链接器(Linker)把多个目标文件以及调用的各种函数库链接起来,我们才能得到一个可执行文件。
我们通过 gcc 的 -o 参数,可以生成对应的可执行文件,对应执行之后,就可以得到这个简单的加法调用函数的结果:
$ gcc -o xxxx xxxx.o
$ ./xxxx
(程序里都有输出语句才能看到效果!)
程序最终是通过装载器变成指令和数据的,所以其实我们生成的可执行代码也并不仅仅是一条条的指令。我们还是通过 objdump 指令,把可执行文件的内容拿出来看看:
objdump -d -M intel -S xxxx
你会发现,可执行代码 dump 出来内容,和之前的目标代码长得差不多,但是长了很多。
因为在 Linux 下,可执行文件和目标文件所使用的都是一种叫ELF(Execuatable and Linkable File Format)的文件格式,中文名字叫可执行与可链接文件格式,这里面不仅存放了编译成的汇编指令,还保留了很多别的数据。在objdump 出来的代码里,你可以看到对应的函数名称,像main 等等,乃至你自己定义的全局可以访问的变量名称,都存放在这个 ELF 格式文件里。
这些名字和它们对应的地址,在 ELF 文件里面,存储在一个叫作符号表(Symbols Table)的位置里,符号表相当于一个地址簿,把名字和地址关联了起来。
你会发现,这里面,main 函数里调用其他函数的跳转地址,不再是下一条指令的地址了,而是其它函数的入口地址了,这就是 EFL 格式和链接器的功劳。这也是为什么,可执行文件里面的函数调用的地址都是正确的。
ELF 文件格式把各种信息,分成一个一个的 Section 保存起来。ELF 有一个基本的文件头(File Header),用来表示这个文件的基本属性,比如是否是可执行文件,对应的 CPU、操作系统等等。除了这些基本属性之外,大部分程序还有这么一些 Section:
链接器会扫描所有输入的目标文件,然后把所有符号表里的信息收集起来,构成一个全局的符号表。然后再根据重定位表,把所有不确定要跳转地址的代码,根据符号表里面存储的地址,进行一次修正。最后,把所有的目标文件的对应段进行一次合并,变成了最终的可执行代码。
在链接器把程序变成可执行文件之后,要装载器去执行程序就容易多了。装载器不再需要考虑地址跳转的问题,只需要解析 ELF 文件,把对应的指令和数据,加载到内存里面供 CPU执行就可以了。
讲到这里,相信你已经猜到,为什么同样一个程序,在 Linux 下可以执行而在 Windows下不能执行了。其中一个非常重要的原因就是,两个操作系统下可执行文件的格式不一样。
我们今天讲的是 Linux 下的 ELF 文件格式,而 Windows 的可执行文件格式是一种叫作PE(Portable Executable Format)的文件格式。Linux 下的装载器只能解析 ELF 格式而不能解析 PE 格式。
如果我们有一个可以能够解析 PE 格式的装载器,我们就有可能在 Linux 下运行 Windows程序了。这样的程序真的存在吗?
没错,Linux 下著名的开源项目 Wine,就是通过兼容PE 格式的装载器,使得我们能直接在 Linux 下运行 Windows 程序的。而现在微软的Windows 里面也提供了 WSL,也就是 Windows Subsystem for Linux,可以解析和加载ELF 格式的文件。
我们去写可以用的程序,也不仅仅是把所有代码放在一个文件里来编译执行,而是可以拆分成不同的函数库,最后通过一个静态链接的机制,使得不同的文件之间既有分工,又能通过静态链接来“合作”,变成一个可执行的程序。
对于 ELF 格式的文件,为了能够实现这样一个静态链接的机制,里面不只是简单罗列了程序所需要执行的指令,还会包括链接所需要的重定位表和符号表。