|
Linux动态链接技术
引自:http://qiuker.spaces.live.com/blog/cns!7B9B144C8022E90D!111.entry?_c11_blogpart_blogpart=blogview&_c=blogpart
Linux动态链接技术(zz) 在动态链接的应用程序或共享库中,ELF的程序头描述表具有一个PT_DYNAMIC类型的描述符,它指出了.dynamic段的位置,dynamic段用来描述动态链接过程。当应用程序调用的共享库函数时,要通过.plt段进行跳转。plt段又称为过程连接表,它是连接器ld所生成的一组静态的 trampline,是只读的可执行的段,包含在.text段一起映射到内存。plt每16个字节为一个槽位,plt的第1个槽位保留给动态解析器使用,其余的槽位表示对不同共享库函数的调用。plt依赖于全局偏移量表(.got段),GOT表是一可写的数据段,包含在.data段中一起映射到内存,用来存放共享符号的绝对地址。应用程序调用共享库函数就是通过plt槽位上的一条jmp指令跳转到GOT表所指的一个共享函数指针。这样,共享库的重定位就化为对GOT表项的重定位。GOT表的第1个指针指向.dynamic段,第2、3个指针与plt段的第1个槽位对应,用来安装动态解析器。为了少做无用功,Linux采用了动态解析技术,就是说在加载共享库时,并不进行函数的解析,而是安装动态解析器,让共享库调用指向解析器,只有当函数调用发生时才进行解析。为此,在ld在生成可执行程序时,让其GOT共享函数指针指向各自plt槽位上的两条指令,一条是pushl指令,将该函数所对应GOT重定位表的索引作为参数压入堆栈,然后通过另一条jmp指令跳转到plt槽位1,它再跳转到GOT表第3个指针所表示的动态解析器入口。这样当发生并成功解析目标函数在共享库中的地址时,该函数在程序GOT表中的指针就被实际的地址刷新。
下面是基于动态解释器ld.so-1.9.9版本的简单分析
简单的测试文件testso.c:
int x = 0;
int test() { return x; }
用gcc -S -fPIC testso.c编绎成的汇编代码:
.globl x .data .align 4 .type x,@object .size x,4 x: .long 0 .text .align 4 .globl test .type test,@function test: pushl %ebp movl %esp,%ebp pushl %ebx call .L2 .L2: popl %ebx # 取标号.L2所在的地址 addl $_GLOBAL_OFFSET_TABLE_+[.-.L2],%ebx # _GLOBAL_OFFSET_TABLE_为当前地址到GOT表的偏移 movl x@GOT(%ebx),%eax # ebx在-fPIC编绎的函数中用于指向本模块的GOT表 movl (%eax),%eax # x@GOT表示符号x在GOT表中的索引 movl -4(%ebp),%ebx leave ret .Lfe1: .size test,.Lfe1-test
用gcc -shared testso.s -o testso.so生成共享库的反汇编的有关输出:
Disassembly of section .plt:
00000258 <.plt>: 258: ff b3 04 00 00 pushl 0x4(%ebx) # GOT表的第2个指针,对-fPIC编绎的函数,ebx总是指向GOT表 25d: 00 25e: ff a3 08 00 00 jmp *0x8(%ebx) # 跳转到GOT表的第3个指针,调用__dl_linux_resolover 263: 00 264: 00 00 addb %al,(%eax) 266: 00 00 addb %al,(%eax) 268: ff a3 0c 00 00 jmp *0xc(%ebx) # 跳转到共享函数test()所在的GOT的指针 26d: 00 26e: 68 00 00 00 00 pushl $0x0 # test()所在GOT指针的初始入口 273: e9 e0 ff ff ff jmp 258 <_init+0x8> # 跳转到plt槽位1
Disassembly of section .text:
000002d8 : 2d8: 55 pushl %ebp 2d9: 89 e5 movl %esp,%ebp 2db: 53 pushl %ebx 2dc: e8 00 00 00 00 call 2e1 2e1: 5b popl %ebx 2e2: 81 c3 ab 10 00 addl $0x10ab,%ebx # 取GOT表指针 2e7: 00 2e8: 8b 83 10 00 00 movl 0x10(%ebx),%eax # 从GOT表中取变量x的地址 2ed: 00 2ee: 8b 00 movl (%eax),%eax 2f0: 8b 5d fc movl 0xfffffffc(%ebp),%ebx 2f3: c9 leave 2f4: c3 ret
引用testso的应用程序文件test.c: main() { printf("%d\n",test()); } 用gcc test.c testso.so -o test生成可执行文件的反汇编输出:
Disassembly of section .plt:
08048398 <.plt>: 8048398: ff 35 54 95 04 pushl 0x8049554 # GOT表的第2个指针 804839d: 08 804839e: ff 25 58 95 04 jmp *0x8049558 # GOT表的第3个指针,运行_dl_linux_resolver 80483a3: 08 80483a4: 00 00 addb %al,(%eax) 80483a6: 00 00 addb %al,(%eax) 80483a8: ff 25 5c 95 04 jmp *0x804955c # printf()在plt的入口 80483ad: 08 80483ae: 68 00 00 00 00 pushl $0x0 80483b3: e9 e0 ff ff ff jmp 8048398 <_init+0x8> 80483b8: ff 25 60 95 04 jmp *0x8049560 80483bd: 08 80483be: 68 08 00 00 00 pushl $0x8 80483c3: e9 d0 ff ff ff jmp 8048398 <_init+0x8> 80483c8: ff 25 64 95 04 jmp *0x8049564 # test()在plt段的调用点 80483cd: 08 # [0x8048564]初始时指向0x80483ce 80483ce: 68 10 00 00 00 pushl $0x10 # test()在GOT表重定位表.rel.got中的索引 80483d3: e9 c0 ff ff ff jmp 8048398 <_init+0x8> # 跳转到plt的第1槽位 80483d8: ff 25 68 95 04 jmp *0x8049568 80483dd: 08 80483de: 68 18 00 00 00 pushl $0x18 80483e3: e9 b0 ff ff ff jmp 8048398 <_init+0x8> 80483e8: ff 25 6c 95 04 jmp *0x804956c 80483ed: 08 80483ee: 68 20 00 00 00 pushl $0x20 80483f3: e9 a0 ff ff ff jmp 8048398 <_init+0x8> 80483f8: ff 25 70 95 04 jmp *0x8049570 80483fd: 08 80483fe: 68 28 00 00 00 pushl $0x28 8048403: e9 90 ff ff ff jmp 8048398 <_init+0x8>
Disassembly of section .text:
080484c8 : 80484c8: 55 pushl %ebp 80484c9: 89 e5 movl %esp,%ebp 80484cb: e8 f8 fe ff ff call 80483c8 <_init+0x38> # 80484d0: 89 c0 movl %eax,%eax 80484d2: 50 pushl %eax 80484d3: 68 38 85 04 08 pushl $0x8048538 80484d8: e8 cb fe ff ff call 80483a8 <_init+0x18> 80484dd: 83 c4 08 addl $0x8,%esp 80484e0: c9 leave 80484e1: c3 ret
ld.so-1.9.9/d-link/i386/resolve.S
#define ALIGN 4 #define RUN linux_run #define RESOLVE _dl_linux_resolve #define RESOLVER _dl_linux_resolver #define EXIT _interpreter_exit #define INIT __loader_bootstrap
.text .align ALIGN .align 16
.globl RESOLVE .type RESOLVE,@function RESOLVE: pusha lea 0x20(%esp),%eax /* eax = tpnt and reloc_entry params */ pushl 4(%eax) /* push copy of reloc_entry param */ pushl (%eax) /* push copy of tpnt param */ pushl %eax /* _dl_linux_resolver expects a dummy * param - this could be removed */ #ifdef __PIC__ call .L24 .L24: popl %ebx addl $_GLOBAL_OFFSET_TABLE_+[.-.L24],%ebx movl RESOLVER@GOT(%ebx),%ebx /* eax = resolved func */ call *%ebx #else call RESOLVER #endif movl %eax,0x2C(%esp) /* store func addr over original * tpnt param */ addl $0xC,%esp /* remove copy parameters */ popa /* restore regs */ ret $4 /* jump to func removing original * reloc_entry param from stack */ .LFE2: .size RESOLVE,.LFE2-RESOLVE
d-link/i386/elfinterp.c:
unsigned int _dl_linux_resolver(int dummy, int i) { unsigned int * sp; int reloc_entry; int reloc_type; struct elf32_rel * this_reloc; char * strtab; struct elf32_sym * symtab; struct elf32_rel * rel_addr; struct elf_resolve * tpnt; int symtab_index; char * new_addr; char ** got_addr; unsigned int instr_addr; sp = &i; reloc_entry = sp[1]; tpnt = (struct elf_resolve *) sp[0];
rel_addr = (struct elf32_rel *) (tpnt->dynamic_info[DT_JMPREL] + tpnt->loadaddr); 取可执行程序的GOT重定位表
this_reloc = rel_addr + (reloc_entry >> 3); reloc_type = ELF32_R_TYPE(this_reloc->r_info); symtab_index = ELF32_R_SYM(this_reloc->r_info);
symtab = (struct elf32_sym *) (tpnt->dynamic_info[DT_SYMTAB] + tpnt->loadaddr); strtab = (char *) (tpnt->dynamic_info[DT_STRTAB] + tpnt->loadaddr);
if (reloc_type != R_386_JMP_SLOT) { _dl_fdprintf(2, "%s: Incorrect relocation type in jump relocations\n", _dl_progname); _dl_exit(1); };
/* Address of jump instruction to fix up */ instr_addr = ((int)this_reloc->r_offset + (int)tpnt->loadaddr); got_addr = (char **) instr_addr;
#ifdef DEBUG _dl_fdprintf(2, "Resolving symbol %s\n", strtab + symtab[symtab_index].st_name); #endif
/* Get the address of the GOT entry */ new_addr = _dl_find_hash(strtab + symtab[symtab_index].st_name, tpnt->symbol_scope, (int) got_addr, tpnt, 0); if(!new_addr) { _dl_fdprintf(2, "%s: can‘t resolve symbol ‘%s‘\n", _dl_progname, strtab + symtab[symtab_index].st_name); _dl_exit(1); }; /* #define DEBUG_LIBRARY */ #ifdef DEBUG_LIBRARY if((unsigned int) got_addr < 0x40000000) { _dl_fdprintf(2, "Calling library function: %s\n", strtab + symtab[symtab_index].st_name); } else { *got_addr = new_addr; } #else *got_addr = new_addr; 更新GOT函数指针 #endif return (unsigned int) new_addr; }
|