动态链接下,可执行文件的装载和静态链接情况基本一样,操作系统先读取可执行文件头部,检查文件合法性,然后从头部中的Program Header中读取每个Segment的虚拟地址,文件偏移和属性,并将他们映射到程序虚拟空间相应位置。
在动态链接下,操作系统将控制权交给了动态链接器ld.so,操作系统通过映射的方式将它加载到进程地址空间中.将控制权交给动态链接器的入口地址。
然后动态链接器进行一系列自身初始化操作,然后根据当前环境参数,开始对可执行文件进行动态链接工作,当所有动态链接工作完成后,将控制权交给可执行程序入口,程序开始执行。
.interp段:
指定了动态链接器的位置。
/lib/ld-linux.so.2
动态链接器在linux下是Glibc中的一部分,属于系统库级别的,
readelf -l program1 | grep interpreter [Requesting program interpreter: /lib/ld-linux.so.2]i
.dynamic段:
动态链接ELF中最重要的段是.dynamic段,这个段里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象,动态链接符号表的位置,动态链接重定位表的位置,共享对象初始化代码的地址等。
Elf32_Dyn由一个类型值加上一个附加的数值或指针。
DT_SYMTAB,动态链接符号表的地址,d_ptr表示.dynsym的地址
DT_STRTAB,动态链接字符串表地址,d_ptr表示.dynstr的地址
DT_HASH,动态链接哈希表大小,d_val表示大小
DT_SONAME,本共享对象的SO-NAME
DT_RPATH,动态链接共享对象搜索路径
DT_INIT,初始化代码地址
DT_FINIT,结束代码地址
DT_NEED,依赖的共享对象文件,d_ptr表示依赖的共享对象文件名
DT_REL,动态链接重定位表地址
DT_RELA,
DT_RELENT,动态重读位表入口数量
DT_RELANET
phil~/repos/my_bible $ readelf -d lib.so
Dynamic section at offset 0x644 contains 24 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x3a4
0x0000000d (FINI) 0x588
0x00000019 (INIT_ARRAY) 0x1638
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x163c
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x118
0x00000005 (STRTAB) 0x234
0x00000006 (SYMTAB) 0x154
0x0000000a (STRSZ) 193 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000003 (PLTGOT) 0x1738
0x00000002 (PLTRELSZ) 32 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x384
0x00000011 (REL) 0x344
0x00000012 (RELSZ) 64 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x314
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x2f6
0x6ffffffa (RELCOUNT) 3
0x00000000 (NULL) 0x0
linux还提供了查看程序主模块或一个共享库依赖哪些共享库:
phil~/repos/my_bible $ ldd ./program1
linux-gate.so.1 => (0xb7734000)
./lib.so (0xb7731000)
libc.so.6 => /lib/libc.so.6 (0xb7578000)
/lib/ld-linux.so.2 (0xb7735000)
其中linux-gate.so.1比较特殊,是一个内核虚拟共享对象
动态符号表:
为了表示动态链接这些模块之间的符号导入导出关系,ELF专门有个动态符号表.dynsym。它只保存与动态链接相关的符号,对于哪些模块内部的符号,比如模块私有变量则不保存。很多动态链接模块同时拥有.symtab和.dynsym。.symtab保存了所有符号,包含.dynsym中的符号。
对应还有动态符号字符串表.dynstr和为了加快符号查找的符号哈希表.
可以使用readelf查看ELF文件的动态符号表和他的哈希表:
phil~/repos/my_bible $ readelf -sD lib.so
Symbol table of `.gnu.hash' for image:
Num Buc: Value Size Type Bind Vis Ndx Name
8 0: 00001758 0 NOTYPE GLOBAL DEFAULT ABS _edata
9 0: 0000175c 0 NOTYPE GLOBAL DEFAULT ABS _end
10 1: 00001758 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
11 1: 000003a4 0 FUNC GLOBAL DEFAULT 9 _init
12 2: 00000588 0 FUNC GLOBAL DEFAULT 12 _fini
13 2: 0000054c 57 FUNC GLOBAL DEFAULT 11 foobar
动态链接重定位表:
共享对象需要重定位主要原因是导入符号的存在。PIC模式下的共享对象一样需要重定位。
对于PIC共享对象,他的代码段不需要重定位,但是数据段包含了绝对地址引用,因为代码段的绝对地址相关部分分离出来放在了GOT,而GOT实际是数据段的一部分。
动态链接器重定位相关结构:
在静态链接时有.rel.text表示代码段重定位段。.rel.data表示数据段重定位段。
在动态链接下.rel.dyn和.rel.plt分别相当于.rel.data和.rel.text。其中.rel.dyn是对数据段应用的修正,它所修正的位置位于.got和数据段。
而.rel.plt是对函数引用的修正,它所修正的位置位于.got.plt。
phil~/repos/my_bible $ readelf -r lib.so
Relocation section '.rel.dyn' at offset 0x344 contains 8 entries:
Offset Info Type Sym.Value Sym. Name
00001638 00000008 R_386_RELATIVE
0000163c 00000008 R_386_RELATIVE
00001754 00000008 R_386_RELATIVE
00001724 00000106 R_386_GLOB_DAT 00000000 _ITM_deregisterTMClone
00001728 00000406 R_386_GLOB_DAT 00000000 __cxa_finalize
0000172c 00000506 R_386_GLOB_DAT 00000000 __gmon_start__
00001730 00000606 R_386_GLOB_DAT 00000000 _Jv_RegisterClasses
00001734 00000706 R_386_GLOB_DAT 00000000 _ITM_registerTMCloneTa
Relocation section '.rel.plt' at offset 0x384 contains 4 entries:
Offset Info Type Sym.Value Sym. Name
00001744 00000207 R_386_JUMP_SLOT 00000000 printf
00001748 00000307 R_386_JUMP_SLOT 00000000 sleep
0000174c 00000407 R_386_JUMP_SLOT 00000000 __cxa_finalize
00001750 00000507 R_386_JUMP_SLOT 00000000 __gmon_start__
[20] .got PROGBITS 00001724 000724 000014 04 WA 0 0 4
[21] .got.plt PROGBITS 00001738 000738 00001c 04 WA 0 0 4
[22] .data PROGBITS 00001754 000754 000004 00 WA 0 0 4
在静态链接中遇到了R_386_32和R_386_PC32,这里有了几种新的重定位入口类型:
R_386_RELATIVE, R_386_GLOB_DAT和R_386_JUMP_SLOT。
对于R_386_GLOB_DAT和R_386_JUMP_SLOT,被修正的位置只需要直接填入符号地址即可。比如printf这个重定位入口,类型是R_386_JUMP_SLOT,偏移是0x00001744,它实际上在.got.plt中。.got.plt的前三项被系统占据的,第四项开始才是真正存放导入函数的地址的地方。而第四项刚好是0x00001724 + 4 * 3 = 0x00001750,即__gmon_start__,,第六项是sleep,第七项是sleep
当动态链接器需要进行重定位时,先查找printf的地址,假设是0x08801234,那么链接器将这个地址填入.got.plt中偏移为0x00001744的位置去,宠儿实现了地址重定位。
R_386_GLOB_DAT对.got的重定位跟R_386_JUMP_SLOT一样。
对于R_386_RELATIVE类型的重定位入口,实际就是基址重置,共享对象的数据段是无法做到地址无关的,它可能包含绝对地址,需要在装载时重定位。
例如
static int a;
static int *p = &a;
在编译时共享对象起始位置是0,假设静态变量a相对于起始地址0的偏移是B,那么p的值是B,一旦共享对象被装载到地址A,那么a的地址是B+A,那么p的值需要加上A。
如果ELF文件以PIC编译,并调用一个外部函数bar(),那么bar会出现在.rel.plt中,如果不是以PIC模式编译的,则bar将出现在.rel.dyn中。
phil~/repos/my_bible $ gcc -shared lib.c -o lib.so
phil~/repos/my_bible $ readelf -r lib.so
Relocation section '.rel.dyn' at offset 0x344 contains 11 entries:
Offset Info Type Sym.Value Sym. Name
0000053c 00000008 R_386_RELATIVE
00001600 00000008 R_386_RELATIVE
00001604 00000008 R_386_RELATIVE
0000171c 00000008 R_386_RELATIVE
00000541 00000202 R_386_PC32 00000000 printf
0000054d 00000302 R_386_PC32 00000000 sleep
000016f4 00000106 R_386_GLOB_DAT 00000000 _ITM_deregisterTMClone
000016f8 00000406 R_386_GLOB_DAT 00000000 __cxa_finalize
000016fc 00000506 R_386_GLOB_DAT 00000000 __gmon_start__
00001700 00000606 R_386_GLOB_DAT 00000000 _Jv_RegisterClasses
00001704 00000706 R_386_GLOB_DAT 00000000 _ITM_registerTMCloneTa
Relocation section '.rel.plt' at offset 0x39c contains 2 entries:
Offset Info Type Sym.Value Sym. Name
00001714 00000407 R_386_JUMP_SLOT 00000000 __cxa_finalize
00001718 00000507 R_386_JUMP_SLOT 00000000 __gmon_start__
可以看到两个导入函数printf, sleep从.rel.plt到了.rel.dyn中,并从类型R_386_JUMP_SLOT变成了R_386_PC32。
而R_386_RELATIVE类型多了一个偏移0x0000171c,它是printf的第一个参数。在PIC时,这个字符串可以看作普通全局变量,地址可以通过PIC中的相对当前指令的位置加上固定偏移计算出来。在非PIC中,代码段采用绝对地址寻址,所以它需要重定位。
动态链接时进程堆栈初始化信息:
进程初始化时,堆栈保存了关于进程执行环境和命令行参数等信息,同时保存了动态链接器需要的一些辅助信息数组(Auxiliary Vector),
先是32位的类型值,后面32位数据部分。
AT_NULL 0 表述辅助数组结束
AT_EXEFD 2 可执行文件的文件句柄
AT_PHDR 3 可执行文件中程序头表在进程中的地址
AT_PHENT 4 可执行文件头中程序头表中每个入口大小
AT_PHNUM 5 可执行文件头中程序头表入口的数量
AT_BASE 7 表示动态链接器本身的装载地址
AT_ENTRY 9 可执行文件入口地址
它位于环境变量指针的后面
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;
}