Android Linker

Table of Contents

1. Android Linker

1.1. linker 启动

1.1.1. kernel 部分

以 exec("/system/bin/linker") 为例:

  1. linker 本身的 interp 是它自己:

    ~@tj02433pcu> readelf -a /system/bin/linker|grep 'program inter'
           [Requesting program interpreter: /system/bin/linker]
    
  2. linker 的 entry 为 __dl__start, 地址为 0x28fc

    ~@tj02433pcu> readelf -a /system/bin/linker|grep 'Entry point'
      Entry point address:               0x28fc
    

kernel 会如下设置:

  1. /system/bin/linker 做为 exectuable 被 map 一次, 然后由于 linker 指定了 iterp 是 /system/bin/linker, 所以它会做为 shared object 再次被 map 一次

    00000000aaaaa000    440K r-x--  /system/bin/linker  <--- 做为 exectuable 被 map
    00000000aab18000     12K rw---  /system/bin/linker
    00000000aab1b000     20K rw---    [heap]
    00000000f777b000    440K r-x--  /system/bin/linker  <--- 做为 interp 再次被 map
    00000000f77e9000     12K rw---  /system/bin/linker
    00000000f77ec000     20K rw---    [anon]
    00000000fffcf000    132K rw---    [stack]
    00000000ffff0000      4K r-x--    [vectors]
    
  2. 控制会转移到 interp, 其入口为 0xf777d8fc, 这个地址对应于 interp 的 __dl__start:

    f777b000 (inter 被 map 的地址) + 0x28fc (entry point) = 0xf777d8fc
    
  3. aux vector
    1. AT_BASE 为 0xf777b000, 即 interp 被 map 的地址
    2. AT_ENTRY 为 0xaaaac8fc, 即 exectuable 本身的 entry, 由于 exectuable 被 map 在0xaaaaa000, 所以其 entry 为 0xaaaaa0000 + 0x28fc = 0xaaaac8fc (至于 exectuable 为什么被 map 在 0xaaaaa000, 参考 ELF_ET_DYN_BASE)

1.1.2. linker 部分

__linker_init:
  ElfW(Addr) linker_addr = args.getauxval(AT_BASE);
  ElfW(Addr) entry_point = args.getauxval(AT_ENTRY);
  if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point):
    __libc_format_fd(STDOUT_FILENO,
                   "This is %s, the helper program\n",
                   args.argv[0]);
    exit(0)
  linker_so.base = linker_addr;
  linker_so.size = phdr_table_get_load_size(phdr, elf_hdr->e_phnum);
  /* load_bias 是 so 中的 vadd 与实际地址的偏移量 */
  linker_so.load_bias = get_elf_exec_load_bias(elf_hdr);
  linker_so.dynamic = nullptr;
  linker_so.phdr = phdr;
  linker_so.phnum = elf_hdr->e_phnum;
  linker_so.set_linker_flag();

get_elf_exec_load_bias:
  ElfW(Addr) offset = elf->e_phoff;
  const ElfW(Phdr)* phdr_table =
    reinterpret_cast<const ElfW(Phdr)*>(reinterpret_cast<uintptr_t>(elf) + offset);
  const ElfW(Phdr)* phdr_end = phdr_table + elf->e_phnum;

  for (const ElfW(Phdr)* phdr = phdr_table; phdr < phdr_end; phdr++):
    if (phdr->p_type == PT_LOAD):
      // elf_hdr + (phdr->p_offset) 是这个 segment 被映射的实际地址,
      // 与p_vaddr 的差即是 load_bias, 这一点只能第一个 PT_LOAD 有效:
      // 因为 elf_hdr 实际就是第一个 PT_LAOD 实际的映射地址, 而且它的
      // phdr->p_offset 为 0
      return reinterpret_cast<ElfW(Addr)>(elf) + phdr->p_offset - phdr->p_vaddr;

_start 即 __dl__start 函数 (参考 ), 这里由于 linker 本身并没有完成重定位, 所以通过 GOT 引用到的 __dl__start 地址是 GOT 中原始的值, 即 0x28fc

objdump -D linker:

0006fddc <__dl__GLOBAL_OFFSET_TABLE_-0x218>:
   6fddc:	000715b8 			; <UNDEFINED> instruction: 0x000715b8
   6fde0:	000711d0 	ldrdeq	r1, [r7], -r0
   6fde4:	000716bc 			; <UNDEFINED> instruction: 0x000716bc
   6fde8:	0006eaf4 	strdeq	lr, [r6], -r4
   6fdec:	00070174 	andeq	r0, r7, r4, ror r1
   6fdf0:	000028fc 	strdeq	r2, [r0], -ip              <--- GOT 中的原始值为 0x28fc

而 entry_point 的值是 0xaaaac8fc, 所以这里的条件并不会成立.

linker 后续的代码会完成 linker 本身及 exectuable (这里也是 linker…) 的重定位, 包括填充 exectuable 对应的 GOT 表, 然后第二次跳转到 __linker_init

~/source/sharklj1@tj02433pcu> d debug exec out/target/product/sp9850j_1h10/symbols/system/bin/linker /system/bin/linker
Remote debugging from host 127.0.0.1
__dl__start () at bionic/linker/arch/arm/begin.S:32
32        mov r0, sp
(gdb) b __dl___linker_init
Breakpoint 1 at 0xaaab6e38: __dl___linker_init. (2 locations)
(gdb) c
Continuing.

Breakpoint 1, __linker_init (raw_args=0xfffefa80) at bionic/linker/linker.cpp:4400
4400      KernelArgumentBlock args(raw_args);
(gdb) n
4402      ElfW(Addr) linker_addr = args.getauxval(AT_BASE);
(gdb)
4403      ElfW(Addr) entry_point = args.getauxval(AT_ENTRY);
(gdb)
4404      ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_addr);
(gdb)
4405      ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_addr + elf_hdr->e_phoff);
(gdb)
4407      soinfo linker_so(nullptr, nullptr, nullptr, 0, 0);
(gdb)
4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
(gdb) ni
0xf7787e80      4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
(gdb) ni
0xf7787e82      4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
(gdb) ni
0xf7787e84      4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
(gdb) ni
0xf7787e86      4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
// 这里引用 r2 (即 _start) 使用的是 interp 的 GOT
(gdb) p /x $r2
$2 = 0x28fc
(gdb) p entry_point
$3 = 2863319292
(gdb) p /x entry_point
$4 = 0xaaaac8fc
(gdb) n
4423      linker_so.base = linker_addr;
(gdb) c
Continuing.

