android so加載


  本文分析so加載的步驟,其實在之前dalvik淺析二中也有提及,但那重點關注的是jni。android中so庫的加載,代碼如下:

loadLibrary("nanosleep");   

  我們來看下它的執行流程吧:

  先調用dlopen來載入so文件;find_library在soinfo結構(進程加載的so鏈)中查找當前so是否已載入,否則去執行so載入流程。so載入后,find_library會返回soinfo,去執行so的CallConstructors函數;如果so包含init、init_array段,則此函數會先執行這init和init_array。dlopen函數執行完畢表示系統對so操作告一段落,接着通過dlsym獲取地址去執行JNI_LOAD。對一般而言so的加載和執行到此為止了,下圖是我們通常會關注到的執行流程:

 

  普通而言就這樣了,但這並不能滿足我們的求知欲。下面以android4.4的linker代碼來分析下后續的流程:

  上面說到通過find_library函數來判斷so是否已加載,而在該函數中實質是調用find_library_internal函數去實現全部功能的。來看下find_library_internal的執行流程,

     在find_loaded_library中通過soinfo結構提取出so的name比較來進行判別so是否已加載。load_library函數分三步執行:1.打開so文件得到文件描述符;2.ElfReader(linker_phdr.h)結構體去加載(mmap)elfheader、elf_phdr、elf_segment;3.將ElfReader解析到的elf信息填充到soinfo結構體中。這里我們先看下load函數,究竟是load了哪些東東

bool ElfReader::Load(const android_dlextinfo* extinfo) {
  return ReadElfHeader() &&                    //讀取elf頭到rc
         VerifyElfHeader() &&                  //驗證rc是否為elf頭,主要是驗證magic、type、version
         ReadProgramHeader() &&                //mmap映射segment 頭
         ReserveAddressSpace(extinfo) &&       //
         LoadSegments() &&                     //映射load segment:代碼段和數據段
         FindPhdr();                           //判斷是否有PT_PHDR segment
}

  當然這里soinfo_link_image是重頭戲:

     phdr_table_get_dynamic_section函數得到.dynamic節區(注意這里是根據segment類型為PT_DYNAMIC來得到.dynamic section),再通過.dynamic section解析出其他section;關於如何定位.dynamic節區和解析其他節區請參考elf文件格式(強烈建議在看本文之前觀看)。不知各位看官注意到沒有,對於載入so來說,只需segment就可以開工了(elf_shdr在這里沒有用)。再來看so加載的重定位操作。

                             

   從rel節中取出elf32_rel並解析其type和elf32_sym,接着根據elf32_sym.name調用soinfo_do_lookup在其他so庫(先前已加載)中找到對應的符號。soinfo_elf_lookup先將name hash並將此值作為hash表的索引得到funIndex,然后判斷elf32_sym[funIndex].name是否相同。沒說清楚,直接看源碼吧

static Elf32_Sym* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name) {
    Elf32_Sym* symtab = si->symtab;
    const char* strtab = si->strtab;

    TRACE_TYPE(LOOKUP, "SEARCH %s in %s@0x%08x %08x %d",
               name, si->name, si->base, hash, hash % si->nbucket);

    for (unsigned n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]) {
        Elf32_Sym* s = symtab + n;
        if (strcmp(strtab + s->st_name, name)) continue;

            /* only concern ourselves with global and weak symbol definitions */
        switch(ELF32_ST_BIND(s->st_info)){
        case STB_GLOBAL:
        case STB_WEAK:
            if (s->st_shndx == SHN_UNDEF) {  //SHN_UNDEF表示為外部調用,不是本so的函數,故還是沒找到真正地址,繼續找
                continue;
            }

            TRACE_TYPE(LOOKUP, "FOUND %s in %s (%08x) %d",
                       name, si->name, s->st_value, s->st_size);
            return s;
        }
    }

    return NULL;
}

 

   ok,從其他共享庫中找到了對應的elf32_sym立刻保存其函數地址elf32_sym.st_value為sys_addr。然后根據type,將sys_addr處理后賦值給elf32_rel.r_offset所指向的值(這一步就是修改.got的值,.got保存着so外部調用符號的地址,類似於PE文件的IAT)。以上就是重定位的全部流程,就是幫外部調用找到其執行位置。

 

    find_library就這樣啦,來看看CallConstructors函數

void soinfo::CallConstructors() {
  if (constructors_called) {
    return;
  }

  if ((flags & FLAG_EXE) == 0 && preinit_array != NULL) {
    // The GNU dynamic linker silently ignores these, but we warn the developer.
    PRINT("\"%s\": ignoring %d-entry DT_PREINIT_ARRAY in shared library!",
          name, preinit_array_count);
  }

  if (dynamic != NULL) {
    for (Elf32_Dyn* d = dynamic; d->d_tag != DT_NULL; ++d) {
      if (d->d_tag == DT_NEEDED) {
        const char* library_name = strtab + d->d_un.d_val;
        TRACE("\"%s\": calling constructors in DT_NEEDED \"%s\"", name, library_name);
        find_loaded_library(library_name)->CallConstructors();
      }
    }
  }

  TRACE("\"%s\": calling constructors", name);

  // DT_INIT should be called before DT_INIT_ARRAY if both are present.
  CallFunction("DT_INIT", init_func);
  CallArray("DT_INIT_ARRAY", init_array, init_array_count, false);
}

void soinfo::CallDestructors() {
  TRACE("\"%s\": calling destructors", name);

  // DT_FINI_ARRAY must be parsed in reverse order.
  CallArray("DT_FINI_ARRAY", fini_array, fini_array_count, true);

  // DT_FINI should be called after DT_FINI_ARRAY if both are present.
  CallFunction("DT_FINI", fini_func);
}

 

    CallConstructors里主要執行2個函數CallFunction、CallArray,即執行.init和.init_array節區的函數(可以在寫so函數時指定在.init_array節):

通過__attribute__((constructor(num)))聲明某一函數(num 值越小, 越先執行 ), 即指定了在.init_array 中的位置, 修改其順序即可實現。 例如:void __attribute__((constructor(101))) kingcoming();       ——by ThomasKing

    CallDestructors在卸載so調用,也是執行2個函數與CallConstructors相對應的。值得一提的是constructors_called,它防止so的Constructors被重復調用,當前so有調用其他共享庫的函數,則先執行其他共享庫的Constructors。

 

    dlopen完結了,而dlsym實質就是soinfo_elf_lookup來找到對應的函數。

 

 

參考資料:

  1 android linker 淺析

      2 【原創】內功修煉之路—鏈接深入剖析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM