动态链接的工作过程
时间:2009-05-24 来源:engine_chen
每一个外部定义的符号在全局偏移表(Global Offset Table GOT)中有相应的条目,如果符号是函数则在过程连接表(Procedure Linkage Table PLT)中也有相应的条目,且一个PLT条目对应一个GOT条目。对外部定义函数解析可能是整个ELF文件规范中最复杂的,下面是函数符号解析过程的一个描述。
1:代码中调用外部函数func,语句形式为call 0xaabbccdd,地址0xaabbccdd实际上就是符号func在PLT表中对应的条目地址(假设地址为标号.PLT2)。
2:PLT表的形式如下
.PLT0: pushl 4(%ebx) /* GOT表的地址保存在寄存器ebx中 */
jmp *8(%ebx)
nop; nop
nop; nop
.PLT1: jmp *name1@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
.PLT2: jmp *func@GOT(%ebx)
pushl $offset
jmp .PLT0@PC
3:查看标号.PLT2的语句,实际上是跳转到符号func在GOT表中对应的条目。
4:在符号没有重定位前,GOT表中此符号对应的地址为标号.PLT2的下一条语句,即是pushl $offset,其中$offset是符号func的重定位偏移量。注意到这是一个二次跳转。
5:在符号func的重定位偏移量压栈后,控制跳到PLT表的第一条目,把GOT[1]的内容压栈,并跳转到GOT[2]对应的地址。
6:GOT[2]对应的实际上是动态符号解析函数的代码,在对符号func的地址解析后,会把func在内存中的地址设置到GOT表中此符号对应的条目中。
7:当第二次调用此符号时,GOT表中对应的条目已经包含了此符号的地址,就可直接调用而不需要利用PLT表进行跳转。
动态连接是比较复杂的,但为了获得灵活性的代价通常就是复杂性。其最终目的是把GOT表中条目的值修改为符号的真实地址,这也可解释节.got包含在可读可写段中。
一个简单的小程序:hello.c
#include<stdio.h> int main(){ puts("hello"); }
$ gcc hello.c -o hello -mpreferred-stack-boundary=2
$ objdump -d -j .plt hello hello: file format elf32-i386 Disassembly of section .plt: 08048288 <puts@plt-0x10>: 8048288: ff 35 94 95 04 08 pushl 0x8049594 804828e: ff 25 98 95 04 08 jmp *0x8049598 8048294: 00 00 add %al,(%eax) ... 08048298 <puts@plt>: 8048298: ff 25 9c 95 04 08 jmp *0x804959c 804829e: 68 00 00 00 00 push $0x0 80482a3: e9 e0 ff ff ff jmp 8048288 <_init+0x18> ...
$ gdb -q hello Using host libthread_db library "/lib/libthread_db.so.1". (gdb) disass main Dump of assembler code for function main: 0x08048384 <main+0>: push %ebp 0x08048385 <main+1>: mov %esp,%ebp 0x08048387 <main+3>: sub $0x4,%esp 0x0804838a <main+6>: movl $0x80484a4,(%esp) 0x08048391 <main+13>: call 0x8048298 <_init+40> 0x08048396 <main+18>: leave 0x08048397 <main+19>: ret
然后下面就是jmp *0x804959c,间接跳转,跳到0x0804959c上放置的地址去执行,0x0804959c就是GOT(global offset table)的第4项
而在puts没有被solve之前那个位置上放置的地址就是jmp *0x804959c的下一句的地址,就是0x0804829e
所以接着运行,pushl $0x0,将$0x0推入堆栈。这个是fixup()要用的一个参数。这个0是相对于JMPREL的一个offset。后面有解释。
跳到plt的开始,jmp 8048288
然后把GOT的第二项推入堆栈,pushl 0x80495a4,这个是一个struct link_map的指针,靠着它,进程地址空间里所有的so将被连成一个链表。这个也是fixup()要用的参数。
struct link_map { ElfW(Addr) l_addr; char *l_name; ElfW(Dyn) *l_ld; struct link_map *l_next, *l_prev; };
typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel;
r_offset的值就是GOT里面我们要改写的项,对于本例的puts来说就是第四项,就是 0x0804959c。来看一下
得到JMPREL的地址:
$ readelf -d hello Dynamic section at offset 0x4c4 contains 20 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000c (INIT) 0x8048270 0x0000000d (FINI) 0x8048480 0x00000004 (HASH) 0x8048168 0x00000005 (STRTAB) 0x80481e0 0x00000006 (SYMTAB) 0x8048190 0x0000000a (STRSZ) 74 (bytes) 0x0000000b (SYMENT) 16 (bytes) 0x00000015 (DEBUG) 0x0 0x00000003 (PLTGOT) 0x8049590 0x00000002 (PLTRELSZ) 16 (bytes) 0x00000014 (PLTREL) REL 0x00000017 (JMPREL) 0x804825c
(gdb) x/2x 0x0804825c 0x804825c: 0x0804959c 0x00000107
fixup在解析完之后就会把puts的真实地址放到0x0804959c上去
以后就不必在解析了
参考资料:
1.glibc 2.3.5 src
2.<<Tool Interface Standard Executable and Linking Format Specification Version 1.2>>
3.<<Linkers and Loaders>>
4.<<Cheating the ELF Subversive Dynamic Linking to Libraries>> write by the grugq
5.<<ELF动态解析符号过程>> by alert7
Copyright 2006-2020 (phpxiu.com) All Rights Reserved.
本站为非盈利性网站,不接受任何广告。php爱好者