_dl_start_user源码分析(一)
在上面几章的分析中,_dl_start_final函数的返回值为_dl_start_user函数指针,最后调用该函数,下面来看,
sysdeps/x86_64/dl-machine.h
_dl_start_user
#define RTLD_START asm ("\n\
.text\n\
.align 16\n\
.globl _start\n\
.globl _dl_start_user\n\
_start:\n\
movq %rsp, %rdi\n\
call _dl_start\n\
_dl_start_user:\n\
movq %rax, %r12\n\
movl _dl_skip_args(%rip), %eax\n\
popq %rdx\n\
leaq (%rsp,%rax,8), %rsp\n\
subl %eax, %edx\n\
pushq %rdx\n\
movq %rdx, %rsi\n\
movq %rsp, %r13\n\
andq $-16, %rsp\n\
movq _rtld_local(%rip), %rdi\n\
leaq 16(%r13,%rdx,8), %rcx\n\
leaq 8(%r13), %rdx\n\
xorl %ebp, %ebp\n\
call _dl_init_internal@PLT\n\
leaq _dl_fini(%rip), %rdx\n\
movq %r13, %rsp\n\
jmp *%r12\n\
.previous\n\
");
前面的章节分析了_dl_start函数的源码,rax寄存器存放了_dl_start函数的返回值,即应用程序的入口点(注意这里不是main函数),也即后面即将分析的_start函数。接着从堆栈中取argc,即参数个数存放在rdx中。
堆栈中接下来是第一个参数的地址,_dl_skip_args中保存了堆栈中需要忽略的参数个数,将其存入rax寄存器中。当ld.so作为应用程序启动,并且包含了诸如“–list”、“–library-path”等参数,则需要忽略这些参数,即移动堆栈的指针rsp向高地址移动rax*8,也即_dl_skip_args个参数占用的堆栈空间。再往下将argc减去_dl_skip_args,得到有效参数的个数rdx,再存入堆栈中,前面的一系列操作等价于在堆栈中忽略了无效参数。
然后将前面计算的有效参数个数保存在rsi寄存器中。将当前堆栈指针rsp存入r13中,当前堆栈指针rsp指向刚刚存入的有效参数个数。接着将rsp按照16位对齐。
_rtld_local是全局的rtld_global结构指针,其第一个元素为_dl_ns数组,而LM_ID_BASE宏定义为0,因此_rtld_local指针也指向GL(dl_ns)[LM_ID_BASE]._ns_loaded,即应用程序对应的link_map指针。接着将堆栈中的环境变量存入rcx寄存器中,将堆栈中的参数argv存入rdx寄存器。然后调用_dl_init_internal函数。_dl_init_internal是_dl_init函数的别名。
接下来恢复前面保存的堆栈指针r13至rsp中,再将_dl_fini的函数地址存储在rdx中,最后执行函数的入口点,也即_start函数。
elf/dl-init.h
_dl_start_user->_dl_init
void
internal_function
_dl_init (struct link_map *main_map, int argc, char **argv, char **env)
{
ElfW(Dyn) *preinit_array = main_map->l_info[DT_PREINIT_ARRAY];
ElfW(Dyn) *preinit_array_size = main_map->l_info[DT_PREINIT_ARRAYSZ];
unsigned int i;
if (__builtin_expect (GL(dl_initfirst) != NULL, 0))
{
call_init (GL(dl_initfirst), argc, argv, env);
GL(dl_initfirst) = NULL;
}
if (__builtin_expect (preinit_array != NULL, 0)
&& preinit_array_size != NULL
&& (i = preinit_array_size->d_un.d_val / sizeof (ElfW(Addr))) > 0)
{
ElfW(Addr) *addrs;
unsigned int cnt;
addrs = (ElfW(Addr) *) (preinit_array->d_un.d_ptr + main_map->l_addr);
for (cnt = 0; cnt < i; ++cnt)
((init_t) addrs[cnt]) (argc, argv, env);
}
i = main_map->l_searchlist.r_nlist;
while (i-- > 0)
call_init (main_map->l_initfini[i], argc, argv, env);
}
_dl_init函数首先在.dynamic段中查找.preinit段的信息,存放在preinit_array地址中,后面要调用该段的函数。dl_initfirst在_dl_map_object_from_fd函数中被赋值,表示需要被预先初始化的共享库,如果存在则执行call_init函数对齐进行初始化。接下来获得preinit_array对应的初始化函数地址addrs并执行。
再往下获得应用程序main_map依赖的所有共享库个数i,循环调用call_init函数执行每个共享库的初始化函数。
elf/dl-init.h
_dl_start_user->_dl_init->call_init
static void
call_init (struct link_map *l, int argc, char **argv, char **env)
{
if (l->l_init_called)
return;
l->l_init_called = 1;
if (__builtin_expect (l->l_name[0], 'a') == '\0'
&& l->l_type == lt_executable)
return;
if (l->l_info[DT_INIT] == NULL
&& __builtin_expect (l->l_info[DT_INIT_ARRAY] == NULL, 1))
return;
if (l->l_info[DT_INIT] != NULL)
{
init_t init = (init_t) DL_DT_INIT_ADDRESS
(l, l->l_addr + l->l_info[DT_INIT]->d_un.d_ptr);
init (argc, argv, env);
}
ElfW(Dyn) *init_array = l->l_info[DT_INIT_ARRAY];
if (init_array != NULL)
{
unsigned int j;
unsigned int jm;
ElfW(Addr) *addrs;
jm = l->l_info[DT_INIT_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr));
addrs = (ElfW(Addr) *) (init_array->d_un.d_ptr + l->l_addr);
for (j = 0; j < jm; ++j)
((init_t) addrs[j]) (argc, argv, env);
}
}
如果共享库对应的link_map结构的l_init_called被置位,则该共享库已被初始化,直接返回,否则开始初始化该共享库,并置位l_init_called。再往下检查共享库是否是可执行类型lt_executable,并且l_name为空,此时直接返回。然后继续检查.init和.init_array段是否都为空,此时也直接返回。如果.init段不为空,则获取函数地址init并执行。如果.init_array段不为空,则获取初始化函数个数jm,然后获取函数数组addrs,最后循环执行其初始化函数。
sysdeps/x86_64/elf/start.S
_dl_start_user->_start
.text
.globl _start
.type _start,@function
_start:
xorl %ebp, %ebp
movq %rdx, %r9
popq %rsi
movq %rsp, %rdx
andq $~15, %rsp
pushq %rax
pushq %rsp
movq __libc_csu_fini@GOTPCREL(%rip), %r8
movq __libc_csu_init@GOTPCREL(%rip), %rcx
movq BP_SYM (main)@GOTPCREL(%rip), %rdi
call BP_SYM (__libc_start_main)@PLT
hlt
下面来看_dl_start_user函数最后执行的_start函数。
rdx寄存器保存了_dl_start_user函数中设置的_dl_fini函数指针。然后从堆栈中取出参数个数argc存储在rsi寄存器中。接着将当前堆栈指针rsp存入rdx寄存器中,当前堆栈指针指向argv,因此rdx寄存器中存储了argv,也即参数地址。为了按照16字节对齐,接下来向堆栈压入8个地址的无用字节用于16字节对齐,再压入8字节的rsp地址,表示用户空间堆栈的最高地址。然后设置__libc_csu_fini函数地址至r8寄存器中,再设置__libc_csu_init的函数地址至rcx寄存器中,然后将应用程序的main函数存入rdi寄存器中,最后调用__libc_start_main函数。