动态库的本质探究:
所有使用某个库的程序可以只共享一份副本(节约空间)。
动态链接,链接器在程序运行前不会将库中段和符号绑定到确切的地址上。
实际查看动态库,发现动态库是将所有目标文件整理到一套链接地址上,进而可以知道动态库如果加载进内存,是以一个库作为整体加载的。
再查看调用动态库的可执行程序中导入的符号:
可以看出,并没有给符号绑定地址,只是进行了标记。
动态库的使用:
动态库的制作
(1)生成地址无关码:
gcc -fPIC -c *.c
(2)生成动态链接库
gcc -shared *.o -o libxxx.so
动态库的使用
首先理解动态库的运行方式:
动态库在被运行前由动态链接器加载到内存,并完成地址绑定。
所以首先需要保证动态链接器能够找到动态库。
使用 ldd 命令查看:
红线部分为动态链接器,
其中 libsort.so 没有找到。
动态链接器会根据环境变量PATH和环境变量LD_LIBRARY_PATH指定的目录查找,所以添加libsort.so的目录。
静态库的本质探究:
每个库在创建时会被绑定到特定的地址,链接器在链接时将程序中引用的库例程绑定到这些特定的地址。
查看库文件符号:
可以看出,静态库的函数被重定位到独立的地址空间。
再查看调用静态的可执行文件的符号:
可以看出被引用的符号,是被重定位到了可执行文件中。
静态库的使用:
静态库的制作:
(1)生成.o文件
(2)生成静态库
ar rcs libxxx.a *.o
静态库的使用
gcc main.c -L ./ -l xxx -o a.out
其中 -L 指定静态库目录,-l 指定该目录中静态库的名字
对于使用静态库和直接编译链接的区别
使用静态库:
链接时,链接器会查询库的符号表和.o文件的符号表,找到需要导入的符号,将其实现和.o文件进行合并,分段。生成可执行文件。
.o文件导入了什么符号,链接后就会添加对应的实现。
直接编译链接:
链接时,链接器会扫描所有.o文件的符号表和段表,整理导入符号和导出符号,合并所有段信息,整理出包含所有.o文件信息的段表,并进行重定位。对于匹配了的导入符号位置会进行代码修改。
.o文件的所有信息都会被重定位到可执行文件中,无论可执行文件是否会运行该二进制处。
代码修改:
eg: a 为本地符号,b为声明符号(需要导入的符号)
mov a, %eax
mov %eax, b
编译后,二进制为:
A1 34 12 00 00 mov a, %eax
A3 00 00 00 00 mov %eax, b
a的地址为0x1234,由于b为外部导入符号,编译时无法确定,所以b的四字节地址用0表示
链接后,二进制位:
A1 34 12 01 00 mov a, %eax
A3 12 9A 00 00 mov %eax, b
链接时进行了重定位,a地址偏移了0x100000,b地址进行了补充,并且对a,b地址相关的指针也进行了修改。
现代计算机中,对该功能的支持高度依赖于硬件性质,意思就是链接器与具体硬件高度相关。