2009年4月17日星期五

Linux 下如何调用动态链接库里的函数

见如下例子,我们调用了 QObject::connect() 方法,它有四个参数,
QObject::connect( &a, SIGNAL( valueChanged(int) ),
&b, SLOT( setValue(int) ) ) ;
编译出来调用的汇编代码如下,
0x080490dd <main+189>:    movl   $0x0,0x10(%esp)
0x080490e5 <main+197>: movl $0x8049545,0xc(%esp)
0x080490ed <main+205>: mov %esi,0x8(%esp)
0x080490f1 <main+209>: movl $0x8049554,0x4(%esp)
0x080490f9 <main+217>: mov %edi,(%esp)
0x080490fc <main+220>: call 0x8048db4 <_ZN7QObject7connectEPKS_PKcS1_S3_N2Qt14ConnectionTypeE@plt>
可见前面通过 movl 将四个参数入栈(%esp 到 %esp+0xc),两个寄存器里面的参数其实是 const char*(因为两个 macro 是转换为字符串的操作),然后调用了一个位于动态链接库里面的 QObject::connect(),可以看见编译器发现该函数不在 source 里面,采用的方式是做了一个 plt 版本的 connect() 函数(在目标代码里面),我们看看该函数的内容,
0x08048db4 <_ZN7QObject7connectEPKS_PKcS1_S3_N2Qt14ConnectionTypeE@plt+0>:    jmp    *0x804a9d4
0x08048dba <_ZN7QObject7connectEPKS_PKcS1_S3_N2Qt14ConnectionTypeE@plt+6>: push $0x80
0x08048dbf <_ZN7QObject7connectEPKS_PKcS1_S3_N2Qt14ConnectionTypeE@plt+11>: jmp 0x8048ca4 <_init+48>
注意到首先 jump 到 0x804a9d4 地址存放的地址,注意这个地址这时指向 jmp 指令下一行,
0x804a9d4 <_GLOBAL_OFFSET_TABLE_+76>:    0x08048dba
这是一个 got 里面的值,换言之是编译器为全局变量等存放的一个 table。之后 push 0x80 作为该函数(connect 的索引),然后 jmp 到
 8048ca4:       ff 35 8c a9 04 08       pushl  0x804a98c
8048caa: ff 25 90 a9 04 08 jmp *0x804a990
首先 push 入栈的是 0x804a98c,然后 jmp,这还是 got 里面的地址,
0x804a990 <_GLOBAL_OFFSET_TABLE_+8>:    0xb7ff5970
可见这部分即将离开用户编译部份代码,进入 dynamically linked library 部分,这个就是传说中动态链接器开始干活的地方,
0xb7ff5970 in ?? () from /lib/ld-linux.so.2
从这里出来之后我们可以看见 got 里面的值被 dynamic linker 修改了,
0x804a9d4 <_GLOBAL_OFFSET_TABLE_+76>:    0xb74fa940
这应该是根据我们入栈的几个地址,如 0x80 是函数 connect() 的索引,然后 0x804a98c 是对应 got 一个地址(内容似乎指向 ld-linux.so)ld-linux.so 根据调用的偏移找到 symbol,通过 symbol 在别的 so 里面寻找对应 symbol,这样就查询到 QOject::connect() 入口地址写入 got 中 0x804a9d4 对应的地方,然后调用该函数,从 ld-linux.so 出来后,我们可以看见
0x804a9d4 <_GLOBAL_OFFSET_TABLE_+76>:    0xb74fa940
而这里 disassemble 就看见
Dump of assembler code for function _ZN7QObject7connectEPKS_PKcS1_S3_N2Qt14ConnectionTypeE:
0xb74fa940 <_ZN7QObject7connectEPKS_PKcS1_S3_N2Qt14ConnectionTypeE+0>: push %ebp
0xb74fa941 <_ZN7QObject7connectEPKS_PKcS1_S3_N2Qt14ConnectionTypeE+1>: mov %esp,%ebp
说明已经通过动态链接使得下一次 jmp *0x804a9d4 直接变成调用该函数了。

这就是大致的过程,以后有时间研究下 ld-linux-so 究竟如何利用上面的信息进行的动态链接。