前一段时间项目上碰到一个诡异的问题,调用一个函数的时候,总是发生crash。调查了一下,发现原来是跟符号解析及重定位有关。
有两个shared lib A和B被一个进程链接,且这两个lib包含同一个函数f的定义,导致本该调用A lib中的f函数,结果调用了B中定义的f函数,最终导致进程crash。需要说明的是,A是在运行时用dlopen打开,而B是在进程加载时就链接进来了(用"$ldd prog_name"可以查看到)。
问题:一个可执行目标文件(Executable object file)foo调用外部共享目标文件(shared object file,即so文件)里符号(函数/全局变量)。但是有两个so文件,其中libtf.so作为加载时链接(可用"$ldd prog_name"查看到),另一个libtf2.so是在运行时链接,即代码用dlopen系列函数打开调用。这两个so定义了同样的符号,那么foo中对一个符号的引用实际上会指向哪一个呢?
写了一个例子来演示问题:
源文件:1. test_main.c
- #include <stdio.h>
-
#include <stdlib.h>
-
#include <dlfcn.h>
-
-
int
-
main(void)
-
{
-
void *handle;
-
char *error;
-
int (*tf_ptr)();
-
-
handle = dlopen("/home/test/test/elf/libtf2.so", RTLD_LAZY);
-
if (!handle) {
-
fprintf (stderr, "%s\n", dlerror());
-
exit(1);
-
}
-
-
dlerror(); /* Clear any existing error */
-
-
tf_ptr = dlsym(handle, "caller_func");
-
if ((error = dlerror()) != NULL) {
-
fprintf (stderr, "%s\n", error);
-
exit(1);
-
}
-
-
tf_ptr();
-
-
dlclose(handle);
-
return 0;
-
}
2. test_func.c (将被编译成so,并在加载时动态链接)
- #include <stdio.h>
-
-
int i = 1;
-
-
int callee_func()
-
{
-
printf("called by test_func in %s\n", __FILE__);
-
return 0;
-
}
-
-
int caller_func()
-
{
-
printf("this is %s, i = %d\n", __FILE__, i);
-
callee_func();
-
return 0;
-
}
3. test_func2.c (基本同test_func.c, 也会被编译成so,但在运行时动态链接)
- #include <stdio.h>
-
-
int i = 2;
-
-
int callee_func()
-
{
-
printf("called by test_func in %s\n", __FILE__);
-
return 0;
-
}
-
-
int caller_func()
-
{
-
printf("this is %s, i = %d\n", __FILE__, i);
-
callee_func();
-
return 0;
-
}
4. Makfile
- .PHONY: foo libtf2.so libtf.so
-
foo: test_main.c libtf2.so libtf.so
- gcc -o foo test_main.c -L. -ltf -ldl -g -Wall
-
-
libtf2.so: test_func2.c
-
gcc -shared -fPIC -o $@ $< -g
-
-
libtf.so: test_func.c
-
gcc -shared -fPIC -o $@ $< -g
编译并运行- [root@Fedora10 elf]# ./foo
-
this is test_func2.c, i = 1
-
called by test_func in test_func.c
从结果来看,引用实际上都指向了libtf.so中的定义,即加载时链接的so。
调试在main处加上断点,此时还没执行到dlopen
- (gdb) b main
-
Breakpoint 1 at 0x80485f5: file test_main.c, line 12.
-
(gdb) r
-
Starting program: /home/test/test/elf/foo
-
-
Breakpoint 1, main () at test_main.c:12
-
12 handle = dlopen("/home/test/test/elf/libtf2.so", RTLD_LAZY);
-
Missing separate debuginfos, use: debuginfo-install glibc-2.9-2.i686
-
(gdb) p callee_func
-
$1 = {int ()} 0x11147c
-
(gdb) p &i
-
$2 = (int *) 0x112698
-
(gdb)
于此同时查看foo进程的内存使用情况,可见两个被引用符号的地址位于libtf.so的地址范围内:
- [root@Fedora10 elf]# cat /proc/`pgrep foo`/maps
00110000-00111000 r-xp 00110000 00:00 0 [vdso]
00111000-00112000 r-xp 00000000 fd:00 2942082 /home/test/test/elf/libtf.so
00112000-00113000 rw-p 00000000 fd:00 2942082 /home/test/test/elf/libtf.so
0067a000-0069a000 r-xp 00000000 fd:00 264420 /lib/ld-2.9.so
0069b000-0069c000 r--p 00020000 fd:00 264420 /lib/ld-2.9.so
0069c000-0069d000 rw-p 00021000 fd:00 264420 /lib/ld-2.9.so
0069f000-0080d000 r-xp 00000000 fd:00 264446 /lib/libc-2.9.so
0080d000-0080f000 r--p 0016e000 fd:00 264446 /lib/libc-2.9.so
0080f000-00810000 rw-p 00170000 fd:00 264446 /lib/libc-2.9.so
00810000-00813000 rw-p 00810000 00:00 0
00840000-00843000 r-xp 00000000 fd:00 264449 /lib/libdl-2.9.so
00843000-00844000 r--p 00002000 fd:00 264449 /lib/libdl-2.9.so
00844000-00845000 rw-p 00003000 fd:00 264449 /lib/libdl-2.9.so
08048000-08049000 r-xp 00000000 fd:00 2942086 /home/test/test/elf/foo
08049000-0804a000 rw-p 00000000 fd:00 2942086 /home/test/test/elf/foo
b7fde000-b7fe0000 rw-p b7fde000 00:00 0
bffeb000-c0000000 rw-p bffeb000 00:00 0 [stack]
由于符号已经完成了重定位(relocation),所以后续即使有其他相同的符号定义,也不会改变符号地址。
结论对于
全局符号,加载时链接的符号首先被重定位。当然,局部符号在编译时就已经决定其位置了。
阅读(1340) | 评论(0) | 转发(0) |