dl_main源码分析(二)

dl_main源码分析(二)

本章紧接着上一章dl_main第七部分的代码继续分析,但是往下首先省略了audit和debug部分的代码,因此从第八部分代码开始看。

elf/rtld.c
dl_main第八部分

  struct link_map **preloads = NULL;
  unsigned int npreloads = 0;

  if (__builtin_expect (preloadlist != NULL, 0))
    {
      char *list = strdupa (preloadlist);
      char *p;

      while ((p = (strsep) (&list, " :")) != NULL)
    if (p[0] != '\0' && (__builtin_expect (! INTUSE(__libc_enable_secure), 1)
        || strchr (p, '/') == NULL))
      npreloads += do_preload (p, main_map, "LD_PRELOAD");
    }

preloadlist由linux的环境变量LD_PRELOAD指定,由上一章分析的函数process_envvars中赋值。接下来通过strdupa函数复制一份LD_PRELOAD的值至list,然后循环调用strsep函数根据分隔符“:”取出LD_PRELOAD中指定的各个路径p。最后通过do_preload函数装载p指定的共享库,npreloads变量最后记录了提前装载的共享库个数。

elf/rtld.c
dl_main->do_preload

static unsigned int
do_preload (char *fname, struct link_map *main_map, const char *where)
{
  const char *objname;
  const char *err_str = NULL;
  struct map_args args;
  bool malloced;

  args.str = fname;
  args.loader = main_map;
  args.mode = __RTLD_SECURE;

  unsigned int old_nloaded = GL(dl_ns)[LM_ID_BASE]._ns_nloaded;

  (void) _dl_catch_error (&objname, &err_str, &malloced, map_doit, &args);
  if (__builtin_expect (err_str != NULL, 0))
    {
        ...
    }
  else if (GL(dl_ns)[LM_ID_BASE]._ns_nloaded != old_nloaded)
    return 1;

  return 0;
}

old_nloaded首先保存了原来共享库的全局列表。然后通过_dl_catch_error函数装载共享库。
_dl_catch_error函数封装了异常处理机制,前三个参数用于捕获异常信息,真正执行的函数为map_doit,参数为args。
当有新的共享库加载到内存中时,会创建link_map结构插入到全局的_dl_ns数组的_ns_nloaded变量中,因此最后如果_ns_nloaded的值发生了改变,就说明加载了新的共享库,此时返回1,否则返回0。

elf/dl-error.c
dl_main->do_preload->_dl_catch_error

int internal_function
_dl_catch_error (const char **objname, const char **errstring,
         bool *mallocedp, void (*operate) (void *), void *args)
{
  int errcode;
  struct catch *volatile old;
  struct catch c;

  c.errstring = NULL;

  struct catch **const catchp = &CATCH_HOOK;
  old = *catchp;

  errcode = __sigsetjmp (c.env, 0);
  if (__builtin_expect (errcode, 0) == 0)
    {
      *catchp = &c;
      (*operate) (args);
      *catchp = old;
      *objname = NULL;
      *errstring = NULL;
      *mallocedp = false;
      return 0;
    }

  *catchp = old;
  *objname = c.objname;
  *errstring = c.errstring;
  *mallocedp = c.malloced;
  return errcode == -1 ? 0 : errcode;
}

_dl_catch_error函数通过__sigsetjmp模拟try、catch代码,正常处理的逻辑在if语句中,此时errcode为0,并执行operate函数,传入的参数为args。当发生异常时,会恢复前面保存的堆栈信息,返回到__sigsetjmp函数,此时errcode不为0,执行最后的一块代码保存异常信息并返回errcode。

elf/rtld.c
dl_main->do_preload->_dl_catch_error->map_doit

static void map_doit (void *a)
{
  struct map_args *args = (struct map_args *) a;
  args->map = _dl_map_object (args->loader, args->str, lt_library, 0,
                  args->mode, LM_ID_BASE);
}

map_doit函数内部通过_dl_map_object函数为新的共享库创建一个link_map结构并初始化,传入的参数args->loader为执行该操作的共享库,后面称为父共享库,args->str为将要打开共享库的路径或名称,lt_library为将要打开的共享库的类型。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object第一部分

