对于两个.c文件,如何形成可执行的目标文件呢?
两个源代码分别经过预处理编译器,编译编译器,汇编编译器,生成了两个可重定向的目标文件,再经过链接器,链接成可执行目标文件。(在编译过程中需要指定两个.c文件)
- 模块化:程序是由许多小的源文件组成,而不是大的单一文件。并且能构建包含许多函数的库。
- 提高效率:节省时间:当修改源文件时,只需要重新编译那个修改的源文件,而不需要重新编译其他源文件。节省空间:公用的函数压缩成库,函数调用库时,只需要加载库中的某一些函数。
- 符号解析(简单解析)
- 程序能够定义和引用符号(全局变量或者函数)
- 这些符号(全局变量/函数)的定义在目标文件(.o)中以符号表的形式保存着,每一个符号对应着名称,大小,符号位置(以structs形式)
- 在符号解析阶段,链接器将每一个符号引用和其定义(名称,大小,符号位置)一一对应。
- 重定位阶段
- 目的是将分开储存的数据和代码组合成单一区域
- 重定位:由符号的相对位置,找到其绝对位置
- 更新符号引用中的位置为它们的绝对位置??
-
三种目标文件(object files)(模块)
- 重定向目标文件(.o file)(汇编器生成的二进制文件):不能直接加载当内存,需要等待链接器的链接,其中数据和代码以某种形式存储(ELF),每一个.o文件对应着一个源文件。
- 可运行目标文件(a.out file)能够直接加载到内存并运行
- 共享目标文件(.so file)(共享库):特殊的重定向目标文件,能够在**加载时间??**和运行时期动态的载入内存和动态链接,在windows中称为dll文件。
-
可执行可链接格式(ELF)
针对二进制目标文件(上面三种)的标准格式,所以上面三种文件又称为ELF二进制。
这种二进制文件的格式如下图所示:每一部分都有详细划分。
.bss文件储存未初始化的全局变量。
- 链接器符号
- 全局符号(global symbols):在模块m中定义,并能被其他模块引用的符号,比如non-static 函数或者non-static 全局变量
- 外部符号(external symbols):在模块m中被引用,在其他模块被定义的全局符号
- 局部符号(local symbols):在模块m中定义,只能在模块m中引用的符号,比如static的函数和全局变量。
- 区分局部非静态变量和局部静态变量:local non-static存在stack上,local static存在.bss或者.data
- 强符号/或符号:强符号:过程/已经初始化的全局变量,弱符号:未初始化的全局变量,不允许多个同名强变量,但允许多个同名弱变量,会随机选择一个初始化。一定要避免弱变量,很容易出Bug
- 全局符号使用原则:尽可能static,尽可能初始化,尽可能声明external
-
链接步骤
可以发现:重定向需要重新规划各个重定向目标文件的.text./data section的地址,从而组成可执行目标文件的.text和.data
可以发现:对于那些需要定位地址的部分,如array的地址和sum函数的地址,都用00000000代替,然后使用了relocation entry,这些entries可以帮助linker有足够的信息,组成可执行的目标文件(详细解释见教材,视频也没怎么讲)
可以发现:之前array和sum的地址确定了!(之前好像用的相对地址)然后main函数好像就是紧接在system code的后面。
这张图的困惑:1,为什么要多开8个字节的stack呢,明明callee个caller使用寄存器来共享数据呀
2,程序计数器(rpi)的相对地址是什么东西。
-
最后,可执行文件被加载到内存,如下图所示
上图针对linux系统,程序开始地址0x400000,有一段内存映射区域供共享库使用。
如何方便的使用那些常用的函数呢:方法1:把常用函数用单一的源文件存储,这样在linker会把一个很大的.o文件链接进去,时间空间成本高。方法2:把常用函数分别用源文件存储,程序员在编译时手动显式的链接要使用的.o文件,这样效率高,但增加程序员负担。
解决办法:把常用的函数打包形成链接库,在main.c文件中包含这些链接库,liner可以在链接库中找到所需的函数的.o文件,链接需要的.o文件
链接库按照链接的时间不同:分为静态链接库和动态链接库。
所有相关的.o文件重新排位后压缩到一个.a(archive file/档案文件)(AR:归档器)中,当liner发现了无法解析的符号时,会在一个或多个.o文件中查找,找到所需要的文件
可以发现:归档器(archiver)允许更新文件以及重新编译替换文件
可以发现:对象文件按字符串排列索引
链接过程:在所需要的归档文件中选择所需函数链接即可(.h文件存储了所需要的函数声明,然后链接器按照声明在归档文件中找到所需函数)(.h文件在cpp过程中被放在主代码部分形成.i文件,其实里面内容就是些全局对象的声明)
链接如下图所示:
编译指令如下:
可以发现:按照linker执行“解析符号”的顺序,不能把.a(即图中的-lmine)文件放在前面,不然无法解析.o文件中的符号。
静态链接库缺点:1,需要不断地复制libc中的函数,如果你的system libraries中有Bug,所有使用了该库的文件都需要重新显式relink。
定义:.o文件可以在应用程序(加载到内存或)执行的时候动态的加载和链接进去。
- 加载时间(load-time)链接:Linux通常情况下加载应用程序会使用此动态加载的方法。
- 运行时间(run-time)链接:可以在程序运行期间,自由选择任何时间link所需的.o文件,通过使用dlopen()接口
链接可以发生在:程序编译完成(静态链接库),程序被加载到内存(动态链接库),程序运行时(动态链接库)
可以在complie time/ link-time/ Run-time三个阶段进行库打桩,前两个略,重点是动态链接库的库打桩技术
首先定义我们自己的mymalloc.c文件,然后生成.so文件,照常编译和链接main.o,但是在运行的时候加入一行命令行,让链接器首先在mymalloc.so这个共享库内解析符号,从而实现了我们自定义的的wraper函数,代替了系统自带的malloc函数。这样我们能够统计malloc地址等等。