Breakpoint 1, __linker_init (raw_args=0xfffefa80) at bionic/linker/linker.cpp:4400
4400      KernelArgumentBlock args(raw_args);
(gdb) n
4402      ElfW(Addr) linker_addr = args.getauxval(AT_BASE);
(gdb)
4403      ElfW(Addr) entry_point = args.getauxval(AT_ENTRY);
(gdb)
4404      ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_addr);
(gdb)
4405      ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_addr + elf_hdr->e_phoff);
(gdb)
4407      soinfo linker_so(nullptr, nullptr, nullptr, 0, 0);
(gdb)
4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
(gdb) ni
0xaaab6e80      4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
(gdb)
0xaaab6e82      4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
(gdb)
0xaaab6e84      4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
(gdb)
0xaaab6e86      4416      if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
// 这里引用 r2 (即 _start) 使用的是 executable 的 GOT
(gdb) p /x $r2
$5 = 0xaaaac8fc
(gdb) p /x entry_point
$6 = 0xaaaac8fc
(gdb) c
This is /system/bin/linker, the helper program

1.1.3. 总结

  1. kernel 负责分别将 linker 和 exectuable(在本例中即 linker 自身) map 到某个位置 (最基本的重定位), 并设置好 AT_BASE 和 AT_ENTRY
  2. kernel 随后跳转到 linker(interp 的 __dl__start
  3. __dl__start 负责进行 linker 自身及 exectuable 的重定位
  4. linker 最后跳转到 AT_ENTRY 指定的 exectuable 的 entry

1.2. linker 自身的重定位

linker 作为 interp 启动后需要首先完成自身的重定位.

__linker_init:
  linker_so.prelink_image()
  linker_so.link_image(g_empty_list, g_empty_list, nullptr)
  /* link_image 后, linker 才能使用其全局变量, 包括后面要使用的 __libc_globals */
  __libc_init_main_thread(args);
  linker_so.protect_relro()
  __libc_init_globals(args);
  linker_so.call_constructors();

1.2.1. linker_so.prelink_image

  • prelink_image

    prelink_image:
      /* 查找 PT_DYNAMIC 类型的 phdr, 以获得 .dynamic section 的地址 */
      phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags);
        for (size_t i = 0; i<phdr_count; ++i) {
          const ElfW(Phdr)& phdr = phdr_table[i];
          if (phdr.p_type == PT_DYNAMIC) {
            *dynamic = reinterpret_cast<ElfW(Dyn)*>(load_bias + phdr.p_vaddr);
            return
       /* 解析 dynamic section 的信息 */
       /* 这里会做最基本的重定位: dynamic section 中记录的各种 p_ptr 加上
        * 一个 load_bias 就是对应的内存的地址 */
       for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d):
         switch (d->d_tag) {
             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_RELA:
               rela_ = reinterpret_cast<ElfW(Rela)*>(load_bias + d->d_un.d_ptr); break;
             case DT_INIT:
               init_func_ = reinterpret_cast<linker_function_t>(load_bias + d->d_un.d_ptr); break;
             case DT_INIT_ARRAY:
               init_array_ = reinterpret_cast<linker_function_t*>(load_bias + d->d_un.d_ptr); break;
             case DT_NEEDED:
               ++needed_count; break;
             case DT_SONAME:
               // this is parsed after we have strtab initialized (see below).
               break;
             case DT_RUNPATH:
               // this is parsed after we have strtab initialized (see below).
               break;
             // ...
        /* 解析 strtab, 给依赖于 strtab 的 entry 赋值 (soname, runpath) */
        for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) {
          switch (d->d_tag) {
            case DT_SONAME:
              set_soname(get_string(d->d_un.d_val)); break;
            case DT_RUNPATH:
              set_dt_runpath(get_string(d->d_un.d_val)); break;
    
  • get_string

    DT_SONAME 和 DT_RUNPATH 的 d_val 是相对于 dynstr 的 offset

    get_string:
      /* 这里的 strtab_ 来自 .dynamic 中 DT_STRTAB, 实际上指向 .dynstr (而
       * 不是 .strtab) */
      return strtab_ + index;
    
    $> readelf -a ./a.out
    
    Dynamic section at offset 0x8a0 contains 27 entries:
      Tag        Type                         Name/Value
     0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
     0x000000000000000f (RPATH)              Library rpath: [/home/sunway/libtest.so]
     ...
     0x0000000000000005 (STRTAB)             0x3a8
    
    
    Section Headers:
      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      ...
      [ 6] .dynstr           STRTAB           00000000000003a8  000003a8
           00000000000000ce  0000000000000000   A       0     0     1
    
    $> readelf -p .synstr ./a.out
    String dump of section '.dynstr':
      [     1]  libc.so.6
      ...
      [    32]  /home/sunway/libtest.so
      ...
    
    $> od -x ./a.out +0x8a0
    0004240 0001 0000 0000 0000 0001 0000 0000 0000
    0004260 000f 0000 0000 0000 0032 0000 0000 0000
    ...
    

1.2.2. linker_so.link_image

link_image:
  // relocate rel{a}.dyn
  relocate(version_tracker, plain_reloc_iterator(rela_, rela_count_), global_group, local_group)
  // relocate rel{a}.plt
  relocate(version_tracker, plain_reloc_iterator(plt_rela_, plt_rela_count_), global_group, local_group)
  // 参考 GNU_RELRO
  protect_relro()

plain_reloc_iterator:
  /* plain_reloc_iterator 用来遍历 {plt_}rel{a}_ (即 .rel{a}.{dyn,plt}) */
  #if defined(USE_RELA)
    typedef ElfW(Rela) rel_t;
  #else
    typedef ElfW(Rel) rel_t;
  #endif

  plain_reloc_iterator(rel_t* rel_array, size_t count)
    : begin_(rel_array), end_(begin_ + count), current_(begin_) {}

  rel_t* next()
    return current_++;

relocate:
  /* 遍历 .rel{a}.plt */
  for (size_t idx = 0; rel_iterator.has_next(); ++idx):
    // rel->r_info 包含了 relocation type 和 sym index
    // rel->r_offset 指示了需要 patch 的 got.plt 的位置
    ElfW(Word) type = ELFW(R_TYPE)(rel->r_info);
    ElfW(Word) sym = ELFW(R_SYM)(rel->r_info);

    ElfW(Addr) reloc = static_cast<ElfW(Addr)>(rel->r_offset + load_bias);
    ElfW(Addr) sym_addr = 0;
    const char* sym_name = nullptr;
    /* rel 和 rela 获得 addend 方式不同: rela 的 addend 直接保存在
     * rel->r_addend, 而 rel 的 addend 保存在 *relc 处 */
    ElfW(Addr) addend = get_addend(rel, reloc);

    /* relocate 时, 若 sym 不为空, 需要从其它地方 (so 或 executable)
     * 中查找 sym 的地址来 patch, 若 sym 为空, 则不需要查找, 直接根据
     * load_bias 之类的进行 patch 即可 */
    if (sym != 0):
      sym_name = get_string(symtab_[sym].st_name);
      soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)
      // sym_name 在所有的 so, executable 中都没有找到, undefined
      if (s == nullptr):
        s = &symtab_[sym];
        // 允许 undefined 的 weak symbol 存在, 参考 `weak_symbol`
        if (ELF_ST_BIND(s->st_info) != STB_WEAK):
          return false;
      // 找到一个 symbol: lsi 是 symbol 所在的 so, s 是 symbol 对应的
      // dynsym entry
      sym_addr = lsi->resolve_symbol_address(s);

    switch type:
      // R_GENERIC_JUMP_SLOT 是最常用的 relocation type, 例如调用 printf
      case R_GENERIC_JUMP_SLOT:
        *reinterpret_cast<ElfW(Addr)*>(reloc) = (sym_addr + addend);

      // R_GENERIC_GLOB_DAT 用来访问全局变量, 例如 extern int x
      case R_GENERIC_GLOB_DAT:
        *reinterpret_cast<ElfW(Addr)*>(reloc) = (sym_addr + addend);

      // 访问 pc relative: 例如访问当前 so 中的全局变量时会使用这种 relocation
      // [[file:PIC_PIE.org::*pic/no-pic%20%E7%9A%84%E5%8C%BA%E5%88%AB][pic/no-pic 的区别]]
      case R_GENERIC_RELATIVE:
        *reinterpret_cast<ElfW(Addr)*>(reloc) = (load_bias + addend);
      // ...

resolve_symbol_address:

resolve_symbol_address:
  // st_value 是 dysym 的成员
  return static_cast<ElfW(Addr)>(s->st_value + load_bias);

关于 weak_symbol

1.2.2.1. soinfo_do_lookup

https://android.googlesource.com/platform/bionic/+/master/android-changes-for-ndk-developers.md

soinfo_do_lookup 的输入是:

  1. symbol name
  2. this so
  3. global_group, 包含 1. exectuable 自己 2. LD_PRELOAD 引入的 so 3. DF_1_GLOBAL 的 so
  4. local_group, 通过 walk_dependencies_tree 获得的 so ,包含所有DT_NEEDED 引入的 so

soinfo_do_lookup 需要考虑几点:

  1. global 或 local group
  2. DT_SYMBOLIC,
  3. HASH

简单的说, soinfo_do_lookup 的作用是: 按一定顺序在 exectuable 和各个 so 中按名字查找某个符号, 返回 so 和符号的信息, 查找过程中使用 .hash 或 .gnu.hash 中的信息来加速.

soinfo_do_lookup:
  if (si_from->has_DT_SYMBOLIC):
    /*
     *  Note that this is unlikely since static linker avoids generating
     *  relocations for -Bsymbolic linked dynamic executables.
     *  这一点参考 DT_SYMBOLIC
     */
    if (!si_from->find_symbol_by_name(symbol_name, vi, &s)):
      return false;

    if (s != nullptr):
      *si_found_in = si_from;

    // 1. Look for it in global_group
    if (s == nullptr):
      global_group.visit([&](soinfo* global_si):
          global_si->find_symbol_by_name(symbol_name, vi, &s)

    // 2. Look for it in the local group
    if (s == nullptr):
      local_group.visit([&](soinfo* local_si):
        if (local_si == si_from && si_from->has_DT_SYMBOLIC):
          // local_group 是包含 this so 的
          // we already did this - skip
          return true;
        local_si->find_symbol_by_name(symbol_name, vi, &s)
1.2.2.2. find_symbol_by_name
find_symbol_by_name:
  bool success =
    is_gnu_hash() ?
    gnu_lookup(symbol_name, vi, &symbol_index) :
    elf_lookup(symbol_name, vi, &symbol_index);

  if (success):
    *symbol = symbol_index == 0 ? nullptr : symtab_ + symbol_index;

elf_lookup:
  uint32_t hash = symbol_name.elf_hash();
  for (uint32_t n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]):
    ElfW(Sym)* s = symtab_ + n;
  if (strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 &&
      is_symbol_global_and_defined(this, s)):
    *symbol_index = n;
    return true;

1.2.3. __libc_init_main_thread

__libc_init_main_thread:
  static pthread_internal_t main_thread;
  // pthread_create 通过 clone 时指定的 tls 参数来设置 tls
  __set_tls(main_thread.tls);
  main_thread.tid = __set_tid_address(&main_thread.tid);
  // 通过 pthread_create 创建的线程也需要调用 __init_tls
  __init_tls(&main_thread);
    thread->tls[TLS_SLOT_SELF] = thread->tls;
    thread->tls[TLS_SLOT_THREAD_ID] = thread;
  __init_alternate_signal_stack(&main_thread);
1.2.3.1. set_tid_address
1.2.3.2. set_tls

1.2.4. linker_so.protect_relro

1.2.5. __libc_init_globals

参考 vsyscall

__libc_init_globals:
  __libc_auxv = args.auxv;
  __libc_globals.mutate([&args](libc_globals* globals) {
    /* 给 __libc_globals->vdso[] 赋值. 相当于以手工的方式完成了对 vdso
     * 中定义的函数 (gettimeofday, clock_gettime) 的重定位, 不仅仅
     * linker init, 应用自己的 __libc_preinit 也需要通过相同的方式给
     * vdso 赋值*/
    __libc_init_vdso(globals, args);
    __libc_init_setjmp_cookie(globals, args);
  });

1.2.6. linker_so.call_constructors

linker 由 c++ 写的并且本身有许多全局变量, 所以 linker_so.call_constructors 用来初始化这些全局变量

call_constructors:
  // 对 linker 来说, 当前还没有 children
  get_children().for_each([] (soinfo* si) {
    si->call_constructors();
  });

  // 调用 init_func_, 类型为 linker_function_t (void (*T) ())
  call_function("DT_INIT", init_func_);
  // .init_array 是多个函数指针组成的数组, c++ 的 ctor 都是通过
  // .init_array 实现的
  call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false);

1.3. linker 主逻辑

1.3.1. __linker_init_post_relocation

__linker_init_post_relocation:
  __libc_init_AT_SECURE(args);
  __system_properties_init();
  debuggerd_init();
  const char* LD_DEBUG = getenv("LD_DEBUG");
  if (LD_DEBUG != nullptr):
    g_ld_debug_verbosity = atoi(LD_DEBUG);

  if (!getauxval(AT_SECURE)):
    ldpath_env = getenv("LD_LIBRARY_PATH");
    ldpreload_env = getenv("LD_PRELOAD");

  char* executable_path = get_executable_path();
  // executable 为 RTLD_GLOBAL
  soinfo* si = soinfo_alloc(&g_default_namespace, executable_path, &file_stat, 0, RTLD_GLOBAL);

  si->phdr = reinterpret_cast<ElfW(Phdr)*>(args.getauxval(AT_PHDR));
  si->phnum = args.getauxval(AT_PHNUM);
  si->entry = args.getauxval(AT_ENTRY);

  for (size_t i = 0; i < si->phnum; ++i):
    if (si->phdr[i].p_type == PT_PHDR):
      si->load_bias = reinterpret_cast<ElfW(Addr)>(si->phdr) - si->phdr[i].p_vaddr;
      si->base = reinterpret_cast<ElfW(Addr)>(si->phdr) - si->phdr[i].p_offset;
      break;

  ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(si->base);

  parse_LD_LIBRARY_PATH(ldpath_env);
  parse_LD_PRELOAD(ldpreload_env);

  for (const auto& ld_preload_name : g_ld_preload_names):
    needed_library_name_list.push_back(ld_preload_name.c_str());
    ++needed_libraries_count;
    ++ld_preloads_count;

  for_each_dt_needed(si, [&](const char* name):
    needed_library_name_list.push_back(name);
    ++needed_libraries_count;

  if (needed_libraries_count > 0):
    /* 1. 加载 exectuable 时 DT_NEEDED 引入的 so 默认为 RTLD_GLOBAL
    *  2. add_as_children 为 true, 与 dlopen 时不同 */
    find_libraries(&g_default_namespace, si, needed_library_names, needed_libraries_count,
                   nullptr, &g_ld_preloads, ld_preloads_count, RTLD_GLOBAL, nullptr,
                   /* add_as_children */ true))
  else if (needed_libraries_count == 0):
    si->link_image(g_empty_list, soinfo::soinfo_list_t::make_list(si), nullptr))
  /* vdso */
  add_vdso(args);
  si->call_constructors();
  return si->entry;

1.3.2. find_libraries

find_libraries:
  for (size_t i = 0; i < library_names_count; ++i):
    const char* name = library_names[i];
    load_tasks.push_back(LoadTask::create(name, start_with, &readers_map));

  /* step 1, expand load_tasks to include all DT_NEEDED libraries */

  for (size_t i = 0; i<load_tasks.size(); ++i):
    LoadTask* task = load_tasks[i];
    soinfo* needed_by = task->get_needed_by();
    find_library_internal(ns, task, &zip_archive_cache, &load_tasks, rtld_flags)

  /* step 2, load libraries in random order */
  shuffle(&load_list);
  for (auto&& task : load_list):
    task->load()

  /* step 3, pre-link all DT_NEEDED libraries in breadth first order. */
  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    if (!si->is_linked() && !si->prelink_image()) {}

  /* step 4, link libraries. */
  soinfo::soinfo_list_t local_group;
  walk_dependencies_tree(
      /* 若 add_as_children 为 true, 表示非 dlopen 的情况, walk_dependencies_tree 的 root 为 start_with
       * 若 add_as_children 为 false, 表示 dlopen, 加载的 so 形成 disjoint tree, root 为 soinfos, 而不是 start_with */
      (start_with != nullptr && add_as_children) ? &start_with : soinfos,
      (start_with != nullptr && add_as_children) ? 1 : soinfos_count,
      [&] (soinfo* si) {
        local_group.push_back(si);
      });

  local_group.visit([&](soinfo* si) {
      if (!si->is_linked()) {
        if (!si->link_image(global_group, local_group, extinfo)) {
          return false;
        }
      }
      return true;
    });

1.3.2.1. find_library_internal
find_library_internal:
  if (find_loaded_library_by_soname(ns, task->get_name(), &candidate)):
    task->set_soinfo(candidate);
    return true;

  /* load_library 会找到并打开 so, 读取 DT_NEEDED, 并把它们加到 load_tasks
   * 中, 但此时并不会做 link_image 的动作 */
  load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags)
