dl_main源码分析(一)

dl_main源码分析(一)

因为dl_main函数太长,分多个章节分析,本章先分析前面的几部分代码。

elf/rtld.c
dl_main第一部分

static void
dl_main (const ElfW(Phdr) *phdr,
     ElfW(Word) phnum,
     ElfW(Addr) *user_entry,
     ElfW(auxv_t) *auxv)
{
  const ElfW(Phdr) *ph;
  enum mode mode;
  struct link_map *main_map;
  size_t file_size;
  char *file;
  bool has_interp = false;
  unsigned int i;
  bool prelinked = false;
  bool rtld_is_main = false;
  void *tcbp = NULL;

  GL(dl_error_catch_tsd) = &_dl_initial_error_catch_tsd;
  GL(dl_init_static_tls) = &_dl_nothread_init_static_tls;
  GL(dl_rtld_lock_recursive) = rtld_lock_default_lock_recursive;
  GL(dl_rtld_unlock_recursive) = rtld_lock_default_unlock_recursive;
  GL(dl_make_stack_executable_hook) = &_dl_make_stack_executable;

  process_envvars (&mode);

首先设置一些全局变量,这些变量等使用的时候再介绍,然后调用process_envvars函数处理环境变量。

elf/rtld.c
dl_main->process_envvars

static void
process_envvars (enum mode *modep)
{
  char **runp = _environ;
  char *envline;
  enum mode mode = normal;
  char *debug_output = NULL;

  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] != '=')
        continue;

      switch (len)
    {

    ...

    case 12:
      if (memcmp (envline, "LIBRARY_PATH", 12) == 0)
        {
          library_path = &envline[13];
          break;
        }

    ...

    }
    }

  *modep = mode;

  ...
}

_environ是环境变量的指针,从上一章_dl_sysdep_start函数中的宏定义DL_FIND_ARG_COMPONENTS获得。
_dl_profile_output取/var/tmp或/var/profile,该变量用于为共享库生成profile数据。
dl_next_ld_env_entry函数依次找到环境变量中以‘LD’开头的后一个字符的地址,例如LD_PRELOAD,最后返回‘P’所在的地址。
接下来统计LD_’开头后的字符长度,保存在len中,例如LD_PRELOAD,则返回‘PRELOAD’的长度,即7。
如果下一个字符不是‘=’号,则继续循环,例如LD_PRELOAD后不是‘=’号,则直接返回。
再往下根据前面统计的len不同长度进行不同的处理,这里只看最重要的LIBRARY_PATH,也即LD_LIBRARY_PATH环境变量,内部存储了共享库的搜索路径,将其设置到library_path即可。

elf/rtld.c
dl_main->process_envvars->_dl_next_ld_env_entry

char* internal_function _dl_next_ld_env_entry (char ***position)
{
  char **current = *position;
  char *result = NULL;

  while (*current != NULL)
    {
      if (__builtin_expect ((*current)[0] == 'L', 0)
      && (*current)[1] == 'D' && (*current)[2] == '_')
    {
      result = &(*current)[3];
      *position = ++current;
      break;
    }
      ++current;
    }

  return result;
}

该函数举个例子就明白了,假设环境变量LD_PRELOAD,经过while循环,if语句判断前三个字符分别为‘L’、‘D’和‘_’,于是result变量从第四个字符开始,最后返回‘P’所在的地址。

elf/rtld.c
dl_main第二部分

  if (*user_entry == (ElfW(Addr)) ENTRY_POINT)
    {
      ...
    }
  else
    {
      main_map = _dl_new_object ((char *) "", "", lt_executable, NULL,
                 __RTLD_OPENEXEC, LM_ID_BASE);
      main_map->l_phdr = phdr;
      main_map->l_phnum = phnum;
      main_map->l_entry = *user_entry;
      _dl_add_to_namespace_list (main_map, LM_ID_BASE);
    }

  main_map->l_map_end = 0;
  main_map->l_text_end = 0;
  main_map->l_map_start = ~0;
  ++main_map->l_direct_opencount;

如果用户程序的入口地址user_entry为ENTRY_POINT也即ld.so的_start函数的起始地址,则表示ld.so是被调用的程序,本章不考虑这种情况。另一种情况ld.so就是作为解释器被调用了。
此时首先通过_dl_new_object函数为用户程序构造link_map,即main_map,对其进行初始化。
然后通过_dl_add_to_namespace_list函数将该main_map添加到全局链表中。