struct link_map * internal_function
_dl_map_object (struct link_map *loader, const char *name,
        int type, int trace_mode, int mode, Lmid_t nsid)
{
  int fd;
  char *realname;
  char *name_copy;
  struct link_map *l;
  struct filebuf fb;

  for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
    {
      if (__builtin_expect (l->l_faked, 0) != 0
      || __builtin_expect (l->l_removed, 0) != 0)
    continue;
      if (!_dl_name_match_p (name, l))
    {
      const char *soname;

      if (__builtin_expect (l->l_soname_added, 1)
          || l->l_info[DT_SONAME] == NULL)
        continue;

      soname = ((const char *) D_PTR (l, l_info[DT_STRTAB])
            + l->l_info[DT_SONAME]->d_un.d_val);
      if (strcmp (name, soname) != 0)
        continue;

      add_name_to_object (l, soname);
      l->l_soname_added = 1;
    }

      return l;
    }

    ...

  if (strchr (name, '/') == NULL)
    {
      size_t namelen = strlen (name) + 1;
      fd = -1;

      if (loader == NULL || loader->l_info[DT_RUNPATH] == NULL)
    {
      struct link_map *main_map = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
      bool did_main_map = false;

      for (l = loader; l; l = l->l_loader)
        if (cache_rpath (l, &l->l_rpath_dirs, DT_RPATH, "RPATH"))
          {
        fd = open_path (name, namelen, mode & __RTLD_SECURE,
                &l->l_rpath_dirs,
                &realname, &fb, loader, LA_SER_RUNPATH,
                &found_other_class);
        if (fd != -1)
          break;

        did_main_map |= l == main_map;
          }

      if (fd == -1 && !did_main_map
          && main_map != NULL && main_map->l_type != lt_loaded
          && cache_rpath (main_map, &main_map->l_rpath_dirs, DT_RPATH,
                  "RPATH"))
        fd = open_path (name, namelen, mode & __RTLD_SECURE,
                &main_map->l_rpath_dirs,
                &realname, &fb, loader ?: main_map, LA_SER_RUNPATH,
                &found_other_class);
    }

_dl_map_object函数首先通过for循环检查将要加载的共享库是否已经加载的内存中,即存在于全局链表_dl_ns中。
for循环内部先检查已经加载的共享库对应的link_map结构的l_faked和l_removed变量,如果被置位,则继续循环,否则通过_dl_name_match_p函数查找已经加载的共享库中是否包含了路径name,如果包含了,直接返回了加载的共享库对应的link_map结构。最后,比较查找的路径name是否和某个共享库的soname相等,soname是考虑兼容方便为共享库起的别名,如果相等,则将该soname插入到对应共享库link_map结构的l_libname链表中,并返回对应的link_map结构,否则继续循环,直到遍历完所有已经加载的共享库。

退出for循环后,说明在已经加载的共享库中不包含对应的路径name,也就是路径name对应的共享库还未加载,此时就要根据该路径将共享库加载到内存中来了。
首先判断路径name是否只包含了共享库名,不包含路径信息,即该路径不包含“\”字符。
如果name只包含了共享库名,首先检查执行该函数的共享库对应的.dynamic段中,RUNPATH位置上是否为空,如果为空,又因为RUNPATH的优先级高于RPATH,因此此时只需要检查RPATH对应的信息。

此时首先遍历执行该操作的共享库以及其父共享库,通过cache_rpath函数从字符串表中提取.dynamic段的DT_RPATH对应的搜索路径,并保存到对应link_map结构,也即loader的l_rpath_dirs变量中。紧接着通过open_path函数在刚刚得到的搜索路径l_rpath_dirs下查找并打开共享库name,如果成功打开了文件,返回文件标识fd,并将共享库的完整路径保存在realname,文件内容保存在缓存fb中。
如果根据参数loader以及其父共享库找不到共享库name,就从应用程序对应的link_map,也即main_map中再查找一次。did_main_map用于标识参数loader和全局的main_map是否为同一个link_map。

elf/dl-misc.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->_dl_name_match_p

int internal_function
_dl_name_match_p (const char *name, const struct link_map *map)
{
  if (strcmp (name, map->l_name) == 0)
    return 1;

  struct libname_list *runp = map->l_libname;

  while (runp != NULL)
    if (strcmp (name, runp->name) == 0)
      return 1;
    else
      runp = runp->next;

  return 0;
}

_dl_name_match_p函数用于匹配名称name对应的共享库是否就是参数map。首先通过strcmp函数检查要加载的共享库名name是否和已经加载的共享库link_map的l_name是否相等,如果相等,直接返回1。否则,检查路径name是否和l_libname链表中的某个库名相等,如果相等也返回1,否则返回0,表示没找到。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->add_name_to_object

static void internal_function
add_name_to_object (struct link_map *l, const char *name)
{
  struct libname_list *lnp, *lastp;
  struct libname_list *newname;
  size_t name_len;

  lastp = NULL;
  for (lnp = l->l_libname; lnp != NULL; lastp = lnp, lnp = lnp->next)
    if (strcmp (name, lnp->name) == 0)
      return;

  name_len = strlen (name) + 1;
  newname = (struct libname_list *) malloc (sizeof *newname + name_len);

  newname->name = memcpy (newname + 1, name, name_len);
  newname->next = NULL;
  newname->dont_free = 0;
  lastp->next = newname;
}

add_name_to_object函数用于将共享库名name添加到link_map结构中。该函数首先循环遍历link_map的l_libname链表并通过strcmp函数检查该链表是否已经包含了待添加的共享库名name,如果已经包含了则直接返回。否则为即将插入的共享库名name分配内存空间newname,进行相应的设置,并通过memcpy函数拷贝字符串name,最后将刚刚分配的newname插入到link_map结构的l_libname链表中。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->cache_rpath

static bool
cache_rpath (struct link_map *l,
         struct r_search_path_struct *sp,
         int tag,
         const char *what)
{
  if (sp->dirs == (void *) -1)
    return false;

  if (sp->dirs != NULL)
    return true;

  if (l->l_info[tag] == NULL)
    {
      sp->dirs = (void *) -1;
      return false;
    }

  return decompose_rpath (sp, (const char *) (D_PTR (l, l_info[DT_STRTAB])
                          + l->l_info[tag]->d_un.d_val), l, what);
}

cache_rpath函数首先检查l_rpath_dirs结构sp的dirs变量是否未分配内存,如果未分配,返回false,如果已经设置,则返回true,再检查.dynamic段中,也即l_info的tag位置上是否为空,如果不为空,则根据该tag获得.dynamic段中的数据d_val,并通过decompose_rpath函数从字符串表DT_STRTAB中拷贝字符串到sp中,该函数在上一章已经分析过了。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->open_path

static int
open_path (const char *name, size_t namelen, int secure,
       struct r_search_path_struct *sps, char **realname,
       struct filebuf *fbp, struct link_map *loader, int whatcode,
       bool *found_other_class)
{
  struct r_search_path_elem **dirs = sps->dirs;
  char *buf;
  int fd = -1;
  const char *current_what = NULL;
  int any = 0;

  buf = alloca (max_dirnamelen + max_capstrlen + namelen);
  do
    {
      struct r_search_path_elem *this_dir = *dirs;
      size_t buflen = 0;
      size_t cnt;
      char *edp;
      int here_any = 0;
      int err;

      edp = (char *) __mempcpy (buf, this_dir->dirname, this_dir->dirnamelen);
      for (cnt = 0; fd == -1 && cnt < ncapstr; ++cnt)
    {
      if (this_dir->status[cnt] == nonexisting)
        continue;

      buflen =
        ((char *) __mempcpy (__mempcpy (edp, capstr[cnt].str, capstr[cnt].len), 
                        name, namelen) 
                        - buf);

      fd = open_verify (buf, fbp, loader, whatcode, found_other_class,
                false);
      if (this_dir->status[cnt] == unknown)
        {
          if (fd != -1)
        this_dir->status[cnt] = existing;
          else if (loader == NULL
               || GL(dl_ns)[loader->l_ns]._ns_loaded->l_auditing == 0)
        {
          struct stat64 st;
          buf[buflen - namelen - 1] = '\0';

          if (__xstat64 (_STAT_VER, buf, &st) != 0
              || ! S_ISDIR (st.st_mode))
            this_dir->status[cnt] = nonexisting;
          else
            this_dir->status[cnt] = existing;
        }
        }

      here_any |= this_dir->status[cnt] != nonexisting;

        ...

    }

      if (fd != -1)
    {
      *realname = (char *) malloc (buflen);
      memcpy (*realname, buf, buflen);
      return fd;
    }
      if (here_any && (err = errno) != ENOENT && err != EACCES)
    return -1;

      any |= here_any;
    }

  while (*++dirs != NULL);
  if (__builtin_expect (! any, 0))
    {
      if (sps->malloced)
    free (sps->dirs);

      if (sps != &rtld_search_dirs)
    sps->dirs = (void *) -1;
    }

  return -1;
}

open_path函数根据路径信息打开共享库文件。
首先遍历共享库的搜索目录dirs,也即已经加载的共享库对应的link_map结构中的l_rpath_dirs,该变量在_dl_init_paths函数或者刚刚前面介绍的cache_rpath函数中被初始化。
然后通过__mempcpy函数复制每个共享库的搜索目录名dirname到buf中。
接下来遍历capstr数组中的所有值,数组大小为ncapstr,该数组的值和计算机硬件有关,本章不关注。取得capstr数组中的值后,就将其值str追加到刚刚添加了目录名dirname的buf之后,再追加共享库名name,至此buf中就保存了共享库的完整路径了。

再往下就通过open_verify函数根据刚刚得到的共享库路径buf打开共享库,获得文件标识符fd,并将文件中的内容拷贝到缓存fbp中,open_verify函数除了打开文件,另一个功能就是验证该文件是否为elf格式的文件。然后根据刚刚打开文件的结果设置当前搜索目录对应的结构r_search_path_elem中的status变量,这部分内容不关心。
如果成功打开了文件,就拷贝完整的共享库路径buf到传入的地址realname中,并返回文件标识fd。
因为前面的代码不仅用于打开共享库,也会检查硬件体系结构相关的目录是否存在,代码最后的if就是检查某个搜索目录r_search_path_elem下是否没有任何和该计算机体系结构相关的目录存在,如果没有,则直接销毁该r_search_path_elem结构,因为该目录也不会包含任何其他的共享库了。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object第二部分

      if (fd == -1 && env_path_list.dirs != (void *) -1)
    fd = open_path (name, namelen, mode & __RTLD_SECURE, &env_path_list,
            &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, "RUNPATH"))
    fd = open_path (name, namelen, mode & __RTLD_SECURE,
            &loader->l_runpath_dirs, &realname, &fb, loader,
            LA_SER_RUNPATH, &found_other_class);

      if (fd == -1
      && (__builtin_expect (! (mode & __RTLD_SECURE), 1)
          || ! INTUSE(__libc_enable_secure)))
    {
      const char *cached = _dl_load_cache_lookup (name);

      if (cached != NULL)
        {
          l = (loader
           ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded
           ?: &GL(dl_rtld_map));

          if (__builtin_expect (l->l_flags_1 & DF_1_NODEFLIB, 0))
        {
          const char *dirp = system_dirs;
          unsigned int cnt = 0;

          do
            {
              if (memcmp (cached, dirp, system_dirs_len[cnt]) == 0)
            {
              cached = NULL;
              break;
            }

              dirp += system_dirs_len[cnt] + 1;
              ++cnt;
            }
          while (cnt < nsystem_dirs_len);
        }

          if (cached != NULL)
        {
          fd = open_verify (cached,
                    &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
                    LA_SER_CONFIG, &found_other_class, false);
          if (__builtin_expect (fd != -1, 1))
            {
              realname = local_strdup (cached);
              if (realname == NULL)
            {
              __close (fd);
              fd = -1;
            }
            }
        }
        }
    }

      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, mode & __RTLD_SECURE, &rtld_search_dirs,
            &realname, &fb, l, LA_SER_DEFAULT, &found_other_class);
    }
  else
    {
      realname = (loader
          ? expand_dynamic_string_token (loader, name, 0)
          : local_strdup (name));
      if (realname == NULL)
    fd = -1;
      else
    {
      fd = open_verify (realname, &fb,
                loader ?: GL(dl_ns)[nsid]._ns_loaded, 0,
                &found_other_class, true);
      if (__builtin_expect (fd, 0) == -1)
        free (realname);
    }
    }

 no_file:
  if (mode & __RTLD_CALLMAP)
    loader = NULL;

  if (__builtin_expect (fd, 0) == -1)
    {
      ...
    }

  void *stack_end = __libc_stack_end;
  return _dl_map_object_from_fd (name, fd, &fb, realname, loader, type, mode,
                 &stack_end, nsid);
}

运行到这里,标识还没有找到共享库文件,下面的代码继续在别的搜索路径下查找。
env_path_list.dirs中存储的是环境变量LD_LIBRARY_PATH指向的搜索路径集合,因此继续通过open_path函数在该路径中寻找共享库name。
如果LD_LIBRARY_PATH中没有包含待查找的共享库,也即文件标识符fd依然为-1,接下来就从.dynamic段中获取DT_RUNPATH对应的搜索路径,再通过open_path函数在该搜索路径寻找共享库。
如果还没找到,就通过_dl_load_cache_lookup函数在ld.so.cache文件中根据共享库名name查找对应的全路径cached,如果找到了该共享库对应的全路径,就比较全路径中的前缀路径名是否在system_dirs中,如果在,则无效,否则通过open_verify函数打开该文件,并拷贝完整路径名到realname中。
如果ld.so.cache文件中还找不到,最后就在系统的默认路径下查找,默认路径rtld_search_dirs.dirs由system_dirs而来,在函数_dl_init_paths中初始化。

