dl_main源码分析(一)

来源:互联网 发布:软件系统安全主要方面 编辑:程序博客网 时间:2024/05/05 06:31

dl_main源码分析(一)

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

elf/rtld.c
dl_main第一部分

static voiddl_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 voidprocess_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

voidinternal_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_functiondecompose_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的后续代码。

0 0
原创粉丝点击