精华内容
下载资源
问答
  • 当系统加载可执行代码的时候,能够知道其依赖的库的名字,但是还需要知道库的绝对路径,此时就需要系统动态载入器(dynamic linker/loader)。 对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索...

    1. 可执行程序在执行的时候如何定位共享库文件?

    当系统加载可执行代码的时候,能够知道其所依赖的库的名字,但是还需要知道库的绝对路径,此时就需要系统动态载入器(dynamic linker/loader)。

    对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的

    DT_RPATH段---环境变量LD_LIBRARY_PATH---/etc/ld.so.cache文件列表---/lib,/usr/lib目录

    找到库文件后将其载入内存。


    2. 如何让系统找到用户自定义的库文件

    如果安装在/lib或/usr/lib下,那么ld默认能够找到,无需其他操作。

    如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:

    1). 编辑/etc/ld.so.conf文件,加入库文件所在目录的路径;

    2). 运行sudo ldconfig,该命令会重建/etc/ld.so.cache文件;

    我们通常把一些共用函数制作成函数库,供其他程序使用。


    我的习惯是,在/etc/ld.so.conf.d/目录下,添加一个文件usr-lib.conf;

    内容如下:

    1. /home/dingq/wrk/tools/lib  

    同时,用户指定的库文件放到/home/dingq/wrk/tools/lib目录下;

    然后,运行命令sudo ldconfig

    再运行程序就可以了。


    展开全文
  • 第一步,解释器(也可以叫动态链接器)首先检查可执行程序依赖共享,并在需要时候对其进行加载。ELF 文件有一个特别节区: .dynamic,它存放了和动态链接相关很多信息,例如动态链接器通过它找到该文件...

    接着上一篇博客。前面的工作都是在内核完成的,接下来会回到用户空间。

    第一步,解释器(也可以叫动态链接器)首先检查可执行程序所依赖的共享库,并在需要的时候对其进行加载。

    ELF 文件有一个特别的节区: .dynamic,它存放了和动态链接相关的很多信息,例如动态链接器通过它找到该文件使用的动态链接库。不过,该信息并未包含动态链接库的绝对路径,但解释器通过 LD_LIBRARY_PATH 参数可以找到(它类似 Shell 解释器中用于查找可执行文件的 PATH 环境变量,也是通过冒号分开指定了各个存放库函数的路径)该变量实际上也可以通过/etc/ld.so.conf 文件来指定,一行对应一个路径名。为了提高查找和加载动态链接库的效率,系统启动后会通过 ldconfig 工具创建一个库的缓存 /etc/ld.so.cache 。如果用户通过 /etc/ld.so.conf 加入了新的库搜索路径或者是把新库加到某个原有的库目录下,最好是执行一下 ldconfig 以便刷新缓存。

    找到动态链接库后,就可以将其加载到内存中。

    第二步,解释器对程序的外部引用进行重定位,并告诉程序其引用的外部变量/函数的地址,此地址位于共享库被加载在内存的区间内。动态链接还有一个延迟定位的特性,即只有在“真正”需要引用符号时才重定位,这对提高程序运行效率有极大帮助。(如果设置了 LD_BIND_NOW 环境变量,这个动作就会直接进行)

    下面具体说明符号重定位的过程。

    首先了解几个概念。符号,也就是可执行程序代码段中的变量名、函数名等。重定位是将符号引用与符号定义进行链接的过程,对符号的引用本质是对其在内存中具体地址的引用,所以本质上来说,符号重定位要解决的是当前编译单元如何访问「外部」符号这个问题。动态链接是在程序运行时对符号进行重定位,也叫运行时重定位(而静态链接则是在编译时进行,也叫链接时重定位)

    现代操作系统中,二进制映像的代码段不允许被修改,而数据段能被修改。

    编写如下代码

    通过gcc编译成.o文件后,再通过objdump-d命令得到文件的汇编指令,如下所示

    call指令的操作数是fc ff ff ff,翻译成16进制数是0xfffffffc,看成有符号是-4。这里应该存放printf函数的地址,但由于编译阶段无法知道printf函数的地址,所以预先放一个-4在这里。所以程序为了正确执行,需要在链接时对其地址进行修正。这里的原理对静态链接和动态链接来说都是一样的。

    但对于动态链接来说,有两个不同的地方:

    (1)因为不允许对可执行文件的代码段进行加载时符号重定位,因此如果可执行文件引用了动态库中的数据符号,则在该可执行文件内对符号的重定位必须在链接阶段完成,为做到这一点,链接器在构建可执行文件的时候,会在当前可执行文件的数据段里分配出相应的空间来作为该符号真正的内存地址,等到运行时加载动态库后,再在动态库中对该符号的引用进行重定位:把对该符号的引用指向可执行文件数据段里相应的区域。

    (2)ELF 文件对调用动态库中的函数采用了所谓的"延迟绑定"(lazy binding)策略, 只有当该函数在其第一次被调用发生时才最终被确认其真正的地址,因此我们不需要在调用动态库函数的地方直接填上假的地址,而是使用了一些跳转地址作为替换,这样一来连修改动态库和可执行程序中的相应代码都不需要进行了,当然延迟绑定的目的不是为了这个,具体先不细说。

    可执行程序对符号的访问又分为模块内和模块间的访问,这里只介绍模块间的访问,也就是访问动态链接库中的符号。

    通过gcc生成test可执行文件,然后同样用objdump-d得到可执行文件的汇编指令,如下所示

    可以看到这里的call指令指向了80482e0地址处,也即是PLT。

    PLT就是程序链接表(Procedure Link Table),属于代码段。用于把位置独立的函数调用重定向到绝对位置。每个动态链接的程序和共享库都有一个PLT,PLT表的每一项都是一小段代码,从对应的GOT表项中读取目标函数地址。程序对某个函数的第一次访问都被调整为对 PLT入口也就是PLT0的访问,也就是说所有的PLT首次执行时,最后都会跳转到第一个PLT中执行。PLT0是一段访问动态链接器的特殊代码,是动态链接做符号解析和重定位的公共入口。这样做的好处是不用每个PLT表都有重复的一份指令,可以减少PLT指令条数。

    PLT表结构如下图所示

    可以看到,PLT会先执行jmp指令跳转到某一个地址,而这个地址就对应的GOT表项。

    GOT就是全局偏移表(Global Offset Table),属于数据段。为了能使得代码段里对数据及函数的引用与具体地址无关,只能再作一层跳转,ELF 的做法是在动态库的数据段中加一个表项,也就是GOT 。GOT表格中放的是数据全局符号的地址,该表项在动态库被加载后由动态加载器进行初始化,动态库内所有对数据全局符号的访问都到该表中来取出相应的地址,即可做到与具体地址了,而该表作为动态库的一部分,访问起来与访问模块内的数据是一样的。

    GOT表结构如下图所示

    GOT[0]对应本ELF动态段(.dynamic段)的装载地址,GOT[1]对应本ELF的link_map数据结构描述符地址,GOT[2]:对应_dl_runtime_resolve动态链接器函数的地址。3个特殊项后面依次是每个动态库函数的GOT表项

    上面讲到PLT通过jmp指令跳转到GOT表中去取函数的真实地址,而符号所对应的表项开始是没有这个地址的,而是存放了该PLT表项jmp指令的下一条指令地址,也就是push指令。回到了PLT表项对应的指令中继续执行,最后一条jmp指令跳转到了PLT0中执行。

    PLT0对应的指令执行了下列过程:首先pushl把 804a004(GOT[1])这块内存里的qword入栈,这个qword是link_map的地址,根据这个地址可以找到动态库的符号表。然后jmp跳转到GOT表中的第三项,找到动态链接器的_dl_runtime_resolve函数地址,开始执行该函数。回想前面讲到的内核中加载目标映像的过程,可执行文件在Linux内核通过exeve装载完成之后,不直接执行,而是先跳到动态链接器(ld-linux-XXX)执行。在ld-linux-XXX里将link_map地址、_dl_runtime_resolve地址写到GOT表项内。所以在此时,该GOT表项的不为空。(前面三个GOT表项都是这样被写入的)然后当程序加载其它动态库的时候,会把动态库的符号信息插入link_map


    _dl_runtime_resolve函数得到动态链接库中函数的地址后(该过程以后再分析),写回到对应的GOT表项中。

    这就是函数第一次被调用时执行的过程。以后每次被调用直接从GOT表中取到函数地址就可以了。

    总的来说,动态重定位的过程可以由下图表示

    部分内容和图片参考:https://blog.csdn.net/linyt/article/category/6267121

    展开全文
  •  1.ELF可执行文件中动态段中DT_RPATH指定路径。这实际上是通过一种不算很常用,却比较实用方法设置:编译目标代码时,可以对gcc加入链接参数“-Wl,-rpath”指定动态搜索路径;  2.环境变量LD_...
  • 对动态库的实际应用还不太熟悉读者可能曾经... 具体说来,动态链接器ld.so按照下面顺序来搜索需要动态共享: 1.ELF可执行文件中动态段中DT_RPATH指定路径。这实际上是通过一种不算很常用,却比较实用...

    对动态库的实际应用还不太熟悉的读者可能曾经遇到过类似“error while loading shared libraries”这样的错误,这是典型的因为需要的动态库不在动态链接器ld.so的搜索路径设置当中导致的。
    具体说来,动态链接器ld.so按照下面的顺序来搜索需要的动态共享库:
    1.ELF可执行文件中动态段中DT_RPATH所指定的路径。这实际上是通过一种不算很常用,却比较实用的方法所设置的:编译目标代码时,可以对gcc加入链接参数“-Wl,-rpath”指定动态库搜索路径;
    2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
    3./etc/ld.so.cache中所缓存的动态库路径(如果支持ld.so.cache的话)。这可以通过修改配置文件/etc/ld.so.conf中指定的动态库搜索路径来改变;
    4.默认的动态库搜索路径/lib;
    5.默认的动态库搜索路径/usr/lib。
           在
    嵌入式Linux
    系统的实际应用中,1和2被经常使用,也有一些相对简单的的嵌入式系统会采用4或5的路径来规范动态库。3在嵌入式系统中使用的比较少,因为有很多系统根本就不支持ld.so.cache。
        4和5的方式非常简单,只要将所需要的库放到/lib或/usr/lib就可以解决找不到库的问题,不过对于大一些的系统来说,不太方便管理。1和2的方式要稍微复杂一些,下面我们用一个非常简单的例子来说明如何应用。
    首先编写一个最简单的动态共享库,源代码pirnt.c如下:
         1  #include
         2
         3  void print_foo()
         4  {
         5      printf("fooooooooo\n");
         6  }
    注意将它编译成共享库:
    # gcc print.c -shared -o libprint.so
    # file libprint.so
    libprint.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
    调用该共享库main.c代码如下:
         1  #include
         2
         3  extern void print_foo();
         4
         5  int main()
         6  {
         7      print_foo();
         8         
         9      return 0;
        10  }
    编译之后的运行结果如下:
    # gcc main.c -L./ -lprint -o pfoo
    # ./pfoo
    ./pfoo: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory
    这便是典型的找不到动态库的错误。通常我们可以通过设置环境变量LD_LIBRARY_PATH来指定动态库的搜索路径(即上面的方法2),比如这样就可以正确运行了:
    # export LD_LIBRARY_PATH=./
    # ./pfoo
    fooooooooo
    但这种方法有一个明显的缺点:一旦LD_LIBRARY_PATH被设定,则在这个环境变量生效的范围之内,所有其他的ELF可执行程序也会按照这个顺序去搜索动态库,这样势必会造成搜索时的一些浪费。
    我们也可以使用另外一种方案来解决这种问题,即利用参数“-Wl,-rpath”在编译时指定运行时的搜索路径(即上面的方法1),如下所示:
    # unset LD_LIBRARY_PATH
    # echo $LD_LIBRARY_PATH
    # gcc main.c -L./ -lprint -o pfoo_r -Wl,-rpath=./
    # ./pfoo
    ./pfoo: error while loading shared libraries: libprint.so: cannot open shared object file: No such file or directory
    # ./pfoo_r
    fooooooooo
    我们首先unset了LD_LIBRARY_PATH,可以看到它已经不再有效了(当然这不是使用参数“-Wl,-rpath”的必要步骤,在这里只是为了说明它已经不再起作用了),而且”pfoo”程序运行时也会发生找不到库的错误,而我们加入编译参数“-Wl,-rpath,./”之后得到的pfoo_r程序则能正常运行。
    事实上我们可以通过readelf工具来查看两个文件的差异:
    # readelf -d pfoo
    Dynamic segment at offset 0x514 contains 21 entries:
      Tag        Type                         Name/Value
    0x00000001 (NEEDED)                     Shared library: [libprint.so]
    0x00000001 (NEEDED)                     Shared library: [libc.so.6]
    0x0000000c (INIT)                       0x8048344
    0x0000000d (FINI)                       0x80484e0
    0x00000004 (HASH)                       0x8048128
    0x00000005 (STRTAB)                     0x8048240
    0x00000006 (SYMTAB)                     0x8048170
    0x0000000a (STRSZ)                      178 (bytes)
    0x0000000b (SYMENT)                     16 (bytes)
    0x00000015 (DEBUG)                      0x0
    0x00000003 (PLTGOT)                     0x80495f8
    0x00000002 (PLTRELSZ)                   16 (bytes)
    0x00000014 (PLTREL)                     REL
    0x00000017 (JMPREL)                     0x8048334
    0x00000011 (REL)                        0x804832c
    0x00000012 (RELSZ)                      8 (bytes)
    0x00000013 (RELENT)                     8 (bytes)
    0x6ffffffe (VERNEED)                    0x804830c
    0x6fffffff (VERNEEDNUM)                 1
    0x6ffffff0 (VERSYM)                     0x80482f2
    0x00000000 (NULL)                       0x0
    [root@localhost ldpath]# readelf -d pfoo_r
    Dynamic segment at offset 0x518 contains 22 entries:
      Tag        Type                         Name/Value
    0x00000001 (NEEDED)                     Shared library: [libprint.so]
    0x00000001 (NEEDED)                     Shared library: [libc.so.6]
    0x0000000f (RPATH)                      Library rpath: [./]
    0x0000000c (INIT)                       0x8048348
    0x0000000d (FINI)                       0x80484e4
    0x00000004 (HASH)                       0x8048128
    0x00000005 (STRTAB)                     0x8048240
    0x00000006 (SYMTAB)                     0x8048170
    0x0000000a (STRSZ)                      181 (bytes)
    0x0000000b (SYMENT)                     16 (bytes)
    0x00000015 (DEBUG)                      0x0
    0x00000003 (PLTGOT)                     0x8049604
    0x00000002 (PLTRELSZ)                   16 (bytes)
    0x00000014 (PLTREL)                     REL
    0x00000017 (JMPREL)                     0x8048338
    0x00000011 (REL)                        0x8048330
    0x00000012 (RELSZ)                      8 (bytes)
    0x00000013 (RELENT)                     8 (bytes)
    0x6ffffffe (VERNEED)                    0x8048310
    0x6fffffff (VERNEEDNUM)                 1
    0x6ffffff0 (VERSYM)                     0x80482f6
    0x00000000 (NULL)                       0x0
    “readelf -d”可以用来查看ELF文件的动态节(Dynamic Section)。对比pfoo 和pfoo_r的结果我们可以发现,pfoo_r中多出来了RPATH项,指定”Library rpath: [./]”。通过这种方式,我们可以用非常小的代价(仅增加几乎可以忽略的空间开销),对每个ELF文件都指定最优化的搜索路径,达到提升性能的目的。这是我们比较推荐的一种方法。当然了,具体如果操作依赖于具体的软件系统的情况,简单的系统中直接将所有的库都放到/lib下也未尝不是一种简单易行的优化方案。

    转载于:https://www.cnblogs.com/skyofbitbit/p/3688299.html

    展开全文
  • (1)使用静态生成的可执行文件大于动态生成的可执行文件(程序占用内存较多) (2)使用静态生成的可执行文件难以升级,使用动态生成的可执行文件易于升级 (3)使用静态生成的可执行文件运行速度快,...
  • 1、动态和静态不同,链接动态不需要将被调用函数代码复制到包含调用代码的可执行文件中,相反链接器会在调用语句处嵌入一段指令,在该程序执行到这段指令时,会加载该动态并寻找被调用函数入口地址并...

    一、什么是动态库?
    1、动态库和静态库不同,链接动态库不需要将被调用的函数代码复制到包含调用代码的可执行文件中,相反链接器会在调用语句处嵌入一段指令,在该程序执行到这段指令时,会加载该动态库并寻找被调用函数的入口地址并执行之。
    2、如果动态库中的代码同时为多个进程所用,动态库在内存的实例仅需一份,为所有使用该库的进程所共享,因此动态库亦称共享库。
    3、动态库的拓展名是 .so 例libxxx.so。
    在这里插入图片描述
    二、动态库的构建顺序以及编译动态库
    A. 编辑库的实现代码和接口声明
    – 计算模块:calc.h、calc.c
    在这里插入图片描述

     – 显示模块:show.h、show.c
    

    在这里插入图片描述

     – 接口文件:math.h
    

    B. 编译成目标文件
    gcc -c -fpic calc.c
    gcc -c -fpic show.c
    C. 打包成动态库
    gcc -shared calc.o show.o -o libmath.so
    编译链接也可以合并成一步完成
    gcc -shared -fpic calc.c show.c -o libmath.so
    编辑库的使用代码
    main.c
    编译并链接动态库
    直接链接动态库
    gcc main.c libmath.so
    用-l指定库名,用-L指定库路径
    gcc mian.c -lmath -L.
    用-l指定库名,用LIBRARY_PATH环境变量指定库路径
    export LIBRARY_PATH=$LIBRARY_PATH:.
    gcc main.c -lmath
    三、动态库的动态加载和卸载动态库
    函数以及功能
    void* dlopen(char const* filename, int flag);
    功能:将共享库载入内存并获得其访问句柄
    参数:filename 动态库路径,若只给文件名不带目录,则根据LD_LIBRARY_PATH环境变量的值搜索动态库
    flag 加载方式,可取以下值:
    RTLD_LAZY - 延迟加载,使用动态库中的符号时才真的加载进内存。
    RTLD_NOW - 立即加载。
    返回值:成功返回动态库的访问句柄,失败返回NULL。
    句柄:句柄唯一地标识了系统内核所维护的共享库对象,将作为或许函数调用的参数

    int dlclose(void* handle);
    功能:从内存中卸载动态库
    参数:handle 动态库句柄
    返回值:成功返回0,失败返回非0。
    所卸载的共享库未必会真的从内存中立即消失,因为其他程序可能还需要使用该库
    只有所有使用该库的程序都显示或隐式地卸载了该库,该库所占用的内存空间才会真正得到释放
    无论所卸载的共享库是否真正被释放,传递给dlclose函数的句柄都会在该函数成功返回后立即失效

    load.c文件

      1 #include<stdio.h>
      2 #include<dlfcn.h>
      3 
      4 int main(){
      5 
      6     //加载动态库
      7     void* handle = dlopen("/home/tarena/day02/shared/libmath.so",RTLD_NOW);
      8     if(handle == NULL){
      9         fprintf(stderr,"dlopen:%s\n",dlerror());
     10         return -1;
     11     }
     12     //使用动态库
     13     int (*p_add)(int,int) = (int (*)(int,int))dlsym(handle,"add");
     14     if(p_add == NULL){
     15         fprintf(stderr,"dlsym:%s\n",dlerror());
     16         return -1;
     17     }
     18 
     19     int (*p_sub)(int,int) = (int(*)(int,int))dlsym(handle,"sub");
     20 
     21     if(p_sub == NULL){
     22         fprintf(stderr,"dlsym:%s\n",dlerror());
     23         return -1;
     24     }
     25     int (*p_show)(int,char,int,char,int) = (int(*)(int,char,int,char,int))dlsym(h    andle,"show");
     26     if(!p_show){
     27         fprintf(stderr,"dlsym:%s\n",dlerror());
     28     }
     29 
     30     int a=3,b=4;
     31     p_show(a,'+',b,'=',p_add(a,b));
     32     //卸载动态库
     33     if(dlclose(handle)){
     34       fprintf(stderr,"dlclose:%s\n",dlerror());
     35       return -1;
     36   }
     37 
     38     return 0;
     39 }
            
    

    构建可执行程序

    gcc load.c -o load
    

    运行程序./load
    在这里插入图片描述

    展开全文
  • linux 动态链接库加载

    千次阅读 2010-02-22 16:19:00
    Linux 动态链接Linux应用程序以以下两种方式之一链接到外部函数:要么在构建时与静态( lib*.a ) 静态地链接,并且将代码包含在该应用程序的可执行文件里;要么在运行时与共享( lib*.so ) 动态地链接...
  • 具体说来,动态链接器ld.so按照下面顺序来搜索需要动态共享: 1.ELF可执行文件中动态段中DT_RPATH指定路径。这实际上是通过一种不算很常用,却比较实用方法设置:编译目标代码时,可以...
  • 确定程序需要的库文件系统如何查找共享库文件加载共享库文件 本文帮助读者准备LPI101中的102.3目标,权重为1. 静态和动态链接 Linux系统里有两种类型的可执行程序: 静态链接的可执行程序。这种程序文件...
  • 对动态库的实际应用还不太熟悉读者可能曾经遇到过类似“error while loading shared libraries”这样错误,这是...1.ELF可执行文件中动态段中DT_RPATH指定路径。这实际上是通过一种不算很常用,却比较实...
  • Linux应用程序以以下两种方式之一链接到外部函数:要么在构建时与静态( lib*.a ) 静态地链接,并且将代码包含在该应用程序的可执行文件里;要么在运行时与共 享( lib*.so ) 动态地链接。通过动态...
  • 对动态库的实际应用还不太熟悉读者可能曾经遇到过类似“error while loading shared libraries”这样错误,这是...1.ELF可执行文件中动态段中DT_RPATH指定路径。这实际上是通过一种不算很常用,却比较实用
  • 库中实际上就是已编译好的函数代码,可以被程序直接调用。 Linux的库一般的位置在/lib或者/usr/lib中 静态库 静态库是复制拷贝到调用函数中的,函数运行的时候不再需要...因为库中相关代码是拷贝到可执行文件中,
  •  对动态库的实际应用还不太熟悉读者可能曾经遇到过类似... 具体说来,动态链接器ld.so按照下面顺序来搜索需要动态共享: 1.ELF可执行文件中动态段中DT_RPATH指定路径。这实际上是通过一种不算...
  • 当使用静态时,连接器会找出程序函数,将它们复制到执行文件中,因为是完整复制,所以一旦链接成功,可执行文件在静态不存在情况下依然可以执行。 动态与静态不同,动态会在程序内留下一个 标记,...
  • 区别:1、静态在编译时候被加载,动态在执行之后被加载2、加载静态编译生成的可执行文件比使用动态库的方式要大3、静态以.a为后缀,动态以.so为后缀静态库的制作:1.通过gcc -c add.c 生成目标文件 ...
  • 在Windows上动态是.dll,Linux上是.so静态链接是在编译时完成,因此可执行文件中以及包含有静态可执行文件可以单独运行;由于动态链接是在程序运行时完成可执行文件加载入内存运行时才会链接...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 188
精华内容 75
关键字:

linux可执行文件所加载的库

linux 订阅