Android Linker
Table of Contents
1. Android Linker
1.1. linker 启动
1.1.1. kernel 部分
以 exec("/system/bin/linker") 为例:
linker 本身的 interp 是它自己:
~@tj02433pcu> readelf -a /system/bin/linker|grep 'program inter' [Requesting program interpreter: /system/bin/linker]
linker 的 entry 为 __dl__start, 地址为 0x28fc
~@tj02433pcu> readelf -a /system/bin/linker|grep 'Entry point' Entry point address: 0x28fc
kernel 会如下设置:
/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]
控制会转移到 interp, 其入口为 0xf777d8fc, 这个地址对应于 interp 的 __dl__start:
f777b000 (inter 被 map 的地址) + 0x28fc (entry point) = 0xf777d8fc
- aux vector
- AT_BASE 为 0xf777b000, 即 interp 被 map 的地址
- 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. 总结
- kernel 负责分别将 linker 和 exectuable(在本例中即 linker 自身) map 到某个位置 (最基本的重定位), 并设置好 AT_BASE 和 AT_ENTRY
- kernel 随后跳转到 linker(interp 的 __dl__start
- __dl__start 负责进行 linker 自身及 exectuable 的重定位
- 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 的输入是:
- symbol name
- this so
- global_group, 包含 1. exectuable 自己 2. LD_PRELOAD 引入的 so 3. DF_1_GLOBAL 的 so
- local_group, 通过 walk_dependencies_tree 获得的 so ,包含所有DT_NEEDED 引入的 so
soinfo_do_lookup 需要考虑几点:
- global 或 local group
- DT_SYMBOLIC,
- 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 功能相同.
- 在 create_namespace 时, 若 parent_ns 的 type 不是 shared, 则只有 RTLD_GLOBAL 类型的 so 才会被 child ns 继承
- exectuable, preload 及所有加载 exectuable 时因为 DT_NEEDED 导入的 so 都是 RTLD_GLOBAL
- dlopen 时指定了 RTLD_GLOBAL 时 load 的 so 以及因为它的 DT_NEEDED 导入的 so 是 RTLD_GLOBAL
- 有 DF_1_GLOBAL flag 的 so 在 dlopen 时默认为 RTLD_GLOBAL, 且会覆盖 dlopen 的 RTLDLOCAL|GLOBAL 参数 (参考 find_libraries)
- 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 无法被找到.
- 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, 例如:
- app 自己的代码想要 link /system/lib/libcutils.so 是不允许的, 因为 libcutils.so 不是稳定的接口, 为了应用的兼容性, android 禁用应用程序使用 libcutils.so.
- 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 有如下的限定:
- soinfo_list_ 已经加载的 so 可以使用 (这通过是从 parent namepsace 复制了一部分过来)
- 包含在 ld_library_paths_ 和 default_library_paths_ 中的 so
- 如果 so 是通过绝对路径加载的 (跳过了 ld_library_paths_ 和 default_library_paths_), 则它必须位于 permitted_paths_ 之内
一共三种 android_namespace:
- g_default_namespace
- g_anonymous_namespace
- 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. 总结
- classloader-namespace 是受限 (isolated) 的 namepsace, 只能加载 app 自己的 so, 从 parent 继承过来的一些 so 以及 public so
- 通过 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 有两个用处:
- link_image
- 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 有关:
- dlopen 时使用 RTLD_GLOBAL flag
- 应用启动时加载的 so (通过 DT_NEEDED, 自动指定了 RTLD_GLOBAL)
- 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 时优先考虑