1.3.2.1.1. load_library
load_library:
  int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath);
  soinfo* si = soinfo_alloc(ns, realpath.c_str(), &file_stat, file_offset, rtld_flags);
  for_each_dt_needed(task->get_elf_reader(), [&](const char* name) {
    load_tasks->push_back(LoadTask::create(name, si, task->get_readers_map()));
  });

open_library:
  if (strchr(name, '/') != nullptr):
    fd = open(name, O_RDONLY | O_CLOEXEC)
    *realpath = name;
    return fd

  /* 按如下顺序查找 so:
   * 1. ld_library_path
   * 2. needed_by->rpath
   * 3. default library path: /system/lib{64} */
  int fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_ld_library_paths(), realpath);
  if (fd == -1 && needed_by != nullptr):
    fd = open_library_on_paths(zip_archive_cache, name, file_offset, needed_by->get_dt_runpath(), realpath);
  if (fd == -1):
    fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_default_library_paths(), realpath);

load_library 在考虑 rpath 时, 使用的是 needed_by->rpath, 但 soinfo->needed_by 在实现上是一个指针而非一个列表, 实际的情况是 soinfo 有可能被多个 so NEEDED, 那哪个 so 才是它的 needed_by? 答案是 find_library_internal 时遇到的第一个 so

