zoukankan      html  css  js  c++  java
  • 由函数符号找不到联想到的动态链接过程

    原创作品,转载请注明出处http://www.cnblogs.com/leo0000/p/5707482.html 

    最近遇到一个因符号找不到导致程序无法启动的情况,原因就是在于字符串表被修改了。下面是我自己创建的一个栗子;因为自己遇到的问题就是函数指针引起的,所以这边还是使用函数指针。

    test1.c,后面将会编译成libtest1.so

    int fun2()
    {
            return -1;
    }
    
    int (*pf)();
    
    
    int fun1()
    {
            pf = fun2;
            return pf() ;
    }

    test3.c,调用libtest1.so中的函数fun1,

    int fun1();
    
    int main()
    {
            fun1();
            return 1;
    }

    这里讲两个elf文件中的知识,elf文件一般有两个符号表,分别是.dynsym和.symtab,和两个字符串表,分别是.dynstr和.strtab,做了一下实验如果是连接动态库时,修改后者是不会对连接以及运行时造成影响的,而如果是静态链接,那么两者必须全部都一致。但是反汇编时使用的是后者。

    本来test3文件是可以运行的,现在我把dynstr中的fun2改成fun6,此时会出现下面的提示,实际上该符号根本不需要重定向。这个可以通过将fun2定义为static时对比得知,如果fun2是static的,那么dynsym表中并不会出现fun2,或者是可执行文件也不会导出fun2。所以这边可以确定.dynsym中的是导出符号或者需要重定位符号。

    ./test: symbol lookup error: libtest1.so: undefined symbol: fun6

    首先修改的是字符串表,间接影响到了符号表,但导出符号中关于fun6函数的信息还是

         2: 00000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.0 (3)
         3: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         4: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
         5: 00002010     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
         6: 00002018     4 OBJECT  GLOBAL DEFAULT   23 pf
         7: 000004a6    62 FUNC    GLOBAL DEFAULT   11 fun1
         8: 0000201c     0 NOTYPE  GLOBAL DEFAULT  ABS _end
         9: 0000049c    10 FUNC    GLOBAL DEFAULT   11 fun6
    未修改前:
         9: 0000049c    10 FUNC    GLOBAL DEFAULT   11 fun2

    接着实验,我把fun1改成fun8此时还是提示fun6找不到

         5: 00002010     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
         6: 00002018     4 OBJECT  GLOBAL DEFAULT   23 pf
         7: 000004a6    62 FUNC    GLOBAL DEFAULT   11 fun8
         8: 0000201c     0 NOTYPE  GLOBAL DEFAULT  ABS _end
         9: 0000049c    10 FUNC    GLOBAL DEFAULT   11 fun6
        10: 00002010     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
        11: 00000368     0 FUNC    GLOBAL DEFAULT    9 _init
        12: 00000528     0 FUNC    GLOBAL DEFAULT   12 _fini

    另一个实验,如果把fun2改成fun1,fun1还是fun1,那么会导致程序一直在调用fun1,知道栈溢出。

    所以现在可以确定几件事情,对应源码来看,pf=fun2这句话相当于变成了pf=fun1,然后变成了解析fun1的函数位置。查看源码可以知道,pf是指向某个字符串表的字符串,通过该字符串来解析的,

    当然这个我们就会想到使用的是同一个字符串表,fun6还是fun6啊,还是可以找到啊,但是实际上如果一个程序的符号很少,当然可以通过这种逐个对比的方式,在我前一篇的博文中热补丁就是使用的这种方式,而实际上,解释器程序会把字符串通过哈希函数映射到字符串表中的某个条目,而我们编译时使用的键值是fun2,而现在使用的是fun6,所以会导致找不到的现象出现。所以当我们把字符串改为fun6时,查找fun6,当然是找不到的。所以确定原因在于pf这个全局变量是需要重定位的。而将fun1改为fun8时还是提示fun6找不到,原因在于解析重定向表时的顺序。

    另外一种情况:

    #include <stdio.h>
    int fun2()
    {
            return -1;
    }
    
    int (*pf)();
    
    
    int fun1()
    {
            printf("fun1
    ");
    //      pf = fun2;
            return fun2() ;
    }

    所有的现象还是不变。查看反汇编代码可以看到,本来调用fun2的位置(也就是现在调用fun1的位置),

    000004a6 <fun1>:
     4a6:    55                       push   %ebp
     4a7:    89 e5                    mov    %esp,%ebp
     4a9:    53                       push   %ebx
     4aa:    83 ec 14                 sub    $0x14,%esp
     4ad:    e8 e5 ff ff ff           call   497 <__i686.get_pc_thunk.bx>
     4b2:    81 c3 42 1b 00 00        add    $0x1b42,%ebx
     4b8:    8d 83 3e e5 ff ff        lea    -0x1ac2(%ebx),%eax
     4be:    89 04 24                 mov    %eax,(%esp)
     4c1:    e8 fa fe ff ff           call   3c0 <puts@plt>
     4c6:    e8 d5 fe ff ff           call   3a0 <fun1@plt>
     4cb:    83 c4 14                 add    $0x14,%esp
     4ce:    5b                       pop    %ebx

    是通过plt表来解析的,这个解析效果和函数指针是一样的,因为是通过符号名称来重定位的。

    曾经一度怀疑自己以前的想法是不是错了,以前我认为解释器只是解析重定向表中的符号,乍一看还以为解释器解析整个符号表。当然现在看来我还是正确的,解释器是只解析重定向表中的符号,只是使用了效率更高的hash表,而不是遍历。

    总结一下,解释器解释函数符号时,通过函数符号经过hash函数运算得到hash值,然后映射到符号表中的一个条目,再通过符号表重定位该符号,不同符号会导致不同的hash值,所以会导致找不到。

  • 相关阅读:
    找水王
    哈利波特图书购买问题
    中序线索化二叉树[C语言实现及注释]
    第一篇随文。
    理解Python函数中的的return
    记录一款实时同步的软件——Lsyncd
    for循环
    while循环
    文件操作
    我的第一个博客
  • 原文地址:https://www.cnblogs.com/leo0000/p/5707482.html
Copyright © 2011-2022 走看看