Chinaunix首页 | 论坛 | 博客
  • 博客访问: 22155
  • 博文数量: 5
  • 博客积分: 235
  • 博客等级: 二等列兵
  • 技术积分: 65
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-30 20:06
文章分类

全部博文(5)

文章存档

2012年(2)

2011年(2)

2010年(1)

我的朋友

分类: 系统运维

2011-09-14 17:27:31

前一段时间项目上碰到一个诡异的问题,调用一个函数的时候,总是发生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
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <dlfcn.h>

  4. int
  5. main(void)
  6. {
  7.         void *handle;
  8.         char *error;
  9.         int (*tf_ptr)();

  10.         handle = dlopen("/home/test/test/elf/libtf2.so", RTLD_LAZY);
  11.         if (!handle) {
  12.                 fprintf (stderr, "%s\n", dlerror());
  13.                 exit(1);
  14.         }

  15.         dlerror(); /* Clear any existing error */

  16.         tf_ptr = dlsym(handle, "caller_func");
  17.         if ((error = dlerror()) != NULL) {
  18.                 fprintf (stderr, "%s\n", error);
  19.                 exit(1);
  20.         }

  21.         tf_ptr();

  22.         dlclose(handle);
  23.         return 0;
  24. }
2. test_func.c (将被编译成so,并在加载时动态链接)
  1. #include <stdio.h>

  2. int i = 1;

  3. int callee_func()
  4. {
  5.     printf("called by test_func in %s\n", __FILE__);
  6.     return 0;
  7. }

  8. int caller_func()
  9. {
  10.     printf("this is %s, i = %d\n", __FILE__, i);
  11.     callee_func();
  12.     return 0;
  13. }
3. test_func2.c (基本同test_func.c, 也会被编译成so,但在运行时动态链接)
  1. #include <stdio.h>

  2. int i = 2;

  3. int callee_func()
  4. {
  5.     printf("called by test_func in %s\n", __FILE__);
  6.     return 0;
  7. }

  8. int caller_func()
  9. {
  10.     printf("this is %s, i = %d\n", __FILE__, i);
  11.     callee_func();
  12.     return 0;
  13. }
4. Makfile
  1. .PHONY: foo libtf2.so libtf.so
  2. foo: test_main.c libtf2.so libtf.so
  3.      gcc -o foo test_main.c -L. -ltf -ldl -g -Wall

  4. libtf2.so: test_func2.c
  5.      gcc -shared -fPIC -o $@ $< -g

  6. libtf.so: test_func.c
  7.      gcc -shared -fPIC -o $@ $< -g

编译并运行
  1. [root@Fedora10 elf]# ./foo
  2. this is test_func2.c, i = 1
  3. called by test_func in test_func.c
从结果来看,引用实际上都指向了libtf.so中的定义,即加载时链接的so。

调试
在main处加上断点,此时还没执行到dlopen

  1. (gdb) b main
  2. Breakpoint 1 at 0x80485f5: file test_main.c, line 12.
  3. (gdb) r
  4. Starting program: /home/test/test/elf/foo

  5. Breakpoint 1, main () at test_main.c:12
  6. 12 handle = dlopen("/home/test/test/elf/libtf2.so", RTLD_LAZY);
  7. Missing separate debuginfos, use: debuginfo-install glibc-2.9-2.i686
  8. (gdb) p callee_func
  9. $1 = {int ()} 0x11147c
  10. (gdb) p &i
  11. $2 = (int *) 0x112698
  12. (gdb)
于此同时查看foo进程的内存使用情况,可见两个被引用符号的地址位于libtf.so的地址范围内:
  1. [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) |
给主人留下些什么吧!~~