zoukankan      html  css  js  c++  java
  • 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 【原创】内功修炼之路—链接深入剖析

  • 相关阅读:
    #Leetcode# 90. Subsets II
    Linux——网段的划分,子网掩码,ABC类地址的表示法
    Linux——文件目录管理(结构)
    Caffe入门与应用 by GX
    1、概述
    6、多态性-4、抽象类
    6、多态性-3、虚函数
    第二课2、ROS
    第一课1、ROS
    6、多态性-2、运算符重载
  • 原文地址:https://www.cnblogs.com/vendanner/p/4979177.html
Copyright © 2011-2022 走看看