elf/object.c
dl_main->_dl_new_object

struct link_map *
internal_function
_dl_new_object (char *realname, const char *libname, int type,
        struct link_map *loader, int mode, Lmid_t nsid)
{
  size_t libname_len = strlen (libname) + 1;
  struct link_map *new;
  struct libname_list *newname;

  new = (struct link_map *) calloc (sizeof (*new) + sizeof (struct link_map *)
                    + sizeof (*newname) + libname_len, 1);

  new->l_real = new;
  new->l_symbolic_searchlist.r_list = (struct link_map **) ((char *) (new + 1));

  new->l_libname = newname
    = (struct libname_list *) (new->l_symbolic_searchlist.r_list + 1);
  newname->name = (char *) memcpy (newname + 1, libname, libname_len);
  newname->dont_free = 1;

  new->l_name = realname;
  new->l_type = type;
  if ((GLRO(dl_debug_mask) & DL_DEBUG_UNUSED) == 0)
    new->l_used = 1;
  new->l_loader = loader;
  new->l_ns = nsid;
  new->l_scope = new->l_scope_mem;
  new->l_scope_max = sizeof (new->l_scope_mem) / sizeof (new->l_scope_mem[0]);

  int idx = 0;
  loader = new;

  if (idx == 0 || &loader->l_searchlist != new->l_scope[0])
    {
      new->l_scope[idx] = &loader->l_searchlist;
    }
  new->l_local_scope[0] = &new->l_searchlist;

  if (realname[0] != '\0')
    {
      size_t realname_len = strlen (realname) + 1;
      char *origin;
      char *cp;

      if (realname[0] == '/')
    {
      cp = origin = (char *) malloc (realname_len);
      if (origin == NULL)
        {
          origin = (char *) -1;
          goto out;
        }
    }
      else
    {
      size_t len = realname_len;
      char *result = NULL;

      origin = NULL;
      do
        {
          char *new_origin;

          len += 128;
          new_origin = (char *) realloc (origin, len);
          if (new_origin == NULL)
        break;
          origin = new_origin;
        }
      while ((result = __getcwd (origin, len - realname_len)) == NULL
         && errno == ERANGE);

      if (result == NULL)
        {
          free (origin);
          origin = (char *) -1;
          goto out;
        }

      cp = (strchr) (origin, '\0');
      if (cp[-1] != '/')
        *cp++ = '/';
    }

      cp = __mempcpy (cp, realname, realname_len);

      do
    --cp;
      while (*cp != '/');

      if (cp == origin)
    ++cp;
      *cp = '\0';

    out:
      new->l_origin = origin;
    }
  return new;
}

_dl_new_object函数主要创建了一个link_map结构并进行相应的初始化,一些相关的变量后面遇到了再分析。值得注意的是glibc很多地方在为一个结构分配内存的时,都多分配了一些内存,本函数中就多分配了sizeof (struct link_map *) + sizeof (*newname) + libname_len这么多的内存(其实还有audit_space,本章不关心audit的内容),前面用于存放l_symbolic_searchlist.r_list的指针,后面用于存放路径字符串。
接着为库路径realname(如果存在)分配内存,如果realname是绝对路径,则直接分配内存并通过__mempcpy函数拷贝字符串到新分配的内存,如果是相对路径,则先通过__getcwd函数获取当前工作路径,再拼接成最后的绝对路径。
最后返回新创建的link_map结构指针new。

elf/dl-object.c
dl_main->_dl_add_to_namespace_list

void
internal_function
_dl_add_to_namespace_list (struct link_map *new, Lmid_t nsid)
{
  if (GL(dl_ns)[nsid]._ns_loaded != NULL)
    {
      struct link_map *l = GL(dl_ns)[nsid]._ns_loaded;
      while (l->l_next != NULL)
    l = l->l_next;
      new->l_prev = l;
      l->l_next = new;
    }
  else
    GL(dl_ns)[nsid]._ns_loaded = new;

  ++GL(dl_ns)[nsid]._ns_nloaded;
  new->l_serial = GL(dl_load_adds);
  ++GL(dl_load_adds);
}

