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源码分析(一)】下一章开始分析dl_main的后续代码。

    推荐阅读