因为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的后续代码。