ELF 和静态链接:为什么程序无法同时在 Linux 和 Windows 下运行?
编译、链接和装载:拆解程序执行
- 形式如 add_lib.o 以及 link_example.o 并不是一个可执行文件(Executable Program),而是目标文件(Object File)。
- 只有通过链接器(Linker)把多个目标文件以及调用的各种函数库链接起来,我们才能得到一个可执行文件。
- 通过 gcc 的 -o 参数,可以生成对应的可执行文件。
- C 语言代码-汇编代码-机器码这个过程,在我们的计算机上进行的时候是由两部分组成的。
- 第一部分由编译(Compile)、汇编(Assemble)以及链接(Link)三个阶段组成。
- 在这三个阶段完成之后,我们就生成了一个可执行文件。
- 第二部分,我们通过装载器(Loader)把可执行文件装载(Load)到内存中。
- CPU 从内存中读取指令和数据,来开始真正执行程序。
- 第一部分由编译(Compile)、汇编(Assemble)以及链接(Link)三个阶段组成。
ELF格式和链接:理解链接过程
- 程序最终是通过装载器变成指令和数据的,所以其实我们生成的可执行代码也并不仅仅是一条条的指令。
- 在 Linux 下,可执行文件和目标文件所使用的都是一种叫 ELF(Execuatable and Linkable File Format)的文件格式,中文名字叫可执行与可链接文件格式,这里面不仅存放了编译成的汇编指令,还保留了很多别的数据。
- 比如所有 objdump 出来的代码里对应的函数名称,像 add、main 等等。
- 自定义的全局可以访问的变量名称。
- 这些名字和它们对应的地址,在 ELF 文件里面,存储在一个叫作符号表(Symbols Table)的位置里。
- 符号表相当于一个地址簿,把名字和地址关联了起来。
- main 函数里调用 add 的跳转地址,不再是下一条指令的地址了,而是 add 函数的入口地址了,这就是 EFL 格式和链接器的功劳。
- ELF 文件格式把各种信息,分成一个一个的 Section 保存起来。
- ELF 有一个基本的文件头(File Header),用来表示这个文件的基本属性,比如是否是可执行文件,对应的 CPU、操作系统等等。
- 除了这些基本属性之外,大部分程序还有这么一些 Section:
- 首先是 .text Section,也叫作代码段或者指令段(Code Section),用来保存程序的代码和指令;
- 接着是 .data Section,也叫作数据段(Data Section),用来保存程序里面设置好的初始化数据信息;
- 然后就是 .rel.text Secion,叫作重定位表(Relocation Table),重定位表里,保留的是当前的文件里面,哪些跳转地址其实是我们不知道的。
- 最后是 .symtab Section,叫作符号表(Symbol Table),符号表保留了我们所说的当前文件里面定义的函数名称和对应地址的地址簿。
- 可执行文件里面的函数调用的地址都是正确的原因:
- 链接器会扫描所有输入的目标文件,然后把所有符号表里的信息收集起来,构成一个全局的符号表。
- 然后再根据重定位表,把所有不确定要跳转地址的代码,根据符号表里面存储的地址,进行一次修正。
- 最后,把所有的目标文件的对应段进行一次合并,变成了最终的可执行代码。
- 在链接器把程序变成可执行文件之后,装载器不再需要考虑地址跳转的问题,只需要解析 ELF 文件,把对应的指令和数据,加载到内存里面供 CPU 执行就可以了。