_dl_start_user源码分析(一)

来源:互联网 发布:淘宝联盟怎么查订单号 编辑:程序博客网 时间:2024/06/05 15:42

_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

voidinternal_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 voidcall_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函数。

0 0
原创粉丝点击