再往下的else代码部分和_dl_map_object第一部分代码中的if语句对应,这里表示共享库名name包含了路径信息,即包含了路径分隔符“/”,此时直接通过expand_dynamic_string_token或者local_strdup拷贝共享库的路径,expand_dynamic_string_token函数会替换name中的特殊字符ORIGIN、PLATFORM、LIB,该函数在前面的章节已经分析过了。获得最终的路径名realname后,直接调用open_verify打开并验证该文件。
如果执行到这里还找不到该文件,也即fd=-1,则无法找到name对应的共享库了,此时报错。否则通过_dl_map_object_from_fd函数加载刚刚打开的共享库。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->_dl_load_cache_lookup

const char * internal_function _dl_load_cache_lookup (const char *name)
{
  int left, right, middle;
  int cmpres;
  const char *cache_data;
  uint32_t cache_data_size;
  const char *best;

  if (cache == NULL)
    {
      void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize,
                           PROT_READ);

      ...

    }

  ...

  if (cache_new != (void *) -1)
    {
      uint64_t platform;

      cache_data = (const char *) cache_new;
      cache_data_size = (const char *) cache + cachesize - cache_data;

      ...

      SEARCH_CACHE (cache_new);
    }
  else
    {
      cache_data = (const char *) &cache->libs[cache->nlibs];
      cache_data_size = (const char *) cache + cachesize - cache_data;
      SEARCH_CACHE (cache);
    }

  return best;
}

LD_SO_CACHE的宏定义表示ld.so.cache文件的路径,_dl_load_cache_lookup函数通过_dl_sysdep_read_whole_file函数读取该文件,将数据保存在file中。
省略的代码和ld.so.cache文件的版本有关,这里不关心。
最后通过SEARCH_CACHE宏根据共享库名在ld.so.cache文件中查找其对应的全路径,后面的函数涉及到具体的搜索算法,这里就不往下看了。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->_dl_map_object_from_fd第一部分

struct link_map *
_dl_map_object_from_fd (const char *name, int fd, struct filebuf *fbp,
            char *realname, struct link_map *loader, int l_type,
            int mode, void **stack_endp, Lmid_t nsid)
{
  struct link_map *l = NULL;
  const ElfW(Ehdr) *header;
  const ElfW(Phdr) *phdr;
  const ElfW(Phdr) *ph;
  size_t maplength;
  int type;
  struct stat64 st;
  const char *errstring = NULL;
  int errval = 0;
  struct r_debug *r = _dl_debug_initialize (0, nsid);
  bool make_consistent = false;

  if (__builtin_expect (__fxstat64 (_STAT_VER, fd, &st) < 0, 0))
    {
      ...
    }

  for (l = GL(dl_ns)[nsid]._ns_loaded; l; l = l->l_next)
    if (l->l_removed == 0 && l->l_ino == st.st_ino && l->l_dev == st.st_dev)
      {
        ...
        return l;
      }

  ...

  header = (void *) fbp->buf;
  l = _dl_new_object (realname, name, l_type, loader, mode, nsid);

  l->l_entry = header->e_entry;
  type = header->e_type;
  l->l_phnum = header->e_phnum;

  maplength = header->e_phnum * sizeof (ElfW(Phdr));
  if (header->e_phoff + maplength <= (size_t) fbp->len)
    phdr = (void *) (fbp->buf + header->e_phoff);
  else
    {
      phdr = alloca (maplength);
      __lseek (fd, header->e_phoff, SEEK_SET);
      if ((size_t) __libc_read (fd, (void *) phdr, maplength) != maplength)
    {
      errstring = N_("cannot read file data");
      goto call_lose_errno;
    }
    }

   uint_fast16_t stack_flags = DEFAULT_STACK_PERMS;

首先通过__fxstat64函数获取共享库文件fd的信息。__fxstat64内部通过系统调用sys_fstat返回一个stat结构st,其st_ino为文件号,st_dev为文件所属的设备号。
接下来遍历所有已经加载到内存中的共享库对应的结构link_map,如果刚刚读出的st_ino和st_dev分别和该link_map中的l_ino和l_dev相等,则表示该共享库已经加载到内存中了,此时直接返回该link_map结构l即可。
再往下将前面打开的文件内容buf赋值给header,因为前面是通过open_verify函数打开共享库的,该函数会验证共享库文件是否为elf格式的文件,因此此时header就是elf文件头指针。

接下来通过_dl_new_object函数为刚刚打开的共享库创建对应的link_map结构,注意这里传入的参数realname为共享库的完整路径,name为共享库名,loader第一次为main_map,也即应用程序自身对应的link_map结构。该函数在之前的章节已经分析过了。
创建完link_map结构后,设置入口点l_entry、文件类型e_type(重定位文件、可执行文件、共享文件)、程序头的个数l_phnum。计算用来保存所有程序头的内存需要分配的大小maplength。如果缓存足够大,则只要移动缓存中的指针就行了,否则需要seek文件指针,重新读取数据到新分配的缓存中。无论哪种情况,最终的Segment头的起始指针保存在变量phdr中。
最后设置stack_flags标识堆栈的属性。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->_dl_map_object_from_fd第二部分

   uint_fast16_t stack_flags = DEFAULT_STACK_PERMS;

  {
    struct loadcmd
      {
    ElfW(Addr) mapstart, mapend, dataend, allocend;
    off_t mapoff;
    int prot;
      } loadcmds[l->l_phnum], *c;
    size_t nloadcmds = 0;
    bool has_holes = false;

    for (ph = phdr; ph < &phdr[l->l_phnum]; ++ph)
      switch (ph->p_type)
    {
    case PT_DYNAMIC:
      l->l_ld = (void *) ph->p_vaddr;
      l->l_ldnum = ph->p_memsz / sizeof (ElfW(Dyn));
      break;

    case PT_PHDR:
      l->l_phdr = (void *) ph->p_vaddr;
      break;

    case PT_LOAD:
      c = &loadcmds[nloadcmds++];
      c->mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1);
      c->mapend = ((ph->p_vaddr + ph->p_filesz + GLRO(dl_pagesize) - 1)
               & ~(GLRO(dl_pagesize) - 1));
      c->dataend = ph->p_vaddr + ph->p_filesz;
      c->allocend = ph->p_vaddr + ph->p_memsz;
      c->mapoff = ph->p_offset & ~(GLRO(dl_pagesize) - 1);

      if (nloadcmds > 1 && c[-1].mapend != c->mapstart)
        has_holes = true;

      c->prot = 0;
      if (ph->p_flags & PF_R)
        c->prot |= PROT_READ;
      if (ph->p_flags & PF_W)
        c->prot |= PROT_WRITE;
      if (ph->p_flags & PF_X)
        c->prot |= PROT_EXEC;
      break;

      ...

    }

    c = loadcmds;
    maplength = loadcmds[nloadcmds - 1].allocend - c->mapstart;
    if (__builtin_expect (type, ET_DYN) == ET_DYN)
      {
    ElfW(Addr) mappref;
    mappref = (ELF_PREFERRED_ADDRESS (loader, maplength,
                      c->mapstart & GLRO(dl_use_load_bias))
           - MAP_BASE_ADDR (l));

    l->l_map_start = (ElfW(Addr)) __mmap ((void *) mappref, maplength,
                          c->prot,
                          MAP_COPY|MAP_FILE,
                          fd, c->mapoff);

    l->l_map_end = l->l_map_start + maplength;
    l->l_addr = l->l_map_start - c->mapstart;

    if (has_holes)
      __mprotect ((caddr_t) (l->l_addr + c->mapend),
              loadcmds[nloadcmds - 1].mapstart - c->mapend,
              PROT_NONE);

    l->l_contiguous = 1;

    goto postmap;
      }

    l->l_map_start = c->mapstart + l->l_addr;
    l->l_map_end = l->l_map_start + maplength;
    l->l_contiguous = !has_holes;

第一部分代码最后获得了elf文件对应的Segment头phdr,接下来就从phdr指针开始,遍历每个Segment。
如果Segment的类型为PT_DYNAMIC,则设置l_ld为.dynamic段的装载地址(注意这里都不是实际的装载地址),设置l_ldnum为.dynamic段中项的个数。
如果Segment的类型为PT_PHDR,则设置Segment头的起始地址l_phdr。
如果Segment的类型为PT_LOAD,则设置mapstart、mapend为该Segment的装载起始地址和结束地址,注意这里执行了对齐操作,dataend为文件映像的结束地址,allocend为内存映像的结束地址,mapoff为文件起始地址。最后设置该Segment的权限prot。如果当前段的起始地址mapstart和前一个段的结束地址不相等,则两个段不连续,设置has_holes为true。
接下来省略的代码处理PT_TLS、PT_GNU_STACK和PT_GNU_RELRO。

maplength为所有类型为PT_LOAD的Segment占用的内存大小。
再往下首先计算将要mmap的起始地址mappref,然后通过mmap将共享库文件中的内容加载的内存中。c的mapoff变量表示第一个Segment在文件中的偏移,maplength是刚刚计算需要装载的内存大小。最后返回实际内存中的起始地址l_map_start。其实这里mmap只装载了共享库文件的第一个PT_LOAD类型的Segment,但是分配了足够的空间,后面会通过循环在这段空间上继续调用mmap函数,为剩余的PT_LOAD类型的Segmen分配空间。
最后再计算内存中装载的程序段的结束地址l_map_end,和整个elf文件的装载地址l_addr(这是一个虚拟的值,用于后面计算偏移,实际只是装载了类型为PT_LOAD类型的Segment)。
如果存在不连续的Segment,也即has_holes为真,则要把第一个Segment的结束地址到最后一个Segment的起始地址这部分的内存属性设置为PROT_NONE,也即无法访问,这是为了安全考虑。然后跳到postmap处继续执行。
如果要装载的程序不是共享库,而是可执行程序,则直接对link_map结构,也即l进行初始化设置,本章不考虑这种情况。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->_dl_map_object_from_fd第三部分

    while (c < &loadcmds[nloadcmds])
      {
    if (c->mapend > c->mapstart
        && (__mmap ((void *) (l->l_addr + c->mapstart),
            c->mapend - c->mapstart, c->prot,
            MAP_FIXED|MAP_COPY|MAP_FILE,
            fd, c->mapoff)
        == MAP_FAILED))
      goto map_error;

      postmap:
    if (c->prot & PROT_EXEC)
      l->l_text_end = l->l_addr + c->mapend;

    if (l->l_phdr == 0
        && (ElfW(Off)) c->mapoff <= header->e_phoff
        && ((size_t) (c->mapend - c->mapstart + c->mapoff)
        >= header->e_phoff + header->e_phnum * sizeof (ElfW(Phdr))))
      l->l_phdr = (void *) (c->mapstart + header->e_phoff - c->mapoff);

    if (c->allocend > c->dataend)
      {
        ElfW(Addr) zero, zeroend, zeropage;

        zero = l->l_addr + c->dataend;
        zeroend = l->l_addr + c->allocend;
        zeropage = ((zero + GLRO(dl_pagesize) - 1)
            & ~(GLRO(dl_pagesize) - 1));

        if (zeroend < zeropage)
          zeropage = zeroend;

        if (zeropage > zero)
          {
        if (__builtin_expect ((c->prot & PROT_WRITE) == 0, 0))
          {
            __mprotect ((caddr_t) (zero & ~(GLRO(dl_pagesize) - 1)),
                    GLRO(dl_pagesize), c->prot|PROT_WRITE) < 0)
          }
        memset ((void *) zero, '\0', zeropage - zero);
        if (__builtin_expect ((c->prot & PROT_WRITE) == 0, 0))
          __mprotect ((caddr_t) (zero & ~(GLRO(dl_pagesize) - 1)),
                  GLRO(dl_pagesize), c->prot);
          }

        if (zeroend > zeropage)
          {
        caddr_t mapat;
        mapat = __mmap ((caddr_t) zeropage, zeroend - zeropage,
                c->prot, MAP_ANON|MAP_PRIVATE|MAP_FIXED,
                -1, 0);
          }
      }

    ++c;
      }
  }

  if (l->l_ld == 0)
    {
      if (__builtin_expect (type == ET_DYN, 0))
        goto call_lose;
    }
  else
    l->l_ld = (ElfW(Dyn) *) ((ElfW(Addr)) l->l_ld + l->l_addr);

