_dl_start源码分析
上一章分析了linux系统调用sys_execv的源码,最终linux会将控制权交给解释器的_start函数,该函数紧接着调用_dl_start函数,本章就分析该函数。因为glibc内部的宏定义太多,不适合代码的阅读,因此本章以及后面的章节删除了一部分定义和代码分支。
elf/rtld.c
_dl_start第一部分
static ElfW(Addr) __attribute_used__ internal_function
_dl_start (void *arg)
{
# define bootstrap_map GL(dl_rtld_map)
#define RTLD_BOOTSTRAP
#define RESOLVE_MAP(sym, version, flags) (&bootstrap_map)
#include "dynamic-link.h"
if (HP_TIMING_INLINE && HP_TIMING_AVAIL)
HP_TIMING_NOW (start_time);
GL的宏定义就是在变量名前面加上下划线,例如GL(dl_rtld_map)也即_dl_rtld_map,其为link_map结构。
HP_TIMING_NOW内部使用rdtsc指令获得cpu的计数器值,计数器一共64位,高位在rdx寄存器中,低位在rax寄存器中。
elf/rtld.c
_dl_start第二部分
bootstrap_map.l_addr = elf_machine_load_address ();
bootstrap_map.l_ld = (void *) bootstrap_map.l_addr + elf_machine_dynamic ();
elf_get_dynamic_info (&bootstrap_map, NULL);
#if NO_TLS_OFFSET != 0
bootstrap_map.l_tls_offset = NO_TLS_OFFSET;
#endif
elf_machine_load_address获得解释器的装载地址,elf_machine_dynamic获得.dynamic节的静态地址,加上装载地址即是.dynamic节的实际地址。
elf_get_dynamic_info设置info,后面的链接过程要大量用到这个数组的信息。
sysdeps/x86_64/dl-machine.h
_dl_start->elf_machine_load_address
static inline Elf64_Addr __attribute__ ((unused))
elf_machine_load_address (void)
{
Elf64_Addr addr;
asm ("leaq _dl_start(%%rip), %0\n\t"
"subq 1f(%%rip), %0\n\t"
".section\t.data.rel.ro\n"
"1:\t.quad _dl_start\n\t"
".previous\n\t"
: "=r" (addr) : : "cc");
return addr;
}
data段中存储了_dl_start静态连接后的地址,汇编的第一句获取了_dl_start当前运行时的实际地址,两者的差即是解释器的装载地址addr。
sysdeps/x86_64/dl-machine.h
_dl_start->elf_machine_dynamic
static inline Elf64_Addr __attribute__ ((unused))
elf_machine_dynamic (void)
{
Elf64_Addr addr;
addr = (Elf64_Addr) &_DYNAMIC;
return addr;
}
elf_machine_dynamic返回.dynamic节的地址,因为.dynamic节的地址是GOT表中的第一个项,所以addr也即GOT表的地址。
sysdeps/x86_64/dl-machine.h
_dl_start->elf_machine_dynamic
inline void __attribute__ ((unused, always_inline))
elf_get_dynamic_info (struct link_map *l, ElfW(Dyn) *temp)
{
ElfW(Dyn) *dyn = l->l_ld;
ElfW(Dyn) **info;
typedef Elf64_Xword d_tag_utype;
info = l->l_info;
while (dyn->d_tag != DT_NULL)
{
if ((d_tag_utype) dyn->d_tag < DT_NUM)
info[dyn->d_tag] = dyn;
else if (dyn->d_tag >= DT_LOPROC &&
dyn->d_tag < DT_LOPROC + DT_THISPROCNUM)
info[dyn->d_tag - DT_LOPROC + DT_NUM] = dyn;
else if ((d_tag_utype) DT_VERSIONTAGIDX (dyn->d_tag) < DT_VERSIONTAGNUM)
info[VERSYMIDX (dyn->d_tag)] = dyn;
else if ((d_tag_utype) DT_EXTRATAGIDX (dyn->d_tag) < DT_EXTRANUM)
info[DT_EXTRATAGIDX (dyn->d_tag) + DT_NUM + DT_THISPROCNUM
+ DT_VERSIONTAGNUM] = dyn;
else if ((d_tag_utype) DT_VALTAGIDX (dyn->d_tag) < DT_VALNUM)
info[DT_VALTAGIDX (dyn->d_tag) + DT_NUM + DT_THISPROCNUM
+ DT_VERSIONTAGNUM + DT_EXTRANUM] = dyn;
else if ((d_tag_utype) DT_ADDRTAGIDX (dyn->d_tag) < DT_ADDRNUM)
info[DT_ADDRTAGIDX (dyn->d_tag) + DT_NUM + DT_THISPROCNUM
+ DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM] = dyn;
++dyn;
}
ADJUST_DYN_INFO (DT_HASH);
ADJUST_DYN_INFO (DT_PLTGOT);
ADJUST_DYN_INFO (DT_STRTAB);
ADJUST_DYN_INFO (DT_SYMTAB);
ADJUST_DYN_INFO (DT_RELA);
ADJUST_DYN_INFO (DT_JMPREL);
ADJUST_DYN_INFO (VERSYMIDX (DT_VERSYM));
ADJUST_DYN_INFO (DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM + DT_THISPROCNUM
+ DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM);
}
ElfW的宏定义为
#define ElfW(type) _ElfW (Elf, __ELF_NATIVE_CLASS, type)
#define _ElfW(e,w,t) _ElfW_1 (e, w, _##t)
#define _ElfW_1(e,w,t) e##w##t
例如上面代码中的ElfW(Addr)其实就是Elf64_Addr(如果是32位机则是Elf32_Addr)。
因此ElfW(Dyn)拓展开来就是Elf64_Dyn,其定义如下,
typedef struct
{
Elf64_Sxword d_tag;
union
{
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
d_tag内存储的是.dynamic节中每个值的类型,d_val和d_ptr则是其值或地址。
elf_get_dynamic_info函数首先遍历.dynamic节中所有的值,根据d_tag类型将不同的值存入link_map的info数组中。
接下来通过ADJUST_DYN_INFO宏调整.dynamic节中对应的地址,例如.hash、.plt.got、.symtab对应的section的地址。
ADJUST_DYN_INFO宏定义定义在函数内部,
# define ADJUST_DYN_INFO(tag)
do
if (info[tag] != NULL)
{
info[tag]->d_un.d_ptr += l_addr;
}
while (0)
这里就是简单加上解释器的实际装载地址。
elf/rtld.c
_dl_start第三部分
if (bootstrap_map.l_addr || ! bootstrap_map.l_info[VALIDX(DT_GNU_PRELINKED)])
{
ELF_DYNAMIC_RELOCATE (&bootstrap_map, 0, 0, 0);
}
bootstrap_map.l_relocated = 1;
ElfW(Addr) entry = _dl_start_final (arg);
# define ELF_MACHINE_START_ADDRESS(map, start) (start)
return ELF_MACHINE_START_ADDRESS (GL(dl_ns)[LM_ID_BASE]._ns_loaded, entry);
}
ELF_DYNAMIC_RELOCATE宏定义用来对ld.so自身进行重定位,主要是修改.rela.dyn和.rela.plt中的各个地址值。这就是鸡生蛋,蛋生鸡的问题,ld.so需要自己给自己重定位。然后将l_relocated设置为1表示重定位完成。后面就可以访问全局变量和函数了。
elf/dynamic-link.h
_dl_start->ELF_DYNAMIC_RELOCATE
# define ELF_DYNAMIC_RELOCATE(map, lazy, consider_profile, skip_ifunc) \
do { \
int edr_lazy = elf_machine_runtime_setup ((map), (lazy), \
(consider_profile)); \
ELF_DYNAMIC_DO_REL ((map), edr_lazy, skip_ifunc); \
ELF_DYNAMIC_DO_RELA ((map), edr_lazy, skip_ifunc); \
} while (0)
elf_machine_runtime_setup什么也不做,假设ELF_DYNAMIC_DO_REL为空定义,因此下面只看ELF_DYNAMIC_DO_RELA。
elf/dynamic-link.h
_dl_start->ELF_DYNAMIC_RELOCATE->_ELF_DYNAMIC_DO_RELOC
# define _ELF_DYNAMIC_DO_RELOC(RELOC, reloc, map, do_lazy, skip_ifunc, test_rel)
do {
struct { ElfW(Addr) start, size;
__typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative; int lazy; } ranges[2] = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }; if ((map)->l_info[DT_##RELOC]) { ranges[0].start = D_PTR ((map), l_info[DT_##RELOC]); ranges[0].size = (map)->l_info[DT_##RELOC##SZ]->d_un.d_val;
if (map->l_info[VERSYMIDX (DT_##RELOC##COUNT)] != NULL)
ranges[0].nrelative
= MIN (map->l_info[VERSYMIDX (DT_##RELOC##COUNT)]->d_un.d_val,
ranges[0].size / sizeof (ElfW(reloc)));
}
if ((map)->l_info[DT_PLTREL] && (!test_rel || (map)->l_info[DT_PLTREL]->d_un.d_val == DT_##RELOC)) { ElfW(Addr) start = D_PTR ((map), l_info[DT_JMPREL]); ranges[0].size += (map)->l_info[DT_PLTRELSZ]->d_un.d_val;
}
elf_dynamic_do_##reloc ((map), ranges[0].start, ranges[0].size,
ranges[0].nrelative, 0, skip_ifunc);
} while (0)
_ELF_DYNAMIC_DO_RELOC宏定义首先根据.dynamic节中的信息,获取.rel.dyn节的起始地址存放在start中,再获取.rel.dyn的大小存放在size中,然后获取.rel.dyn中类型为R_X86_64_RELATIVE的大小存放在nrelative中,因为.rel.dyn和.rel.plt节是紧挨着的,再往下获取.rel.plt节的大小,累加在size中,根据这些信息,最后调用elf_dynamic_do_Rel函数执行重定位。
elf/do-rel.h
_dl_start->ELF_DYNAMIC_RELOCATE->_ELF_DYNAMIC_DO_RELOC->elf_dynamic_do_Rel
auto inline void __attribute__ ((always_inline))
elf_dynamic_do_Rel (struct link_map *map,
ElfW(Addr) reladdr, ElfW(Addr) relsize,
__typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative, int lazy, int skip_ifunc) { const ElfW(Rel) *r = (const void *) reladdr; const ElfW(Rel) *end = (const void *) (reladdr + relsize); ElfW(Addr) l_addr = map->l_addr;
const ElfW(Sym) *const symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
const ElfW(Rel) *relative = r;
r += nrelative;
for (; relative < r; ++relative)
DO_ELF_MACHINE_REL_RELATIVE (map, l_addr, relative);
const ElfW(Half) *const version = (const void *) D_PTR (map, l_info[VERSYMIDX (DT_VERSYM)]);
for (; r < end; ++r)
{
ElfW(Half) ndx = version[ELFW(R_SYM) (r->r_info)] & 0x7fff;
elf_machine_rel (map, r, &symtab[ELFW(R_SYM) (r->r_info)], &map->l_versions[ndx],
(void *) (l_addr + r->r_offset), skip_ifunc);
}
}
传入的参数reladdr为.rel.dyn节的起始地址,relsize为.rel.dyn和.rel.plt节的总大小,因此end为.rel.plt的结束地址。参数nrelative为.rel.dyn中类型为R_X86_64_RELATIVE的项的个数。
首先从ld.so对应的link_map结构中获取符号表,relative为.rel.dyn的起始地址,r累加后变为.rel.dyn中最后一个类型为R_X86_64_RELATIVE的项的结束地址,也即第一个类型为R_X86_64_GLOB_DAT的起始地址。
首先通过DO_ELF_MACHINE_REL_RELATIVE宏对.rel.dyn节中类型为R_X86_64_RELATIVE的项进行重定位。然后通过elf_machine_rel函数对.rel.dyn中剩余的项进行重定位。其中重定位项中的r_info成员变量存储了该重定位项在符号表中的索引信息。
elf/do-rel.h
_dl_start->ELF_DYNAMIC_RELOCATE->_ELF_DYNAMIC_DO_RELOC->elf_dynamic_do_Rel->DO_ELF_MACHINE_REL_RELATIVE
# define DO_ELF_MACHINE_REL_RELATIVE(map, l_addr, relative)
elf_machine_rel_relative (l_addr, relative,
(void *) (l_addr + relative->r_offset))
auto inline void
__attribute ((always_inline))
elf_machine_rela_relative (Elf64_Addr l_addr, const Elf64_Rela *reloc,
void *const reloc_addr_arg)
{
Elf64_Addr *const reloc_addr = reloc_addr_arg;
*reloc_addr = l_addr + reloc->r_addend;
}
DO_ELF_MACHINE_REL_RELATIVE宏定义继而调用elf_machine_rel_relative函数,elf_machine_rel_relative的宏定义为elf_machine_rela_relative,传入的参数l_addr为ld.so的实际装载地址,reloc为.rel.dyn中类型为R_X86_64_RELATIVE的项的地址,reloc_addr_arg为需要更改值的最终地址(例如在GOT表中),将.rel.dyn中的起始数r_addend加上装载地址ld.so写入最终地址reloc_addr_arg中。
sysdeps/x86_64/dl-machine.h
_dl_start->ELF_DYNAMIC_RELOCATE->_ELF_DYNAMIC_DO_RELOC->elf_dynamic_do_Rel->elf_machine_rela
auto inline void
__attribute__ ((always_inline))
elf_machine_rela (struct link_map *map, const Elf64_Rela *reloc,
const Elf64_Sym *sym, const struct r_found_version *version,
void *const reloc_addr_arg, int skip_ifunc)
{
Elf64_Addr *const reloc_addr = reloc_addr_arg;
const unsigned long int r_type = ELF64_R_TYPE (reloc->r_info);
if (__builtin_expect (r_type == R_X86_64_NONE, 0))
return;
else
{
struct link_map *sym_map = RESOLVE_MAP (&sym, version, r_type);
Elf64_Addr value = (sym == NULL ? 0
: (Elf64_Addr) sym_map->l_addr + sym->st_value);
if (sym != NULL
&& __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC,0)
&& __builtin_expect (sym->st_shndx != SHN_UNDEF, 1)
&& __builtin_expect (!skip_ifunc, 1))
value = ((Elf64_Addr (*) (void)) value) ();
switch (r_type)
{
case R_X86_64_GLOB_DAT:
case R_X86_64_JUMP_SLOT:
*reloc_addr = value + reloc->r_addend;
break;
...
}
}
}
首先检查.rel.dyn和.rel.plt中项的类型是否为R_X86_64_NONE,如果是则直接返回。
接下来获取符号表中对应符号的地址st_value,加上装载地址l_addr。
ld.so中.rel.dyn节中的项类型有R_X86_64_RELATIVE和R_X86_64_GLOB_DAT两种,.rel.plt中的项类型只有R_X86_64_JUMP_SLOT一种,因此无论哪种类型,最终都加上项中的初始值r_addend填入最终地址reloc_addr中。
elf/rtld.c
_dl_start->_dl_start_final
static inline ElfW(Addr) __attribute__ ((always_inline))
_dl_start_final (void *arg)
{
ElfW(Addr) start_addr;
_dl_setup_hash (&GL(dl_rtld_map));
GL(dl_rtld_map).l_real = &GL(dl_rtld_map);
GL(dl_rtld_map).l_map_start = (ElfW(Addr)) _begin;
GL(dl_rtld_map).l_map_end = (ElfW(Addr)) _end;
GL(dl_rtld_map).l_text_end = (ElfW(Addr)) _etext;
__libc_stack_end = __builtin_frame_address (0);
start_addr = _dl_sysdep_start (arg, &dl_main);
return start_addr;
}
首先通过_dl_setup_hash函数获取ld.so中的hash表并将其设置到全局link_map中(_dl_rtld_map)。
接下来设置l_map_start设置装载的起始地址,即0,l_map_end指向.bss节的结束地址,_etext为.text的结束地址,其中_begin在elf文件夹下的makefile中指定,其余位置在链接脚本中确定。
__builtin_frame_address是gcc提供的用于获得调用栈的地址。
最终调用_dl_sysdep_start函数继续执行。
elf/rtld.c
_dl_start->_dl_start_final->_dl_setup_hash
void
internal_function
_dl_setup_hash (struct link_map *map)
{
Elf_Symndx *hash;
if (!map->l_info[DT_HASH])
return;
hash = (void *) D_PTR (map, l_info[DT_HASH]);
map->l_nbuckets = *hash++;
hash++;
map->l_buckets = hash;
hash += map->l_nbuckets;
map->l_chain = hash;
}
这里通过前面DYNAMIC节的信息获取hash表的地址hash,然后访问该地址,获取桶的数量存储在l_nbuckets中,然后跳过nchain即链表的数量,再访问桶的起始地址存储在l_buckets中,最后将链表地址存储在l_chain中。
elf/dl-sysdep.c
_dl_start->_dl_start_final->_dl_sysdep_start
ElfW(Addr)
_dl_sysdep_start (void **start_argptr,
void (*dl_main) (const ElfW(Phdr) *phdr, ElfW(Word) phnum,
ElfW(Addr) *user_entry, ElfW(auxv_t) *auxv))
{
const ElfW(Phdr) *phdr = NULL;
ElfW(Word) phnum = 0;
ElfW(Addr) user_entry;
ElfW(auxv_t) *av;
uid_t uid = 0;
gid_t gid = 0;
unsigned int seen = 0;
# define set_seen_secure() (seen = -1)
# define M(type) (1 << (type))
# define set_seen(tag) seen |= M ((tag)->a_type)
__libc_stack_end = DL_STACK_END (start_argptr);
DL_FIND_ARG_COMPONENTS (start_argptr, _dl_argc, INTUSE(_dl_argv), _environ,
GLRO(dl_auxv));
user_entry = (ElfW(Addr)) ENTRY_POINT;
GLRO(dl_platform) = NULL;
for (av = GLRO(dl_auxv); av->a_type != AT_NULL; set_seen (av++))
switch (av->a_type)
{
case AT_PHDR:
phdr = (void *) av->a_un.a_val;
break;
case AT_PHNUM:
phnum = av->a_un.a_val;
break;
case AT_PAGESZ:
GLRO(dl_pagesize) = av->a_un.a_val;
break;
case AT_ENTRY:
user_entry = av->a_un.a_val;
break;
case AT_SECURE:
seen = -1;
INTUSE(__libc_enable_secure) = av->a_un.a_val;
break;
case AT_PLATFORM:
GLRO(dl_platform) = (void *) av->a_un.a_val;
break;
case AT_HWCAP:
GLRO(dl_hwcap) = (unsigned long int) av->a_un.a_val;
break;
case AT_HWCAP2:
GLRO(dl_hwcap2) = (unsigned long int) av->a_un.a_val;
break;
case AT_CLKTCK:
GLRO(dl_clktck) = av->a_un.a_val;
break;
case AT_FPUCW:
GLRO(dl_fpu_control) = av->a_un.a_val;
break;
case AT_RANDOM:
_dl_random = (void *) av->a_un.a_val;
break;
}
if (GLRO(dl_pagesize) == 0)
GLRO(dl_pagesize) = __getpagesize ();
DL_SYSDEP_INIT;
DL_PLATFORM_INIT;
if (GLRO(dl_platform) != NULL)
GLRO(dl_platformlen) = strlen (GLRO(dl_platform));
if (__sbrk (0) == _end)
__sbrk (GLRO(dl_pagesize)
- ((_end - (char *) 0) & (GLRO(dl_pagesize) - 1)));
(*dl_main) (phdr, phnum, &user_entry, GLRO(dl_auxv));
return user_entry;
}
首先通过DL_FIND_ARG_COMPONENTS宏获取栈中的数据,根据sys_execve的源码可知这里依次保存了参数数量、用户变量、环境变量和其他变量(该变量在linux源码的create_elf_table函数中设置),DL_FIND_ARG_COMPONENTS宏将这些变量分别存储在用户空间的_dl_argc、_dl_argv、_environ和_dl_auxv中。
接着初始化user_entry为ENTRY_POINT。然后遍历create_elf_table函数中设置的变量,依次设置到各个变量中,其中最重要的就是获取用户程序的入口设置到user_entry中。再往下设置_dl_pagesize和_dl_platformlen变量。
然后通过DL_SYSDEP_INIT宏用于设置堆的起始地址,再通过DL_PLATFORM_INIT检查相应变量。接着调整堆的起始地址和页面对齐。
最后调用dl_main准备为用户程序的执行搭建环境,传入的参数phdr为用户程序的程序头,phnum为程序头的个数,user_entry为程序的起始点,_dl_auxv为create_elf_table中设置的变量,dl_main函数留在下一章分析。
elf/dl-sysdep.c
_dl_start->_dl_start_final->_dl_sysdep_start->DL_FIND_ARG_COMPONENTS
# define DL_FIND_ARG_COMPONENTS(cookie, argc, argv, envp, auxp)
do {
void **_tmp;
(argc) = *(long int *) cookie;
(argv) = (char **) ((long int *) cookie + 1);
(envp) = (argv) + (argc) + 1;
for (_tmp = (void **) (envp); *_tmp; ++_tmp)
continue;
(auxp) = (void *) ++_tmp;
} while (0)
cookie是栈的指针,这里移动该指针依次获取argc、argv、envp和auxp。
sysdeps/unix/sysv/linux/dl-sysdep.c
_dl_start->_dl_start_final->_dl_sysdep_start->DL_SYSDEP_INIT
# define DL_SYSDEP_INIT frob_brk ()
static inline void
frob_brk (void)
{
__brk (0);
}
int
__brk (void *addr)
{
void *newbrk;
__curbrk = newbrk = (void *) INLINE_SYSCALL (brk, 1, addr);
return 0;
}
SYSCALL_DEFINE1(brk, unsigned long, brk)
{
...
min_brk = mm->start_brk;
if (brk < min_brk)
goto out;
...
retval = mm->brk;
return retval;
}
__brk函数会执行系统调用sys_brk,但是由于传入的参数为0,小于start_brk,因此最后只会返回当前堆的位置,但是由于当前进程还没有使用堆,因此实际上返回的就是堆的起始地址start_brk,然后返回到glibc中,将其保存到__curbrk中。