Android Linker Bootstrap
linker作为动态链接器,但本身也是一个共享库,那么它由谁来链接?
1. linker自举
linker的链接是由它本身完成的,称为自举(bootstrap)。
linker不能依赖其他共享库,其内部的全局和静态变量等的relocation必须由其自身完成。
2. 自举实现
linker的入口函数,即为其自举执行的入口,即_start:
ENTRY(_start)
// Force unwinds to end in this function.
.cfi_undefined x30
mov x0, sp
bl __linker_init
/* linker init returns the _entry address in the main image */
br x0
END(_start)
mov x0, sp :以栈指针作为参数,借用x0寄存器传参给__linker_init,以便后续函数获取参数和环境变量等;
bl __linker_init : 跳转到_linker_init函数,开始进行实际的初始化工作;
br x0 : 用于执行完linker初始化后,回到执行进程的实际入口;
3. linker初始化
3.1 Elf format
在开始读代码前,要牢牢记住ELF文件的格式和数据结构:

3.2 linker执行前的堆栈
在linker实际进行初始化前,也就是在linker elf被kernel装载后,进程堆栈内已经存在一些必要的信息供后续linker使用,在此不展开详细讨论,只说明当前进程堆栈的情况(具体可以参照:)

3.3 初始化执行
__linker_init实际进行linker的初始化,符号解释,重定位等一系列操作:
/*
* This is the entry point for the linker, called from begin.S. This
* method is responsible for fixing the linker's own relocations, and
* then calling __linker_init_post_relocation().
*
* Because this method is called before the linker has fixed it's own
* relocations, any attempt to reference an extern variable, extern
* function, or other GOT reference will generate a segfault.
*/
extern "C" ElfW(Addr) __linker_init(void* raw_args) {
// Initialize TLS early so system calls and errno work.
//初始化主线程一些信息,主要是tcb/tls
KernelArgumentBlock args(raw_args); //读取压入栈中的参数和环境变量,堆栈情况参看上面3.2图片
bionic_tcb temp_tcb __attribute__((uninitialized));
linker_memclr(&temp_tcb, sizeof(temp_tcb));
__libc_init_main_thread_early(args, &temp_tcb);
// When the linker is run by itself (rather than as an interpreter for
// another program), AT_BASE is 0.
ElfW(Addr) linker_addr = getauxval(AT_BASE);//通过辅助向量获取linker interpreter的装载地址,对于linker自举来说,linker_addr为0(linker没有interpreter),如果为0,则通过进程执行文件的program header获取基址
if (linker_addr == 0) {
// The AT_PHDR and AT_PHNUM aux values describe this linker instance, so use
// the phdr to find the linker's base address.
ElfW(Addr) load_bias;
get_elf_base_from_phdr(
reinterpret_cast<ElfW(Phdr)*>(getauxval(AT_PHDR)), getauxval(AT_PHNUM),
&linker_addr, &load_bias);//计算linker elf的基址和load bias(对于linker来说,load bias为0)
}
ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_addr);//对于linker来说,linker_addr == elf_hdr
ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_addr + elf_hdr->e_phoff);//linker程序头表地址
// string.h functions must not be used prior to calling the linker's ifunc resolvers.
const ElfW(Addr) load_bias = get_elf_exec_load_bias(elf_hdr);//对于linker来说,load bias为0
//与ifunc的重定位相关,现在只有string.h有相关的ifunc实现(获取IFUNC resolver的地址,重定位.rela.iplt),详细可以看:https://github.com/xuwakao/wakao-blogs/blob/master/android-linker/android-ifunc.md
call_ifunc_resolvers(load_bias);
soinfo tmp_linker_so(nullptr, nullptr, nullptr, 0, 0);
tmp_linker_so.base = linker_addr;
tmp_linker_so.size = phdr_table_get_load_size(phdr, elf_hdr->e_phnum);
tmp_linker_so.load_bias = load_bias;
tmp_linker_so.dynamic = nullptr;
tmp_linker_so.phdr = phdr;
tmp_linker_so.phnum = elf_hdr->e_phnum;
tmp_linker_so.set_linker_flag();
// Prelink the linker so we can access linker globals.
//prelink_image的作用比较简单,就是解释.dynamic section,获取动态链接符号表位置、重定位表位置、so名字等等一些基础全局信息,方便后续使用
if (!tmp_linker_so.prelink_image()) __linker_cannot_link(args.argv[0]);
//link_image进行符号解释和重定位(代码太多,就不一一展开了,就是根据符号表,重定位表等等循环解释全部的符号,包括全局变量,函数)
if (!tmp_linker_so.link_image(SymbolLookupList(&tmp_linker_so), &tmp_linker_so, nullptr, nullptr)) __linker_cannot_link(args.argv[0]);
return __linker_init_post_relocation(args, tmp_linker_so);
}
整个初始化流程为:
初始化
main thread,主要是TCB/TLS。详细参考:Linker和主线程初始化。计算一些地址相关,
elf_hdr,phdr等。
ifunc解释。详细参考:Android IFUNC支持。执行
linker的符号解释,重定位等工作。
linker重定位完成后的,最终完成初始化linker,加载和解释exe等工作,并返回exe main。详细参考:Linker重定位后初始化。
本文探讨了Android Linker的自举过程,如何通过其内部功能完成自身的链接。详细介绍了自举实现,包括入口函数及初始化的步骤。同时,文章还涉及linker初始化阶段,如Elf文件格式的理解,装载后堆栈的状态,以及初始化执行中的符号解释和重定位等关键操作。
5964

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



