注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

wrong library or version mismatch.  

2013-03-16 12:38:00|  分类: gdb源代码分析 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
原创文件,转载时请注明出处……
一、调试问题
在gdb调试一个core文件或者远程调试一个文件的时候,此时系统可能会给出提示
warning: .dynamic section for "./libfoo.so" is not at the expected address
,这个提示无疑是gdb给出的一个提示,好在可以下载到gdb的代码,所以下载之后在里面搜索一下这个字符串,在gdb的gdb-7.2\gdb\ssolib-svr4.c:
static CORE_ADDR
LM_ADDR_CHECK (struct so_list *so, bfd *abfd)
{
  if (so->lm_info->l_addr == (CORE_ADDR)-1)
    {
      struct bfd_section *dyninfo_sect;
      CORE_ADDR l_addr, l_dynaddr, dynaddr;

      l_addr = LM_ADDR_FROM_LINK_MAP (so);

      if (! abfd || ! HAS_LM_DYNAMIC_FROM_LINK_MAP ())
goto set_addr;

      l_dynaddr = LM_DYNAMIC_FROM_LINK_MAP (so);

      dyninfo_sect = bfd_get_section_by_name (abfd, ".dynamic");
      if (dyninfo_sect == NULL)
goto set_addr;

      dynaddr = bfd_section_vma (abfd, dyninfo_sect);

      if (dynaddr + l_addr != l_dynaddr)
……
   warning (_(".dynamic section for \"%s\" "
      "is not at the expected address "
      "(wrong library or version mismatch?)"), so->so_name);
}

但是很多时候就是这样,源代码命名就在那里,你却不知道是啥意思,这就是程序员所说的世界上最远的距离。套用一个流行的语体:调试的时候遇到一个问题,你哭了,因为没有源代码;给你源代码之后,你哭的更厉害了,因为代码看不懂。
gdb的提示就是在这里给出的,但是里面的宏太多了,而且很多数据结构并不知道是啥意思。
二、复现一下问题
好在这个提示本身给出了一个大致的意思:就是说一个文件的.dynamic节的地址没有在期望的地址。但是这个提示在没有解释之前,有很多疑点,就像我们现在看先秦的散文一样,在没有看注释之前,觉得这篇文章大致是在说这个事,但是具体是个啥事,在没有背景的情况下就虚无缥缈很多了。比如这里:这个期望地址是什么地址,从哪里来,而不在期望地址的错误地址是从哪里来,为什么确定期望的地址就是正确的,而当前的地址就是错误的呢?
我们可以先复现一下这个现象吧:
[root@Harry conflicsoaddr]# cat foo.c 
int foo()
{
return 0x1234<<16;
}
int something()
{
return 0x12345678;
}
[root@Harry conflicsoaddr]# cat bar.c 
int bar()
{
return 0x5678;
}
[root@Harry conflicsoaddr]# cat main.c 
int main()
{
return *(int*)0 + foo() + bar();
}
[root@Harry conflicsoaddr]# gcc -shared -fpic -o libfoo.so foo.c -Ttext=500000 
[root@Harry conflicsoaddr]# gcc -shared -fpic -o libbar.so bar.c -Ttext=500000 
[root@Harry conflicsoaddr]# gcc main.c -o main -L. -lfoo -lbar -g
[root@Harry conflicsoaddr]# ./main 
./main: error while loading shared libraries: libfoo.so: cannot open shared object file: No such file or directory
[root@Harry conflicsoaddr]# LD_LIBRARY_PATH=. ./main 
Segmentation fault
[root@Harry conflicsoaddr]# ulimit -c unlimited
[root@Harry conflicsoaddr]# LD_LIBRARY_PATH=. ./main 
Segmentation fault (core dumped)
[root@Harry conflicsoaddr]# gcc -shared -fpic -o libbar.so bar.c -Ttext=800000 
[root@Harry conflicsoaddr]# ll -lrt
total 620
-rw-r--r--. 1 root root     30 2013-03-13 23:12 bar.c
-rw-r--r--. 1 root root  42642 2013-03-13 23:22 debug.txt
-rw-r--r--. 1 root root     49 2013-03-14 00:15 main.c
-rw-r--r--. 1 root root     74 2013-03-14 23:03 foo.c
-rw-r--r--. 1 root root  30964 2013-03-14 23:09 libmess.so
-rw-------. 1 root root 167936 2013-03-15 00:04 core.23190.11.0
-rw-------. 1 root root 286720 2013-03-15 00:09 core.23884.11.0
-rw-r--r--. 1 root root  41200 2013-03-15 00:29 err.txt
-rw-------. 1 root root 167936 2013-03-15 00:29 core.27235.11.0
-rwxrwxr-x. 1 root root   7364 2013-03-16 11:43 libfoo.so
-rwxrwxr-x. 1 root root   6174 2013-03-16 11:44 main
-rw-------. 1 root root 167936 2013-03-16 11:45 core.19022.11.0
-rwxrwxr-x. 1 root root   7322 2013-03-16 11:45 libbar.so
[root@Harry conflicsoaddr]# gdb -c core.19022.11.0 main
GNU gdb (GDB) Fedora (7.0-3.fc12)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/tsecer/CodeTest/conflicsoaddr/main...done.
Missing separate debuginfo for ./libfoo.so
Try: yum --enablerepo='*-debuginfo' install /usr/lib/debug/.build-id/13/3851464d9fa158329368bb683bb87b615cfc7f
Missing separate debuginfo for ./libbar.so
Try: yum --enablerepo='*-debuginfo' install /usr/lib/debug/.build-id/88/588235d0c5b06bfd81150686d3d5479ece47ed

warning: .dynamic section for "./libbar.so" is not at the expected address

warning: difference appears to be caused by prelink, adjusting expectations
Reading symbols from ./libfoo.so...(no debugging symbols found)...done.
Loaded symbols for ./libfoo.so
Reading symbols from ./libbar.so...(no debugging symbols found)...done.
Loaded symbols for ./libbar.so
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./main'.
Program terminated with signal 11, Segmentation fault.
#0  0x080484d3 in main () at main.c:3
3 return *(int*)0 + foo() + bar();
Missing separate debuginfos, use: debuginfo-install glibc-2.11.2-3.i686
(gdb) 
三、加载器对so文件的加载
对于一个可执行文件来说,它的ELF文件格式中描述了某个特定节加载到内存的特定位置,以刚才生成的libbar.so为例,看一下其输出内容
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00000000 0x00000000 0x0032c 0x0032c R E 0x1000
  LOAD           0x001000 0x00800000 0x00800000 0x00138 0x00138 R E 0x1000
  LOAD           0x001138 0x00801138 0x00801138 0x000f8 0x00100 RW  0x1000
  DYNAMIC        0x001150 0x00801150 0x00801150 0x000c0 0x000c0 RW  0x4
  NOTE           0x0000f4 0x000000f4 0x000000f4 0x00024 0x00024 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
可以看到这里指明了DYNAMIC节期望被加载入的内存位置,这个只是期望,但是加载器并不能满足这个需求,就像大家都期望呆在帝都,在一环之内买个100平的大房子,但是这个地方就这么大,不能满足需求。对于so文件也是这样,如果大家都期望加载到一个特定的位置,那肯定是不可能的,此时总不能因为这个伤了和气,整个可执行文件就不运行了吧。
看一下libc库的期望地址
[root@Harry conflicsoaddr]# readelf -l /lib/libc-2.11.2.so 

Elf file type is DYN (Shared object file)
Entry point 0x220d10
There are 10 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x0020a034 0x0020a034 0x00140 0x00140 R E 0x4
  INTERP         0x13fc90 0x00349c90 0x00349c90 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x0020a000 0x0020a000 0x171bcc 0x171bcc R E 0x1000
  LOAD           0x1721c0 0x0037d1c0 0x0037d1c0 0x027bc 0x057a8 RW  0x1000
  DYNAMIC        0x173d7c 0x0037ed7c 0x0037ed7c 0x000f8 0x000f8 RW  0x4
  NOTE           0x000174 0x0020a174 0x0020a174 0x00044 0x00044 R   0x4
  TLS            0x1721c0 0x0037d1c0 0x0037d1c0 0x00008 0x00040 R   0x4
  GNU_EH_FRAME   0x13fca4 0x00349ca4 0x00349ca4 0x06d5c 0x06d5c R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  GNU_RELRO      0x1721c0 0x0037d1c0 0x0037d1c0 0x01e40 0x01e40 R   0x1
但是事实上libc很少被加载到这个地址
[root@Harry conflicsoaddr]# cat /proc/*/maps | grep libc-2.11.2.so 
0022c000-0039e000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
0039e000-0039f000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0039f000-003a1000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
003a1000-003a2000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so
0050f000-00681000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
00681000-00682000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so
00682000-00684000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
00684000-00685000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so
0020a000-0037c000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
0037c000-0037d000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0037d000-0037f000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0037f000-00380000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so
0012a000-0029c000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
0029c000-0029d000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0029d000-0029f000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0029f000-002a0000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so
00110000-00282000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
00282000-00283000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so
00283000-00285000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
00285000-00286000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so
0020a000-0037c000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
而linux下的动态加载库位于glibc中,也就是我们通过ldd可以看到的信息
[root@Harry conflicsoaddr]# ldd main
linux-gate.so.1 =>  (0x00dee000)
libfoo.so => not found
libbar.so => not found
libc.so.6 => /lib/libc.so.6 (0x0020a000)
/lib/ld-linux.so.2 (0x001e8000)
glibc中对于这个加载地址的处理为
glibc-2.11.2\elf\dl-load.c
if (__builtin_expect (type, ET_DYN) == ET_DYN)
      {
/* This is a position-independent shared object.  We can let the
  kernel map it anywhere it likes, but we must have space for all
  the segments in their specified positions relative to the first.
  So we map the first segment without MAP_FIXED, but with its
  extent increased to cover all the segments.  Then we remove
  access from excess portion, and there is known sufficient space
  there to remap from the later segments.

  As a refinement, sometimes we have an address that we would
  prefer to map such objects at; but this is only a preference,
  the OS can do whatever it likes. */
ElfW(Addr) mappref;
mappref = (ELF_PREFERRED_ADDRESS (loader, maplength,
 c->mapstart & GLRO(dl_use_load_bias))
  - MAP_BASE_ADDR (l));

/* Remember which part of the address space this object uses.  */
l->l_map_start = (ElfW(Addr)) __mmap ((void *) mappref, maplength,
     c->prot,
     MAP_COPY|MAP_FILE,
     fd, c->mapoff);
if (__builtin_expect ((void *) l->l_map_start == MAP_FAILED, 0))
 {
 map_error:
   errstring = N_("failed to map segment from shared object");
   goto call_lose_errno;
 }

l->l_map_end = l->l_map_start + maplength;
l->l_addr = l->l_map_start - c->mapstart;

if (has_holes)
 /* Change protection on the excess portion to disallow all access;
    the portions we do not remap later will be inaccessible as if
    unallocated.  Then jump into the normal segment-mapping loop to
    handle the portion of the segment past the end of the file
    mapping.  */
 __mprotect ((caddr_t) (l->l_addr + c->mapend),
     loadcmds[nloadcmds - 1].mapstart - c->mapend,
     PROT_NONE);

l->l_contiguous = 1;

goto postmap;
      }
这里注释说了,加载的时候mmap地址传入的期望地址就是ELF文件中指明的地址,但是不加MAP_FIXED字段,所以如果内核判断说,这个地址来不了,那我也可以将就其它地址,这是您做主,看着办就行。所以说这个期望的地址并不是一个绝对的地址。
需要补充说明的是,这个地方加载地址并不是对所有的每个section都mmap一次,而是整个so被mmap一次,然后通过mprotect来修改这些不同节的信息,所以so文件中只有第一个LOAD节的基地址是生效的,它也就是SO的真正mmap地址。
四、调试器判断逻辑
大家可能以为说这个期望地址就是so文件中指明的地址和实际的加载地址,那你就错了,因为从现在运行的情况看,系统中运行的好好的进程中就有大量这样所谓的期望不一致问题,所以再来审视一下gdb的处理代码
  if (so->lm_info->l_addr == (CORE_ADDR)-1)
    {
      struct bfd_section *dyninfo_sect;
      CORE_ADDR l_addr, l_dynaddr, dynaddr;

      l_addr = LM_ADDR_FROM_LINK_MAP (so);从名字看是从link_map中找到逻辑加载地址,也就是整个SO的加载地址

      if (! abfd || ! HAS_LM_DYNAMIC_FROM_LINK_MAP ())
goto set_addr;

      l_dynaddr = LM_DYNAMIC_FROM_LINK_MAP (so); 从link_map中找到找到动态节地址

      dyninfo_sect = bfd_get_section_by_name (abfd, ".dynamic");
      if (dyninfo_sect == NULL)
goto set_addr;

      dynaddr = bfd_section_vma (abfd, dyninfo_sect);从ELF文件中找到dynamic节的期望地址

      if (dynaddr + l_addr != l_dynaddr)
这里涉及到link_mao的概念,我们不再追究下去了的,但是大家知道是一个动态库管理所有加载信息的结构,每个so文件都有这样一个队形信息就可以了。在link_map中保存了进程被加载入内存的动态信息,它包含了so被加载入内存的真实地址,动态节地址,等基本信息,我们可以通过动态连接器的功能看一下这个信息:
(gdb) b main
Breakpoint 1 at 0x80483bd: file uselibc.c, line 4.
(gdb) set environment LD_DEBUG=files 
(gdb) r
Starting program: /home/tsecer/CodeTest/conflicsoaddr/uselib 
     25233:
     25233: file=libtinfo.so.5 [0];  needed by /bin/bash [0]
     25233: file=libtinfo.so.5 [0];  generating link map
     25233:  dynamic: 0x00c81300  base: 0x00000000   size: 0x00018c58
     25233:    entry: 0x00c6eab0  phdr: 0x00c69034  phnum:          6
     25233:
     25233:
     25233: file=libdl.so.2 [0];  needed by /bin/bash [0]
     25233: file=libdl.so.2 [0];  generating link map
     25233:  dynamic: 0x00388ed0  base: 0x00000000   size: 0x00004074
     25233:    entry: 0x00385a60  phdr: 0x00385034  phnum:          9
     25233:
     25233:
     25233: file=libc.so.6 [0];  needed by /bin/bash [0]
     25233: file=libc.so.6 [0];  generating link map
     25233:  dynamic: 0x0037ed7c  base: 0x00000000   size: 0x00178968
     25233:    entry: 0x00220d10  phdr: 0x0020a034  phnum:         10
     25233:
(gdb) info proc 
process 25233
cmdline = '/home/tsecer/CodeTest/conflicsoaddr/uselib'
cwd = '/home/tsecer/CodeTest/conflicsoaddr'
exe = '/home/tsecer/CodeTest/conflicsoaddr/uselib'
(gdb) shell cat /proc/25233/maps
001e8000-00206000 r-xp 00000000 fd:00 1280       /lib/ld-2.11.2.so
00206000-00207000 r--p 0001d000 fd:00 1280       /lib/ld-2.11.2.so
00207000-00208000 rw-p 0001e000 fd:00 1280       /lib/ld-2.11.2.so
0020a000-0037c000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
0037c000-0037d000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0037d000-0037f000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0037f000-00380000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so
00380000-00383000 rw-p 00000000 00:00 0 
可以看到的时,虽然base地址打印的是0,但是libc.so是被加载到了期望的0x20a000地址,所以这个base地址并不是so文件被加载入内存的绝对起始地址。起始在刚才贴出来的glibc的so加载代码中已经可以看到这个base地址的由来
l->l_addr = l->l_map_start - c->mapstart;
也就是逻辑地址减去配置中起始地址,所以这个地址是实际加载地址和期望加载地址的差值,由于大部分时候我们都没有指定期望地址,也就是期望地址为零,所以从这个地址就显示为实际加载的逻辑地址。这个调试信息的打印同样在dl-load.c文件中,有兴趣的同学可以看一下。
所以总结gdb的判断逻辑,就是判断dynamic节的逻辑
(dynaddr + l_addr != l_dynaddr)
的依据就是由于
l->l_addr = l->l_map_start - c->mapstart; ==>>  l->l_map_start - l->l_addr == c->mapstart,
也就是说,实际加载地址减去l_addr就是配置文件中配置的dynamic节的位置,而gdb的
dynaddr + l_addr != l_dynaddr ==>>  l_dynaddr -  l_addr != dynaddr 
相当于判断逻辑空间中计算出的dynamic节的配置文件位置和当前加载的so文件的地址是否相同,如果不同则任务core时使用的so和现在调试环境下使用的so文件不一致。这个是一个必要条件判断,也就是说如果gdb提示这个警告,那么一定版本一定是二进制不匹配的,但是不提示就不表示一定是二进制匹配的,这个地方只是一个简单的校验。

  评论这张
 
阅读(1019)| 评论(1)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017