ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

android源码分析之linker初始化

2022-05-23 00:31:07  阅读:207  来源: 互联网

标签:tmp 定位 linker __ 源码 so rel android


linker入口函数

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

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

__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与重定位相关的核心代码如下

  • 通过获取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()->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;
}

标签:tmp,定位,linker,__,源码,so,rel,android
来源: https://www.cnblogs.com/revercc/p/16299712.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有