我们都知道动态链接是由一个叫做动态链接器的工具来来完成,在linux中即为ld.so(ld.linux.so),
但是它本身也作为一个共享对象存在,那么它本身的重定位,GOT表解析又是谁完成的呢?
解开这个先有鸡还是先有蛋的谜团的是使glibc中的一段代码,也就是本文要分析的代码,
它使用了一种要做bootstrap的思想,中文我也想不好太好的词来翻译,只可意会不可言喻呀!
static ElfW(Addr)//Elf32_Addr或者Elf64_Addr
__attribute_used__ internal_function
_dl_start (void *arg)
{
#ifdef DONT_USE_BOOTSTRAP_MAP
# define bootstrap_map GL(dl_rtld_map)
//展开之后为#define bootstap_map rtld_local._dl_trld_map
#else
struct dl_start_final_info info;
# define bootstrap_map info.l
#endif
/* This #define produces dynamic linking inline functions for
bootstrap relocation instead of general-purpose relocation.
Since ld.so must not have any undefined symbols the result
is trivial: always the map of ld.so itself. */
#define RTLD_BOOTSTRAP
#define RESOLVE_MAP(sym, version, flags) (&bootstrap_map)
#include "dynamic-link.h"
/*里面有几个动态链接需要用的宏和函数,会用到前面这两个宏
如头文件dynamic-link.h中下面这段代码,会根据上面宏的定义与否功能不同
#ifndef RESOLVE_MAP
static
#else
auto
#endif
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;
#ifndef RTLD_BOOTSTRAP
if (dyn == NULL)
return;
#endif
*/
if (HP_TIMING_INLINE && HP_TIMING_AVAIL)
#ifdef DONT_USE_BOOTSTRAP_MAP
HP_TIMING_NOW (start_time);//获取开始时间,这个宏的内容跟平台相关
#else
HP_TIMING_NOW (info.start_time);
#endif
/* Partly clean the `bootstrap_map' structure up. Don't use
`memset' since it might not be built in or inlined and we cannot
make function calls at this point. Use '__builtin_memset' if we
know it is available. We do not have to clear the memory if we
do not have to use the temporary bootstrap_map. Global variables
are initialized to zero by default. */
#ifndef DONT_USE_BOOTSTRAP_MAP
# ifdef HAVE_BUILTIN_MEMSET
/*初始化bootstrap_map的l_info字段,不能使用memset是因为这段代码绝对不能依赖任何其他模块
而memset属于其他模块,需要切断这个依赖,如果HAVE_BUILTIN_MEMSET有效,则使用__builtin_memset
如果没有,则手动循环清零*/
__builtin_memset (bootstrap_map.l_info, '\0', sizeof (bootstrap_map.l_info));
# else
for (size_t cnt = 0;
cnt < sizeof (bootstrap_map.l_info) / sizeof (bootstrap_map.l_info[0]);
++cnt)
bootstrap_map.l_info[cnt] = 0;
# endif
# if USE___THREAD
bootstrap_map.l_tls_modid = 0;
# endif
#endif
/* Figure out the run-time load address of the dynamic linker itself. */
bootstrap_map.l_addr = elf_machine_load_address ();
/*_rtld_local._dl_rtld_map.l_addr = elf_machine_load_address(),
elf_machine_load_address()跟平台相关,表示 the run-time load address of the shared object.*/
/* Read our own dynamic section and fill in the info array. */
bootstrap_map.l_ld = (void *) bootstrap_map.l_addr + elf_machine_dynamic ();
/*_rtld_local._dl_rtld_map.l_ld =(void *) _rtld_local._dl_rtld_map.l_addr elf_machine_dynamic(),
elf_machine_dynamic()跟平台相关,表示 the link-time address of _DYNAMIC.*/
elf_get_dynamic_info (&bootstrap_map, NULL);
/* Read the dynamic section at DYN and fill in INFO with indices DT_*. */
#if NO_TLS_OFFSET != 0
bootstrap_map.l_tls_offset = NO_TLS_OFFSET;
#endif
/* Get the dynamic linker's own program header. First we need the ELF
file header. The `_begin' symbol created by the linker script points
to it. When we have something like GOTOFF relocs, we can use a plain
reference to find the runtime address. Without that, we have to rely
on the `l_addr' value, which is not the value we want when prelinked. */
#if USE___THREAD
dtv_t initdtv[3];
ElfW(Ehdr) *ehdr
# ifdef DONT_USE_BOOTSTRAP_MAP
= (ElfW(Ehdr) *) &_begin;
# else
# error This will not work with prelink.
= (ElfW(Ehdr) *) bootstrap_map.l_addr;//上面获取的地址
# endif
ElfW(Phdr) *phdr = (ElfW(Phdr) *) ((void *) ehdr + ehdr->e_phoff);
/* e_phoff 为 Program header table file offset */
size_t cnt = ehdr->e_phnum;
/* e_phnum 为 Program header table entry count */
while (cnt-- > 0)
if (phdr[cnt].p_type == PT_TLS)/* PT_TLS is usually the last phdr. */
{
void *tlsblock;
size_t max_align = MAX (TLS_INIT_TCB_ALIGN, phdr[cnt].p_align);
char *p;
bootstrap_map.l_tls_blocksize = phdr[cnt].p_memsz;
bootstrap_map.l_tls_align = phdr[cnt].p_align;
if (phdr[cnt].p_align == 0)
bootstrap_map.l_tls_firstbyte_offset = 0;
else
bootstrap_map.l_tls_firstbyte_offset = (phdr[cnt].p_vaddr
& (phdr[cnt].p_align - 1));
assert (bootstrap_map.l_tls_blocksize != 0);
bootstrap_map.l_tls_initimage_size = phdr[cnt].p_filesz;
bootstrap_map.l_tls_initimage = (void *) (bootstrap_map.l_addr
+ phdr[cnt].p_vaddr);
/* We can now allocate the initial TLS block. This can happen
on the stack. We'll get the final memory later when we
know all about the various objects loaded at startup
time. */
# if TLS_TCB_AT_TP
/* 这个宏为平台相关,具体解释为The TCB can have any size and the memory following the address the
thread pointer points to is unspecified. Allocate the TCB there. */
tlsblock = alloca (roundup (bootstrap_map.l_tls_blocksize,
TLS_INIT_TCB_ALIGN)
+ TLS_INIT_TCB_SIZE
+ max_align);
/*#define alloca(size) __builtin_alloca (size)*/
# elif TLS_DTV_AT_TP/* The TP points to the start of the thread blocks. */
tlsblock = alloca (roundup (TLS_INIT_TCB_SIZE,
bootstrap_map.l_tls_align)
+ bootstrap_map.l_tls_blocksize
+ max_align);
# else
/* In case a model with a different layout for the TCB and DTV
is defined add another #elif here and in the following #ifs. */
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
# endif
/* Align the TLS block. */
tlsblock = (void *) (((uintptr_t) tlsblock + max_align - 1)
& ~(max_align - 1));
/* Initialize the dtv. [0] is the length, [1] the generation
counter. */
initdtv[0].counter = 1;
initdtv[1].counter = 0;
/* Initialize the TLS block. */
# if TLS_TCB_AT_TP
initdtv[2].pointer = tlsblock;
# elif TLS_DTV_AT_TP
bootstrap_map.l_tls_offset = roundup (TLS_INIT_TCB_SIZE,
bootstrap_map.l_tls_align);
initdtv[2].pointer = (char *) tlsblock + bootstrap_map.l_tls_offset;
# else
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
# endif
/*__builtin_mempcpy*/
p = __mempcpy (initdtv[2].pointer, bootstrap_map.l_tls_initimage,
bootstrap_map.l_tls_initimage_size);
# ifdef HAVE_BUILTIN_MEMSET
__builtin_memset (p, '\0', (bootstrap_map.l_tls_blocksize
- bootstrap_map.l_tls_initimage_size));
# else
{
size_t remaining = (bootstrap_map.l_tls_blocksize
- bootstrap_map.l_tls_initimage_size);
while (remaining-- > 0)
*p++ = '\0';
}
# endif
/* Install the pointer to the dtv. */
/* Initialize the thread pointer. */
# if TLS_TCB_AT_TP
bootstrap_map.l_tls_offset
= roundup (bootstrap_map.l_tls_blocksize, TLS_INIT_TCB_ALIGN);
INSTALL_DTV ((char *) tlsblock + bootstrap_map.l_tls_offset,
initdtv);
const char *lossage = TLS_INIT_TP ((char *) tlsblock
+ bootstrap_map.l_tls_offset, 0);
# elif TLS_DTV_AT_TP
INSTALL_DTV (tlsblock, initdtv);
const char *lossage = TLS_INIT_TP (tlsblock, 0);
# else
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
# endif
if (__builtin_expect (lossage != NULL, 0))
_dl_fatal_printf ("cannot set up thread-local storage: %s\n",
lossage);
/* So far this is module number one. */
bootstrap_map.l_tls_modid = 1;
/* There can only be one PT_TLS entry. */
break;
}
#endif /* USE___THREAD */
#ifdef ELF_MACHINE_BEFORE_RTLD_RELOC
ELF_MACHINE_BEFORE_RTLD_RELOC (bootstrap_map.l_info);
#endif
if (bootstrap_map.l_addr || ! bootstrap_map.l_info[VALIDX(DT_GNU_PRELINKED)])
{
/* Relocate ourselves so we can do normal function calls and
data access using the global offset table. */
ELF_DYNAMIC_RELOCATE (&bootstrap_map, 0, 0);
}
bootstrap_map.l_relocated = 1;/*bootstrap完成*/
/* Please note that we don't allow profiling of this object and
therefore need not test whether we have to allocate the array
for the relocation results (as done in dl-reloc.c). */
/* Now life is sane; we can call functions and access global data.
Set up to use the operating system facilities, and find out from
the operating system's program loader where to find the program
header table in core. Put the rest of _dl_start into a separate
function, that way the compiler cannot put accesses to the GOT
before ELF_DYNAMIC_RELOCATE. */
{
#ifdef DONT_USE_BOOTSTRAP_MAP
ElfW(Addr) entry = _dl_start_final (arg);
#else
ElfW(Addr) entry = _dl_start_final (arg, &info);
#endif
#ifndef ELF_MACHINE_START_ADDRESS
# define ELF_MACHINE_START_ADDRESS(map, start) (start)
#endif
return ELF_MACHINE_START_ADDRESS (GL(dl_ns)[LM_ID_BASE]._ns_loaded, entry);
}
}
本文深入探讨了glibc中的动态链接器ld.so如何在Linux环境中完成自身加载和重定位过程。ld.so作为一个共享对象,其GOT表解析和初始化涉及到一个特殊的bootstrap机制,该机制解决了启动过程中的先有鸡还是先有蛋的问题。

被折叠的 条评论
为什么被折叠?



