android源码分析之linker初始化

linker入口函数

在内核程序加载了ELF可执行文件后会判断是否含有动态链接信息。如果需要进行动态链接就会通过ELF可执行文件的PT_INTERP程序段获得需要加载的加载器的路径,然后将应用层的入口函数设置为加载器linker的入口函数。

  • linker的入口函数为_start,但是实际在编译时会为其加上__dl前缀,所以一般我们在IDA中看到的都是__dl_start。
  • 此函数会将堆栈指针作为参数并调用__linker_init函数,最后__linker_init()返回待执行ELF文件的入口函数并执行

__linker_init

  • 调用prelink_image()解析linker文件自身的dynamic程序段,获取dynamic程序段中的各种类型的section节区的信息
  • 调用link_image()会对linker自身进行重定位
  • 调用__linker_init_post_relocation()重定位待执行的ELF文件,同时会加载ELF文件的所有依赖库并进行重定位。
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

prelink_image与重定位相关的核心代码如下

  • 通过获取PT_DYNAMIC程序头的地址后进行遍历各个类型的节区的信息
  • DT_STRTAB对应的是.dynstr节区,此节区存放的是.dynamic节区的字符串(包含依赖的动态库名称,外部函数调用名称等等)
  • DT_SYMTAB对应的是.dynsym节区,此节区的每一项都指向.dynstr节区中的字符串
  • DT_JMPREL对应的是.rel.plt节区,其存放的是外部函数引用的重定位信息(相当于windows的重定位表)
  • DT_REL对应的是.rel.dyn节区,此节区包含了除了外部函数引用之外其他需要的重定位信息(相当于windows的重定位表)
  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:   //对应的是.dynsym节区
        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

link_image()->soinfo::relocate(),soinfo::relocate()的主要代码如下。

  • 使用显示加数的和未使用显示加数的分类处理
  • 调用两次plain_relocate(),分别对.rel.dyn和.rel.plt节区中的重定位信息进行重定位
#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()->plain_relocate_impl()->process_relocation()->process_relocation_impl()。

  • process_relocation_impl最终会对.rel.plt和.rel.dyn节区中指向的重定位数据进行修正
  • 对于R_GENERIC_JUMP_SLOT,R_GENERIC_GLOB_DAT和R_GENRIC_ABCOLUTE类型的重定位数据只需找到其重定位数据对应的符号并获取到实际内存地址,然后写回修正即可
  • 对于R_GENERIC_RELATIVE类型的重定位数据需要获取其原来相对与0基地址的值,加上实际的内存加载基地址,然后写回修正即可
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;           //需要重定位的数据修正为sym_addr,即其内存中的实际地址
      return true;
    }
  }

  if constexpr (IsGeneral || Mode == RelocMode::Typical) {
    //R_GENERIC_ABSOLUTE为数据引用的重定位类型
    if (r_type == R_GENERIC_ABSOLUTE) {

      count_relocation_if<IsGeneral>(kRelocAbsolute);
      const ElfW(Addr) result = sym_addr + get_addend_rel();  //get_addend_rel()返回重定位的数据的值,但其实际重定位的数据的值也为0。result = symaddr
      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;
    }
  }

__linker_init_post_relocation

  • __linker_init_post_relocation通过调用linker_main重定位ELF文件,加载其对应的依赖库文件并进行重定位
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));
    }
  }

  //调用和linker_main
  ElfW(Addr) start_address = linker_main(args, exe_to_load);
  return start_address;
}

linker_main

  • 先获取待执行ELF文件的ELF文件头信息并保存在soinfo,注意ELF执行文件默认会被放在g_default_namespace命名空间中,而此空间对应的就是default命名空间

  • 将所有ELF需要加载的库文件路径保存在 needed_library_names中(包括LD_PRELOAD预加载的so和待执行ELF文件依赖的so),并获取需要加载的库文件的数目

  • 调用prelink_imgae(),获取.dynamic节区中包含的其他节区的信息。(其中包含了.rel.plt和.rel.dyn重定位相关节区的信息)

for_each_dt_needed函数就是通过获取.dynamic节区中的DT_NEEDED类型,通过DT_NEEDED类型中保存的依赖库名称得到对应的依赖库路径

template<typename F>
void for_each_dt_needed(const soinfo* si, F action) {
  for (const ElfW(Dyn)* d = si->dynamic; d->d_tag != DT_NULL; ++d) {
    if (d->d_tag == DT_NEEDED) {          //获取所有DT_NEEDED类型的信息(包含了依赖的库文件名称)
      action(fix_dt_needed(si->get_string(d->d_un.d_val), si->get_realpath()));
    }
  }
}
  • 先判断需要加载的依赖库的数量,如果大于0就调用find_libraries()去重定位ELF文件,加载依赖库文件并进行重定位。需要注意其是以default命名空间作为调用者命名空间(因为ELF可执行文件位于default命名空间中)
  • 如果需要加载的依赖库数量为0,直接调用Link_image()重定位此ELF文件。link_image重定位流程和linker调用的link_image一样。

最后linker_main返回待执行ELF文件的入口地址,一路返回到__linker_init_post_relocation-->__linker_init->_dl_start()中,并执行Bx R0跳转到待执行ELF文件的入口处。

find_libraries

看一下find_libraries()对ELF重定位和加载依赖库并重定位的过程,一共分为step0 - step7八个过程。

step0

  • 创建一个加载任务列表 LoadTaskList load_tasks ,此列表将保存所有需要加载的so库的soinfo等其他信息。
  • 将所有需要加载的so库都加入队列

step1

  • 遍历所有load_tasks 加载任务队列中的so库,并将so库依赖的其他库也加入到load_tasks加载任务队列中

  • 这样一直增加load_tasks 队列,直到所有的so库,以及so库的依赖的其他库.....都加载到load_tasks加载任务队列中

  • find_library_internal()函数内部会调用 load_library(ns, task, load_tasks, rtld_flags, realpath, search_linked_namespaces);

  • load_library调用open_library打开so文件并获得文件句柄。

  • load_library还会调用 for_each_dt_needed获取此so库的所有依赖库,并将这些依赖库也都加入到load_tasks加载任务队列中。

step2

task->load()会将所有load_tasks加载任务队列中的so库都mmap到内存中

  • task->load()内部会调用ElfReader::Load()
  • 设置加载后的so库对应的加载基地址等信息

  • ElfReader::Load()调用LoadSegments(),LoadSegments()中调用__mmap2将so文件的PT_LOAD段都map到加载基地址load_bias_中
  • ElfReader::Load()调用phdr_table_protect_segments(),phdr_table_protect_segments()中调用mprotect修改各个PT_LOAD程序段的内存属性

step3

调用SOINFO::prelink_image()函数获取load_tasks加载任务队列中所有so库的.dynamic节区中其他节区的信息

step4/step5

step6

  • 待执行的ELF文件和依赖的所有的so文件的 soinfo信息都会保存在local_group.visit队列中
  • 通过为每一个soinfo调用link_image()函数进行重定位

step7

posted @ 2022-05-23 00:24  怎么可以吃突突  阅读(1005)  评论(0编辑  收藏  举报