zoukankan      html  css  js  c++  java
  • Linux动态链接(5)动态库符号搜索顺序

    一、动态搜索与静态搜索
    这里的动态搜索是指通过dlopen+dlsym来搜索动态库符号的过程,而静态搜索则是指程序在运行的过程中的惰性链接实现。这里其实又是一个比较边界的问题,但是也是可能存在的,另外这些问题可以促使感兴趣的同学看一下真正的实现代码。
    问题是这样的:
    假设说一个静态链接的文件通过dlopen打开一个文件,例如pthread.so文件,这个pthread库会依赖glibc.so,但是glibc的实现代码已经通过静态链接存在于主程序中,此时如果pthread库中执行printf(注意:pthread库是通过dlopen打开的,它并没有静态链接入可执行程序),此时pthread库中执行的printf到底位于exe中还是glibc.so中?
    二、测试代码工程
    [tsecer@Harry searchorder]$ ls
    callee.c  caller.c  main.c  Makefile

    [tsecer@Harry searchorder]$ cat callee.c 
    #include <stdio.h>
    void callee(void)
    {
        printf("callee in %s ",__FILE__);
    }

    [tsecer@Harry searchorder]$ cat caller.c  caller库在被加载的时候执行callee函数,这个函数在它的加载者main.exe和它的依赖库libcallee.so中均有定义。
    void __attribute__((constructor)) caller(void)
    {
        extern void callee(void);
        callee();
    }

    [tsecer@Harry searchorder]$ cat main.c 
    #include <stdio.h>
    #include <dlfcn.h>
    void callee(void)
    {
        printf("callee in %s ",__FILE__);
    }

    int main()
    {
        void * handle = dlopen("libcaller.so",RTLD_LAZY);
        printf("handle is %x ",handle);
        void (*callcallee)();
        callcallee=dlsym(handle,"callee"); 之后将会看到,在caller中执行的callee函数和通过dlsym搜索到的符号不同
        (*callcallee)();
        return 0;    
    }

    [tsecer@Harry searchorder]$ cat Makefile 
    default:libcaller.so
        gcc -L. -lcaller main.c -o main.exe -ldl
        LD_LIBRARY_PATH=. ./main.exe
    libcaller.so:caller.c libcallee.so
        gcc  -shared -fPIC -o $@ $< -L. -lcallee
    libcallee.so:callee.c
        gcc -shared -fPIC -o $@ $< 
    clean:
        rm -f *.so *.o *.exe
        
    [tsecer@Harry searchorder]$ make
    gcc -shared -fPIC -o libcallee.so callee.c 
    gcc  -shared -fPIC -o libcaller.so caller.c -L. -lcallee
    gcc -L. -lcaller main.c -o main.exe -ldl
    /usr/bin/ld: warning: libcallee.so, needed by ./libcaller.so, not found (try using -rpath or -rpath-link)
    LD_LIBRARY_PATH=. ./main.exe
    callee in main.c 这个函数调用是libcaller.so文件中的初始化函数calle调用的callee,它搜索到了主程序中的callee定义
    handle is b7897410
    callee in callee.c 这里是通过dlsym搜索到的callee定义,它搜索到了libcallee.so中定义。
    [tsecer@Harry searchorder]$
    三、搜索列表的构造
    主要在dl-object.c文件中_dl_new_object函数,它构造了两个独立的搜索列表(用C库的表达方法叫scope),每个scope是一个so搜索链表,所以可以认为其中的scope都是四重指针, link_map ****scope,这是一个本质数据结构,大家可以慢慢理解。
      if (GL(dl_ns)[nsid]._ns_loaded != NULL)
        {
    ……
          /* Add the global scope.  */
          new->l_scope[idx++] = &GL(dl_ns)[nsid]._ns_loaded->l_searchlist;
        }
    ……
    if (idx == 0 || &loader->l_searchlist != new->l_scope[0])
        {
          if ((mode & RTLD_DEEPBIND) != 0 && idx != 0)
        {
          new->l_scope[1] = new->l_scope[0];
          idx = 0;
        }

          new->l_scope[idx] = &loader->l_searchlist;
        }

      new->l_local_scope[0] = &new->l_searchlist;
     这里可以看到,每个so构造了两个搜索空间,它们相互独立又有联系,具体来说,l_scope的第一项为全局搜索空间(也就是RTLD_GLOBAL空间),第二项为局部搜索空间,它和l_local_scope搜索空间相同,而l_local_scope则只有自己的一个l_searchlist空间这空间在_dl_map_object_deps函数中初始化,它的初始化顺序是从自己的依赖中搜索的,所以称之为“局部”空间。这个搜索顺序和初始化函数执行的顺序刚好完全相反,使用广度优先搜索。为了了解这个代码中广度优先方法,大家需要首先理解广度优先算法的实现原理。在_dl_map_object_deps函数中,runp就是深度优先队列的头指针,依赖被不断的加到这个队列中,然后再循环,所以这个广度优先本质上还是比较简单的。
    四、dlsym的搜索顺序
    dlsym是一个主动搜索过程glibc-2.7elfdl-sym.c:do_sym函数中
        {
          /* Search the scope of the given object.  */
          struct link_map *map = handle;
          result = GLRO(dl_lookup_symbol_x) (name, map, &ref, map->l_local_scope,
                         vers, 0, flags, NULL);
        }
    主动调用dlsym函数时,它只搜索自己和自己的依赖,所以能够搜索到libcallee.so中定义的callee函数。
    五、惰性链接时搜索顺序
    dl-runtime.c:_dl_fixup 函数

          result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
                        version, ELF_RTYPE_CLASS_PLT, flags, NULL);
    这里优先搜索全局空间,所以搜索到主程序中callee定义,如果主程序main中没有定义,那么就会搜索到libcallee.so中实现。但是这个行为可以通过dlopen时使用RTLD_DEEPBIND标志位来让动态链接优先搜索到直接依赖中定义,有兴趣的同学可以自己试一下,我这里的glibc2.7版本是有定义这个标志的,不知道是什么时候引入C库的,如果提示找不到,那需要换一个高版本的c库。
     
     
     
     
     
  • 相关阅读:
    jquery摘要
    一步一步学Linq to sql系列文章
    公布一些常用的WebServices
    ASP.NET AJAX入门系列(8):自定义异常处理
    jquery制作图片幻灯片插件
    ASP.NET AJAX入门系列(2):使用ScriptManager控件
    ClientScript.GetCallbackEventReference几个参数的使用实例
    jquery中this的使用说明
    程序员的最后归宿究竟是什么?
    英语26个字母的日语读法
  • 原文地址:https://www.cnblogs.com/tsecer/p/10486389.html
Copyright © 2011-2022 走看看