Tchaikov’s Journal

July 24, 2006

CS:APP 你还记得多少?

Filed under: Programming

CS:APP是上个学期读的。花了一个学期的功夫看完了。但是我刚才发现我把它给忘了!

昨天在看巴别塔上的雇工上关于 GNU/Linux 系统上关于运行时链接的一篇How PLT Works。觉得挺对胃口,操起 Emacs 比划了两下。突然有似曾相识的感觉。咦?CS:APP!从书堆里翻出它一看,原来都在里面呢。唉……

不过在自己操练的时候,有一点要注意的。先记下来:在用 gdb 调试 shared object 的时候,为了让 gdb 能不出类似 “Error while mapping shared library sections: libtest.so: sucess” 的错,最好能:

export LD_LIBRARY_PATH=/path/to/so-files:$LD_LIBRARY_PATH

而且新版的 gcc 产生的代码和文中 Red Hat 所带 gcc 产生的代码还颇不同,主要体现在 foo():

(gdb) disass
Dump of assembler code for function foo:
0xa7fe3510 :     push   %ebp
0xa7fe3511 :     mov    %esp,%ebp
0xa7fe3513 :     push   %ebx
0xa7fe3514 :     call   0xa7fe3505 <__i686 .get_pc_thunk.bx>
0xa7fe3519 :     add    $0x1187,%ebx
0xa7fe351f :    sub    $0x14,%esp
0xa7fe3522 :    mov    0xfffffff0(%ebx),%eax
0xa7fe3528 :    mov    (%eax),%eax
0xa7fe352a :    mov    %eax,0x4(%esp)
0xa7fe352e :    lea    0xffffef0e(%ebx),%eax
0xa7fe3534 :    mov    %eax,(%esp)
0xa7fe3537 :    call   0xa7fe340c

0xa7fe353c :    add    $0x14,%esp
0xa7fe353f :    xor    %eax,%eax
0xa7fe3541 :    pop    %ebx
0xa7fe3542 :    pop    %ebp
0xa7fe3543 :    ret
0xa7fe3544 :    nop

__i686.get_pc_thunk.bx是干什么的?

(gdb) disass 0xa7fe3505
Dump of assembler code for function __i686.get_pc_thunk.bx:
0xa7fe3505 <__i686 .get_pc_thunk.bx+0>:  mov    (%esp),%ebx
0xa7fe3508 <__i686 .get_pc_thunk.bx+3>:  ret
0xa7fe3509 <__i686 .get_pc_thunk.bx+4>:  nop

原来上面的函数功能相当简单,就是利用call命令的副作用,把下一个指令的地址压栈,再从栈顶取出,放到%ebx里。接着加上 0x1187 的偏移量,算出 GOT 的位置。下面就是例行公事一般地给 printf 分配 0x14 的栈空间。再根据 xyz 的地址在 GOT 上的偏移量(-0xF)和"OK %d\n"字符串的地址在 GOT 上的偏移量(-0x10F1),分别算出 xyz 的值和格式化字符串的地址,先后压栈。调用 0xa7fe340c。细心的同学可能注意到了,printf@plt。说明 printf 也是在 PLT 上的。的确,printf 是 libc 的一员,a.out 动态地调用它,这样可以省一些存储空间,这和调用 foo 用的方法同出一辙。。

通过这个手段给 %ebx 赋值完毕以后,我们检查一下两个参数:

(gdb) x *($ebx+0xfffffff0)
0x8049710 :        0x00000004
(gdb) x/s $ebx+0xffffef0e
0xa7fe35ae <_fini +26>:   “OK %d\n”

在 PLT 的 foo 表项中,

(gdb) disass
Dump of assembler code for function foo@plt:
0x080483ac : jmp    *0x8049700
0x080483b2 : push   $0x10
0x080483b7 :        jmp    0x804837c <_init +24>
End of assembler dump.

和文中导致段错误的 disass 不同,在我这里根本就无法反汇编到 0x804837c 地址的代码:

(gdb) disass 0x804837c
No function contains specified address.

难免有遗珠之憾。

本以为不必专门写一个 foo() 函数的,只要 printf 两次就可以探究 GOT 和 PLT 的奥秘。因为 printf 本身就是在 PLT 上的。但是,当我第二次进入 printf 的时候发现,它进入的 printf 实现代码里,没有看到 PIC 函数(即 printf 自己)引用外部数据的代码(即__i686.get_pc_thunk.bx)。另外,在调用 printf 时不能一次加“/n”,另一次不加。因为 gcc 会把加上“\n”的 printf 翻译成 puts@plt,把不加的翻译成真正的 printf@plt,否则两次调用都会写 GOT 中的相应表项。

2 Comments »

The URI to TrackBack this entry is: http://tchaikov.blogsome.com/2006/07/24/p47/trackback/

  1. 你编译的时候加-g选项了吗?
    不大理解为什么新版本需要__i686.get_pc_thunk.bx,难道为了觉得获得eip值得调用可能很频繁,所以干脆独立出来减少产生的code的size?

    Comment by Morgan — July 27, 2006 @ 10:57 am

  2. 我加 -g 和不加 -g 都试过,反汇编的结果是一样的。我在 koders 上找到的这个文件 http://www.koders.com/c/fidBCA780D03B02A33E8C9EDC8936D79951760DDE77.aspx
    同时包含了两种 PIC INIT 方式,显然后者是 deprecated 的。
    这里 http://www.gentoo.org/proj/en/hardened/pic-fix-guide.xml 也说了零星 __i686.get_pc_thunk.bx 的知识,只说它是标准的获得 GOT 的办法,至于用 call-mov-ret 而不用 call-pop ,也没有说明。
    另外,我看到 http://gcc.gnu.org/ml/gcc-prs/2003-05/msg02140.html 中提到,GCC 3.4 (时间是2003年,而你的 gdb 的编译时间是2002年)用 -O3 -fPIC 生成的代码里面也有 __i686.get_pc_thunk.bx。我觉得你不妨用 -Os 来编译代码试试看,看看那时的GCC 会不会因为要减小程序大小而使用 __i686.get_pc_thunk.bx。

    Comment by Administrator — July 27, 2006 @ 5:55 pm

RSS feed for comments on this post.

Leave a comment

Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>


Get free blog up and running in minutes with Blogsome
Theme designed by Jay of onefinejay.com