从上一部分的分析可以知道,函数此时从postmap处开始执行。但是当返回到while循环后,继续通过__mmap对共享文件作映射,注意此时传入的flags包含了MAP_FIXED,表示映射的起始地址必须是l->l_addr + c->mapstart。
如果是可执行的段,即标志位为PROT_EXEC,则为代码段,此时设置代码段的结束地址l_text_end。
接下来l_phdr表示Segment头的装载地址,如果该值没有设置,则需要查找对应的Segment对l_phdr进行赋值。一般而言,elf文件的第一个类型为PT_LOAD的段(并不是代码段或者数据段,而是包含了elf文件头部信息的一个段,该段也要装载到内存中)可以用来计算l_phdr,这和elf文件的设计有关。
再往下,如果allocend大于dataend,即elf文件的filesz小于memsz,则dataend到allocend这段区域为.bss段。因此下面要初始化该段了,即将该段对应的内存清0。
接下来设置zero为.bss段的起始地址,zeroend为.bss段的结束地址,zeropage为zero的值向上取整。
如果zeroend小于zeropage,则.bss段的起始地址和结束地址在同一个页面内,此时只要将zero至zeroend这段内存清0就行了。但是如果这段内存没有写权限PROT_WRITE,因为前面很可能将这段内存的权限设置为PROT_NONE,要通过__mprotect为这部分内存添加写权限,然后通过memset函数将这段内存清0,完成后再调用一次__mprotect函数恢复这段内存的原始权限。
如果zeroend大于zeropage,则zeropage至zeroend还没有分配虚拟内存,此时继续通过__mmap分配虚拟内存,注意前面mmap函数创建的都是文件映射,此时的标志位MAP_ANON,即创建匿名映射,因为zeropage至zeroend这段内存对应.bss段,而.bss段不存在于elf文件中的,只是通过filesz和memsz的差来决定该段的大小。
最后,如果l_ld为0,而elf类型为ET_DYN,则说明该共享库没有.dynamic段,此时发生错误,否则将l_ld重定位,即加上前面计算的elf文件的实际装载地址l_addr。

elf/dl-load.c
dl_main->do_preload->_dl_catch_error->map_doit->_dl_map_object->_dl_map_object_from_fd第四部分

  elf_get_dynamic_info (l, NULL);
  if (__builtin_expect (l->l_flags_1 & DF_1_NOOPEN, 0)
      && (mode & __RTLD_DLOPEN))
    {
        ...
    }

  if (l->l_phdr == NULL)
    {
      ElfW(Phdr) *newp = (ElfW(Phdr) *) malloc (header->e_phnum
                        * sizeof (ElfW(Phdr)));
      l->l_phdr = memcpy (newp, phdr, (header->e_phnum * sizeof (ElfW(Phdr))));
      l->l_phdr_allocated = 1;
    }
  else
    l->l_phdr = (ElfW(Phdr) *) ((ElfW(Addr)) l->l_phdr + l->l_addr);

  if (__builtin_expect ((stack_flags &~ GL(dl_stack_flags)) & PF_X, 0))
    {
      ...
    }

  if (l->l_tls_initimage != NULL)
    l->l_tls_initimage = (char *) l->l_tls_initimage + l->l_addr;

  __close (fd) != 0, 0)
  fd = -1;

  if (l->l_type == lt_library && type == ET_EXEC)
    l->l_type = lt_executable;

  l->l_entry += l->l_addr;

  _dl_setup_hash (l);

  l->l_dev = st.st_dev;
  l->l_ino = st.st_ino;

  if (__builtin_expect (GLRO(dl_profile) != NULL, 0)
      && l->l_info[DT_SONAME] != NULL)
    add_name_to_object (l, ((const char *) D_PTR (l, l_info[DT_STRTAB])
                + l->l_info[DT_SONAME]->d_un.d_val));
  _dl_add_to_namespace_list (l, nsid);

解析玩elf文件的各个Segment后,接下来通过elf_get_dynamic_info函数获得加载的共享库.dynamic段的信息,将其存入link_map结构的l_info中。
然后检查是否是通过dlopen打开的共享库,此时标签flag不能包含DF_1_NODELETE。
接下来的l_phdr,即Segment头的装载地址在前面有两种赋值的途径,一种是elf文件中包含了类型为PT_PHDR的段,直接从该段中取值就行了,另一种是根据类型为PT_LOAD的段计算获得,如果两种途径都无法得到l_phdr,则直接为Segment头分配空间newp,然后通过memcpy将文件中程序头的信息拷贝到newp中,再赋值给l_phdr。如果前面计算得到了l_phdr,则直接将其加上elf文件实际的装载地址l_addr得到程序头的实际地址。
再往下检查stack_flags标签,也即PT_GNU_STACK段中的标签是否有可执行权限PF_X,没有则错误返回。
然后将tls映像的地址加上elf文件的装载地址l_addr获得tls映像的实际装载地址。
前面的所有工作已经将共享库文件的信息拷贝到内存中了,此时共享文件已经没有用了,通过__close关闭该文件,并将文件描述符fd置为-1。

再往下,如果打开的共享库的类型为ET_EXEC,即可执行文件,则需要将link_map的类型从lt_library修改为lt_executable。
然后将程序的入口地址l_entry加上装载地址l_addr获得实际的入口地址,如果共享库没有入口地址,该值为0,则直接指向elf文件的装载地址l_addr。
接着通过_dl_setup_hash函数获取共享库的.hash段的信息并初始化,该信息主要用来快速查找该共享库中的符号。
接下来,如果定义了DT_SONAME,也即别名,就将该别名添加到link_map的l_libname链表中,该函数在前面也已经分析过了。
最后通过_dl_add_to_namespace_list函数将该link_map添加到全局链表_dl_ns中去,该函数在上一章已经分析过了。

