bionic linker代碼分析(1) - linker自舉

Android在啓動一個新的進程的時候,是由execv函數族trap到內核,由kernel去檢查和加載可執行文件;kernel做完可執行文件的加載同時會加載/system/bin/linker,然後由linker去加載依賴的動態庫,並調用可執行文件的入口函數,完成控制權的轉移。

linker本身也是一個ELF格式的動態庫文件,它的入口代碼位於${bionic}/linker/arch/arm/begin.S文件中

#include <private/bionic_asm.h>
ENTRY(_start)
  mov r0, sp
  bl __linker_init

  /* linker init returns the _entry address in the main image */
  bx r0
END(_start)

在_start函數中,棧頂指針寄存器被賦值給r0寄存器作爲參數調用__linker_init。__linker_init做完linker的初始化和依賴庫的加載後,通過r0返回了可執行文件入口函數,接下來的bx r0 指令就會將控制權移交給可執行文件。

__linker_init() 位於${bionic}/linker/linker_main.cpp文件中:

492 extern "C" ElfW(Addr) __linker_init(void* raw_args) {
493   KernelArgumentBlock args(raw_args);
494 
495   // AT_BASE is set to 0 in the case when linker is run by iself
496   // so in order to link the linker it needs to calcuate AT_BASE
497   // using information at hand. The trick below takes advantage
498   // of the fact that the value of linktime_addr before relocations
499   // are run is an offset and this can be used to calculate AT_BASE.
500   static uintptr_t linktime_addr = reinterpret_cast<uintptr_t>(&linktime_addr);
501   ElfW(Addr) linker_addr = reinterpret_cast<uintptr_t>(&linktime_addr) - linktime_addr;

493行的KernelArgumentBlock類在 ${bionic}/libc/private/KernelArgumentBlock.h文件中定義。kernel在加載linker時,已經在堆棧中初始化好了命令行參數、環境變量以及後面的ELF輔助向量(Auxiliary Vector)。raw_args即_start中傳入的棧頂指針寄存器,通過args對象只是對上述信息進行封裝,提供一系列讀寫接口而已。堆棧的內存佈局如下:

position            content                     size (bytes)  comment
  ------------------------------------------------------------------------
stack pointer ->  [ argc = number of args ]     4      
                  [ argv[0] (pointer) ]         4      
                  [ argv[1] (pointer) ]         4      
                  [ argv[..] (pointer) ]        4 * n  
                  [ argv[n - 1] (pointer) ]     4      
                  [ argv[n] (pointer) ]         4           = NULL

                  [ envp[0] (pointer) ]         4     
                  [ envp[1] (pointer) ]         4      
                  [ envp[..] (pointer) ]        4      
                  [ envp[term] (pointer) ]      4           = NULL

                  [ auxv[0] (Elf32_auxv_t) ]    8      
                  [ auxv[1] (Elf32_auxv_t) ]    8
                  [ auxv[..] (Elf32_auxv_t) ]   8 
                  [ auxv[term] (Elf32_auxv_t) ] 8           = AT_NULL vector

                  [ padding ]                   0 - 16     

                  [ argument ASCIIZ strings ]   >= 0   
                  [ environment ASCIIZ str. ]   >= 0   

(0xbffffffc)      [ end marker ]                4          = NULL 結束

(0xc0000000)       < bottom of stack >          0          (virtual)

500行定義的linker_addr變量就是linker文件在內存中實際映射的基地址,在Android 7之前,linker_addr是通過是直接從ELF輔助向量中讀取AT_BASE獲得。Android 8之後,通過定義靜態變量linktime_addr是來計算linker_addr。這裏通過對linker反彙編,來理解這兩行代碼的trick,是如何計算linker_addr,

先找到__linker_init函數的實現

0xf57ed的指令r2 = r2(0x78c2e) + pc(linker_addr + 0xf582) = linker_addr + 0x881B0; 因爲實際在內存運行的時候指令寄存器pc的值是基地址+偏移地址,所以實際當前的pc = linker_addr + 0xf582,armv7三級流水線pc等於取指地址0xf582。

接來下的指令ldr r3, [r2]也就是將linker_addr+0x881B0中的內容賦值給r3,圖2中0x881B0地址的值等於0x881B0,所以r3的值等於0x881B0。

然後在地址0xf588的指令,r5 = r2(linker_addr+0x881B0) - r3(0x881B0) = linker_addr

這裏寫圖片描述

圖1 計算linker_addr彙編指令

這裏寫圖片描述

圖2 計算linktime_addr內存值

503 #if defined(__clang_analyzer__)
504   // The analyzer assumes that linker_addr will always be null. Make it an
505   // unknown value so we don't have to mark N places with NOLINTs.
506   //
507   // (`+=`, rather than `=`, allows us to sidestep a potential "unused store"
508   // complaint)
509   linker_addr += reinterpret_cast<uintptr_t>(raw_args);
510 #endif
511 
512   ElfW(Addr) entry_point = args.getauxval(AT_ENTRY);
513   ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_addr);
514   ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_addr + elf_hdr->e_phoff);
515 
516   soinfo linker_so(nullptr, nullptr, nullptr, 0, 0);
517 
518   linker_so.base = linker_addr;
519   linker_so.size = phdr_table_get_load_size(phdr, elf_hdr->e_phnum);
520   linker_so.load_bias = get_elf_exec_load_bias(elf_hdr);
521   linker_so.dynamic = nullptr;
522   linker_so.phdr = phdr;
523   linker_so.phnum = elf_hdr->e_phnum;
524   linker_so.set_linker_flag();
525 
526   // Prelink the linker so we can access linker globals.
527   if (!linker_so.prelink_image()) __linker_cannot_link(args.argv[0]);
528 
529   // This might not be obvious... The reasons why we pass g_empty_list
530   // in place of local_group here are (1) we do not really need it, because
531   // linker is built with DT_SYMBOLIC and therefore relocates its symbols against
532   // itself without having to look into local_group and (2) allocators
533   // are not yet initialized, and therefore we cannot use linked_list.push_*
534   // functions at this point.
535   if (!linker_so.link_image(g_empty_list, g_empty_list, nullptr)) __linker_cannot_link(args.argv[0]);
536 