这里就是将新创建的link_map即new插入到全局列表dl_ns当中,nsid确定插入的位置,并更新相应参数。

elf/rtld.c
dl_main第三部分

  for (ph = phdr; ph < &phdr[phnum]; ++ph)
    switch (ph->p_type)
      {
      case PT_PHDR:
    main_map->l_addr = (ElfW(Addr)) phdr - ph->p_vaddr;
    break;
      case PT_DYNAMIC:
    main_map->l_ld = (void *) main_map->l_addr + ph->p_vaddr;
    break;
      case PT_INTERP:
    _dl_rtld_libname.name = ((const char *) main_map->l_addr
                 + ph->p_vaddr);
    GL(dl_rtld_map).l_libname = &_dl_rtld_libname;
    if (GL(dl_rtld_map).l_ld == NULL)
      {
        const char *p = NULL;
        const char *cp = _dl_rtld_libname.name;

        while (*cp != '\0')
          if (*cp++ == '/')
        p = cp;

        if (p != NULL)
          {
        _dl_rtld_libname2.name = p;
        _dl_rtld_libname.next = &_dl_rtld_libname2;
          }
      }

    has_interp = true;
    break;
      case PT_LOAD:
    {
      ElfW(Addr) mapstart;
      ElfW(Addr) allocend;

      mapstart = (main_map->l_addr
              + (ph->p_vaddr & ~(GLRO(dl_pagesize) - 1)));
      if (main_map->l_map_start > mapstart)
        main_map->l_map_start = mapstart;

      allocend = main_map->l_addr + ph->p_vaddr + ph->p_memsz;
      if (main_map->l_map_end < allocend)
        main_map->l_map_end = allocend;
      if ((ph->p_flags & PF_X) && allocend > main_map->l_text_end)
        main_map->l_text_end = allocend;
    }
    break;

      case PT_TLS:
    if (ph->p_memsz > 0)
      {
        main_map->l_tls_blocksize = ph->p_memsz;
        main_map->l_tls_align = ph->p_align;
        if (ph->p_align == 0)
          main_map->l_tls_firstbyte_offset = 0;
        else
          main_map->l_tls_firstbyte_offset = (ph->p_vaddr
                          & (ph->p_align - 1));
        main_map->l_tls_initimage_size = ph->p_filesz;
        main_map->l_tls_initimage = (void *) ph->p_vaddr;

        GL(dl_tls_max_dtv_idx) = main_map->l_tls_modid = 1;
      }
    break;

      case PT_GNU_STACK:
    GL(dl_stack_flags) = ph->p_flags;
    break;

      case PT_GNU_RELRO:
    main_map->l_relro_addr = ph->p_vaddr;
    main_map->l_relro_size = ph->p_memsz;
    break;
      }

接下来遍历用户程序的Segment头。
类型为PT_PHDR的Segment标识了第一个Segment头的装载地址p_vaddr,将实际的装载地址phdr减去该值就是整个elf文件的装载地址,存储在l_addr中。
用上面确定的装载地址加上.dynamic节的装载地址p_vaddr就得到该节实际的装载地址,将其存储在l_ld中。
再往下找到类型为PT_INTERP的Segment头,其装载地址就是解释器自身路径的起始地址,将该路径保存在_dl_rtld_libname中,将标准的路径保存在_dl_rtld_libname2中,两个变量的类型都是libname_list,用来形成字符串链表。
接下来计算代码段、数据段、bss段(这些段的类型都为PT_LOAD)的最低起始地址,保存在main_map的l_map_start中,最高结束地址保存在l_map_end中。
再往下是类型分别为PT_TLS、PT_GNU_STACK和PT_GNU_RELRO的Segment头,依次存储其中的信息,这里就不仔细看了。

