ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Linux系统编程:从库原理到动静态库链接

2026/9/16 10:02:49 拓冰建站 浏览量
Linux系统编程:从库原理到动静态库链接 前言本章讲述了库的原理以及动静态库链接的本质。当然了就不制作动静态库了。一、认识库1、实验及结论1、查看库文件指令ls -l /lib/x86_64-linux-gnu.so的是动态库.a的是静态库。2、查看具体信息指令file /lib/x86_64-linux-gnu/libc.so.6看到ELF 64-bit LSB shared object就是说明这个是一个动态库文件。2、相关知识1、库是一个成熟的、可直接用、可复用的二进制代码2、后缀为.a的是静态库文件.so的是动态库文件3、为什么输入通过上边的指令能够看到既有.a又有.so答gcc默认动态编译找不到动态库就会找静态库。4、可以使用gcc -static强制静态链接可能文件大小会非常大。5、静态库在编译链接时被复制到可执行文件中所以不依赖静态库6、动态库在运行的时候执行多个进程可以共享动态库与此同时GCC是动态链接。二、ELF文件1、实验及结论1、编译.ohello.c:#includestdio.hint global100;int bss_var;int main(){printf(hello :%d\n,global);return 0;}输入指令gcc -c hello.c -o hello.o可以看到entry point address是入口地址为0类型为REL。REL类型的意思为编译成功还未链接。2、指令readelf -S hello.o这个是ELF文件的节头表可以看到一些关键节后边会细讲。3、将.o文件链接可以发现类型变成了DYN这个DYN就是说已经链接完成该文件可能是可执行文件也可能是动态库。同时可以观察到entry point address地址已经变成了0x1060不是0了。4、查看段头输入指令readelf -l hello2、ELF文件类型1、可重定位文件xxx.o类型这个类型是编译之后未链接的状态包含代码和数据但是由于未链接所以地址未定所以我们上边通过readelf -h看到入口地址为0的情况。2、可执行文件链接之后可以直接运行的文件。3、可共享文件.so类型动态库可以被其他程序加载。4、内核转储进程崩溃时的内存快照。3、ELF文件格式上图时ELF格式图下边细讲一下上述格式的部分1、ELFheader(ELF头):描述文件的主要特征放在文件的开始。位置包含魔数类型入口等信息。 也就是上文readelf -h 看到的内容。2、program header table(程序头表)执行试图告诉OS怎么存储到内存之中。也就是readelf -l看到的内容。3、section header table(节头表)链接试图描述所有节。也就是readelf -S看到的内容。4、section(节):ELF文件的基本单位包含了特定类型数据。4、额外扩展1、为什么section和segment要分为两个视图编译视图(section)按照功能划分链接器把多个.o相同的section合并。链接视图(segment)按照加载属性划分划分为可读可写可执行将多个section划分为segment减少页面碎片。2、为什么section合并成为segment可以减少页面碎片答不同的section单独占一个空间此时就有可能造成页面用不完而导致浪费此时将多个section按照可读可写可执行将不同的section合并此时该section没有用完的就可以被其他的section占用从而减少页面碎片。三、静态链接的本质1、实验及结论1、创建code.c hello.c文件code.c:void run(){printf(running\n);}/////////////////////////////////////////hello.c:int main()void run();{printf(hello\n);run();return 0;}2、编译链接3、查看现象指令:objdmp -d code.o可以看到run这个函数的地址为0可以看到这个函数内部的call这个就是函数调用的一个指令。指令:objdump -d hello.o可以看到两个call说明调用了两个函数我们知道hello.c这个函数调用了两个函数一个是printf和一个run。4、把code.c 和hello.c链接到一个新的文件5、观察现象可以看到run是有了地址并且在main函数中确实调用了这个函数地址也是相同的。6、总结这个实验可以证明出来链接器可以把独立的文件整合到一个文件之后可以把分散的代码整合到一起同时占位符会变成真实的地址从而可以让函数去真正的跳转。2、核心机制静态链接的本质就是将不同的.o文件的数据段代码段去整合并且将真实地址去替换占位符的过程。将真实地址替换占位符的过程其实就是重定向。编译时编译器不知道函数的地址所以才用占位符去临时采用等知道真实的地址之后才会去替换。链接器通过.o的重定位表(.rela.text)和符号表(.symtab)来修正地址。3、深度补充1、为什么call地址是e8 00 00 00 00?答e8是call的操作码上文提到过在.o时期编译器是不知道函数的地址的所以用占位符去临时才用。当链接之后知道了函数的真实地址就会去替换了。2、重定位表指令:readelf -r 文件打个比喻来说其实重定位表其实相当于以恶搞待办的列表可以看到有一个puts这个puts讲就是调用printf的位置run就是函数run的具体位置。这个offset就是偏移量准确的来说就是把具体位置告诉了OS知道这里有一个代办去干。3、符号表指令readelf -s 文件文件表记录了函数\文件的类型大小所在区等。4、静态链接的过程1、合并:将所有.o文件的.data合成一个大的.data,.text合成一个大的.text。2、符号解析找到未定义的符号也就是UND。3、重定位修正地址。4、生成可执行文件。5、静态链接和动态链接的区别如果不考虑动态链接的got表plt跳转什么的二者其实最本质的区别就是多个进程的页表都映射到一个库还是一个进程自己映射一个库其他的差别不大。其他的话比如说所谓的编译时固定还是运行时解析其实就是为了应对多对一还是一对一的解决手段而已。四、动态链接的本质1、和静态链接的区别静态链接链接的过程是在链接的时候去链接的我们知道链接的本质就是符号解析重定位段合并将各个独立的目标文件拼接成一个完整的程序将地址换成虚拟地址让跨模块的函数可以互相调用。动态链接本质上就是将链接的过程推迟到运行的时候我们知道在虚拟内存空间内部在堆栈之间有一个叫做共享区的位置。2、粗略过程这个过程是这样的首先会在虚拟地址空间的共享区里划分一个区域这个区域内部划分了动态库各个函数的范围当需要访问该库函数的时候会查页表此时没有就会触发缺页中断然后就会将磁盘上需要的库文件加载到物理内存中然后会产生虚拟地址空间和物理空间的映射此时就相当于在虚拟内存空间就会有这个动态库文件了。此时就会产生一个问题就是动态库文件在共享区之后共享区那么大它的地址一定不会说是固定的那么如果需要库文件内部的一个函数应该怎么找到共享区内部的函数?我们知道库文件内部的地址是固定的比如说函数1就是在库文件的第二个位置函数2就是在函数1的下一个位置也就是第三个位置也就是说偏移量是知道的所以当我们找到了库文件在共享区内部的起始地址此时加上偏移量的话我们就知道这个库函数的具体虚拟地址空间。那么下边通过指令和代码去更加详细的了解。代码及指令1、看依赖指令ldd hello最下边的红框子是动态链接器然后上边就是所需要的库文件。流程就是先加载动态链接然后看依赖列表然后加载对应的库文件简单来说就是看你需要哪些库文件就会让动态链接器去加载对应的库文件。2、查看该文件需要的解释器指令:readelf -l hello | grep A2 INTERP可以看到最下边就是之前看到的解释器也叫动态链接器。动态链接器的作用:加载:把需要的库加载到虚拟地址空间。链接重定位填GOT表等跳转:跳转到程序入口开始执行也就是_start.3、查看动态段指令:readelf -d hello我们之前讲过加载动态链接之后就会看需要哪些库然后就会将这些库加载到内存之中这里就是动态链接所需要加载的库。4、查看plt表指令objdump -d hello5、查看GOT表指令readelf -S hello3、具体过程及知识我们知道链接的过程需要修改代码段的数据比如说重定位的时候需要把地址修改为虚拟地址但是这里就引出了一个问题就是如果数据在代码段的话权限是只读的不能被修改那么听过怎么去重定位呢也就是修改地址呢我们知道数据段的权限是可以写的所以在数据段那个代码段里放一个GOT表这个GOT表里存放着一张函数跳转的表。我们可以通过终端来看这个过程我们知道_start是程序的开始会为程序做初始化等工作然后调用main此时就会进入main函数。然后看main函数里边的过程。可以看到画红框的地方的话就是调用的plt表也就是说此时就会跳转到plt表内部然后查看plt表内部长什么样。这就是main函数内部跳转过来的plt表可以发现这里也是一个jmp跳转函数那么这个就是跳转到GOT表的一个过程。那么什么是plt表呢有一个问题就是如果有成千上万的动态库函数的话我们的程序不会用这么多的库函数只需要使用其中的部分而已如果全部加载解析就会造成效率低下的问题所以此时有一个机制就是延迟绑定的机制也就是说当你使用这个库函数的时候才会去解析这个函数链接这个函数此时这个函数的地址就会存储在GOT表内部等下一次再使用这个函数的时候就会使用这个之前解析过的地址直接跳转即可这就是plt表的作用。动态链接是动态链接器做的所以此时就需要跳转到动态链接器那里下图就是跳转到动态链接器的反汇编。所以整体的过程就是首先跳转到PLT表进入GOT然后查看有没有这个函数的具体地址如果没有就会跳转到动态链接器然后解析该函数将虚拟地址写入到GOT表中如果进入GOT表中查看存在函数具体地址的话就直接访问函数地址不会再次调用动态链接器。此时通过上文的解析我们就可以得到函数的虚拟地址此时通过页表映射就可以得到函数的物理内存地址此时就可以访问了。所以最终得出结论对于程序而言自己独享整个动态库认为动态库文件位于自己虚拟地址空间中的共享区所以说当程序需要调用动态库函数的时候其实就是从代码区跳转到共享区的过程可以发现GOT表是在数据区的也就可以修改了。后记后边更新进程间通信