Android源码学习——linker(4)

本文学习的源码参考AndroidXRef,版本为Lollipop 5.1.0_r1。


前面讲完了so的加载,这一章来讲so的链接过程。so的链接是实际上就是完成符号的重定位。

分别看下PrelinkImage和LinkImage的实现。首先是PrelinkImage,这个函数很长,我们一段段来看:

bool soinfo::PrelinkImage() {
  /* Extract dynamic section */
  ElfW(Word) dynamic_flags = 0;
  phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags);

  /* We can't log anything until the linker is relocated */
  bool relocating_linker = (flags & FLAG_LINKER) != 0;
  if (!relocating_linker) {
    INFO("[ linking %s ]", name);
    DEBUG("si->base = %p si->flags = 0x%08x", reinterpret_cast<void*>(base), flags);
  }

  if (dynamic == nullptr) {
    if (!relocating_linker) {
      DL_ERR("missing PT_DYNAMIC in \"%s\"", name);
    }
    return false;
  } else {
    if (!relocating_linker) {
      DEBUG("dynamic = %p", dynamic);
    }
  }

#if defined(__arm__)
  (void) phdr_table_get_arm_exidx(phdr, phnum, load_bias,
                                  &ARM_exidx, &ARM_exidx_count);
#endif

  ......

首先是调用phdr_table_get_dynamic_section获取动态节区。
看下怎么获得的:

void phdr_table_get_dynamic_section(const ELF::Phdr* phdr_table,
                                    int phdr_count,
                                    ELF::Addr load_bias,
                                    const ELF::Dyn** dynamic,
                                    size_t* dynamic_count,
                                    ELF::Word* dynamic_flags) {
  const ELF::Phdr* phdr = phdr_table;
  const ELF::Phdr* phdr_limit = phdr + phdr_count;

  for (phdr = phdr_table; phdr < phdr_limit; phdr++) {
    if (phdr->p_type != PT_DYNAMIC) {
      continue;
    }

    *dynamic = reinterpret_cast<const ELF::Dyn*>(load_bias + phdr->p_vaddr);
    if (dynamic_count) {
      *dynamic_count = (unsigned)(phdr->p_memsz / sizeof(ELF::Dyn));
    }
    if (dynamic_flags) {
      *dynamic_flags = phdr->p_flags;
    }
    return;
  }
  *dynamic = NULL;
  if (dynamic_count) {
    *dynamic_count = 0;
  }
}

从第一个程序头表项开始遍历,找类型为PT_DYNAMIC的项,那么就可以找到这一段对应的动态节区。并且,用该段内存大小p_memsz 除以一个动态节区符号对象的大小sizeof(ELF::Dyn))得到动态节区中符号的数目。

回到PrelinkImage中,继续往下看:

  // Extract useful information from dynamic section.
  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) {
  

然后开始一项项地遍历动态节区里面的符号对象,看下这个对象的结构:

struct Elf32_Dyn
{
  Elf32_Sword d_tag;            // Type of dynamic table entry.
  union
  {
      Elf32_Word d_val;         // Integer value of entry.
      Elf32_Addr d_ptr;         // Pointer value of entry.
  } d_un;
};

两部分,一个4字节的d_tag,然后一个4字节的联合体,可能为d_val,也可能为一个地址d_ptr。
而这里对Elf32_Dyn这个结构做解析,就是针对不同的d_tag取值进行不同的操作。
后面内容很长,我们挑几个重要的来说:

      case DT_HASH:
        nbucket = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[0];
        nchain = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr)[1];
        bucket = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr + 8);
        chain = reinterpret_cast<uint32_t*>(load_bias + d->d_un.d_ptr + 8 + nbucket * 4);
        break;

这个动态符号对象是关于哈希表的描述,d_un.d_ptr给出了哈希表的地址。然后就依次可以取到nbucket和nchain,以及保存符号表索引的bucket和chain数组。这是为了方便我们后面查找符号表。

      case DT_STRTAB:
        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 = reinterpret_cast<ElfW(Sym)*>(load_bias + d->d_un.d_ptr);
        break;

给出了符号表的地址。

      case DT_SYMENT:
        if (d->d_un.d_val != sizeof(ElfW(Sym))) {
          DL_ERR("invalid DT_SYMENT: %zd", static_cast<size_t>(d->d_un.d_val));
          return false;
        }
        break;

判断所给的符号表的表项大小是不是正确。

      case DT_PLTREL:
#if defined(USE_RELA)
        if (d->d_un.d_val != DT_RELA) {
          DL_ERR("unsupported DT_PLTREL in \"%s\"; expected DT_RELA", name);
          return false;
        }
#else
        if (d->d_un.d_val != DT_REL) {
          DL_ERR("unsupported DT_PLTREL in \"%s\"; expected DT_REL", name);
          return false;
        }
#endif
        break;

给出过程连接表(PLT)所引用的重定位项的类型,可能为DT_RELA(元素为显示对齐)或DT_REL(元素为隐式对齐)。

      case DT_JMPREL:
#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_PLTRELSZ:
#if defined(USE_RELA)
        plt_rela_count = d->d_un.d_val / sizeof(ElfW(Rela));
#else
        plt_rel_count = d->d_un.d_val / sizeof(ElfW(Rel));
#endif
        break;

DT_JMPREL指明了重定位表的地址,而DT_PLTRELSZ则指明了重定位表的大小(字节数)。

      case DT_PLTGOT:
#if defined(__mips__)
        // Used by mips and mips64.
        plt_got = reinterpret_cast<ElfW(Addr)**>(load_bias + d->d_un.d_ptr);
#endif
        // Ignore for other platforms... (because RTLD_LAZY is not supported)
        break;

如果是mips架构,会给出一个跟过程链接表(PLT)关联的全局偏移表(GOT)的地址,但是其他平台上并不支持RTLD_LAZY &

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值