elf/rtld.c
dl_main第九部分

  static const char preload_file[] = "/etc/ld.so.preload";
  if (__builtin_expect (__access (preload_file, R_OK) == 0, 0))
    {
      file = _dl_sysdep_read_whole_file (preload_file, &file_size,
                     PROT_READ | PROT_WRITE);
      if (__builtin_expect (file != MAP_FAILED, 0))
    {
      char *problem;
      char *runp;
      size_t rest;

      runp = file;
      rest = file_size;
      while (rest > 0)
        {
          char *comment = memchr (runp, '#', rest);
          if (comment == NULL)
        break;

          rest -= comment - runp;
          do
        *comment = ' ';
          while (--rest > 0 && *++comment != '\n');
        }

      if (file[file_size - 1] != ' ' && file[file_size - 1] != '\t'
          && file[file_size - 1] != '\n' && file[file_size - 1] != ':')
        {
          problem = &file[file_size];
          while (problem > file && problem[-1] != ' '
             && problem[-1] != '\t'
             && problem[-1] != '\n' && problem[-1] != ':')
        --problem;

          if (problem > file)
        problem[-1] = '\0';
        }
      else
        {
          problem = NULL;
          file[file_size - 1] = '\0';
        }

      if (file != problem)
        {
          char *p;
          runp = file;
          while ((p = strsep (&runp, ": \t\n")) != NULL)
        if (p[0] != '\0')
          npreloads += do_preload (p, main_map, preload_file);
        }

      if (problem != NULL)
        {
          char *p = strndupa (problem, file_size - (problem - file));

          npreloads += do_preload (p, main_map, preload_file);
        }

      __munmap (file, file_size);
    }
    }

  if (__builtin_expect (*first_preload != NULL, 0))
    {
      struct link_map *l = *first_preload;
      preloads = __alloca (npreloads * sizeof preloads[0]);
      i = 0;
      do
    {
      preloads[i++] = l;
      l = l->l_next;
    } while (l);
    }

前面部分的代码通过do_preload函数加载环境变量LD_PRELOAD中定义的共享库,这段代码继续从/etc/ld.so.preload文件中加载预先定义的共享库。
首先通过_dl_sysdep_read_whole_file函数读取/etc/ld.so.preload文件,并将其中的内容映射到内存中,返回内存的起始指针file。
接下来的while循环用于消除以特殊符号“#”开头的注释,具体做法是查找file文件中的“#”符号,将该“#”符号到下一个换行符之间的所有字符comment替换成空字符串。
接下来就是从文件中依次读取路径,通过do_preload函数针对路径创建link_map并初始化。值得注意的是,如果文件的结尾不是“ ”、“\t”、“\n”和“:”中的任何一个,则要找到这四个特殊符号出现的最后位置,也就是problem[-1],将其替换成结束字符“\0”;如果是,则直接将其替换成“\0”。接下来调用do_preload时,如果problem不为null,则通过strndupa函数将problem至文件结尾的字符拷贝到指针p地址上,再调用一次do_preload函数。
最后通过__munmap释放文件占用的内存。

elf/dl-misc.c
dl_main->_dl_sysdep_read_whole_file

void * internal_function
_dl_sysdep_read_whole_file (const char *file, size_t *sizep, int prot)
{
  void *result = MAP_FAILED;
  struct stat64 st;
  int flags = O_RDONLY;
  int fd = __open (file, flags);
  if (fd >= 0)
    {
      if (__fxstat64 (_STAT_VER, fd, &st) >= 0)
    {
      *sizep = st.st_size;

      if (*sizep != 0)
        result = __mmap (NULL, *sizep, prot,MAP_PRIVATE, fd, 0);
    }
      __close (fd);
    }
  return result;
}

_dl_sysdep_read_whole_file 函数首先通过__open打开文件,获得文件标识符fd。然后通过__fxstat64函数获取文件大小,保存在sizep中,该函数内部通过sys_fstat系统调用获取文件信息st。最后通过__mmap函数将文件映射到内存中,返回起始指针result。

elf/rtld.c
dl_main第十部分

  if (__builtin_expect (*first_preload != NULL, 0))
    {
      struct link_map *l = *first_preload;
      preloads = __alloca (npreloads * sizeof preloads[0]);
      i = 0;
      do
    {
      preloads[i++] = l;
      l = l->l_next;
    } while (l);
    }

  _dl_map_object_deps (main_map, preloads, npreloads, mode == trace, 0);

程序运行到这里,全局的link_map列表,也即_dl_ns[LM_ID_BASE]里的第一个位置存放的是应用程序对应的link_map,也即前面的main_map,第二个位置存放的ld.so对应的link_map,也即_dl_rtld_map,第三个位置开始存放的是刚刚前面预加载的共享库对应的link_map。而此时first_preload指向的是第一个预加载共享库link_map的起始指针。
这段代码首先分配npreloads个预加载的link_map指针大小的空间preloads,然后将刚刚预加载的共享库对应的link_map结构指针存入preloads数组中,最后调用_dl_map_object_deps开始处理共享库间的依赖关系。该函数依然很复杂,因此下面分多个部分进行分析。

elf/dl-deps.c
dl_main->_dl_map_object_deps第一部分

void internal_function
_dl_map_object_deps (struct link_map *map,
             struct link_map **preloads, unsigned int npreloads,
             int trace_mode, int open_mode)
{
  struct list *known = __alloca (sizeof *known * (1 + npreloads + 1));
  struct list *runp, *tail;
  unsigned int nlist, i;
  const char *name;
  int errno_saved;
  int errno_reason;
  const char *errstring;
  const char *objname;

  auto inline void preload (struct link_map *map);

  inline void preload (struct link_map *map)
    {
      known[nlist].done = 0;
      known[nlist].map = map;
      known[nlist].next = &known[nlist + 1];

      ++nlist;
      map->l_reserved = 1;
    }

  nlist = 0;
  preload (map);
  for (i = 0; i < npreloads; ++i)
    preload (preloads[i]);

  known[nlist - 1].next = NULL;
  tail = &known[nlist - 1];

  struct link_map **needed_space = NULL;
  size_t needed_space_bytes = 0;

  errno_saved = errno;
  errno_reason = 0;
  errstring = NULL;
  errno = 0;
  name = NULL;

第一部分代码相对简单,都是一些变量的初始化工作。
list类型的结构用于封装link_map结构,其done变量用于表示该link_map的依赖关系是否被处理,map指向link_map自身。
首先分配类型为list的数组known,该数组的每个元素通过list结构的next变量形成链表,可以通过该函数内部定义的内联函数preload看出来。
首先为known数组分配空间,然后通过内联函数preload将应用程序对应的link_map和预加载的link_map添加到该数组中,形成链表,nlist表示当前known数组也即链表中link_map的个数,tail指向链表末尾。

elf/dl-deps.c
dl_main->_dl_map_object_deps第二部分

  for (runp = known; runp; )
    {
      struct link_map *l = runp->map;
      struct link_map **needed = NULL;
      unsigned int nneeded = 0;
      runp->done = 1;

      if (l->l_searchlist.r_list == NULL && l->l_initfini == NULL
      && l != map && l->l_ldnum > 0)
    {
      size_t new_size = l->l_ldnum * sizeof (struct link_map *);

      if (new_size > needed_space_bytes)
        needed_space
          = extend_alloca (needed_space, needed_space_bytes, new_size);

      needed = needed_space;
    }

      if (l->l_info[DT_NEEDED] || l->l_info[AUXTAG] || l->l_info[FILTERTAG])
    {
      const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);
      struct openaux_args args;
      struct list *orig;
      const ElfW(Dyn) *d;

      args.strtab = strtab;
      args.map = l;
      args.trace_mode = trace_mode;
      args.open_mode = open_mode;
      orig = runp;

      for (d = l->l_ld; d->d_tag != DT_NULL; ++d)
        if (__builtin_expect (d->d_tag, DT_NEEDED) == DT_NEEDED)
          {
        struct link_map *dep;

        name = expand_dst (l, strtab + d->d_un.d_val, 0);
        args.name = name;

        bool malloced;
        _dl_catch_error (&objname, &errstring, &malloced,
                       openaux, &args);
        dep = args.aux;

        if (! dep->l_reserved)
          {
            struct list *newp;

            newp = alloca (sizeof (struct list));
            newp->map = dep;
            newp->done = 0;
            newp->next = NULL;
            tail->next = newp;
            tail = newp;
            ++nlist;
            dep->l_reserved = 1;
          }

          if (needed != NULL)
            needed[nneeded++] = dep;
          }
        else if (d->d_tag == DT_AUXILIARY || d->d_tag == DT_FILTER)
          {
        struct list *newp;
        name = expand_dst (l, strtab + d->d_un.d_val,
                   d->d_tag == DT_AUXILIARY);
        args.name = name;
        bool malloced;
        _dl_catch_error (&objname, &errstring, &malloced,
                        openaux, &args);

        newp = alloca (sizeof (struct list));
        memcpy (newp, orig, sizeof (*newp));

        orig->done = 0;
        orig->map = args.aux;

        if (needed != NULL)
          needed[nneeded++] = args.aux;

        if (args.aux->l_reserved)
          {
            struct list *late;
            for (late = newp; late->next != NULL; late = late->next)
              if (late->next->map == args.aux)
            break;

            if (late->next != NULL)
              {
            orig->next = newp;

            if (tail == late->next)
              tail = late;
            late->next = late->next->next;

            if (args.aux->l_prev != NULL)
              args.aux->l_prev->l_next = args.aux->l_next;
            if (args.aux->l_next != NULL)
              args.aux->l_next->l_prev = args.aux->l_prev;

            args.aux->l_prev = newp->map->l_prev;
            newp->map->l_prev = args.aux;
            if (args.aux->l_prev != NULL)
              args.aux->l_prev->l_next = args.aux;
            args.aux->l_next = newp->map;
              }
            else
              {
            memcpy (orig, newp, sizeof (*newp));
            continue;
              }
          }
        else
          {
            orig->next = newp;
            ++nlist;
            args.aux->l_reserved = 1;

            if (args.aux->l_prev)
              args.aux->l_prev->l_next = args.aux->l_next;
            if (args.aux->l_next)
              args.aux->l_next->l_prev = args.aux->l_prev;

            args.aux->l_prev = newp->map->l_prev;
            newp->map->l_prev = args.aux;
            if (args.aux->l_prev != NULL)
              args.aux->l_prev->l_next = args.aux;
            args.aux->l_next = newp->map;
          }

        if (orig == tail)
          tail = newp;

        orig = newp;
          }
    }

      if (needed != NULL)
    {
      needed[nneeded++] = NULL;

      struct link_map **l_initfini = (struct link_map **)
        malloc ((2 * nneeded + 1) * sizeof needed[0]);
      l_initfini[0] = l;
      memcpy (&l_initfini[1], needed, nneeded * sizeof needed[0]);
      memcpy (&l_initfini[nneeded + 1], l_initfini,
          nneeded * sizeof needed[0]);
      atomic_write_barrier ();
      l->l_initfini = l_initfini;
    }

      if (runp->done)
    do
      runp = runp->next;
    while (runp != NULL && runp->done);
    }