503行到510行之間的__clang_analyzer__,不知道原理是什麼,只能假裝沒有定義這個宏。繼續後面的分析。

513-514行就是拿linker_addr作爲linker的加載地址,分別拿到linker文件的Ehdr和Phdr結構的指針,關於ELF格式,也只能假裝看這邊文章的人是都瞭解的!

516行的soinfo對象,在linker裏面是代表動態庫的一個類,每個加載到內存的動態庫都會有一個soinfo對象表示,同一個動態庫文件dlopen兩次是有能創建兩個soinfo對象,並且動態庫文件被影射到不同的內存邏輯地址上;這個現象只有在android 6以上纔會出現,因爲有android_namespace這個東西,這裏先不展開。

phdr_table_get_load_size()函數用於計算program headers中所有PT_LOAD節的長度之和,dlopen加載ELF格式的動態庫時,除了映射相應的頭部數據,會將program headers中所有PT_LOAD分別map到內存[ 通過ElfReader::LoadSegments()方法 ]。

get_elf_exec_load_bias()函數計算load_bias,load_bias等於base_addr加上第一個PT_LOAD節的 offset - vaddr, 然而so動態庫的第一個PT_LOAD的offset和vaddr一般情況下都等於0,也就是load_bias等於base_addr; 不過我們也是有遇到過例外的情況,所以在處理dynamic的對象時,要用load_bias作爲基地址。

只有表示linker的soinfo對象會調用set_linker_flag()標記自己爲linker,這個標記在後面很多地方用於區分linker和一般動態庫文件。

這裏還要留意linker_so是創建在棧上的,後面會使用get_libdl_info()函數在堆上再分配一個soinfo對象,放入solist列表中管理。

527行的soinfo::prelink_image()函數也是linker中一個很重要的知識點!這個函數的作用是解析.dynamic,解析出符號表、字符串表、got、plt、hash表等等數據結構的內存位置、大小和一些相關參數。這個函數打算單獨做寫一篇分析。

535行的soinfo::linker_image()函數是用在prelink_image之後做重定位的,這裏也不展開分析了。

