在内核程序加载了ELF可执行文件后会判断是否含有动态链接信息。如果需要进行动态链接就会通过ELF可执行文件的PT_INTERP程序段获得需要加载的加载器的路径,然后将应用层的入口函数设置为加载器linker的入口函数。
extern "C" ElfW(Addr) __linker_init(void* raw_args) { //获得linker的基地址(在内核中保存在了新进程默认栈中) ElfW(Addr) linker_addr = getauxval(AT_BASE); if (linker_addr == 0) { 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文件头 ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_addr); //linker的e_phoff程序段偏移地址 ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_addr + elf_hdr->e_phoff); //初始化linker对应的ELF文件的soinfo结构体,soinfo结构体保存了此ELF的基本信息 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 = get_elf_exec_load_bias(elf_hdr); 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_image()会解析ELF文件dynamic程序段的各种类型的segment节区的信息 if (!tmp_linker_so.prelink_image()) __linker_cannot_link(args.argv[0]); //link_image()会对linker进行重定位 if (!tmp_linker_so.link_image(SymbolLookupList(&tmp_linker_so), &tmp_linker_so, nullptr, nullptr)) __linker_cannot_link(args.argv[0]); //__linker_init_post_relocation()会重定位待执行的ELF文件,并且会加载ELF文件依赖的其他库文件并进行重定位 return __linker_init_post_relocation(args, tmp_linker_so); }
prelink_image与重定位相关的核心代码如下
uint32_t needed_count = 0; for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) { DEBUG("d = %p, d[0](tag) = %p d[1](val) = %p", d, reinterpret_cast<void*>(d->d_tag), reinterpret_cast<void*>(d->d_un.d_val)); switch (d->d_tag) { case DT_STRTAB: //对应的是.dynstr节区 strtab_ = reinterpret_cast<const char*>(load_bias + d->d_un.d_ptr); break; case DT_STRSZ: strtab_size_ = d->d_un.d_val; break; case DT_SYMTAB: //对应的是.symtab节区 symtab_ = reinterpret_cast<ElfW(Sym)*>(load_bias + d->d_un.d_ptr); break; case DT_JMPREL: //对应的是.rel.plt节区 #if defined(USE_RELA) plt_rela_ = reinterpret_cast<ElfW(Rela)*>(load_bias + d->d_un.d_ptr); #else plt_rel_ = reinterpret_cast<ElfW(Rel)*>(load_bias + d->d_un.d_ptr); #endif break; case DT_REL: //对应的是.rel.dyn节区 rel_ = reinterpret_cast<ElfW(Rel)*>(load_bias + d->d_un.d_ptr); break; ...... }
link_image()->soinfo::relocate(),soinfo::relocate()的主要代码如下。
#if defined(USE_RELA) //如果使用了显式加数(一般64位使用) if (rela_ != nullptr) { if (!plain_relocate<RelocMode::Typical>(relocator, rela_, rela_count_)) { return false; } } if (plt_rela_ != nullptr) { if (!plain_relocate<RelocMode::JumpTable>(relocator, plt_rela_, plt_rela_count_)) { return false; } } #else //如果没有使用显式加数(一般32位不使用) if (rel_ != nullptr) { //.rel.dyn节区中的重定位信息进行重定位 if (!plain_relocate<RelocMode::Typical>(relocator, rel_, rel_count_)) { return false; } } if (plt_rel_ != nullptr) { //.rel.plt节区中的重定位信息进行重定位 if (!plain_relocate<RelocMode::JumpTable>(relocator, plt_rel_, plt_rel_count_)) { return false; } } #endif
plain_relocate()->plain_relocate_impl()->process_relocation()->process_relocation_impl()。
static bool process_relocation_impl(Relocator& relocator, const rel_t& reloc) { constexpr bool IsGeneral = Mode == RelocMode::General; //relocator.si->load_bias为模块实际的加载基地址 //rel_target为对应的待重定位数据的实际内存地址(.got表项的地址) void* const rel_target = reinterpret_cast<void*>(reloc.r_offset + relocator.si->load_bias); //r_type为重定位类型 const uint32_t r_type = ELFW(R_TYPE)(reloc.r_info); //r_sym为对应重定位数据的符号表索引 const uint32_t r_sym = ELFW(R_SYM)(reloc.r_info); //利用r_sym符号表索引从.symtab中获取对应的表项,并利用表项的st_name字段在.dynstr中找到对应的重定位符号字符串 if (r_sym != 0) { sym_name = relocator.get_string(relocator.si_symtab[r_sym].st_name); } #if defined(USE_RELA) //如果使用了显式加数 auto get_addend_rel = [&]() -> ElfW(Addr) { return reloc.r_addend; }; auto get_addend_norel = [&]() -> ElfW(Addr) { return reloc.r_addend; }; #else //如果没使用显示加数 auto get_addend_rel = [&]() -> ElfW(Addr) { return *static_cast<ElfW(Addr)*>(rel_target); }; auto get_addend_norel = [&]() -> ElfW(Addr) { return 0; }; #endif //symaddr = 对应符号实际在内存中的地址 //一下解析以没有使用显式加数的为例 if constexpr (IsGeneral || Mode == RelocMode::JumpTable) { //R_GENERIC_JUMP_SLOT是外部函数引用的重定位类型 if (r_type == R_GENERIC_JUMP_SLOT) { count_relocation_if<IsGeneral>(kRelocAbsolute); const ElfW(Addr) result = sym_addr + get_addend_norel(); //get_addend_norel()返回0,result = symaddr trace_reloc("RELO JMP_SLOT %16p <- %16p %s", rel_target, reinterpret_cast<void*>(result), sym_name); *static_cast<ElfW(Addr)*>(rel_target) = result; //需要重定位的数据修正为symaddr,即其内存中的实际地址 return true; } } if constexpr (IsGeneral || Mode == RelocMode::Typical) { if (r_type == R_GENERIC_ABSOLUTE) { count_relocation_if<IsGeneral>(kRelocAbsolute); const ElfW(Addr) result = sym_addr + get_addend_rel(); trace_reloc("RELO ABSOLUTE %16p <- %16p %s", rel_target, reinterpret_cast<void*>(result), sym_name); *static_cast<ElfW(Addr)*>(rel_target) = result; return true; } //R_GENERIC_GLOB_DAT为外部符号引用的重定位类型 else if (r_type == R_GENERIC_GLOB_DAT) { count_relocation_if<IsGeneral>(kRelocAbsolute); const ElfW(Addr) result = sym_addr + get_addend_norel(); //get_addend_norel()返回0,result = symaddr trace_reloc("RELO GLOB_DAT %16p <- %16p %s", rel_target, reinterpret_cast<void*>(result), sym_name); *static_cast<ElfW(Addr)*>(rel_target) = result; //需要重定位的数据修正为symaddr,即其内存中的实际地址 return true; } //R_GENERIC_RELATIVE为静态或全局变量指针的重定位类型 else if (r_type == R_GENERIC_RELATIVE) { count_relocation_if<IsGeneral>(kRelocRelative); //get_addend_rel()返回重定位的数据的值 const ElfW(Addr) result = relocator.si->load_bias + get_addend_rel();//result = 基地址 + get_addend_rel()返回重定位的数据的值 trace_reloc("RELO RELATIVE %16p <- %16p", rel_target, reinterpret_cast<void*>(result)); *static_cast<ElfW(Addr)*>(rel_target) = result; //需要重定位的数据修正为:基地址 + get_addend_rel()返回重定位的数据的值,即指针指向的静态或全局变量实际的内存地址 return true; } }
static ElfW(Addr) __attribute__((noinline)) __linker_init_post_relocation(KernelArgumentBlock& args, soinfo& tmp_linker_so) { __libc_init_main_thread_late(); if (!tmp_linker_so.protect_relro()) __linker_cannot_link(args.argv[0]); set_bss_vma_name(&tmp_linker_so); __libc_init_globals(); tmp_linker_so.call_constructors(); for (const ElfW(Dyn)* d = tmp_linker_so.dynamic; d->d_tag != DT_NULL; ++d) { if (d->d_tag == DT_SONAME) { tmp_linker_so.set_soname(tmp_linker_so.get_string(d->d_un.d_val)); } } ElfW(Addr) start_address = linker_main(args, exe_to_load); return start_address; }