• 欢迎光临~

android源码分析之linker初始化

开发技术 开发技术 2022-05-22 次浏览

linker入口函数

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

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

android源码分析之linker初始化

__linker_init()

  • 调用prelink_image()解析ELF文件dynamic程序段的各种类型的segment节区的信息
  • 调用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_DT_SYMTAB对应的是.symtab节区,此节区的每一项都指向.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:   //对应的是.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()

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

  • 使用显示加数的和未使用显示加数的分类处理
  • 调用两次plain_relocate(),分别对.rel.dyn和.rel.ple节区中的重定位信息进行重定位
#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_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;           //需要重定位的数据修正为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;
    }
  }

__linker_init_post_relocation

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;
}
程序员灯塔
转载请注明原文链接:android源码分析之linker初始化
喜欢 (0)