537 #if defined(__i386__)
538   // On x86, we can't make system calls before this point.
539   // We can't move this up because this needs to assign to a global.
540   // Note that until we call __libc_init_main_thread below we have
541   // no TLS, so you shouldn't make a system call that can fail, because
542   // it will SEGV when it tries to set errno.
543   __libc_init_sysinfo(args);
544 #endif
545 
546   // Initialize the main thread (including TLS, so system calls really work).
547   __libc_init_main_thread(args);
548 
549   // We didn't protect the linker's RELRO pages in link_image because we
550   // couldn't make system calls on x86 at that point, but we can now...
551   if (!linker_so.protect_relro()) __linker_cannot_link(args.argv[0]);
552 
553   // Initialize the linker's static libc's globals
554   __libc_init_globals(args);
555 
556   // store argc/argv/envp to use them for calling constructors
557   g_argc = args.argc;
558   g_argv = args.argv;
559   g_envp = args.envp;
560 

__libc_init_main_thread()方法和__libc_init_globals()方法是libc的內容,本文不做深入分析。

551行的soinfo::protect_relro()函數最終調用到$(bionic)/linker/linker_phdr.cpp文件777行的_phdr_table_set_gnu_relro_prot()函數會將PT_GNU_RELRO段指向的內存地址通過mprotoct函數設置爲PROT_READ。該函數會對PT_GNU_RELRO段指向的內存起始和結束地址取頁對齊,如果PT_GNU_RELRO段指向的內存段不是頁對齊,則會被over-protective as read-only

561   // Initialize the linker's own global variables
562   linker_so.call_constructors();
563 
564   // If the linker is not acting as PT_INTERP entry_point is equal to
565   // _start. Which means that the linker is running as an executable and
566   // already linked by PT_INTERP.
567   //
568   // This happens when user tries to run 'adb shell /system/bin/linker'
569   // see also https://code.google.com/p/android/issues/detail?id=63174
570   if (reinterpret_cast<ElfW(Addr)>(&_start) == entry_point) {
571     async_safe_format_fd(STDOUT_FILENO,
572                      "This is %s, the helper program for dynamic executables.\n",
573                      args.argv[0]);
574     exit(0);
575   }
576 
577   init_linker_info_for_gdb(linker_addr, kLinkerPath);
578 

562行的soinfo::call_constructors()函數位於${bionic}/linker/linker_soinfo.cpp文件的388行, call_constructors遞歸了get_childred()返回列表中所有的soinfo,然後執行對應的init_func_函數和init_array_列表中的函數。 init_func_函數對應ELF文件中的DT_INIT段指向的函數;init_array_列表則是DT_INIT_ARRAY段包含的函數列表,DT_INIT_ARRAY段中的函數列表是代碼中通過__attribute__ ((constructor)) 前綴修飾的全局函數,以及編譯器爲一些全局變量生成的構造函數。

388 void soinfo::call_constructors() {
389   if (constructors_called) {
390     return;
391   }
392 
393   // We set constructors_called before actually calling the constructors, otherwise it doesn't
394   // protect against recursive constructor calls. One simple example of constructor recursion
395   // is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:
396   // 1. The program depends on libc, so libc's constructor is called here.
397   // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
398   // 3. dlopen() calls the constructors on the newly created
399   //    soinfo for libc_malloc_debug_leak.so.
400   // 4. The debug .so depends on libc, so CallConstructors is
401   //    called again with the libc soinfo. If it doesn't trigger the early-
402   //    out above, the libc constructor will be called again (recursively!).
403   constructors_called = true;
404 
405   if (!is_main_executable() && preinit_array_ != nullptr) {
406     // The GNU dynamic linker silently ignores these, but we warn the developer.
407     PRINT("\"%s\": ignoring DT_PREINIT_ARRAY in shared library!", get_realpath());
408   }
409 
410   get_children().for_each([] (soinfo* si) {
411     si->call_constructors();
412   });
413 
414   if (!is_linker()) {
415     bionic_trace_begin((std::string("calling constructors: ") + get_realpath()).c_str());
416   }
417 
418   // DT_INIT should be called before DT_INIT_ARRAY if both are present.
419   call_function("DT_INIT", init_func_, get_realpath());
420   call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false, get_realpath());
421 
422   if (!is_linker()) {
423     bionic_trace_end();
424   }
425 }

570行判斷_start和entry_point是否相當是爲了檢查新進程要加載的可執行文件是不是linker;_start是linker的入口地址,entry_point是在512行從輔助向量裏讀出來AT_ENTRY,可執行文件的地址。

