_dl_start_user源码分析(一)

_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函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值