接下来遍历known数组,由第一部分代码可知,这时候known数组的第一个项为应用程序对应的link_map,也即前面的main_map,也即这里传入的参数map,剩余的项为预加载的link_map,当然后面的代码对哪个link_map都一视同仁了。
首先将runp的done置位,表示该共享库已经解析过了。
如果即将解析的共享库l不是应用程序的link_map,并且其l_searchlist和l_initfini没有初始化,则分配new_size大小的空间needed,l_ldnum是.dynamic段中项的数目,因此new_size是需要打开的共享库数目的上界。
然后查找.dynamic段中,也即l_info中是否有类型为DT_NEEDED、AUXTAG或者FILTERTAG的项,这三个类型都是后面需要解析出来的依赖共享库,至于这三个类型的意义,网上都有介绍,这里就不多说了。如果有,接着获取字符串列表strtab,l_ld变量为.dynamic在内存中的实际加载地址。
接下来将当前处理的link_map保存在orig中。

第一种情况,如果.dynamic段中存在类型为DT_NEEDED的项,就从字符串列表strtab中获取共享库名称name,通过expand_dst对name中的特殊字符例如ORIGIN、PLATRORM和LIB进行处理。然后通过_dl_catch_error函数根据刚刚处理完的共享库名name打开共享库,_dl_catch_error函数包含了异常处理,其内部调用了传入的函数指针openaux打开共享库,打开共享库对应的link_map结构保存在参数args的aux变量中,也即dep。
dep的l_reserved标志表示该共享库是否已经在待处理的known数组中,如果还没处理,就分配一个list结构,封装刚刚打开的共享库dep的信息,然后插入到known数组中,其中tail指向该数组的结尾,接着递增nlist并将l_reserved标志置位。最后如果出了应用程序以外的link_map没有初始化l_searchlist和l_initfini,就将刚刚打开的共享库dep添加到前面分配的needed数组中。

第二种情况,.dynamic段中存在类型为DT_AUXILIARY或者DT_FILTER的项,则同样从字符串列表strtab中获取共享库名称name,通过expand_dst函数处理name中的特殊字符,接着将name设置进参数args中,并通过openaux函数打开该共享库。因为对类型为DT_AUXILIARY和DT_FILTER的共享库而言,要排在被依赖的共享库前面,这样在搜索的时候能优先搜到,因此接下来分配一个新的list结构newp,复制原list也即orig至newp中,并将orig的done设置为0,map设置为刚刚打开的共享库,这样newp保存了待处理的link_map,orig中保存了刚刚打开的依赖的共享库。如果need数组不为null,则将依赖的共享库添加到needed数组中。
接下来分成两种情况,第一种情况是刚刚打开的依赖的共享库,也即args.aux前面已经处理过了,即已经在known数组中了,此时l_reserved标志位被置位。这种情况下首先从newp,也即待处理的link_map开始向链表末尾遍历,查找是否有打开的共享库args.aux,如果共享库存在,也即late->next不为null,进入if语句部分,就要修改list的next指针将该共享库从原来的位置删除,用前面创建的orig替代。最后,修改该共享库的l_prev和l_next指针,移动该共享库在全局列表_dl_ns[LM_ID_BASE]中的位置,将其从原有位置移动到待处理的共享库在全局列表中的位置之前。如果在链表中没有找到刚刚打开的共享库args.aux,则说明该共享库args.aux已经排在待处理的共享库前面了,此时进入else语句部分,直接将newp的内容复制回orig,也即原本计划互换newp和orig数据取消了,什么也不用做了。
另一种情况是刚刚打开的共享库没有处理过,也即l_reserved标志位为0,此时直接设置orig的next指向newp,这样orig就是刚刚打开的依赖的共享库,newp是前面待处理的共享库,因此两者互换了位置。然后递增nlist并设置标志位l_reserved,最后通过l_prev和l_next指针将依赖的共享库args.aux从全局链表的末尾(一般情况下)移动至newp之前。
最后处理尾部指针tail,并将orig从新指向待处理的共享库,也即此时的newp。

如果待处理的共享库不是main_map,此时needed数组中保存了待处理的共享库依赖的所有共享库,nneeded为其个数,此时分配2 * nneeded + 1大小的空间l_initfini,l_initfini数组的第一个位置保存待处理的共享库l,接着连续拷贝两次needed数组至l_initfini中,之所以要拷贝两次,是为了后续排序用的。最后设置进待处理的共享库l的l_initfini变量。

最后通过while循环将runp指向下一个未处理的共享库,返回到开头的for循环,继续执行。

因为这段代码比较复杂,而且很重要,下面举一个例子以说明。

假设known中存在A、B、C是三个待处理的共享库,即known[0]=A,known[1]=B,known[2]=C。A为应用程序,依赖A1,A1类型为DT_NEEDED、B为preload的共享库,依赖B1,B1类型为DT_NEEDED,C为preload的共享库,依赖C1,C1类型为DT_FILTER,C1依赖C2、C2类型为DT_AUXILIARY。ld.so对应的共享库为D。
for循环开始处理这三个共享库,首先处理A,打开共享库A1,然后插入到known数组中,因此known数组此时为A、B、C、A1。_dl_ns中为A、D、B、C、A1。
然后开始处理B,打开共享库B1,插入到known数组中,此时known数组为A、B、C、A1、B1。_dl_ns中为A、D、B、C、A1、B1。
接下来处理C,打开共享库C1,因为C1的类型为DT_FILTER,因此需要调换C和C1的位置,最后known数组为A、B、C1、C、A1、B1。_dl_ns中为A、D、B、C1、C、A1、B1。
因为runp指针没向前移动,因此接下来处理C1,打开共享库C2,因为C2的类型为DT_AUXILIARY,因此需要调用C1和C2的位置,最后known数组为A、B、C2、C1、C、A1、B1。_dl_ns中为A、D、B、C2、C1、C、A1、B1。
再下来依次处理C2、C1、C、A1、B1,因为C2、A1、B1没有依赖,并且C1、C的done标志已经处理过了,因此什么也不做。
最后的known数组为A、B、C2、C1、C、A1、B1。全局_dl_ns中为A、D、B、C2、C1、C、A1、B1。

elf/dl-deps.c
dl_main->_dl_map_object_deps->openaux

static void openaux (void *a)
{
  struct openaux_args *args = (struct openaux_args *) a;

  args->aux = _dl_map_object (args->map, args->name,
                  (args->map->l_type == lt_executable
                   ? lt_library : args->map->l_type),
                  args->trace_mode, args->open_mode,
                  args->map->l_ns);
}

openaux函数内部通过_dl_map_object打开共享库,该函数前面已经分析过了。