159 /* gdb expects the linker to be in the debug shared object list.
160  * Without this, gdb has trouble locating the linker's ".text"
161  * and ".plt" sections. Gdb could also potentially use this to
162  * relocate the offset of our exported 'rtld_db_dlactivity' symbol.
163  * Note that the linker shouldn't be on the soinfo list.
164  */
165 static link_map linker_link_map;
166 
167 static void init_linker_info_for_gdb(ElfW(Addr) linker_base, char* linker_path) {
168   linker_link_map.l_addr = linker_base;
169   linker_link_map.l_name = linker_path;
170 
171   /*
172    * Set the dynamic field in the link map otherwise gdb will complain with
173    * the following:
174    *   warning: .dynamic section for "/system/bin/linker" is not at the
175    *   expected address (wrong library or version mismatch?)
176    */
177   ElfW(Ehdr)* elf_hdr = reinterpret_cast<ElfW(Ehdr)*>(linker_base);
178   ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr)*>(linker_base + elf_hdr->e_phoff);
179   phdr_table_get_dynamic_section(phdr, elf_hdr->e_phnum, linker_base,
180                                  &linker_link_map.l_ld, nullptr);
181 
182 }

577行的init_linker_info_for_gdb()實現在linker_main.cpp文件的167行,用來填充linker_link_map對象,link_map是一個雙向鏈表節點類,定義在$(bionic)/libc/include/link.h文件中。link_map保存了動態庫的文件名、base_addr和.dynamic段的地址,

linker_link_map鏈表保存了所有載入內存的動態庫,看註釋是給gdb用的,保持和ld.linux.so的兼容,記得看過一篇文章@jmpews還是哪位牛人用這個結構來枚舉所有已加載的動態庫!

579   // Initialize static variables. Note that in order to
580   // get correct libdl_info we need to call constructors
581   // before get_libdl_info().
582   sonext = solist = get_libdl_info(kLinkerPath, linker_so, linker_link_map);
583   g_default_namespace.add_soinfo(solist);
584 
585   // We have successfully fixed our own relocations. It's safe to run
586   // the main part of the linker now.
587   args.abort_message_ptr = &g_abort_message;
588   ElfW(Addr) start_address = __linker_init_post_relocation(args);
589 
590   INFO("[ Jumping to _start (%p)... ]", reinterpret_cast<void*>(start_address));
591 
592   // Return the address that the calling assembly stub should jump to.
593   return start_address;
594 }

582行的get_libdl_info()函數的實現位於${bionic}/linker/dlfcn.cpp的279行,這也是比較有意思的地方,之前在__linker_init()函數的516行在棧上定義了linker_so對象用來表示linker二進制影響,而get_libdl_info()相當於在堆上拷貝linker_so構造了一個新的soinfo,然後添加到solist和sonext鏈表中。

android 7.1.2之前,/system/lib/libdl.so這個動態庫並沒有加載到內存中的,get_libdl_info()創建的soinfo,被命名爲libdl.so, 所以dlopen, dlclose, dlsym, dladdr這幾個函數實際上都是直接鏈接linker當中的符號。

而在android7.1.2之後,get_libdl_info()創建的soinfo對象soname=”ld-android.so”,/system/lib/libdl.so在加載依賴庫時被載入內存,dlopen, dlclose, dlsym, dladdr這幾個函數由libdl.so導出。

588行的__linker_init_post_relocation()函數同樣位於linker_main.cpp文件的215行,這個函數是__linker_init()在返回前最後調用的函數,它要創建可執行文件對應的soinfo、調用find_librarys加載依賴庫等工作,完成可執行文件運行所需的一系列準備工作。

