【转】Linux动态链接(4)ldd与ldconfig

原文网址:http://tsecer.blog.163.com/blog/static/15018172012414105551345/

一、动态链接工具
ldd和ldconfig是动态链接的两个重要辅助工具,所谓“辅助”,是相对于真正的主角动态链接器ld.so,说它是工具,是只它相对于配置文件/etc/ld.so.conf文件。ldd不直接参与链接过程,它依赖于ld.so,但是ld.so不依赖于这个工具,事实上,ldd只是一个脚本,它在调用ld.so的时候传递了一些约定好的环境变量,从而使ld.so执行并打印一些内容出来。而ldconfig则间接的供ld.so使用,它构造的ld.so.cache文件中包含了系统一些特定文件夹中的所有的so文件,它还会根据so文件内部DT_SONAME动态标签来建立一些符号链接,并且生成ld.so.cache文件,在动态链接器查找so文件的时候,动态链接器在某些情况下(按照一定的顺序)会读取并解析这个文件的内容,从而得到一些so文件的绝对位置,毕竟在很多时候,可执行文件的DT_NEEDED中指明的so只有文件名而没有绝对路径。
二、ldd文件
1、实现方法
这个工具并不是一个可执行文件,而是一个非常简单的脚本。在我的
[[email protected] Desktop]# uname -a
Linux Harry 2.6.31.5-127.fc12.i686.PAE #1 SMP Sat Nov 7 21:25:57 EST 2009 i686 athlon i386 GNU/Linux
系统中,输出主要相关内容为
[[email protected] Desktop]# cat `which ldd`
#! /bin/bash
……
add_env="LD_TRACE_LOADED_OBJECTS=1 LD_WARN=$warn LD_BIND_NOW=$bind_now"
add_env="$add_env LD_LIBRARY_VERSION=\$verify_out"
add_env="$add_env LD_VERBOSE=$verbose"
if test "$unused" = yes; then
  add_env="$add_env LD_DEBUG=\"$LD_DEBUG${LD_DEBUG:+,}unused\""
fi

# The following use of cat is needed to make ldd work in SELinux
# environments where the executed program might not have permissions
# to write to the console/tty.  But only bash 3.x supports the pipefail
# option, and we don‘t bother to handle the case for older bash versions.
if set -o pipefail 2> /dev/null; then
  try_trace() {
    eval $add_env ‘"[email protected]"‘ | cat
  }
else
  try_trace() {
    eval $add_env ‘"[email protected]"‘  这里的[email protected]展开为/lib/ld-linux.so.2 argument
  }
fi
……
对于我们常见的ldd filename命令来说,它无条件添加的LD_TRACE_LOADED_OBJECTS=1 是最为关键的,也是整个ldd中无条件添加的一个导出环境变量,其它的都是可选变量,所以知道了这一点,我们就可以构造自己的ldd工具,例如:
[[email protected] Desktop]# LD_TRACE_LOADED_OBJECTS=1  /lib/ld-linux.so.2 /bin/ls 
    linux-gate.so.1 =>  (0x00110000)
    librt.so.1 => /lib/librt.so.1 (0x003d4000)
    libselinux.so.1 => /lib/libselinux.so.1 (0x003f4000)
    libcap.so.2 => /lib/libcap.so.2 (0x00c62000)
    libacl.so.1 => /lib/libacl.so.1 (0x00dd5000)
    libc.so.6 => /lib/libc.so.6 (0x0020a000)
    libpthread.so.0 => /lib/libpthread.so.0 (0x0038c000)
    /lib/ld-linux.so.2 (0x001e8000)
    libdl.so.2 => /lib/libdl.so.2 (0x00385000)
    libattr.so.1 => /lib/libattr.so.1 (0x001cd000)
