Chinaunix首页 | 论坛 | 博客
  • 博客访问: 334495
  • 博文数量: 107
  • 博客积分: 2825
  • 博客等级: 少校
  • 技术积分: 795
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-19 12:19
文章分类

全部博文(107)

文章存档

2013年(2)

2012年(31)

2011年(18)

2010年(12)

2009年(44)

我的朋友

分类: LINUX

2009-10-03 20:05:30

这篇文章以一个具体的例子讲述了动态连接的可执行程序里的函数的symbol resolving的过程是怎样的。最好对动态连接的概念先有点基本的认识。有一些基础的概念我没有讲,可以看看后面参考资料里的书和文章。

一个简单的小程序:hello.c
代码:
#include int main(){ puts("hello"); }
编译:
代码:
$ gcc hello.c -o hello -mpreferred-stack-boundary=2
反汇编plt段:
代码:
$ objdump -d -j .plt hello hello: file format elf32-i386 Disassembly of section .plt: 08048288 : 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 : 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调试hello:
代码:
$ gdb -q hello Using host libthread_db library "/lib/libthread_db.so.1". (gdb) disass main Dump of assembler code for function main: 0x08048384 : push %ebp 0x08048385 : mov %esp,%ebp 0x08048387 : sub $0x4,%esp 0x0804838a : movl $0x80484a4,(%esp) 0x08048391 : call 0x8048298 <_init+40> 0x08048396 : leave 0x08048397 : ret
注意看一下, call 0x08048298,这个地址就是puts@plt
然后下面就是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; };
然后跳到GOT第三项保存的地址上去执行,这个地址就是符号解析函数的入口。在glibc源代码的这个文件sysdeps/i386/dl- machine.h有定义,形式是一个macro,名字叫ELF_MACHINE_RUNTIME_TRAMPOLINE。在这个函数里,会调用 fixup(),这个才是真正做事情的函数。fixup会通过那个struct link_map指针遍历所有的so来找puts。是通过so里的hash table来找的,所以速度会比较快。找到以后通过JMPREL+0这个地址来保存。JMPREL里放置的是和PLT相关的relocation entry
代码:
typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel;
JMPREL+0就是里面第一个entry
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(所以你最好开两个terminal):
代码:
(gdb) x/2x 0x0804825c 0x804825c: 0x0804959c 0x00000107
0x0804959c,是不是和前面反汇编puts@plt的第一句间接跳转的地址一样?:-D
fixup在解析完之后就会把puts的真实地址放到0x0804959c上去
以后就不必在解析了

参考资料:

1.glibc 2.3.5 src
2.<>
3.<>
4.<> write by the grugq
5.<> by alert7
阅读(537) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~