elf/rtld.c
dl_main第四部分

  if (main_map->l_tls_initimage != NULL)
    main_map->l_tls_initimage
      = (char *) main_map->l_tls_initimage + main_map->l_addr;
  if (! main_map->l_map_end)
    main_map->l_map_end = ~0;
  if (! main_map->l_text_end)
    main_map->l_text_end = ~0;
  if (! GL(dl_rtld_map).l_libname && GL(dl_rtld_map).l_name)
    {
      _dl_rtld_libname.name = GL(dl_rtld_map).l_name;
      GL(dl_rtld_map).l_libname =  &_dl_rtld_libname;
    }

  if (GL(dl_rtld_map).l_info[DT_SONAME] != NULL
      && strcmp (GL(dl_rtld_map).l_libname->name,
         (const char *) D_PTR (&GL(dl_rtld_map), l_info[DT_STRTAB])
         + GL(dl_rtld_map).l_info[DT_SONAME]->d_un.d_val) != 0)
    {
      static struct libname_list newname;
      newname.name = ((char *) D_PTR (&GL(dl_rtld_map), l_info[DT_STRTAB])
              + GL(dl_rtld_map).l_info[DT_SONAME]->d_un.d_ptr);
      newname.next = NULL;
      newname.dont_free = 1;

      GL(dl_rtld_map).l_libname->next = &newname;
    }

l_tls_initimage是tls数据映像地址,需要加上装载地址l_addr。
接下来如果没有设置l_map_end和l_text_end就对其进行重置。
接下来如果没有使用解释器,或者ld.so被单独调用,就设置_dl_rtld_libname为l_name。
再往下如果指定了DT_SONAME,就将其加入到全局的l_libname中。

elf/rtld.c
dl_main第六部分

  if (! rtld_is_main)
    {
      elf_get_dynamic_info (main_map, NULL);
      _dl_setup_hash (main_map);
    }

  struct link_map **first_preload = &GL(dl_rtld_map).l_next;
  _dl_init_paths (library_path);
  struct r_debug *r = _dl_debug_initialize (GL(dl_rtld_map).l_addr,
                        LM_ID_BASE);
  r->r_state = RT_CONSISTENT;

如果ld.so以解释器身份运行,这里通过elf_get_dynamic_info获取用户程序.dynamic段的信息,然后通过_dl_setup_hash函数获取.hash节的信息并初始化,这两个函数在上一章分析过了。
接下来通过_dl_init_paths函数设置库的搜索路径,传入的参数library_path是在process_envvars函数中从堆栈中取出的LD_LIBRARY_PATH的值。
剩余的代码和调试相关,本章不关心,后面有时间再来研究。

elf/dl-load.c
dl_main->_dl_init_paths第一部分