这里也就可以理解为什么在交叉开发环境中没有交叉的ldd工具的原因,因为ldd需要可执行程序真正的在环境上加载运行之后才能确定各种地址,而这个条件在交叉环境上无法满足。
2、glibc支持代码
glibc中对该功能的解析集中在glibc-2.7\elf\rtld.c文件中
①、环境变量解析
该文件中process_envvars (enum mode *modep)函数负责对环境变量的解析
static void
process_envvars (enum mode *modep)
{
  char **runp = _environ;
  char *envline;
  enum mode mode = normal;
  char *debug_output = NULL;

/* This is the default place for profiling data file.  */
  GLRO(dl_profile_output)
    = &"/var/tmp\0/var/profile"[INTUSE(__libc_enable_secure) ? 9 : 0];

while ((envline = _dl_next_ld_env_entry (&runp)) != NULL)
    {
      size_t len = 0;

while (envline[len] != ‘\0‘ && envline[len] != ‘=‘)
    ++len;

if (envline[len] != ‘=‘)
    /* This is a "LD_" variable at the end of the string without
       a ‘=‘ character.  Ignore it since otherwise we will access
       invalid memory below.  */
    continue;

switch (len)
    {
……
    case 5:
      /* Debugging of the dynamic linker?  */
      if (memcmp (envline, "DEBUG", 5) == 0)
        {
          process_dl_debug (&envline[6]);
          break;
        }
      if (memcmp (envline, "AUDIT", 5) == 0)
        process_dl_audit (&envline[6]);
      break;
……
    case 9:
      /* Test whether we want to see the content of the auxiliary
         array passed up from the kernel.  */
      if (!INTUSE(__libc_enable_secure)
          && memcmp (envline, "SHOW_AUXV", 9) == 0)
        _dl_show_auxv ();
      break;
……
    case 12:
      /* The library search path.  */
      if (memcmp (envline, "LIBRARY_PATH", 12) == 0)
        {
          library_path = &envline[13];
          break;
        }
……
    case 20:
      /* The mode of the dynamic linker can be set.  */
      if (memcmp (envline, "TRACE_LOADED_OBJECTS", 20) == 0)
        mode = trace;
      break;

}
这里高亮了一些比较有意思的行为,大家可以自己尝试一下,其中LD_DEBUG选项对于动态库的调试尤为有用。
②、模式的使用
 if (__builtin_expect (mode, normal) == verify)
    {
      const char *objname;
      const char *err_str = NULL;
      struct map_args args;
      bool malloced;

args.str = rtld_progname;
      args.loader = NULL;
      args.is_preloaded = 0;
      args.mode = __RTLD_OPENEXEC;
      (void) _dl_catch_error (&objname, &err_str, &malloced, map_doit,
                  &args);
      if (__builtin_expect (err_str != NULL, 0))
        /* We don‘t free the returned string, the programs stops
           anyway.  */
        _exit (EXIT_FAILURE);
    }
……
  if (__builtin_expect (mode, normal) != normal)
    {
      /* We were run just to list the shared libraries.  It is
     important that we do this before real relocation, because the
     functions we call below for output may no longer work properly
     after relocation.  */
……
      for (l = main_map->l_next; l; l = l->l_next)
        if (l->l_faked)
          /* The library was not found.  */
          _dl_printf ("\t%s => not found\n", l->l_libname->name);
        else if (strcmp (l->l_libname->name, l->l_name) == 0)
          _dl_printf ("\t%s (0x%0*Zx)\n", l->l_libname->name,
              (int) sizeof l->l_map_start * 2,
              (size_t) l->l_map_start);
        else
          _dl_printf ("\t%s => %s (0x%0*Zx)\n", l->l_libname->name,
              l->l_name, (int) sizeof l->l_map_start * 2,
              (size_t) l->l_map_start);

  _exit (0);
    }
这里对于mode的解析和处理也比较直观,比较值得注意的是只要mode不是normal执行了操作之后都会执行_exit退出可执行程序,这意味着如果导出了某个使能trace模式(例如LD_TRACE_PRELINKING、LD_TRACE_LOADED_OBJECTS),那么所有的子进程都无法完成真正的功能,而是在打印完trace模式之后直接退出。
三、ldconfig
该文件是一个货真价实的可执行程序,源代码位于glibc-2.7\elf\ldconfig.c,它定义了cache文件的名称和格式,默认使用
#ifndef LD_SO_CACHE
# define LD_SO_CACHE SYSCONFDIR "/ld.so.cache"
#endif
配置文件。
1、文件格式
#define CACHEMAGIC "ld.so-1.7.0"

/* libc5 and glibc 2.0/2.1 use the same format.  For glibc 2.2 another
   format has been added in a compatible way:
   The beginning of the string table is used for the new table:
    old_magic
    nlibs
    libs[0]
    ...
    libs[nlibs-1]
    pad, new magic needs to be aligned
         - this is string[0] for the old format
    new magic - this is string[0] for the new format
    newnlibs
    ...
    newlibs[0]
    ...
    newlibs[newnlibs-1]
    string 1
    string 2
    ...
*/
struct file_entry
{
  int flags;        /* This is 1 for an ELF library.  */
  unsigned int key, value; /* String table indices.  */
};

struct cache_file
{
  char magic[sizeof CACHEMAGIC - 1];
  unsigned int nlibs;
  struct file_entry libs[0];
};
这个格式大致来说还是比较简单的,只是我们现在看到的一般使用的是glibc-2.11.2\elf\cache.c:print_cache (const char *cache_name)中的
  else if (format == 1)
    {
      printf (_("%d libs found in cache `%s‘\n"),
          cache_new->nlibs, cache_name);

/* Print everything.  */
      for (unsigned int i = 0; i < cache_new->nlibs; i++)
    print_entry (cache_data + cache_new->libs[i].key,
             cache_new->libs[i].flags,
             cache_new->libs[i].osversion,
             cache_new->libs[i].hwcap,
             cache_data + cache_new->libs[i].value);
    }
这一分支。我们看一下系统中打印以及计算方法:
[[email protected] Desktop]# ldconfig -p | more
1074 libs found in cache `/etc/ld.so.cache‘
    libz.so.1 (libc6) => /lib/libz.so.1
    libz.so (libc6) => /usr/lib/libz.so
    libx86.so.1 (libc6) => /usr/lib/libx86.so.1
    libxul.so (libc6) => /usr/lib/xulrunner-1.9.1/libxul.so
[[email protected] Desktop]# hexdump -C /etc/ld.so.cache | more
00000000  6c 64 2e 73 6f 2d 31 2e  37 2e 30 00 2a 04 00 00  |ld.so-1.7.0.*...|
00000010  03 00 00 00 e0 64 00 00  ea 64 00 00 03 00 00 00  |.....d...d......|
根据代码计算方法,此时真正偏移为
0x00000010 + 0x42a* sizeof (struct file_entry)=0x00000010 + 0x42a*0xC=0x3208
[[email protected] Desktop]# hexdump -Cs 0x3208  /etc/ld.so.cache | more
00003208  67 6c 69 62 63 2d 6c 64  2e 73 6f 2e 63 61 63 68  |glibc-ld.so.cach|
00003218  65 31 2e 31 32 04 00 00  51 b7 00 00 00 00 00 00  |e1.12...Q.......|
00003228  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
其中0x432==1074,也就是ldconfig -p显示的cache项个数目。
2、ldconfig简单功能
它会找到so文件中的DT_SONAME节,然后建立一些符号链接,从而可以完成快速搜索和引用。它同样会对自己收集的so文件进行排序,但是排序的标准并不是完全的字符比较,同样还要参考其他的信息,我看了一下我的系统的ldconfig -p 输出,是按照逆序排列的,其真正使用的比较函数为glibc-2.7\elf\cache.c。
3、cache文件何时使用
在加载一个共享库时,其搜索顺序为:
_dl_map_object (struct link_map *loader, const char *name, int preloaded,
        int type, int trace_mode, int mode, Lmid_t nsid)
……
     /* When the object has the RUNPATH information we don‘t use any
         RPATHs.  */
      if (loader == NULL || loader->l_info[DT_RUNPATH] == NULL)如果定义了DT_RUNPATH,忽略DT_RPTAH,其中DT_RUNPATH好像是比较新引入的概念,关于这两点,之后可以再讨论,默认使用-rpath添加的路径都放入DT_RPATH中。
{
 for (l = loader; l; l = l->l_loader)
        if (cache_rpath (l, &l->l_rpath_dirs, DT_RPATH, "RPATH"))从DT_RPATH指明文件夹下搜索。
          {
        fd = open_path (name, namelen, preloaded, &l->l_rpath_dirs,
                &realname, &fb, loader, LA_SER_RUNPATH,
                &found_other_class);
        if (fd != -1)
          break;

did_main_map |= l == main_map;
          }
}
      /* Try the LD_LIBRARY_PATH environment variable.  */
      if (fd == -1 && env_path_list.dirs != (void *) -1)
    fd = open_path (name, namelen, preloaded, &env_path_list,从LD_LIBRARY_PATH中指明的环境变量中查找。
            &realname, &fb,
            loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
            LA_SER_LIBPATH, &found_other_class);
……
   if (fd == -1 && loader != NULL
      && cache_rpath (loader, &loader->l_runpath_dirs,从DT_RUNPATH中搜索。
              DT_RUNPATH, "RUNPATH"))
    fd = open_path (name, namelen, preloaded,
            &loader->l_runpath_dirs, &realname, &fb, loader,
            LA_SER_RUNPATH, &found_other_class);
……
      /* Check the list of libraries in the file /etc/ld.so.cache,
         for compatibility with Linux‘s ldconfig program.  */
      const char *cached = _dl_load_cache_lookup (name);从cache文件中搜索。
……
       /* Finally, try the default path.  */搜索系统默认文件夹下so文件,这个文件夹一般为/lib和/usr/lib两个系统文件夹。这意味着放入系统默认文件夹/usr和/usr/lib下的so文件不需要重新执行ldconfig就可以被动态链接器找到。
      if (fd == -1
      && ((l = loader ?: GL(dl_ns)[nsid]._ns_loaded) == NULL
          || __builtin_expect (!(l->l_flags_1 & DF_1_NODEFLIB), 1))
      && rtld_search_dirs.dirs != (void *) -1)
    fd = open_path (name, namelen, preloaded, &rtld_search_dirs,
            &realname, &fb, l, LA_SER_DEFAULT, &found_other_class);

4、ldconfig何时执行
在系统启动的时候会执行一次,之后如果在系统文件夹中添加新的文件,需要手动执行ldconfig。我在我使用的fedora core发行版本中没有找到在哪个脚本中执行了这个脚本,只是在etc文件夹搜索到一个ldconfig字符串。
[[email protected] Desktop]# grep -r ldconfig /etc/selinux/targeted/
/etc/selinux/targeted/modules/active/file_contexts:/var/cache/ldconfig(/.*)?system_u:object_r:ldconfig_cache_t:s0
/etc/selinux/targeted/modules/active/file_contexts:/sbin/ldconfig    --    system_u:object_r:ldconfig_exec_t:s0
……
删掉该文件的这一行,重启系统,放在标准/usr/lib下的so文件不会被添加到cache文件中,所以猜测自动加载可能和这一项有关。
5、DT_RUNPATH和DT_RPATH区别
简单看了一下ld的代码(链接器有些代码是动态生成的,所以有些比较晦涩)。看到通常-rpath只放入DT_RPATH标签中,这也是默认行为,但是如果在命令行上通过enable-new-tags选项,那么这个目录在放入DT_RPATH之后,还会在DT_RUNPATH中放入一份,并且两者完全相同。下面是一个测试例子:
[[email protected] RUNPATHAndRPath]$ ls
foo.c  Makefile

[[email protected] RUNPATHAndRPath]$ cat Makefile 
test:normal newtag
    readelf -d normal.so
    readelf -d newtag.so
normal:foo.o
    ld -shared -o normal.so foo.o -rpath $(shell pwd)
newtag:foo.o
    ld -shared -o newtag.so foo.o -rpath $(shell pwd) --enable-new-dtags
foo.o:foo.c
    gcc -fPIC -c $< -o [email protected]
clean:
    rm -f *.o *.so

[[email protected] RUNPATHAndRPath]$ make 
gcc -fPIC -c foo.c -o foo.o
ld -shared -o normal.so foo.o -rpath /home/tsecer/CodeTest/RUNPATHAndRPath
ld -shared -o newtag.so foo.o -rpath /home/tsecer/CodeTest/RUNPATHAndRPath --enable-new-dtags
readelf -d normal.so

Dynamic section at offset 0x17c contains 7 entries:
  Tag        Type                         Name/Value
 0x0000000f (RPATH)                      Library rpath: [/home/tsecer/CodeTest/RUNPATHAndRPath]
 0x00000004 (HASH)                       0xb4
 0x00000005 (STRTAB)                     0x12c
 0x00000006 (SYMTAB)                     0xdc
 0x0000000a (STRSZ)                      67 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000000 (NULL)                       0x0
readelf -d newtag.so

Dynamic section at offset 0x17c contains 8 entries:
  Tag        Type                         Name/Value
 0x0000000f (RPATH)                      Library rpath: [/home/tsecer/CodeTest/RUNPATHAndRPath]
 0x0000001d (RUNPATH)                    Library runpath: [/home/tsecer/CodeTest/RUNPATHAndRPath]
 0x00000004 (HASH)                       0xb4
 0x00000005 (STRTAB)                     0x12c
 0x00000006 (SYMTAB)                     0xdc
 0x0000000a (STRSZ)                      67 (bytes)
 0x0000000b (SYMENT)                     16 (bytes)
 0x00000000 (NULL)                       0x0
[[email protected] RUNPATHAndRPath]$ 
从输出可以看到,当使能了--enable-new-dtags选项之后,最终的文件中除了DT_RPATH之外,还有DT_RUNPATh,两者相同。但是即使这样,它将会影响这个-rpath路径在动态链接器中对LD_LIBRARY_PATH的前后搜索顺序,因为如果定义了DT_RUNPATH,那么LD_LIBRARY_PATH在最开始,否则rpath在最开始。链接器中相关处理代码 BinUtils\binutils-2.21.1\bfd\elflink.c:bfd_elf_size_dynamic_sections

if (rpath != NULL)
    {
      bfd_size_type indx;

indx = _bfd_elf_strtab_add (elf_hash_table (info)->dynstr, rpath,
                      TRUE);
      if (indx == (bfd_size_type) -1
          || !_bfd_elf_add_dynamic_entry (info, DT_RPATH, indx))
        return FALSE;

if  (info->new_dtags)
        {
          _bfd_elf_strtab_addref (elf_hash_table (info)->dynstr, indx);
          if (!_bfd_elf_add_dynamic_entry (info, DT_RUNPATH, indx))
        return FALSE;
        }
    }
6、ld.so.cache文件格式
无论是
struct file_entry
{
  int flags;        /* This is 1 for an ELF library.  */
  unsigned int key, value; /* String table indices.  */
};
还是
struct file_entry_new
{
  int32_t flags;        /* This is 1 for an ELF library.  */
  uint32_t key, value;        /* String table indices.  */
  uint32_t osversion;        /* Required OS version.     */
  uint64_t hwcap;        /* Hwcap entry.     */
};
它们最为核心的就是 key和value,其中key是不带路径的so文件,而value则是so文件的绝对路径。由于搜索文件夹并不唯一,所以同一名称的so可能在多个文件夹中存在,所以这里的搜索代码有对这种情况的处理。
glibc-2.7\elf\dl-cache.c文件中
#define SEARCH_CACHE(cache) \
宏在二分查找的时候内部循环即是为了这种情况处理。

时间: 2024-11-08 21:45:49

【转】Linux动态链接(4)ldd与ldconfig的相关文章

再探Linux动态链接 -- 关于动态库的基础知识

  在近一段时间里,由于多次参与相关专业软件Linux运行环境建设,深感有必要将这些知识理一理,供往后参考. 编译时和运行时 纵观程序编译整个过程,细分可分为编译(Compiling,指的是语言到平台相关目标文件这一层次)和链接(Linking,指目标文件到最终形成可执行文件这一层次),这个总的过程可称为编译时:就动态链接而言,还存在一个运行时,即程序在被操作系统加载的过程中,系统将该程序需要的动态库加载至内存到程序开始运行的这一段过程.明确这两个过程在一般linux开发中的地位,以及了解每个"

Linux动态链接(1)惰性链接

一.动态链接在Linux(unix族谱)下,共享目标文件称为so文件,它和windows下的DLL机制对应,该功能在节省物理内存使用量上有重要意义,但是更重要的它还是一种扩展框架,也就是很多所谓的“插件”的实现基础.从它的出现频率上来看,它和Linux下的多线程具有同等重要的地位,甚至更高.因为很多可执行文件都没有使用pthread库,但是几乎所有的Linux发行版本的可执行文件都是动态链接生成的可执行文件.从实现机制上看,两种机制也有相同之处,它们都有共享和隔离,而线程和共享库内的线程私有数据

Linux动态链接(2)so初始化执行

一.so文件和exe文件这两种文件其实具有很多相似自出,或者说so文件是介于obj文件和exe文件的一种中间过渡形式,它虽然不能直接运行(但是经过特殊编写的so文件内核是支持加载运行的,例如ld.so),但是具有了自己的一些更为高级的内容,例如一些初始化节,got表等内容,虽然孱弱,但是它具有了更加完善的生物形态.但是大家不要用进化论的观点来认为so文件比exe文件出现的早,事实上so是比较新的一个概念.我们看一下这些文件类型的标识类型说明#define ET_NONE   0#define E

linux动态链接

1, 编译,使用-shared和-fpic 生成动态链接库库源码:test.c #include <stdio.h> #include <string.h> #include <stdlib.h> static void printline(int len) { int i; for(i = 0;i<len;i++) { printf("="); } printf("\n"); } void print(char * s)

Linux动态链接之GOT与PLT

转载于:http://www.cnblogs.com/xingyun/archive/2011/12/10/2283149.html 我们知道函数名就是一个内存地址,这个地址指向函数的入口.调用函数就是压入参数,保存返回地址,然后跳转到函数名指向的代码.问题是,如果函数在共享库中,共享库加载的地址本身就不确定,函数地址也就不确定了,那如何调用共享库中的函数呢?这就是本文要回答的. 我们先来看一小段代码(test.c): #include <stdio.h>void hello_world(vo

程序员的自我修养-链接、装载与库-7 动态链接

动态链接 静态链接的好处:使得不同部门的开发者能够相对独立的开发和测试自己的程序模块,促进了开发效率,原先限制程序的规模也随之扩大. 缺点:浪费内存空间和磁盘空间,模块更新困难 种种罪行: 空间浪费:想想一下每个程序内部除了printf, scanf, strlen等公用库函数,还有非常多的其他库函数以及他们所需的辅助数据结构.在Linux中一个普通的c程序需要的静态库至少1MB以上. 简而言之就是,相同的目标模块每一个程序都会保留一份obj文件. 更新困难:一旦程序中有任何模块更新,整个程序都

装载与动态链接

装载与动态链接 1可执行文件的装载与进程 可执行文件只有装载到内存后才能被CPU执行.早期的程序装载十分简陋,装载的基本过程就是把程序从外部存储器中读取到内存中的某个位置. 历史有过的装载方式包括覆盖装载.页映射. 1.1 进程虚拟地址空间 程序是一个静态的概念,它就是一些预先编译好的指令和数据集合的一个文件:进程则是一个动态的概念,它是程序运行的一个过程. 每个程序被运行起来以后,都有自己的虚拟地址空间,这个虚拟地址空间的大小由计算机的硬件平台决定,具体地说是由CPU的位数决定的. 1.2 装

Linux下的库操作工具-nm、ar、ldd、ldconfig和ld.so

Linux下的库操作工具-nm.ar.ldd.ldconfig和ld.so 1.nm [options] file 列出file中的所有符号 [option] -c 将符号转化为用户级的名字 -s 当用于.a文件即静态库时,输出把符号名映射到定义该符号的模块或成员名的索引 -u 显示在file外定义的符号或没有定义的符号 -l 显示每个符号的行号,或为定义符号的重定义项 2.ar {dmpqrtx} [member] archive file 用于操作高度结构化的存档文件(.a) [option

Linux 静态链接库和动态连接库

(0)文件夹 VMware 下安装Ubuntu的吐血经历 零基础学习Shell编程 Linux下的makefile的妙用 Linux调试神器 -- gdb 十分钟学会Python的基本类型 Linux 静态链接库和动态连接库 一:静态链接库的应用  三步走~~~ ##g++ -c StaticMath.cpp ##ar -crv libstaticmath.a StaticMath.o ##g++ -o run test_a.cpp -L. -lstaticmath #[@sjs_37_33 l