elf/dl-deps.c
dl_main->_dl_map_object_deps第三部分

  struct link_map **old_l_initfini = NULL;

  struct link_map **l_initfini =
    (struct link_map **) malloc ((2 * nlist + 1)
                 * sizeof (struct link_map *));

  map->l_searchlist.r_list = &l_initfini[nlist + 1];
  map->l_searchlist.r_nlist = nlist;

  for (nlist = 0, runp = known; runp; runp = runp->next)
    {
      map->l_searchlist.r_list[nlist++] = runp->map;
      runp->map->l_reserved = 0;
    }

  memcpy (l_initfini, map->l_searchlist.r_list,
      nlist * sizeof (struct link_map *));
  if (__builtin_expect (nlist > 1, 1))
    {
      i = 1;
      char seen[nlist];
      memset (seen, 0, nlist * sizeof (seen[0]));
      while (1)
    {
      ++seen[i];
      struct link_map *thisp = l_initfini[i];

      unsigned int k = nlist - 1;
      while (k > i)
        {
          struct link_map **runp = l_initfini[k]->l_initfini;
          if (runp != NULL)
        while (*runp != NULL)
          if (__builtin_expect (*runp++ == thisp, 0))
            {
              memmove (&l_initfini[i], &l_initfini[i + 1],
                   (k - i) * sizeof (l_initfini[0]));
              l_initfini[k] = thisp;

              if (seen[i + 1] > 1)
            {
              ++i;
              goto next_clear;
            }

              char this_seen = seen[i];
              memmove (&seen[i], &seen[i + 1],
                   (k - i) * sizeof (seen[0]));
              seen[k] = this_seen;

              goto next;
            }

          --k;
        }

      if (++i == nlist)
        break;
    next_clear:
      memset (&seen[i], 0, (nlist - i) * sizeof (seen[0]));

    next:;
    }
    }

  l_initfini[nlist] = NULL;
  atomic_write_barrier ();
  map->l_initfini = l_initfini;
}

nlist为known数组中共享库的个数。首先分配2 * nlist + 1大小的空间l_initfini。然后将known数组中的共享库指针拷贝到l_initfini数组的最后nlist个元素中。再通过memcpy函数拷贝l_initfini数组的最后nlist个元素,其实就是拷贝known数组中的共享库指针到l_initfini的前nlist个元素中。
接下来分配seen数组并通过memset函数将每个元素初始化为0,seen用来标记共享库的处理次数。
再往下进入while循环,首先获取共享库thisp,并递增对应的处理次数。
然后从known数组的尾部向前遍历每个共享库l_initfini[k],获取该共享库依赖的所有共享库,如果这些共享库包含了本次处理的共享库thisp,则将thisp至l_initfini[k]间的所有共享库向前平移一个位置,然后互换thisp和l_initfini[k]。并且,如果互换前下一个待处理的共享库l_initfini[i+1]对应的处理次数大于1,则说明出现了循环依赖,并且下一个共享库在循环依赖中并且被处理了两次,互换后,下一个共享库为l_initfini[i],因此需要停止处理该共享库,以免无限循环下去,递增i,并跳转到next_clear,将l_initfini[i]之后的计数清0,从新对后面的共享库进行排序。
如果没有循环依赖,并且i之后的共享库不依赖第i个共享库,则直接递增i,处理下一个共享库,如果已经处理完最后一个共享库,则直接退出循环。
最后将设置应用程序link_map的l_initfini变量,到此为止,应用程序link_map的l_searchlist.r_list变量保存了未排序的依赖的共享库,l_initfini变量保存了排序后的共享库。

由于这段代码依然比较复杂,下面举个例子说明。
假设known数组中的共享库为A、B、C、D,其中B依赖D,D依赖B。
则map->l_searchlist.r_list最终为A、B、C、D。
下面用中括号代表seen数组中的计数,初始值为0,记为A[0],B[0],C[0],D[0]。
初始i为0,因为A不依赖于任何共享库,while第一个次的循环结果为A[1],B[0],C[0],D[0]。
此时i为1,因为B依赖于D,因此要向前平移C和D,处理结果为A[1],C[0],D[0],B[1]。
此时i为1,因为C不依赖于任何共享库,最终会跳转到next_clear处清空1之后的计数,处理结果为A[1],C[1],D[0],B[0]。
此时i为2,因为D依赖B,因此互换D和B,处理结果为A[1],C[1],B[0],D[1]。
此时i为2,因为B依赖D,因此互换B和D,最终处理结果为A[1],C[1],D[1],B[1]。
此时i为2,因为D依赖B,因此互换D和B,处理结果为A[1],C[1],B[1],D[2]。
此时i为2,因为B依赖D,因此互换B和D,但是D的计数为2>1,此时停止排序,跳转到next_clear处,最终处理结果为A[1],C[1],D[2],B[0]。
此时i为3,因为后面没有需要排序的共享库了,因此map的l_initfini变量最终排序结果为A,C,D,B。

elf/rtld.c
dl_main第十一部分

  for (i = main_map->l_searchlist.r_nlist; i > 0; )
    main_map->l_searchlist.r_list[--i]->l_global = 1;

  GL(dl_rtld_map).l_prev->l_next = GL(dl_rtld_map).l_next;
  if (GL(dl_rtld_map).l_next != NULL)
    GL(dl_rtld_map).l_next->l_prev = GL(dl_rtld_map).l_prev;

  for (i = 1; i < main_map->l_searchlist.r_nlist; ++i)
    if (main_map->l_searchlist.r_list[i] == &GL(dl_rtld_map))
      break;

  bool rtld_multiple_ref = false;
  if (__builtin_expect (i < main_map->l_searchlist.r_nlist, 1))
    {
      rtld_multiple_ref = true;

      GL(dl_rtld_map).l_prev = main_map->l_searchlist.r_list[i - 1];
      GL(dl_rtld_map).l_next = (i + 1 < main_map->l_searchlist.r_nlist
                    ? main_map->l_searchlist.r_list[i + 1]: NULL);
      GL(dl_rtld_map).l_prev->l_next = &GL(dl_rtld_map);
      if (GL(dl_rtld_map).l_next != NULL)
    {
      GL(dl_rtld_map).l_next->l_prev = &GL(dl_rtld_map);
    }
    }

  {
    struct version_check_args args;
    args.doexit = mode == normal;
    args.dotrace = mode == trace;
    _dl_receive_error (print_missing_version, version_check_doit, &args);
  }

首先将main_map依赖的所有共享库的l_global置位,标识为全局。接下来将ld.so自身的link_map,也即dl_rtld_map从全局链表_dl_ns中移除。因为依赖的共享库l_searchlist.r_list数组的第一个项为main_map自身,因此i从1开始,查找其依赖的共享库是否包含了ld.so。接下来,如果i小于r_nlist,则表示应用程序main_map依赖的共享库包含了解释器ld.so自身,这时将ld.so的link_map结构dl_rtld_map重新插入到全局链表_dl_ns中去。

elf/rtld.c
dl_main->version_check_doit

static void
version_check_doit (void *a)
{
  struct version_check_args *args = (struct version_check_args *) a;
  if (_dl_check_all_versions (GL(dl_ns)[LM_ID_BASE]._ns_loaded, 1,
                  args->dotrace) && args->doexit)
    _exit (1);
}

int internal_function
_dl_check_all_versions (struct link_map *map, int verbose, int trace_mode)
{
  struct link_map *l;
  int result = 0;

  for (l = map; l != NULL; l = l->l_next)
    result |= (! l->l_faked
           && _dl_check_map_versions (l, verbose, trace_mode));

  return result;
}

version_check_doit函数从main_map开始,向后遍历所有的共享库link_map,通过_dl_check_map_versions检查其符号信息。

elf/dl-version.c
dl_main->version_check_doit->_dl_check_all_versions->_dl_check_map_versions

