动态链接在不同的系统上有不同的实现方式,ELF比PE简单些。这里只介绍ELF。
第一,操作系统读取可执行文件的头部,检查文件的合法性,
第二,从头部中的program header 中读取每个 segment的虚拟地址,文件地址和属性
并将它们映射到进程虚拟空间相应位置
第三,操作系统把控制权交给可执行文件的入口,然后执行。
动态链接情况下,操作系统还不能在装载完可执行文件后,就把控制权交给
可执行文件。
原因:可执行文件依赖很多共享对象。
即可执行文件里对于很多外部符号的引用还处于无效地址的状态,
就是还没有跟相应的共享对象中的实际位置连接起来。
所以,在影射完可执行文件之后,操作系统会先启动一个动态连接器。
操作系统加载完动态连接器之后,就将控制权交给动态连接器的入口地址
(与可执行文件一样,共享对象也有入口地址)
系统中哪个是动态链接库?
它的位置谁决定?
ELF中有个专门的段.interp (interpreter解释器)段。
它指向连接器。
root@ubuntu-admin-a1:/home/6Chapter# objdump -s a.out
a.out: file format elf64-x86-64
Contents of section .interp:
400238 2f6c6962 36342f6c 642d6c69 6e75782d /lib64/ld-linux-
400248 7838362d 36342e73 6f2e3200 x86-64.so.2.
这里 interp 指向的连接器是: ld-linux-x86-64.so.2.
这里是ubuntu的宿主机查看的结果。
linux下,动态连接器的路径几乎都是 /lib/ld-linux.so.2
dynamic 段是最重要的段。
它保持了动态连接器需要的基本信息。
比如依赖哪些共享对象,共享对象初始化代码的地址,动态链接符号表的位置,重定位表的位置
root@ubuntu-admin-a1:/home/6Chapter# readelf -d a.out
Dynamic section at offset 0xe28 contains 24 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x400498
0x000000000000000d (FINI) 0x400774
0x0000000000000019 (INIT_ARRAY) 0x600e10
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x600e18
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x400298
0x0000000000000005 (STRTAB) 0x400360
0x0000000000000006 (SYMTAB) 0x4002b8
0x000000000000000a (STRSZ) 102 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x601000
0x0000000000000002 (PLTRELSZ) 120 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x400420
0x0000000000000007 (RELA) 0x400408
0x0000000000000008 (RELASZ) 24 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000006ffffffe (VERNEED) 0x4003d8
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x4003c6
0x0000000000000000 (NULL) 0x0
root@ubuntu-admin-a1:/home/6Chapter#
root@ubuntu-admin-a1:/home/6Chapter# ldd a.out
linux-vdso.so.1 => (0x00007ffc379b8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa425b86000)
/lib64/ld-linux-x86-64.so.2 (0x000055910f911000)
root@ubuntu-admin-a1:/home/6Chapter#
为了完成动态链接,最关键的还是所依赖的符号和相关文件的信息。
静态链接中,有个专门的段叫做 .symtab(symbol table),里面保存了所有关于该
目标文件的符号的定义和引用。
如程序pro1使用了hello.so 里面的 printHello 函数
我们就称 printHello 是 pro1 的导入函数;
printHello 是 hello.so 的导出函数;
为了表示动态链接这些模块之间的符号导入导出关系,ELF专门有个段来保存这些信息。
这个段就是:动态符号表(Dynamic Symbol Table), .dynsym。
与 symtab 类似,动态符号表也需要一些辅助的表。
如保存符号名的字符串表 symtab 中叫做:.strtab (String Table)
dynsym 中叫做 .dynstr (Dynamic String Table) 动态符号字符串表。
还有符号哈希表 .hash
root@ubuntu-admin-a1:/home/6Chapter# readelf -sD libtest.so
Symbol table of `.gnu.hash' for image:
Num Buc: Value Size Type Bind Vis Ndx Name
8 0: 0000000000201028 0 NOTYPE GLOBAL DEFAULT 23 _edata
9 0: 00000000000006f3 19 FUNC GLOBAL DEFAULT 12 test_b
10 0: 0000000000201030 0 NOTYPE GLOBAL DEFAULT 24 _end
11 1: 0000000000000706 19 FUNC GLOBAL DEFAULT 12 test_c
12 1: 0000000000201028 0 NOTYPE GLOBAL DEFAULT 24 __bss_start
13 1: 0000000000000590 0 FUNC GLOBAL DEFAULT 9 _init
14 2: 00000000000006e0 19 FUNC GLOBAL DEFAULT 12 test_a
15 2: 000000000000071c 0 FUNC GLOBAL DEFAULT 13 _fini
root@ubuntu-admin-a1:/home/6Chapter#
参考:
https://www.cnblogs.com/Burgess-Fan/p/6138687.html
dynsym 它只保持了与动态链接相关的符号。
symtab 保存了所有的符号,包括 dynsym 中的符号。
共享对象需要重定位的原因是:导入符号的存在。
在编译时,这些导入符号的地址是未知的,静态连接中,
这些未知的地址引用在最终链接时被修正。
动态连接中,导入符号的地址在运行时才确定,所以需要在运行时将这些
导入符号的引用修正,即需要重定位。
静态连接中,目标文件里包含有专门用于表示重定位信息的重定位表,
如: .rel.text 表示代码段的重定位表
.rel.data 是数据段的重定位表。
动态连接的目标文件中,也有类似的重定位表叫做: .rel.dyn 和 rel.plt。
.rel.dyn 类似于 静态链接的 .rel.text。
它实际上是对数据引用的修正。修正的位置位于 .got以及数据段。
rel.plt 类似于 静态链接的 .rel.data
它是对函数引用的修正。
root@ubuntu-admin-a1:/home/6Chapter# readelf -r libtest.so
Relocation section '.rela.dyn' at offset 0x4b8 contains 8 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000200e00 000000000008 R_X86_64_RELATIVE 6b0
000000200e08 000000000008 R_X86_64_RELATIVE 670
000000201020 000000000008 R_X86_64_RELATIVE 201020
000000200fd8 000200000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTMClone + 0
000000200fe0 000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000200fe8 000500000006 R_X86_64_GLOB_DAT 0000000000000000 _Jv_RegisterClasses + 0
000000200ff0 000600000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCloneTa + 0
000000200ff8 000700000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0
Relocation section '.rela.plt' at offset 0x578 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000201018 000300000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
root@ubuntu-admin-a1:/home/6Chapter#
root@ubuntu-admin-a1:/home/6Chapter# readelf -S libtest.so
There are 29 section headers, starting at offset 0x18f8:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
……
[21] .got PROGBITS 0000000000200fd8 00000fd8
0000000000000028 0000000000000008 WA 0 0 8
[22] .got.plt PROGBITS 0000000000201000 00001000
0000000000000020 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000201020 00001020
0000000000000008 0000000000000000 WA 0 0 8
……
打印堆栈中的初始化信息
参考: https://blog.csdn.net/hs794502825/article/details/16940105
auxiliary_vestor.c
#include
#include
int main(int argc, char * argv[])
{
int *p = (int *)argv;
int i;
Elf32_auxv_t *aux;
printf("Argument count:%d\n", *(p-1) );
for(i=0; i<*(p-1); i++)
{
printf("Argument %d : %s\n", i, *(p+i) );
}
p += i;
p++;
printf("Environment:\n");
while(*p)
{
printf("%s\n", *p);
p++;
}
p++;
printf("Auxiliary Vectors:\n");
aux = (Elf32_auxv_t *)p;
while(aux->a_type != AT_NULL)
{
printf("Type: %02d Value: %x\n", aux->a_type, aux->a_un.a_val);
aux++;
}
return 0;
}