$> cat r1.c

void r() {
    printf("r1\n");
}

$> cat r2.c

void r() {
    printf("r2\n");
}

$> cat foo.c

extern void r();
void foo() {
    printf("foo\n");
    r();
}

$> cat bar.c

extern void r();
void bar() {
    printf("bar\n");
}

$> cat main.c

#include <stdio.h>
extern void foo();
int main(int argc, char *argv[]) {
    foo();
    return 0;
}

$> gcc -shared r1.c -o /home/sunway/r1/libr.so
$> gcc -shared r2.c -o /home/sunway/r2/libr.so
$> cp /home/sunway/r1/libr.so ./libr.so
$> gcc -shared foo.c -o libfoo.so -Wl,-rpath=/home/sunway/r1 libr.so
$> gcc -shared bar.c -o libbar.so -Wl,-rpath=/home/sunway/r2 libr.so
$> gcc main.c libfoo.so libbar.so
$> LD_LIBRARY_PATH=. ./a.out
r1
$> gcc main.c libbar.so libfoo.so
$> LD_LIBRARY_PATH=. ./a.out
r2
1.3.2.2. walk_dependencies_tree

walk_dependencies_tree 把树上以 start_with 为根的所有的 so (并非 solist 中所有的 so, 因为因为 dlopen 加载的 so 并不在树上) 以 BFS 方式进行遍历放在 local_group中, 后面 link_image 也会按这个顺序在 local_group 中进行soinfo_do_lookup