void internal_function _dl_init_paths (const char *llp)
{
  size_t idx;
  const char *strp;
  struct r_search_path_elem *pelem, **aelem;
  size_t round_size;
  struct link_map *l;

  const char *errstring = NULL;

  capstr = _dl_important_hwcaps (GLRO(dl_platform), GLRO(dl_platformlen),
                 &ncapstr, &max_capstrlen);
  aelem = rtld_search_dirs.dirs = (struct r_search_path_elem **)
    malloc ((nsystem_dirs_len + 1) * sizeof (struct r_search_path_elem *));

  round_size = ((2 * sizeof (struct r_search_path_elem) - 1
         + ncapstr * sizeof (enum r_dir_status))
        / sizeof (struct r_search_path_elem));

  rtld_search_dirs.dirs[0] = (struct r_search_path_elem *)
    malloc ((sizeof (system_dirs) / sizeof (system_dirs[0]))
        * round_size * sizeof (struct r_search_path_elem));

  rtld_search_dirs.malloced = 0;
  pelem = GL(dl_all_dirs) = rtld_search_dirs.dirs[0];
  strp = system_dirs;
  idx = 0;

  do
    {
      size_t cnt;

      *aelem++ = pelem;
      pelem->what = "system search path";
      pelem->where = NULL;
      pelem->dirname = strp;
      pelem->dirnamelen = system_dirs_len[idx];
      strp += system_dirs_len[idx] + 1;
      for (cnt = 0; cnt < ncapstr; ++cnt)
    pelem->status[cnt] = unknown;
      pelem->next = (++idx == nsystem_dirs_len ? NULL : (pelem + round_size));
      pelem += round_size;
    }
  while (idx < nsystem_dirs_len);

  max_dirnamelen = SYSTEM_DIRS_MAX_LEN;
  *aelem = NULL;

  ...

_dl_init_paths函数的第一部分代码首先分配内存空间,然后遍历system_dirs,将其中的nsystem_dirs_len个路径依次添加到pelem中,通过next变量形成链表,最后其实都添加到_dl_all_dirs中。system_dirs、system_dirs_len和nsystem_dirs_len三个变量的宏定义如下,

#include "trusted-dirs.h"
static const char system_dirs[] = SYSTEM_DIRS;
static const size_t system_dirs_len[] =
{
  SYSTEM_DIRS_LEN
};
#define nsystem_dirs_len \
  (sizeof (system_dirs_len) / sizeof (system_dirs_len[0]))

SYSTEM_DIRS、SYSTEM_DIRS_LEN两个宏定义定义在trusted-dirs.h头文件中,trusted-dirs.h头文件并不是glibc源文件,而是在Makefile中,在gcc编译阶段形成。

elf/dl-load.c
dl_main->_dl_init_paths第二部分

  ...

  l = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
  if (l != NULL)
    {
      if (l->l_info[DT_RUNPATH])
    {
      decompose_rpath (&l->l_runpath_dirs,
               (const void *) (D_PTR (l, l_info[DT_STRTAB])
                       + l->l_info[DT_RUNPATH]->d_un.d_val),
               l, "RUNPATH");
      l->l_rpath_dirs.dirs = (void *) -1;
    }
      else
    {
      l->l_runpath_dirs.dirs = (void *) -1;

      if (l->l_info[DT_RPATH])
        {
          decompose_rpath (&l->l_rpath_dirs,
                   (const void *) (D_PTR (l, l_info[DT_STRTAB])
                           + l->l_info[DT_RPATH]->d_un.d_val),
                   l, "RPATH");
          l->l_rpath_dirs.malloced = 0;
        }
      else
        l->l_rpath_dirs.dirs = (void *) -1;
    }
    }

  ...

这里的_ns_loaded是在前面通过_dl_add_to_namespace_list添加到全局中去的,该link_map就是用户程序对应的link_map。
其.dynamic段中的信息DT_RUNPATH和DT_RPATH都是应用程序本身提供的库搜索路径,glibc的老版本使用DT_RPATH,而新版本使用DT_RUNPATH,因此这里先查看是否有DT_RUNPATH,再查看是否有DT_RPATH。
无论是哪种,最关键的就是通过decompose_rpath函数对其进行解析并设置到link_map中。

elf/dl-load.c
dl_main->_dl_init_paths->decompose_rpath

static bool internal_function
decompose_rpath (struct r_search_path_struct *sps,
         const char *rpath, struct link_map *l, const char *what)
{
  const char *where = l->l_name;
  char *copy;
  char *cp;
  struct r_search_path_elem **result;
  size_t nelems;
  const char *errstring = NULL;

  if (__builtin_expect (GLRO(dl_inhibit_rpath) != NULL, 0)
      && !INTUSE(__libc_enable_secure))
    {
      const char *inhp = GLRO(dl_inhibit_rpath);

      do
    {
      const char *wp = where;

      while (*inhp == *wp && *wp != '\0')
        {
          ++inhp;
          ++wp;
        }

      if (*wp == '\0' && (*inhp == '\0' || *inhp == ':'))
        {
          sps->dirs = (void *) -1;
          return false;
        }

      while (*inhp != '\0')
        if (*inhp++ == ':')
          break;
    }
      while (*inhp != '\0');
    }

  copy = expand_dynamic_string_token (l, rpath, 1);

  nelems = 0;
  for (cp = copy; *cp != '\0'; ++cp)
    if (*cp == ':')
      ++nelems;

  result = (struct r_search_path_elem **) malloc ((nelems + 1 + 1)
                          * sizeof (*result));

  fillin_rpath (copy, result, ":", 0, what, where);
  free (copy);

  sps->dirs = result;
  sps->malloced = 1;
  return true;
}

第一个if语句检查用户程序对应的link_map是否在dl_inhibit_rpath中,dl_inhibit_rpath变量用“:”分割路径,用于忽略RUNPATH或者RPATH中提供的信息,如果找到一个路径inhp和where一致,则直接退出。
expand_dynamic_string_token检查rpath中是否有例如$ORIGINAL字符,如果有,要进行替换。该函数在后面会进行分析。
再往下统计rpath中路径的个数nelems,然后根据nelems分配内存,用于存储r_search_path_elem结构。
然后通过fillin_rpath函数解析copy,将其中的路径存储在刚分配的内存result中。

elf/dl-load.c
dl_main->_dl_init_paths->decompose_rpath->expand_dynamic_string_token

static char * expand_dynamic_string_token (struct link_map *l, const char *s, int is_path)
{
  size_t cnt;
  size_t total;
  char *result;

  cnt = DL_DST_COUNT (s, is_path);
  if (__builtin_expect (cnt, 0) == 0)
    return local_strdup (s);

  total = DL_DST_REQUIRED (l, s, strlen (s), cnt);
  result = (char *) malloc (total + 1);

  return _dl_dst_substitute (l, s, result, is_path);
}

DL_DST_COUNT用于统计路径s中特殊符号的个数,这些特殊符号包括“ORIGIN”,“PLATFORM”,“LIB”等,具体这些符号的作用可以上网上查,例如ORIGIN就代表可执行文件所在目录。
接下来如果路径中没有这些特殊符号,则通过local_strdup函数拷贝路径s并返回。
如果包含了这些特殊符号,首先通过DL_DST_REQUIRED宏计算将特殊符号替换成实际值后的路径长度total,并根据该长度分配内存空间result,最后通过_dl_dst_substitute函数替换特殊字符串并返回替换后的字符串。

elf/dl-load.c
dl_main->_dl_init_paths->decompose_rpath->expand_dynamic_string_token->_dl_dst_substitute

char * _dl_dst_substitute (struct link_map *l, const char *name, char *result,
            int is_path)
{
  const char *const start = name;

  char *wp = result;
  char *last_elem = result;
  bool check_for_trusted = false;

  do
    {
      if (__builtin_expect (*name == '$', 0))
    {
      const char *repl = NULL;
      size_t len;

      ++name;
      if ((len = is_dst (start, name, "ORIGIN", is_path,
                 INTUSE(__libc_enable_secure))) != 0)
        {
          if (l == NULL)
        repl = _dl_get_origin ();
          else
        repl = l->l_origin;

          check_for_trusted = (INTUSE(__libc_enable_secure)
                   && l->l_type == lt_executable);
        }
      else if ((len = is_dst (start, name, "PLATFORM", is_path, 0)) != 0)
        repl = GLRO(dl_platform);
      else if ((len = is_dst (start, name, "LIB", is_path, 0)) != 0)
        repl = DL_DST_LIB;

      if (repl != NULL && repl != (const char *) -1)
        {
          wp = __stpcpy (wp, repl);
          name += len;
        }
      else if (len > 1)
        {
          wp = last_elem;
          name += len;
          while (*name != '\0' && (!is_path || *name != ':'))
        ++name;
          if (wp == result && is_path && *name == ':' && name[1] != '\0')
        ++name;
        }
      else
        *wp++ = '$';
    }
      else
    {
      *wp++ = *name++;
      if (is_path && *name == ':')
        {
          if (__builtin_expect (check_for_trusted, false)
          && !is_trusted_path_normalize (last_elem, wp - last_elem))
        wp = last_elem;
          else
        last_elem = wp;

          check_for_trusted = false;
        }
    }
    }
  while (*name != '\0');

  if (__builtin_expect (check_for_trusted, false)
      && !is_trusted_path_normalize (last_elem, wp - last_elem))
    wp = last_elem;

  *wp = '\0';

  return result;
}

简单分析下这个函数,首先通过while循环遍历name中的所有路径,如果没有特殊字符,即路径中没有特殊符号“$”,则进入else代码部分,该部分代码其实就是简单的复制name中的对应路径到result中。
如果包含了特殊字符,则进入if代码部分,is_dst计算name中特殊字符的长度存储在len中,repl变量存储了替换的字符串,如果是ORIGIN特殊字符,则替换为环境变量LD_ORIGIN_PATH指向的路径或者link_map中的l_origin指向的路径,如果是PLATFORM特殊字符,则替换为_dl_platform,如果是LIB特殊字符,则替换为DL_DST_LIB,DL_DST_LIB宏在编译阶段确定,这里不深入看了。下面的代码就是将特殊字符替换成repl,如果找不到repl用来替换,也即不是上述三个任何特殊字符的其中一个,则忽略该路径。

elf/dl-load.c
dl_main->_dl_init_paths->decompose_rpath->fillin_rpath

static struct r_search_path_elem **
fillin_rpath (char *rpath, struct r_search_path_elem **result, const char *sep,
          int check_trusted, const char *what, const char *where)
{
  char *cp;
  size_t nelems = 0;

  while ((cp = __strsep (&rpath, sep)) != NULL)
    {
      struct r_search_path_elem *dirp;
      size_t len = strlen (cp);

      if (len == 0)
    {
      static const char curwd[] = "./";
      cp = (char *) curwd;
    }

      while (len > 1 && cp[len - 1] == '/')
    --len;

      if (len > 0 && cp[len - 1] != '/')
    cp[len++] = '/';

      if (__builtin_expect (check_trusted, 0) && !is_trusted_path (cp, len))
    continue;

      for (dirp = GL(dl_all_dirs); dirp != NULL; dirp = dirp->next)
    if (dirp->dirnamelen == len && memcmp (cp, dirp->dirname, len) == 0)
      break;

      if (dirp != NULL)
    {
      size_t cnt;
      for (cnt = 0; cnt < nelems; ++cnt)
        if (result[cnt] == dirp)
          break;

      if (cnt == nelems)
        result[nelems++] = dirp;
    }
      else
    {
      size_t cnt;
      enum r_dir_status init_val;
      size_t where_len = where ? strlen (where) + 1 : 0;

      dirp = (struct r_search_path_elem *)
        malloc (sizeof (*dirp) + ncapstr * sizeof (enum r_dir_status)
            + where_len + len + 1);

      dirp->dirname = ((char *) dirp + sizeof (*dirp)
               + ncapstr * sizeof (enum r_dir_status));
      *((char *) __mempcpy ((char *) dirp->dirname, cp, len)) = '\0';
      dirp->dirnamelen = len;

      if (len > max_dirnamelen)
        max_dirnamelen = len;

      init_val = cp[0] != '/' ? existing : unknown;
      for (cnt = 0; cnt < ncapstr; ++cnt)
        dirp->status[cnt] = init_val;

      dirp->what = what;
      if (__builtin_expect (where != NULL, 1))
        dirp->where = memcpy ((char *) dirp + sizeof (*dirp) + len + 1
                  + (ncapstr * sizeof (enum r_dir_status)),
                  where, where_len);
      else
        dirp->where = NULL;

      dirp->next = GL(dl_all_dirs);
      GL(dl_all_dirs) = dirp;
      result[nelems++] = dirp;
    }
    }

  result[nelems] = NULL;

  return result;
}

while循环首先通过函数__strsep,利用分割符号sep,也就是“:”遍历字符串rpath中的所有路径cp,再往下通过strlen函数计算路径cp的长度len。
如果长度为0,也就是空路径,则默认为当前路径,也就是“./”。
接下来的while循环和再往下的if语句配合,删除路径最后的多个“/”,只保留最后一个。
再往下如果需要,则通过is_trusted_path检查路径,如果不安全,则忽略当前路径。
再往下检查是否已经向_dl_all_dirs中添加了对应路径的r_search_path_elem结构。
如果已经添加了对应路径的r_search_path_elem结构,也即dirp不为null,则继续查找结果result中是否已经添加了该dirp,如果没有,则添加到result数组最后。
相反,如果并未向_dl_all_dirs链表中添加相应路径对应的dirp,则为其分配内存,设置相应的信息,其中在设置dirname和where变量时,需要先进行指针的移动,该两个字符串的存放位置紧挨着r_search_path_elem结构,然后将新创建的dirp添加到全局_dl_all_dirs链表中,并添加到结果数据result中。

elf/dl-load.c
dl_main->_dl_init_paths第三部分

  ...

  if (llp != NULL && *llp != '\0')
    {
      size_t nllp;
      const char *cp = llp;
      char *llp_tmp;

      size_t cnt = DL_DST_COUNT (llp, 1);
      if (__builtin_expect (cnt == 0, 1))
    llp_tmp = strdupa (llp);
      else
    {
      size_t total = DL_DST_REQUIRED (l, llp, strlen (llp), cnt);

      llp_tmp = (char *) alloca (total + 1);
      llp_tmp = _dl_dst_substitute (l, llp, llp_tmp, 1);
    }

      nllp = 1;
      while (*cp)
    {
      if (*cp == ':' || *cp == ';')
        ++nllp;
      ++cp;
    }

      env_path_list.dirs = (struct r_search_path_elem **)
    malloc ((nllp + 1) * sizeof (struct r_search_path_elem *));
      if (env_path_list.dirs == NULL)
    {
      errstring = N_("cannot create cache for search path");
      goto signal_error;
    }

      (void) fillin_rpath (llp_tmp, env_path_list.dirs, ":;",
               INTUSE(__libc_enable_secure), "LD_LIBRARY_PATH",
               NULL);

      if (env_path_list.dirs[0] == NULL)
    {
      free (env_path_list.dirs);
      env_path_list.dirs = (void *) -1;
    }

      env_path_list.malloced = 0;
    }
  else
    env_path_list.dirs = (void *) -1;
}

