LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 4101|回复: 10

从函数调用到符号解析

[复制链接]
发表于 2005-8-25 15:44:50 | 显示全部楼层 |阅读模式
这篇文章以一个具体的例子讲述了动态连接的可执行程序里的函数的symbol resolving的过程是怎样的。最好对动态连接的概念先有点基本的认识。有一些基础的概念我没有讲,可以看看后面参考资料里的书和文章。

一个简单的小程序:hello.c
  1. #include<stdio.h>
  2. int main(){
  3.         puts("hello");
  4. }
复制代码

编译:
  1. $ gcc hello.c -o hello -mpreferred-stack-boundary=2
复制代码

反汇编plt段:
  1. $ objdump -d -j .plt hello
  2. hello:     file format elf32-i386

  3. Disassembly of section .plt:

  4. 08048288 <puts@plt-0x10>:
  5. 8048288:       ff 35 94 95 04 08       pushl  0x8049594
  6. 804828e:       ff 25 98 95 04 08       jmp    *0x8049598
  7. 8048294:       00 00                   add    %al,(%eax)
  8.         ...

  9. 08048298 <puts@plt>:
  10. 8048298:       ff 25 9c 95 04 08       jmp    *0x804959c
  11. 804829e:       68 00 00 00 00          push   $0x0
  12. 80482a3:       e9 e0 ff ff ff          jmp    8048288 <_init+0x18>
  13. ...
复制代码

gdb调试hello:
  1. $ gdb -q hello
  2. Using host libthread_db library "/lib/libthread_db.so.1".
  3. (gdb) disass main
  4. Dump of assembler code for function main:
  5. 0x08048384 <main+0>:    push   %ebp
  6. 0x08048385 <main+1>:    mov    %esp,%ebp
  7. 0x08048387 <main+3>:    sub    $0x4,%esp
  8. 0x0804838a <main+6>:    movl   $0x80484a4,(%esp)
  9. 0x08048391 <main+13>:   call   0x8048298 <_init+40>
  10. 0x08048396 <main+18>:   leave
  11. 0x08048397 <main+19>:   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()要用的参数。
  1. struct link_map {
  2.     ElfW(Addr) l_addr;  
  3.     char *l_name;
  4.     ElfW(Dyn) *l_ld;
  5.     struct link_map *l_next, *l_prev;
  6. };
复制代码

然后跳到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
  1. typedef struct
  2. {
  3.   Elf32_Addr    r_offset;   
  4.   Elf32_Word   r_info;  
  5. } Elf32_Rel;
复制代码

JMPREL+0就是里面第一个entry
r_offset的值就是GOT里面我们要改写的项,对于本例的puts来说就是第四项,就是 0x0804959c。来看一下
得到JMPREL的地址:
  1. $ readelf -d hello
  2. Dynamic section at offset 0x4c4 contains 20 entries:
  3.   Tag        Type                         Name/Value
  4. 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
  5. 0x0000000c (INIT)                       0x8048270
  6. 0x0000000d (FINI)                       0x8048480
  7. 0x00000004 (HASH)                       0x8048168
  8. 0x00000005 (STRTAB)                     0x80481e0
  9. 0x00000006 (SYMTAB)                     0x8048190
  10. 0x0000000a (STRSZ)                      74 (bytes)
  11. 0x0000000b (SYMENT)                     16 (bytes)
  12. 0x00000015 (DEBUG)                      0x0
  13. 0x00000003 (PLTGOT)                     0x8049590
  14. 0x00000002 (PLTRELSZ)                   16 (bytes)
  15. 0x00000014 (PLTREL)                     REL
  16. 0x00000017 (JMPREL)                     0x804825c
复制代码

回到gdb(所以你最好开两个terminal):
  1. (gdb) x/2x 0x0804825c
  2. 0x804825c:      0x0804959c      0x00000107
复制代码

0x0804959c,是不是和前面反汇编puts@plt的第一句间接跳转的地址一样?:-D
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
 楼主| 发表于 2005-10-17 17:03:57 | 显示全部楼层
之前有个重要的内存地址写错了
已经改正

这贴是我自己用来备忘的
但愿能对别人有用
回复 支持 反对

使用道具 举报

发表于 2005-10-17 18:25:20 | 显示全部楼层
先收入精华了 ^_^
回复 支持 反对

使用道具 举报

发表于 2005-10-17 21:02:20 | 显示全部楼层
看标题比较有意思
先占个位,有空再来看吧
回复 支持 反对

使用道具 举报

发表于 2005-10-17 22:14:09 | 显示全部楼层
来学习,来学习
回复 支持 反对

使用道具 举报

发表于 2005-10-17 22:19:08 | 显示全部楼层
在补充一句,我看不懂
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-10-18 01:32:39 | 显示全部楼层
有兴趣又有时间的话
可以看我后面附的资料
先看linkers and loaders的相关章节
然后看看4和5两篇文章

我的这篇没有理论
只是对理论的验证过程
回复 支持 反对

使用道具 举报

发表于 2005-10-18 09:10:41 | 显示全部楼层
zhllg 觉得直接看 ld 的源代码如何?我手里有早期 UNIX 和它上面的程序的代码,虽然比起现在的程序来说简陋得多,但毕竟可以从全局上理解操作系统的原理和编译器的一部分原理。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-10-18 09:46:03 | 显示全部楼层
不如一步到位了
早期,不知道有多早?
如果还没有用ELF格式,那还是不要看了
回复 支持 反对

使用道具 举报

发表于 2005-10-18 12:59:30 | 显示全部楼层
呵呵,格式应该还是 a.out 吧
就是这里:

http://minnie.tuhs.org/UnixTree/

那些代码中有一种美,是我在现在的许多代码中找不到的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表