215 static ElfW(Addr) __linker_init_post_relocation(KernelArgumentBlock& args) {
216   ProtectedDataGuard guard;
      ....

226   // Initialize system properties
227   __system_properties_init(); // may use 'environ'
      ....

240   g_linker_logger.ResetState();
      ....

277   const char* executable_path = get_executable_path();
278   soinfo* si = soinfo_alloc(&g_default_namespace, executable_path, &file_stat, 0, RTLD_GLOBAL);
279   if (si == nullptr) {
280     async_safe_fatal("Couldn't allocate soinfo: out of memory?");
281   }
282 
283   /* bootstrap the link map, the main exe always needs to be first */
284   si->set_main_executable();
285   link_map* map = &(si->link_map_head);
      ....

339   parse_LD_LIBRARY_PATH(ldpath_env);
340   parse_LD_PRELOAD(ldpreload_env);
341 
342   somain = si;
      ....

358   // Load ld_preloads and dependencies.
359   std::vector<const char*> needed_library_name_list;
360   size_t ld_preloads_count = 0;
361 
362   for (const auto& ld_preload_name : g_ld_preload_names) {
363     needed_library_name_list.push_back(ld_preload_name.c_str());
364     ++ld_preloads_count;
365   }
366 
367   for_each_dt_needed(si, [&](const char* name) {
368     needed_library_name_list.push_back(name);
369   });
370 
371   const char** needed_library_names = &needed_library_name_list[0];
372   size_t needed_libraries_count = needed_library_name_list.size();
373
374   // readers_map is shared across recursive calls to find_libraries so that we
375   // don't need to re-load elf headers.

376   std::unordered_map<const soinfo*, ElfReader> readers_map;
377   if (needed_libraries_count > 0 &&
378       !find_libraries(&g_default_namespace,
379                       si,
380                       needed_library_names,
381                       needed_libraries_count,
382                       nullptr,
383                       &g_ld_preloads,
384                       ld_preloads_count,
385                       RTLD_GLOBAL,
386                       nullptr,
387                       true /* add_as_children */,
388                       true /* search_linked_namespaces */,
389                       readers_map,
390                       &namespaces)) {
391     __linker_cannot_link(g_argv[0]);
392   } else if (needed_libraries_count == 0) {
393     if (!si->link_image(g_empty_list, soinfo_list_t::make_list(si), nullptr)) {
394       __linker_cannot_link(g_argv[0]);
395     }
396     si->increment_ref_count();
397   }
      ....

403   si->call_pre_init_constructors();
404 
405   /* After the prelink_image, the si->load_bias is initialized.
406    * For so lib, the map->l_addr will be updated in notify_gdb_of_load.
407    * We need to update this value for so exe here. So Unwind_Backtrace
408    * for some arch like x86 could work correctly within so exe.
409    */
410   map->l_addr = si->load_bias;
411   si->call_constructors();
      ....

454   ElfW(Addr) entry = args.getauxval(AT_ENTRY);
455   TRACE("[ Ready to execute \"%s\" @ %p ]", si->get_realpath(), reinterpret_cast<void*>(entry));
456   return entry;
457 }

在__linker_init_post_relocation()函數中,227行調用了__system_properties_init()函數初始化system_properties讀寫相關的數據結構。

240行的LinkerLogger::ResetState()函數會讀取system_propertys參數裏面的的’debug.ld.greylist_disabled’值,用來設置全局變量g_greylist_disabled;在g_greylist_disabled = true的情況下,is_greylisted()函數將會直接返回false,使得dlopen在動態庫加載時的灰名單機制失效。

之後創建可執行文件二進制映射對應的soinfo對象,並加到somain作爲鏈表表頭,然後在339行和340行解析LD_LIBRARY_PATH和LD_PRELOAD環境變量傳入參數。LD_PRELOAD中的動態庫會被添加到linker_main.cpp第110行聲明的g_ld_preload_names字符串向量中;而LD_LIBRARY_PATH則會被插入全局變量g_default_namespaces名空間裏。

358行到372行之間的代碼是將g_ld_preload_names的動態庫列表,以及可執行文件ELF中DT_NEED段指明的依賴庫列表的動態庫文件路徑添加到needed_library_name_list向量中,然後在378行調用find_libraries()函數加載到內存。find_libraries()函數也是dlopen加載動態庫的關鍵核心函數,它要負責解析動態庫ELF文件、映射內存、建立相應個的soinfo對象、檢查權限、以及加載依賴庫等工作。

403行soinfo::call_pre_init_constructors()會調用soinfo中以及解析出來的DT_PREINIT_ARRAY段中函數列表內的函數。411行的soinfo::call_constructors()之前已經描述過。

做完上述動作,可執行文件的加載基本完成,從輔助向量中讀出可執行文件的入口地址,返回給__linker_init()函數,之後linker完成自舉了,移交控制權給可執行程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章