main.c
----------

#include <stdio.h>

extern void foo();

int main(int argc, char *argv[]) {
    foo();
    return 0;
}

foo.c
----------
extern void bar();

void x() {
    printf("x from foo\n");
}

void foo() {
    bar();
}

bar.c
----------
void x() {
    printf("x from bar\n");
}

void bar() {
    x();
}

foo2.c
----------

void x() {
    printf("x from foo2\n");
}

$> gcc bar.c -shared -O0 -g3 -o libbar.so
$> gcc foo.c -shared -O0 -g3 -o libfoo.so libbar.so
$> gcc foo2.c -shared -O0 -g3 -o libfoo2.so

$> gcc main.c -O0 -g3 libfoo2.so libfoo.so libbar.so
$> LD_LIBRARY_PATH=. ./a.out
x from foo2

$> gcc main.c -O0 -g3 libfoo.so libbar.so libfoo2.so
$> LD_LIBRARY_PATH=. ./a.out
x from foo

$> gcc main.c -O0 -g3 libbar.so libfoo2.so libfoo.so
$> LD_LIBRARY_PATH=. ./a.out
x from bar

1.3.3. LoadTask::load

LoadTask::load 的作用是 kernel 的 load_elf_library 基本相同

LoadTask::load:
  /* elf_reader.Load 会 mmap so 中的 PTLOAD segments */
  elf_reader.Load(extinfo_)
  si_->base = elf_reader.load_start();
  si_->size = elf_reader.load_size();
  si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller());
  si_->load_bias = elf_reader.load_bias();
  si_->phnum = elf_reader.phdr_count();
  si_->phdr = elf_reader.loaded_phdr();

ElfReader::Load:
  ReserveAddressSpace(extinfo)
  LoadSegments()
  FindPhdr()

1.3.4. LoadSegments

LoadSegments 负责相应的 PT_LOAD mmap 进来, 但对于 .bss 有特殊处理:

$> readelf -a a.out

   [24] .bss              NOBITS           0000000000201030  00001030
       0000000000000010  0000000000000000  WA       0     0     4

   LOAD           0x0000000000000e10 0x0000000000200e10 0x0000000000200e10
                  0x0000000000000220 0x0000000000000230  RW     0x200000

   03     .init_array .fini_array .dynamic .got .got.plt .data .bss

第二个 PT_LOAD 的 file siz 为 0x220, 但 memory size 为 0x230, 因为这个
PT_LOAD 最后是一个大小为 0x10 的 .bss

LoadSegments 的这段代码负责把超出 file size 的部分都 zero fill, 这与
.bss 的要求是一致的, 所以 .bss 放在 PT_LOAD 的最后并不是巧合.

zero fill 时 linker 是通过 anonymous mmap 做到的:

man 2 mmap:

  MAP_ANONYMOUS
              The mapping is not backed by any file; its contents are initialized to zero.

通过 mmap 而不是直接 zero fill 可以利用 COW 达到更好的性能

1.3.5. link_image

1.4. libdl

android 的 libdl 的实现在 linker 中, 而不是在 libdl.so 中. libdl.so 只是用来保证编译时能通过. 实际运行时 libdl.so 并不会被加载,因为它只是提供了一些保证编译通过的 stub 函数. android linker 在运行时通过 get_libdl_info 自己构造了一个对应于 libdl.so 的 soinfo.

1.4.1. dlopen

void* dlopen(const char* filename, int flags):
  void* caller_addr = __builtin_return_address(0);
  dlopen_ext(filename, flags, nullptr, caller_addr);
    do_dlopen(filename, flags, extinfo, caller_addr);

do_dlopen:
  soinfo* const caller = find_containing_library(caller_addr);
  android_namespace_t* ns = get_caller_namespace(caller);
    caller != nullptr ? caller->get_primary_namespace() : g_anonymous_namespace;
  /* 通过 system.loadLibrary() 发起的 dlopen 调用需要使用
   * classloader-namepsace, 指定在 extinfo 中 */
  if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0):
    ns = extinfo->library_namespace;

  /* dlopen 时指定的 flags (例如 RTLD_GLOBAL) 会影响因为它的 DT_NEEDED
   * 而加载的其它的 so 也使用相同的 flag */
  soinfo* si = find_library(ns, name, flags, extinfo, caller);
    /* find_library 会调用 find_libraries, 但 add_as_children 为
     * false, 则 dlopen load 的 so 并不会挂在 dependencies tree 中 (但
     * 还在 soinfo list), 这就导致了 dlopen 的 so 无法通过
     * walk_dependencies_tree 找到 (例如 link_image)*/
    find_libraries()
      // ...
      // dlopen 时 laod_tasks 初始只有一项, 即要 dlopen 的 so, start_with 为调用 dlopen
      // 的代码所在的 so
      for (size_t i = 0; i<load_tasks.size(); ++i):
        /* 初始时 load_tasks 中的 task->get_needed_by 均为 start_with  */
        soinfo* needed_by = task->get_needed_by();
        /* needed_by != start_with 的判断会导致 dlopen 最终会形成一棵
         * disjoint tree: 它的根是 load_tasks[0], 且 load_tasks[0] 并没
         * 有加到 start_with 所在的树中 */
        bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);
        find_library_internal()
        if (is_dt_needed):
          needed_by->add_child(si);
        // ...

  si->call_constructors();
  return si->to_handle();

1.4.2. dlsym

