动态库如何被加载
linux的可执行文件都是ELF格式,它肯定是会有个section叫.interp, 这里面保存的是动态链接器的路径。
我们在执行这个ELF格式的可执行文件时,内核会先根据.interp节找到动态链接器,然后把控制权交给动态链接器,由动态链接器去加载依赖的动态库。
1、链接器如何找到依赖的动态库
搞一个简单的小程序
A.cpp生成动态库libA.so
#include <stdio.h> void func() { printf("this is libA funcC\n"); }
g++ -fpic -shared A.cpp -o libA.so
main.cpp生成可执行文件main
#include <stdio.h> extern void func(); int main() { func(); return 0; }
g++ main.cpp -o main -L./ -lA -Wl,-rpath=./
在main文件里有个.dynamic的节,这里就包含了它依赖的动态库信息,我们反汇编看看,执行objdump -j .dynamic -S main,得到如下图1
通过readelf -d main可以更直观的查看,得到如下图2
图1中第一行是600dd8: 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
图2中第一行是0x0000000000000001 (NEEDED) 共享库:[libA.so]
下面分析下怎么从图1得到图2的:
图1中第一个字节01表示类型,这里类型是NEEDED,就是依赖的动态库, 0x01表示动态库名称的偏移地址,要在string table里查找,string stable就是一个存放所有符号字符串的地方,即图2中的STRTAB
图2中有以下四行
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x4003a8
0x0000000000000006 (SYMTAB) 0x4002d0
0x000000000000000a (STRSZ) 151 (bytes)
GNU_HASH 是一个hash table, key是符号名称,value是该符号在符号表即SYMTAB里的索引
STRTAB就是存放符号字符串名称的数组
SYMTAB是符号表,是一个key-value表,key是STRTAB里面符号偏移地址, value是该符号在代码段里的起始地址
STRSZ是STRTAB的长度
我们反汇编0X01+0X4003a8 到 0X01+0X4003A8+151的字符串,
objdump -s --start-address=0X4003a9 --stop-address=0X40043f main
得到如下图,里面正是所依赖的动态库libA.so名称
当动态链接器看到libA.so这些依赖的动态库名称后,就会去找这些动态库加载,寻找的路径优先级依次是:
1) rpath
2) LD_LIBRARY_PATH环境变量目录
3) /etc/ld.so.conf配置路径
4) /lib /usr/lib
2、外部函数如何被调用
先介绍三个section
.got ---- (Global Offset Table)全局偏移表,链接器将外部符号存在这里
.got.plt ---- 这是got专门为plt准备的节,可当做是got的一部分,它包含plt表所需的地址
.plt ---- (Procedure Linkage Table)程序链接表,它里面都是关于每个外部函数的一小段代码,就是去.got.plt查找外部函数地址
在可执行文件main中,并不知道外部声明的函数func()的真实地址,就使用func()&plt代替,
再看看func()&plt有啥
0x601028是.got.plt里的地址, .plt和.got.plt一一对应
执行objdump -j .got.plt -S main
记住这里0x601028的数据。
我们执行gdb ./main,gdb看下运行时这里是啥,
变成了一个虚拟内存地址,
执行i proc mapping 找到libA.so的加载偏移地址0x7ffff7bd9000,0x7ffff7bd96d5 - 0x7ffff7bd9000=0x6d5, 这就是func()函数在libA.so的文本段的地址