Chinaunix首页 | 论坛 | 博客
  • 博客访问: 185173
  • 博文数量: 20
  • 博客积分: 543
  • 博客等级: 下士
  • 技术积分: 411
  • 用 户 组: 普通用户
  • 注册时间: 2009-06-06 20:38
文章存档

2012年(12)

2009年(8)

分类: LINUX

2012-07-14 15:43:02

在一个最简单的hello world程序中,printf是一个属于libc库的动态库函数。在编译时是不知道printf所在地址的,那么,在程序运行的时候是怎么找到printf所在地址并且跳到那个地址去执行的呢?本文将以printf为例,用GDB跟踪动态库函数的查找过程。汇编以MIPS为例, 以后有时间再分析ARM版本的。

首先给出程序代码:

  1. #include <stdio.h>

  2. int main(int argc, char* argv[])
  3. {
  4.     printf("Hello world %d.\n", argc);
  5.     return 0;
  6. }
用以下命令编译得到可执行程序,并DUMP其汇编指令:

  1. mipsel-linux-gnu-gcc main.c -o main
  2. mipsel-linux-gnu-objdump -xD main > main.dump
一. 汇编代码静态分析

在得到的main.dump文件中, 找到main函数所在的地方如下:

  1. 004005b0
    :
  2. 4005b0: 27bdffe8 addiu sp,sp,-24
  3. 4005b4: afbf0014 sw ra,20(sp)
  4. 4005b8: afbe0010 sw s8,16(sp)
  5. 4005bc: 03a0f021 move s8,sp
  6. 4005c0: afc40018 sw a0,24(s8)
  7. 4005c4: afc5001c sw a1,28(s8)
  8. 4005c8: 3c020040 lui v0,0x40
  9. 4005cc: 24440760 addiu a0,v0,1888
  10. 4005d0: 8fc50018 lw a1,24(s8)
  11. 4005d4: 3c020040 lui v0,0x40
  12. 4005d8: 24590470 addiu t9,v0,1136
  13. 4005dc: 0320f809 jalr t9
  14. 4005e0: 00000000 nop
  15. 4005e4: 00001021 move v0,zero
  16. 4005e8: 03c0e821 move sp,s8
  17. 4005ec: 8fbf0014 lw ra,20(sp)
  18. 4005f0: 8fbe0010 lw s8,16(sp)
  19. 4005f4: 27bd0018 addiu sp,sp,24
  20. 4005f8: 03e00008 jr ra
  21. 4005fc: 00000000 nop
蓝色的三行是调用printf的汇编。注意这里的lui指令,并不是吧0x40放在v0寄存器里,而是把0x40左移16位再放进v0寄存器,也就是说v0=0x400000。
1136换成十六进制是0x470,那么t9的值就是0x400470,那么jalr t9就是跳转到0x400470的位置。这个位置所在处就是printf@plt.

  1. 00400470 :
  2. 400470: 3c0f0041 lui t7,0x41
  3. 400474: 8df907c0 lw t9,1984(t7)
  4. 400478: 25f807c0 addiu t8,t7,1984
  5. 40047c: 03200008 jr t9
  6. 400480: 00000000 nop
这里的流程是:t7=0x410000, t9=*(0x4107c0), t8=0x4107c0, jr t9
也就是寄存器t8的值是0x4107c0, 而t9的值是内存地址0x4107c0处存放的值。然后跳转到t9指示的地址处。所以,这里的关键就是内存地址0x4107c0处存放的值到底是什么。

  1. Disassembly of section .got.plt:
  2. 004107b4 <.got.plt>:
  3. ...
  4. 4107bc: 00400440 0x400440
  5. 4107c0: 00400440 0x400440
