动态库使用方式分为:
- 隐式链接(隐式加载),编译时通过编译选项指定动态库
- 显式链接(显式加载),通过代码调用dlopen、dlsym、dlclose指定动态库
动态库的符号解析分为:
- 立即绑定
- 延迟绑定
使用方法:
隐式链接+立即绑定 | -Wl,-z,now |
隐式链接+延迟绑定 | 默认行为 |
显式链接+立即绑定 | RTLD_NOW |
显式链接+延迟绑定 | RTLD_LAZY |
例子:
#include
//gcc -g -fcf-protection=none -o hello hello.c
//gcc -g -no-pie -fcf-protection=none -Wl,-z,now -o hello hello.c
void hello(void)
{
printf("Hello, World!
");
}
int main(int argc, char **argv)
{
hello();
return 0;
}
readelf -r hello.o
readelf -r hello
objdump -d -s -j .plt -j .got.plt hello
- 重定位是连接符号引用与符号定义的过程(符号和地址的绑定)
- .rel.text(链接时重定位) 重定位的地方在.text段内,以offset指定具体要定位位置。在连接时候由连接器完成。注意比 较.text段前后变化。指的是比较.o文件和最终的执行文件(或者动态库文件)。就是重定位前后比 较,以上是说明了具体比较对象而已。
- .rel.dyn(启动时重定位) 重定位的地方在.got段内。主要是针对外部数据变量符号。例如全局数据。重定位在程序运行时定 位,一般是在.init段内。定位过程:获得符号对应value后,根据rel.dyn表中对应的offset,修 改.got表对应位置的value。另外,.rel.dyn 含义是指和dyn有关,一般是指在程序运行时候,动态 加载。区别于rel.plt,rel.plt是指和plt相关,具体是指在某个函数被调用时候加载。
- .rel.plt(调用时重定位) 重定位的地方在.got.plt段内(注意也是.got内,具体区分而已)。 主要是针对外部函数符号。一般 是函数首次被调用时候重定位。可看汇编,理解其首次访问是如何重定位的,实际很简单,就是 初次重定位函数地址,然后把最终函数地址放到.got.plt内,以后读取该.got.plt就直接得到最终函 数地址(参考过程说明)。 所有外部函数调用都是经过一个对应桩函数,这些桩函数都在.plt段内。
- .got(Global Offset Table,全局偏移表) 是Linux ELF文件中用于定位全局变量和函数的一个表。GOT表前三项是特殊的:GOT[0]本ELF动态 段(.dynamic段)的装载地址;GOT[1]本ELF的link_map数据结构描述符地址(编译时填充 0);GOT[2]_dl_runtime_resolve函数的地址(编译时填充0)。GOT的其他表项为全局变量的地址 或者为不需要延迟绑定的函数的地址。
- .got.plt 用于存放需要延迟绑定的函数的地址。
- .plt(Procedure Linkage Table,过程链接表)(延迟绑定) 是Linux ELF文件中用于延迟绑定的表,即函数第一次被调用的时候才进行绑定。因此,第一次调 用函数时开销比较大,但是其后的每次调用都只会花费一条指令和一个间接的存储器引用。其中 PLT[0]是一个特殊的表项,它跳转到动态链接器中执行。.plt 关联 GOT 项是在 .rela.plt中。
- .plt.got(立即绑定) 非延迟绑定的plt。.plt.got 关联 GOT 项是在 .rela.dyn。
- .plt.sec 启用-fcf-protection编译选项时,原先属于.plt的现在属于.plt.sec。