dlsym:
  void* caller_addr = __builtin_return_address(0);
  soinfo* caller = find_containing_library(caller_addr);
  android_namespace_t* ns = get_caller_namespace(caller);
  if (handle == RTLD_DEFAULT || handle == RTLD_NEXT):
    sym = dlsym_linear_lookup(ns, sym_name, vi, &found, caller, handle);
  else:
    soinfo* si = soinfo_from_handle(handle);
    sym = dlsym_handle_lookup(si, &found, sym_name, vi);

  if ((bind == STB_GLOBAL || bind == STB_WEAK) && sym->st_shndx != 0):
    *symbol = reinterpret_cast<void*>(found->resolve_symbol_address(sym));
    return true;
  return false;

dlsym_linear_lookup:
  /* 若 handle 为 RTLD_DEFAULT 或 RTLD_NEXT, 则进行线性查找, 即按照 so
   * 被加载的顺序还查找 */
  auto& soinfo_list = ns->soinfo_list();
  auto start = soinfo_list.begin();

  if (handle == RTLD_NEXT):
    auto it = soinfo_list.find(caller);
    /* RTLD_NEXT 是一个特殊的 handle: 从它的 `下一个` so 开始查找, `下
     * 一个` 在这里实际就是 so 被加载的顺序 (BFS)*/
    start = ++it;
  /* 若 RTLD_DEFAULT, 则从第一个 so 开始查找 */
  for (auto it = start, end = soinfo_list.end(); it != end; ++it):
    soinfo* si = *it;
    if ((si->get_rtld_flags() & RTLD_GLOBAL) == 0):
      continue;
    if (si->find_symbol_by_name(symbol_name, vi, &s)):
      *found = si
      break

dlsym_handle_lookup:
  /* root 为 handle 代表的 soinfo */
  walk_dependencies_tree(&root, 1, [&](soinfo* current_soinfo) {
    if (!current_soinfo->find_symbol_by_name(symbol_name, vi, &result)):
      result = nullptr;
      return false;

    if (result != nullptr):
      *found = current_soinfo;
      return false;
    }

1.4.3. RTLD_XXX

1.4.3.1. RTDL_DEFAULT

参考 dlsym_linear_lookup

1.4.3.2. RTLD_NEXT

参考 dlsym_linear_lookup

1.4.3.3. RTLD_GLOBAL

dlopen 时指定 RTLD_GLOBAL 与 DF_1_GLOBAL 功能相同.

  1. 在 create_namespace 时, 若 parent_ns 的 type 不是 shared, 则只有 RTLD_GLOBAL 类型的 so 才会被 child ns 继承
  2. exectuable, preload 及所有加载 exectuable 时因为 DT_NEEDED 导入的 so 都是 RTLD_GLOBAL
  3. dlopen 时指定了 RTLD_GLOBAL 时 load 的 so 以及因为它的 DT_NEEDED 导入的 so 是 RTLD_GLOBAL
  4. 有 DF_1_GLOBAL flag 的 so 在 dlopen 时默认为 RTLD_GLOBAL, 且会覆盖 dlopen 的 RTLDLOCAL|GLOBAL 参数 (参考 find_libraries)
  5. link_image 时, global_group (包括 executable, preload, RTLD_GLOBAL 和 DF_1_GLOBAL), 优先被查找, 其它的 so 通过 dependency tree 的 BFS 遍历被查找, 由于 RTLD_LOCAL 的so 因为没有挂在 start_from so 的 dependency tree (add_as_children)上, 所以 link_image 时这些 so 无法被找到.
  6. dlsym 使用 RTLD_DEFAULT 或 RTLD_NEXT 时使用线性查找, 但只会从 RTLD_GLOBAL 类型的 so 中查找符号
1.4.3.4. RTLD_LOCAL
1.4.3.5. RTLD_LAZY
1.4.3.6. RTLD_NOLOAD
1.4.3.7. RTLD_NODELETE

1.5. android_namespace

android_namespace 的作用是阻止某些情况下 link 某些 so, 例如:

  1. app 自己的代码想要 link /system/lib/libcutils.so 是不允许的, 因为 libcutils.so 不是稳定的接口, 为了应用的兼容性, android 禁用应用程序使用 libcutils.so.
  2. android 原生的 native 代码(例如 installd) 使用 libcutils.so 是允许的.

为了支持以上的功能, android 提供了 android_namespace 的概念

1.5.1. android_namespace_t

struct android_namespace_t {
  const char* name_;
  bool is_isolated_;
  std::vector<std::string> ld_library_paths_;
  std::vector<std::string> default_library_paths_;
  std::vector<std::string> permitted_paths_;
  soinfo::soinfo_list_t soinfo_list_;
}

app 代码自己使用的 namepsace 是 isolated, 它能使用的 so 有如下的限定:

  1. soinfo_list_ 已经加载的 so 可以使用 (这通过是从 parent namepsace 复制了一部分过来)
  2. 包含在 ld_library_paths_ 和 default_library_paths_ 中的 so
  3. 如果 so 是通过绝对路径加载的 (跳过了 ld_library_paths_ 和 default_library_paths_), 则它必须位于 permitted_paths_ 之内

一共三种 android_namespace:

  1. g_default_namespace
  2. g_anonymous_namespace
  3. classloader-namespace

1.5.2. g_default_namespace

g_default_namespace 是不受任何限制的 namepsace, 它可以访问任何位置

1.5.3. g_anonymous_namespace

通常情况下所有代码都有其 caller namespace, 例如 g_default_namespace 加载了 libx.so 后, libx.so 中代码调用 dlopen 时, 通过 caller 代码的位置所在的 so (libx.so) 可以确定它的 caller namespace 是 g_default_namespace

dlopen 时会根据 caller namespace 为决定是否可以 dlopen 成功.

但如果代码是自动生成的 (例如 jit), 则无法获得 caller namespace, 这时会使用 g_anonymous_namespace 作为 caller namespace, 它的能力也是受限的.

1.5.4. classloader-namespace

classloader-namespace 是 android 引入 namespace 的主要原因:

java 通过 PathClassLoader 加载 java 类后, java 可能会通过 jni load 某个 so, 这时 android 会通过 classloader-namespace 限制这个 load 的行为只能 load app 自己的 so (/data/app/lib…) 或某些 public 的 so

1.5.5. g_public_namespace

g_public_namespace 并不是一个 android_namespace_t, 它只是一些 soinfo 的集合, classloader-namespace 在 dlopen 时允许使用 g_public_namespace 中的 soinfo

1.5.6. classloader-namespace 初始化

JNI_CreateJavaVM:
  android::InitializeNativeLoader();
    g_namespaces->Initialize();
      /* 读取 /system/etc/public.libraries.txt */
      ReadConfig(public_native_libraries_system_config, &sonames)
      /* 确保 public_libraries_ 都已经被 load 到 g_default_namespace,
       * 后面会用到这一点 */
      for (const auto& soname : sonames):
        dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE);
      public_libraries_ = base::Join(sonames, ':');