int internal_function
_dl_check_map_versions (struct link_map *map, int verbose, int trace_mode)
{
  int result = 0;
  const char *strtab;
  ElfW(Dyn) *dyn;
  ElfW(Dyn) *def;
  unsigned int ndx_high = 0;

  const char *errstring = NULL;
  int errval = 0;

  if (map->l_info[DT_STRTAB] == NULL)
    return 0;
  strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);

  dyn = map->l_info[VERSYMIDX (DT_VERNEED)];
  def = map->l_info[VERSYMIDX (DT_VERDEF)];

  if (dyn != NULL)
    {
      ElfW(Verneed) *ent = (ElfW(Verneed) *) (map->l_addr + dyn->d_un.d_ptr);

      if (__builtin_expect (ent->vn_version, 1) != 1)
    {
      ...
    }

      while (1)
    {
      ElfW(Vernaux) *aux;
      struct link_map *needed = find_needed (strtab + ent->vn_file, map);

      if (__builtin_expect (! trace_mode, 1)
          || ! __builtin_expect (needed->l_faked, 0))
        {
          aux = (ElfW(Vernaux) *) ((char *) ent + ent->vn_aux);
          while (1)
        {
          result |= match_symbol ((*map->l_name
                       ? map->l_name : rtld_progname),
                      map->l_ns, aux->vna_hash,
                      strtab + aux->vna_name,
                      needed->l_real, verbose,
                      aux->vna_flags & VER_FLG_WEAK);

          if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high)
            ndx_high = aux->vna_other & 0x7fff;

          if (aux->vna_next == 0)
            break;

          aux = (ElfW(Vernaux) *) ((char *) aux + aux->vna_next);
        }
        }

      if (ent->vn_next == 0)
        break;

      ent = (ElfW(Verneed) *) ((char *) ent + ent->vn_next);
    }
    }

  if (def != NULL)
    {
      ElfW(Verdef) *ent;
      ent = (ElfW(Verdef) *) (map->l_addr + def->d_un.d_ptr);
      while (1)
    {
      if ((unsigned int) (ent->vd_ndx & 0x7fff) > ndx_high)
        ndx_high = ent->vd_ndx & 0x7fff;

      if (ent->vd_next == 0)
        break;

      ent = (ElfW(Verdef) *) ((char *) ent + ent->vd_next);
    }
    }

  if (ndx_high > 0)
    {
      map->l_versions = (struct r_found_version *)
    calloc (ndx_high + 1, sizeof (*map->l_versions));
      if (__builtin_expect (map->l_versions == NULL, 0))
    {
      errstring = N_("cannot allocate version reference table");
      errval = ENOMEM;
      goto call_error;
    }

      map->l_nversions = ndx_high + 1;
      map->l_versyms = (void *) D_PTR (map, l_info[VERSYMIDX (DT_VERSYM)]);

      if (dyn != NULL)
    {
      ElfW(Verneed) *ent;
      ent = (ElfW(Verneed) *) (map->l_addr + dyn->d_un.d_ptr);
      while (1)
        {
          ElfW(Vernaux) *aux;
          aux = (ElfW(Vernaux) *) ((char *) ent + ent->vn_aux);
          while (1)
        {
          ElfW(Half) ndx = aux->vna_other & 0x7fff;
          if (__builtin_expect (ndx < map->l_nversions, 1))
            {
              map->l_versions[ndx].hash = aux->vna_hash;
              map->l_versions[ndx].hidden = aux->vna_other & 0x8000;
              map->l_versions[ndx].name = &strtab[aux->vna_name];
              map->l_versions[ndx].filename = &strtab[ent->vn_file];
            }

          if (aux->vna_next == 0)
            break;

          aux = (ElfW(Vernaux) *) ((char *) aux + aux->vna_next);
        }

          if (ent->vn_next == 0)
        break;

          ent = (ElfW(Verneed) *) ((char *) ent + ent->vn_next);
        }
    }

      if (def != NULL)
    {
      ElfW(Verdef) *ent;
      ent = (ElfW(Verdef)  *) (map->l_addr + def->d_un.d_ptr);
      while (1)
        {
          ElfW(Verdaux) *aux;
          aux = (ElfW(Verdaux) *) ((char *) ent + ent->vd_aux);

          if ((ent->vd_flags & VER_FLG_BASE) == 0)
        {
          ElfW(Half) ndx = ent->vd_ndx & 0x7fff;
          map->l_versions[ndx].hash = ent->vd_hash;
          map->l_versions[ndx].name = &strtab[aux->vda_name];
          map->l_versions[ndx].filename = NULL;
        }

          if (ent->vd_next == 0)
        break;

          ent = (ElfW(Verdef) *) ((char *) ent + ent->vd_next);
        }
    }
    }

  return result;
}

首先从共享库.dynamic段中找到Verneed结构ent,vn_version变量代表整个Verneed结构的版本,目前必须是1。vn_file指向依赖的共享库名,通过find_needed函数找到该共享库。然后通过ent的vn_aux变量找到Vernaux结构的地址aux,进入while循环,通过match_symbol函数在刚刚vn_file指向的共享库对应的DT_VERDEF段内查找是否有该符号,如果没有,则返回错误,即result为1,然后利用vna_next获取下一个待检查的结构Vernaux。另外ndx_high保存了待验证符号的最高版本。
寻找完一个Verneed结构ent后,利用vn_next找到下一个Verneed结构,继续循环查找。
遍历完DT_VERNEED段后,接下来遍历DT_VERDEF段,计算后面索引值ndx_high的最大值。
接下来分配ndx_high大小的内存空间l_versions,然后再一次遍历DT_VERNEED和DT_VERDEF对应的段,将其中的信息存储到l_versions中。

elf/dl-version.c
dl_main->version_check_doit->_dl_check_all_versions->_dl_check_map_versions->find_needed

static inline struct link_map *__attribute ((always_inline))
find_needed (const char *name, struct link_map *map)
{
  struct link_map *tmap;
  unsigned int n;

  for (tmap = GL(dl_ns)[map->l_ns]._ns_loaded; tmap != NULL;
       tmap = tmap->l_next)
    if (_dl_name_match_p (name, tmap))
      return tmap;

  for (n = 0; n < map->l_searchlist.r_nlist; n++)
    if (_dl_name_match_p (name, map->l_searchlist.r_list[n]))
      return map->l_searchlist.r_list[n];

  return NULL;
}

_dl_name_match_p函数首先遍历全局_dl_ns中的link_map结构,然后通过_dl_name_match_p函数检查l_libname链表是否有匹配的name,如果有,最终返回包含该name的link_map结构。如果全局_dl_ns中找不到,则在map依赖的link_map结构中继续查找。

elf/dl-version.c
dl_main->version_check_doit->_dl_check_all_versions->_dl_check_map_versions->find_needed->match_symbol

static int internal_function
match_symbol (const char *name, Lmid_t ns, ElfW(Word) hash, const char *string,
          struct link_map *map, int verbose, int weak)
{
  const char *strtab = (const void *) D_PTR (map, l_info[DT_STRTAB]);
  ElfW(Addr) def_offset;
  ElfW(Verdef) *def;
  const char *errstring = NULL;
  int result = 0;

  if (__builtin_expect (map->l_info[VERSYMIDX (DT_VERDEF)] == NULL, 0))
    {
      return 0;
    }

  def_offset = map->l_info[VERSYMIDX (DT_VERDEF)]->d_un.d_ptr;

  def = (ElfW(Verdef) *) ((char *) map->l_addr + def_offset);
  while (1)
    {
      if (__builtin_expect (def->vd_version, 1) != 1)
    {
      goto call_cerror;
    }

      if (hash == def->vd_hash)
    {
      ElfW(Verdaux) *aux = (ElfW(Verdaux) *) ((char *) def + def->vd_aux);

      if (__builtin_expect (strcmp (string, strtab + aux->vda_name), 0)
          == 0)
        return 0;
    }

      if (def->vd_next == 0)
    break;

      def = (ElfW(Verdef) *) ((char *) def + def->vd_next);
    }

  if (__builtin_expect (weak, 1))
    {
      return 0;
    }

  result = 1;
  return result;
}

首先检查DT_VERDEF段是否为空,如果没有,则直接返回0。
如果存在,接下来从.dynamic段中获得DT_VERDEF段的偏移,加上共享库的装载地址l_addr获得该段在内存中的实际地址def_offset,对应的结构为Verdef。
首先vd_version变量标识该结构的版本,必须为1。
接下来如果原共享库的Vernaux结构的hash和依赖的共享库的Verdef结构的vd_hash一致,则直接通过strcmp函数比较原共享库的符号名string是否和依赖的共享库的符号名(vda_name指向符号表的位置)一致,如果一致则找到了该符号,直接返回。如果没有找到,则通过vd_next找到下一个Verdef结构,返回while循环,继续查找符号。
接下来,如果没有在依赖共享库map中找到符号,但是该符号是一个弱应用符号,则不返回错误。否则,返回错误,即result位1。

elf/rtld.c
dl_main第十二部分


  ...

  GL(dl_ns)[LM_ID_BASE]._ns_main_searchlist = &main_map->l_searchlist;
  GLRO(dl_initial_searchlist) = *GL(dl_ns)[LM_ID_BASE]._ns_main_searchlist;
  GLRO(dl_init_all_dirs) = GL(dl_all_dirs);


  unsigned i = main_map->l_searchlist.r_nlist;
  while (i-- > 0)
  {
    struct link_map *l = main_map->l_initfini[i];
    struct libname_list *lnp = l->l_libname->next;

    if (l != &GL(dl_rtld_map))
      _dl_relocate_object (l, l->l_scope, GLRO(dl_lazy) ? RTLD_LAZY : 0,
                 consider_profiling);
  }

  ...

}

这段代码省略了一些tls部分等不重要的代码。dl_main函数的最后这部分代码首先设置一些全局变量,然后遍历应用程序main_map依赖的共享库(除了ld.so,因为前面在_dl_start函数中已经自己对自己进行了重定位了),调用_dl_relocate_object对其进行重定位,该函数在《_dl_start源码分析》中已经分析过了。

至此,分析完了dl_main函数的所有代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值