可见,地址0x4107c0位于.got.plt段,存放的值为0x400440. 也就是说刚才程序的执行流程会跳转到0x400440地址处,这正是.plt段的位置。

  1. Disassembly of section .plt:
  2. 00400440 <_PROCEDURE_LINKAGE_TABLE_>:
  3. 400440: 3c1c0041 lui gp,0x41
  4. 400444: 8f9907b4 lw t9,1972(gp)
  5. 400448: 279c07b4 addiu gp,gp,1972
  6. 40044c: 031cc023 subu t8,t8,gp
  7. 400450: 03e07821 move t7,ra
  8. 400454: 0018c082 srl t8,t8,0x2
  9. 400458: 0320f809 jalr t9
  10. 40045c: 2718fffe addiu t8,t8,-2
  11. 00400460 <__libc_start_main@plt>:
  12. 400460: 3c0f0041 lui t7,0x41
  13. 400464: 8df907bc lw t9,1980(t7)
  14. 400468: 25f807bc addiu t8,t7,1980
  15. 40046c: 03200008 jr t9
先看蓝色部分,t9=*(0x4107b4),其实就是以0x4107b4处存放的值做目的地址去跳转。而从上面.got.plt段的汇编看,0x4107b4处是省略号,内容未知,怎么回事?
用mipsel-linux-gnu-objdump –xDsStT main > main.dump 可以看到以下内容:

  1. Contents of section .got.plt:
  2. 4107b4 00000000 00000000 40044000 40044000 ........@.@.@.@.
所以0x4107b4处的值应该是0. 难道Jalr跳到零地址吗?

二. 汇编代码运行时跟踪

我们在main函数处设置断点,发现在运行到main函数时,0x4107b4地址处的值已经不是0x0了,而被修改成了0x4081718c。 从下图中可以看出,加载完后.got.plt段的内容为:


  1. 004107b4 <.got.plt>:
  2. 4107b4: 4081718c 0x4081718c...
  3. 4107b8: 40830a18 0x40830a18
  4. 4107bc: 40857830 0x40857830
  5. 4107c0: 00400440 0x400440
前三个word都变了,最后一个没变。(在运行完printf后,最后一个变成了0x4088ce90.)
TODO:搞清楚这四个word各自的含义。
所以,jalr t9不是跳到零地址,而是跳到了0x4081718c。从图中可知,这是ld.so.1被map后的_dl_runtime_pltresolve() 函数的地址。
NOTES:_dl_runtime_pltresolve在ld.so.1内的偏移量是0x1618c, 而ld.so.1被map的基地址为0x40801840,两者相加 等于0x408179cc, 并不等于0x4081718c啊,有0x840的偏差,怎么回事?看ld.so.1的反汇编,第六行有写”start address 0x00000840”, 而且.text段的起始地址为0x840, 是不是map的时候是从.text开始map的,map的基地址对应于.text的起始地址?)

可以猜想,_dl_runtime_pltresolve函数的作用就是查找printf的地址(具体怎么查找的以后再关心)。单步运行发现,它调用了_dl_fixup函数修改0x4107c0处的值为printf的地址,如下图所示,0x417c0处的值在 _dl_runtime_pltresolve+56即jalr t9返回后被修改成了0x4088ce90,这正是printf的地址。


这里的jalr t9 就是调用_dl_fixup函数。
在_dl_runtime_pltresolve的末尾跳到printf执行。Printf执行完后回到main函数。
TODO: 阅读libc源代码搞清楚这两个函数的实现。
TODO: 跳转过程中的堆栈处理,printf执行完怎么跳回main的?
由于0x4107c0处已经改成了printf的地址,如果程序中下一次再调用到printf, 就会跳到printf@plt处,在这里直接从0x4107c0读出printf的地址然后完成跳转。不需要再运行一次_dl_runtime_pltresolve了。

灰色部分:gp=0x4107b4, t8=t8-gp

刚才t8里的值是0x4107c0,这里的t8=t8-gp等于12,即是0x4107c0相对于.got.plt起始处的偏移量。


阅读(3849) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~