PathClassLoaderFactory:createClassLoader (librarySearchPath, libraryPermittedPath):
  /* librarySearchPath 一般是 /data/app/xxx/lib 这种形式,
   * libraryPermittedPath 一般是 /data */

  /* 创建出来的 namespace 会与 classloader 绑定在一起 */
  createClassloaderNamespace(pathClassloader,
                               librarySearchPath,
                               libraryPermittedPath,
                               isNamespaceShared);

    g_namespaces->Create(env,class_loader, is_shared, library_path, permitted_path);

android_namespace_t* Create(JNIEnv* env,
                            jobject class_loader,
                            bool is_shared,
                            jstring java_library_path,
                            jstring java_permitted_path):

  /* init g_public_namespace */
  !initialized_ && !InitPublicNamespace(library_path.c_str())



InitPublicNamespace(library_path.c_str():
    /* public_libraries_ 保存在 /system/etc/public.libraries.txt */
    android_init_namespaces(public_libraries_.c_str(), library_path)
      init_namespaces(public_ns_sonames, anon_ns_library_path)
        for (const auto& soname : sonames):
          /* 前面 InitializeNativeLoader 可以确保 public_libraries_ 都
           * 已经被 load 到 g_default_namespace 中了 */
          find_loaded_library_by_soname(&g_default_namespace, soname.c_str(), &candidate);
          g_public_namespace.push_back(candidate);
        /* 在这里初始化了 g_anonymous_namespace, 可见它的 library_path
         * 是 /dat/app/xxx/lib, 它会从 g_default_namespace copy 那些已
         * 经 load 进来的 GLOBAL 的 so (参考 create_namespace) */
        anon_ns = create_namespace(nullptr, "(anonymous)", nullptr, anon_ns_library_path, ANDROID_NAMESPACE_TYPE_REGULAR, nullptr, &g_default_namespace);

1.5.7. 使用 classloader-namespace 来 load so

1.5.7.1. java 通过 loadLibrary
OpenNativeLibrary:
  ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader);
  android_dlopen_ext(path, RTLD_NOW, &extinfo);
    void* caller_addr = __builtin_return_address(0);
    dlopen_ext(filename, flags, extinfo, caller_addr);
      do_dlopen(filename, flags, extinfo, caller_addr);

do_dlopen:
  soinfo* const caller = find_containing_library(caller_addr);
  android_namespace_t* ns = get_caller_namespace(caller);
  /* 整个调用是通过 java 层的 loadLibrary 发起的, 所以 ns 是通过
   * classloader 获得的, 而不是通过 get_caller_namespace */
  if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0):
    ns = extinfo->library_namespace;

  soinfo* si = find_library(ns, name, flags, extinfo, caller);
  if (si != nullptr):
    si->call_constructors();
    return si->to_handle();
1.5.7.2. jni 中通过 dlopen
void* dlopen(const char* filename, int flags):
  void* caller_addr = __builtin_return_address(0);
  /* dlopen 直接使用 dlopen_ext, 查找 ns 依赖于 caller_addr */
  return dlopen_ext(filename, flags, nullptr, caller_addr);

1.5.8. find_library

find_library 的过程在前面 linker 初始化已经提过, 但没有考虑 ns

find_library:
  find_libraries(ns, needed_by, &name, 1, &si, nullptr, 0, rtld_flags, extinfo, /* add_as_children */ false)
    // ...
    find_library_internal(ns, task, &zip_archive_cache, &load_tasks, rtld_flags)
    /* 由于 ns 创建时从 parent copy 了一批 soinfo, 所以这里有可能直接
     * 返回前面已经加载过的 so (可能是从 parent copy 过来的) */
    if (find_loaded_library_by_soname(ns, task->get_name(), &candidate)):
      task->set_soinfo(candidate);
      return true;

    // 是否属于 g_public_namespace
    if (ns != &g_default_namespace):
      candidate = g_public_namespace.find_if([&](soinfo* si) {
          return strcmp(task->get_name(), si->get_soname()) == 0;
        });

      if (candidate != nullptr):
        ns->add_soinfo(candidate);
        task->set_soinfo(candidate);
        return true;

    load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags)
      open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath)
      /* open_library 找到这个 so, 但不是 is_accessible, 例如通过绝对
       * 路径指定了一个 so, 但 so 并不在 permitted_paths 中 */
      if (!ns->is_accessible(realpath)):
        return false;
      // ...

1.5.9. 总结

  1. classloader-namespace 是受限 (isolated) 的 namepsace, 只能加载 app 自己的 so, 从 parent 继承过来的一些 so 以及 public so
  2. 通过 classloader 或 caller_address 来确定当前使用的 ns

1.6. dependencies tree

find_libraries 一方面会把 so 放在 soinfo list 中, 另一方面会通过 need_by 把它们组织成一个 dependencies tree.

但有一个例外: dlopen 一个 so 时会把所有涉及到的 so 加到 list 中, 但这些 so 自身形成一棵独立的 dependencies tree, 它们并不会被加到 start_with 所在的那棵树上.

为了能在后续 link_image 时能使用 dlopen(RTLD_GLOBAL) 方式打开的 so, 这棵独立的树上的 GLOBAL 的 so 会被加入到 global_group (make_global_group) 中, 以便能使用这样so.

dependencies tree 有两个用处:

  1. link_image
  2. dlsym_handle_lookup

1.7. misc

1.7.1. version_tracker

1.7.1.1. init
link_image:
  version_tracker::init
  init_verneed(si_from) && init_verdef(si_from);
  /* init 完成后, version_tracker 有如下的数组:

     version_infos[source_index].name = ...;
     version_infos[source_index].target_si = ...;

     该数组大小等于 dynsym 大小, 对于当前 so 定义的符号, 信息是从 init_verdef 获得的, target_si 为当前 so,
     对于未定义的符号, 信息是从 init_verneed 获得的, target_si 为 .gnu.version_r 中指定的 so

     后续在查找符号时, 需要比较 version_info 是否一致
   */

init_verneed:
  /* 根据 .gnu.version_r 获得如下信息:
   * 1. sym 依赖的 version 的名字
   * 2. 该 version 在哪个 soinfo 被定义
   * */

init_verdef:
  /* 根据 .gnu.version_d 获得如下信息:
   * 1. sym 依赖的 version 的名字
   * 2. 不需要查找 version 在哪定义: 它肯定是在当前 so 定义的 */