这部分代码根据环境变量LD_LIBRARY_PATH,也即指针llp设置搜索路径。
首先通过宏DL_DST_COUNT、DL_DST_REQUIRED以及函数_dl_dst_substitute查找并替换LD_LIBRARY_PATH中的特殊符号ORIGIN、PLATFORM和LIB,这些宏和函数在前面都分析了。
接下来通过分隔符“:”或者“;”统计LD_LIBRARY_PATH中的路径个数nllp,然后根据该路径个数分配内存空间env_path_list.dirs。
最后通过fillin_rpath函数将LD_LIBRARY_PATH中的各个路径分开并保存在env_path_list.dirs和全局的_dl_all_dirs链表中。

elf/rtld.c
dl_main第七部分

  if (! GL(dl_rtld_map).l_name)
    GL(dl_rtld_map).l_name = (char *) GL(dl_rtld_map).l_libname->name;
  GL(dl_rtld_map).l_type = lt_library;
  main_map->l_next = &GL(dl_rtld_map);
  GL(dl_rtld_map).l_prev = main_map;
  ++GL(dl_ns)[LM_ID_BASE]._ns_nloaded;
  ++GL(dl_load_adds);

  if (GLRO(dl_use_load_bias) == (ElfW(Addr)) -2)
    GLRO(dl_use_load_bias) = main_map->l_addr == 0 ? -1 : 0;

  ElfW(Ehdr) *rtld_ehdr = (ElfW(Ehdr) *) GL(dl_rtld_map).l_map_start;
  ElfW(Phdr) *rtld_phdr = (ElfW(Phdr) *) (GL(dl_rtld_map).l_map_start
                      + rtld_ehdr->e_phoff);
  GL(dl_rtld_map).l_phdr = rtld_phdr;
  GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;

  size_t cnt = rtld_ehdr->e_phnum;
  while (cnt-- > 0)
    if (rtld_phdr[cnt].p_type == PT_GNU_RELRO)
      {
    GL(dl_rtld_map).l_relro_addr = rtld_phdr[cnt].p_vaddr;
    GL(dl_rtld_map).l_relro_size = rtld_phdr[cnt].p_memsz;
    break;
      }

  if (GL(dl_rtld_map).l_tls_blocksize != 0)
    GL(dl_rtld_map).l_tls_modid = _dl_next_tls_modid ();

如果ld.so作为解释器执行,则GL(dl_rtld_map).l_name不被设置,此时设置其为elf应用程序的PT_INTERP段给出的解释器路径,也即l_libname->name。
接下来将应用程序对应的link_map,也即main_map插入到GL(dl_rtld_map)链表中,然后递增link_namespaces的_ns_nloaded和_dl_load_adds表示链表中link_map的个数。
再往下获取ld.so的elf头rtld_ehdr和Segment头rtld_phdr,将其设置到GL(dl_rtld_map)中,这里的l_map_start是在前面的_dl_start_final函数中设置为_begin,而_begin在重定位后指向elf的文件头地址。
然后找到ld.so中类型为PT_GNU_RELRO的Segment头,将其信息设置到dl_rtld_map中,该信息和只读段有关。
最后如果包含了tls信息,该信息在类型为PT_TLS的Segment头中,则通过_dl_next_tls_modid函数设置l_tls_modid,即载入的模块数。

下一章开始分析dl_main的后续代码。

你可能感兴趣的:(linux逆向编程)