1.7.1.2. relocate
relocate:
  /* 当前 so 中需要重定位的 sym */
  sym_name = get_string(symtab_[sym].st_name);
  /* 获得 version_info */
  lookup_version_info(version_tracker, sym, sym_name, &vi))
    const ElfW(Versym) sym_ver = *(get_versym(sym));
    /* *sym_ver 是 sym 的 version 的 index 值 */
    *vi = version_tracker.get_version_info(sym_ver);
      return &version_infos[source_symver];
  /* 通过 lookup_version_info, 获得了 sym 要求的 version name, 以及 */

  soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)
    si->find_symbol_by_name(symbol_name, vi, &s)
      /* suppose not using gnu hash */
      elf_lookup(symbol_name, vi, &symbol_index)

elf_lookup:
  if (!find_verdef_version_index(vi, &verneed)):
    for_each_verdef(this,
      [&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) {
                      if (verdef->vd_hash == vi->elf_hash &&
                          strcmp(vi->name, get_string(verdaux->vda_name)) == 0) {
                        *versym = verdef->vd_ndx;
                        return true;
                      }

                      return false;
                    }
      );
    return false;
  /* 在当前 so 的 .gnu.version_d 中找到要求的 version name 对应的
   * version index (verneed) */

  for (uint32_t n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]):
    ElfW(Sym)* s = symtab_ + n;
    /* 通过读取 .gnu.version 获得当前 so 中 sym (n) 的 version (verdef) */
    const ElfW(Versym)* verdef = get_versym(n);
    if (
      /* check version */
      check_symbol_version(verneed, verdef) &&
      /* check name */
      strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 &&
      /* check visibility */
      is_symbol_global_and_defined(this, s)):
      *symbol_index = n;
      return true;

1.7.1.3. 总结

link_image 时会针对要 link 的当前 so 构造一个 version_tracker, 主要就是读取了 .gnu.version 和 .gnu.version_r, 确定 UNDEF 的符号要求的 version 的名字 (verneed)

relocate 时需要判断 so 中某个 sym 的 verdef 与 verneed 是否相同. 而 verdef 是通过读取 so 的 .gnu.version 和 .gnu.version_d 获得的.

1.7.2. how linker is compiled

clang++ -nostdlib -Bstatic  -Wl,--gc-sections -o /linker_intermediates/LINKED/linker64 -Lout/target/product/sp9850j_1h10/obj/lib

-Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,--build-id=md5 -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,-maarch64linux -Wl,--hash-style=gnu -Wl,--fix-cortex-a53-843419 -fuse-ld=gold -Wl,--icf=safe -Wl,--no-undefined-version -Wl,--allow-shlib-undefined    -target aarch64-linux-android -Bprebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/aarch64-linux-android/bin   -shared -Wl,-Bsymbolic -Wl,--exclude-libs,ALL -Wl,--no-undefined

/linker_intermediates/arch/arm64/begin.o   /linker_intermediates/debugger.o /linker_intermediates/dlfcn.o /linker_intermediates/linker.o /linker_intermediates/linker_allocator.o /linker_intermediates/linker_block_allocator.o /linker_intermediates/linker_dlwarning.o /linker_intermediates/linker_gdb_support.o /linker_intermediates/linker_mapped_file_fragment.o /linker_intermediates/linker_memory.o /linker_intermediates/linker_phdr.o /linker_intermediates/linker_sdk_versions.o /linker_intermediates/linker_utils.o /linker_intermediates/rt.o       /linker_intermediates/linker_libc_support.o

-Wl,--whole-archive   -Wl,--no-whole-archive /libziparchive_intermediates/libziparchive.a /libutils_intermediates/libutils.a /libbase_intermediates/libbase.a /libz_intermediates/libz.a /liblog_intermediates/liblog.a /libc++_static_intermediates/libc++_static.a /libm_intermediates/libm.a /libdl_intermediates/libdl.a /libcompiler_rt-extras_intermediates/libcompiler_rt-extras.a

-Wl,--start-group
/libc_intermediates/libc.a /libc_nomalloc_intermediates/libc_nomalloc.a  prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/../lib/gcc/aarch64-linux-android/4.9/../../../../aarch64-linux-android/lib/../lib64/libatomic.a prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/../lib/gcc/aarch64-linux-android/4.9/libgcc.a
-Wl,--end-group

aarch64-linux-android-objcopy --prefix-symbols=__dl_ /linker_intermediates/LINKED/linker64

所以 linker 是一个 DSO, 而且它编译时使用了 Bsymbolic, 所以在完成自身的重定位之前, 它就已经可以正确的调用自身的各种函数了, 因为这些函数不是通过 plt 的方式来调用的.

1.7.3. RTLD_GLOBAL, DF_1_GLOBAL, global_group

RTLD_GLOBAL 与 DF_1_GLOBAL 有关:

  1. dlopen 时使用 RTLD_GLOBAL flag
  2. 应用启动时加载的 so (通过 DT_NEEDED, 自动指定了 RTLD_GLOBAL)
  3. dlopen 时 load 了一个指定了 DF_1_GLOBAL 的 so

三种情况 load 的 so 都带有 RTLD_GLOBAL 标记, 后续 dlopen 打开的 so 可以直接使用这些 so中的符号. http://www.informit.com/articles/article.aspx?p=22435

global_group 也和 DF_1_GLOBAL 有关:

exectuable, LD_PRELOAD (不包含其 DT_NEEDED 引入的 so) 以及 DF_1_GLOBAL 的 so 属于 global_group, 在 lookup 时优先考虑

1.7.4. environment variable

1.7.4.1. LD_DEBUG
1.7.4.2. LD_LIBRARY_PATH
1.7.4.3. LD_BIND_NOW
1.7.4.4. LD_PRELOAD
1.7.4.5. LD_SHOW_AUXV

1.8. summary

linker 主要的代码是:

  1. load_library, 找到 so
  2. find_libraries, 解析 DT_NEEDED, 构造依赖树
  3. 对各个 so 以 BFS 的顺序进行 link_image, 进行 GOT 和 PLT 的重定位
    1. 根据 dynamic 获得 rel.dynrel.plt 以及 dynsym (以及 dynstr)
    2. 根据 reldyn,plt 找到需要 patch 的位置, 并结合 dynsym (和 dynstr) 找到对应的符号, 然后进行 patch

Author: [email protected]
Date: 2017-04-27 Thu 00:00
Last updated: 2024-02-01 Thu 14:04

知识共享许可协议