欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创] 细说So动态库的加载流程

[原创] 细说So动态库的加载流程

博客:www.wireghost.cn

细说So动态库的加载流程

dlopen之内存装载

dlopen用来打开一个动态链接库,并将其装入内存。它的定义在Android源码中的路径为/bionic/linker/dlfcn.cpp,执行流程如下:

其核心代码在do_dlopen中实现,根据传入的路径或文件名去查找一个动态库,并执行该动态链接库的初始化代码。

void* dlopen(const char* filename, int flags) {

  ScopedPthreadMutexLocker locker(&gDlMutex);

  soinfo* result = do_dlopen(filename, flags);

  if (result == NULL) {

    __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());

    return NULL;

  }

  return result;

}

soinfo* do_dlopen(const char* name, int flags) {

  if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL)) != 0) {

    DL_ERR("invalid flags to dlopen: %x", flags);

    return NULL;

  }

  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);

  soinfo* si = find_library(name);

  if (si != NULL) {

    si->CallConstructors();

  }

  set_soinfo_pool_protection(PROT_READ);

  return si;

}

再来看find_library这个方法,它会先在solist(已经加载的动态链接库链表)里进行查找,如果找到了就返回对应的soinfo结构体指针。否则,就调用load_library进行加载。然后,调用soinfo_link_image方法,根据soinfo结构体解析相应的Section。

static soinfo *find_loaded_library(const char *name)

{

    soinfo *si;

    const char *bname;

    // TODO: don't use basename only for determining libraries

    // http://code.google.com/p/android/issues/detail?id=6670

    bname = strrchr(name, '/');

    bname = bname ? bname + 1 : name;

    for (si = solist; si != NULL; si = si->next) {

        if (!strcmp(bname, si->name)) {

            return si;

        }

    }

    return NULL;

}

static soinfo* find_library_internal(const char* name) {

  if (name == NULL) {

    return somain;

  }

  soinfo* si = find_loaded_library(name);

  if (si != NULL) {

    if (si->flags & FLAG_LINKED) {

      return si;

    }

    DL_ERR("OOPS: recursive link to \"%s\"", si->name);

    return NULL;

  }

  TRACE("[ '%s' has not been loaded yet.  Locating...]", name);

  si = load_library(name);

  if (si == NULL) {

    return NULL;

  }

  // At this point we know that whatever is loaded @ base is a valid ELF

  // shared library whose segments are properly mapped in.

  TRACE("[ init_library base=0x%08x sz=0x%08x name='%s' ]",

        si->base, si->size, si->name);

  if (!soinfo_link_image(si)) {

    munmap(reinterpret_cast<void*>(si->base), si->size);

    soinfo_free(si);

    return NULL;

  }

  return si;

}

static soinfo* find_library(const char* name) {

  soinfo* si = find_library_internal(name);

  if (si != NULL) {

    si->ref_count++;

  }

  return si;

}

load_library调用open_library打开一个动态链接库,返回一个句柄,将其与共享库所在的路径作为参数,对ElfReader进行初始化。


ElfReader作用域中的Load函数,会执行以下操作:

  1. 读取并校验ELF文件头
  2. 读ELF程序头并映射至内存
  3. 将Load Segment加载进内存
  4. 在内存中找到程序的起始地址
    ``` C++
    bool ElfReader::Load() {
    return ReadElfHeader() &&
      VerifyElfHeader() &&
    
      ReadProgramHeader() &&
    
      ReserveAddressSpace() &&
    
      LoadSegments() &&
    
      FindPhdr();
    
    
    }

bool ElfReader::ReadElfHeader() {
ssize_t rc = TEMP_FAILURERETRY(read(fd, &header, sizeof(header)));
if (rc < 0) {
DLERR("can't read file \"%s\": %s", name, strerror(errno));
return false;
}
if (rc != sizeof(header_)) {
DLERR("\"%s\" is too small to be an ELF executable", name);
return false;
}
return true;
}

<center><font color=#006666 size=3 face="黑体">**读ELF文件头**</font></center>

``` C++

// Loads the program header table from an ELF file into a read-only private

// anonymous mmap-ed block.

bool ElfReader::ReadProgramHeader() {

  phdr_num_ = header_.e_phnum;

  // Like the kernel, we only accept program header tables that

  // are smaller than 64KiB.

  if (phdr_num_ < 1 || phdr_num_ > 65536/sizeof(Elf32_Phdr)) {

    DL_ERR("\"%s\" has invalid e_phnum: %d", name_, phdr_num_);

    return false;

  }

  Elf32_Addr page_min = PAGE_START(header_.e_phoff);  //页的起始地址

  Elf32_Addr page_max = PAGE_END(header_.e_phoff + (phdr_num_ * sizeof(Elf32_Phdr)));  //页的结束地址

  Elf32_Addr page_offset = PAGE_OFFSET(header_.e_phoff);  //程序头部在页中的偏移

  phdr_size_ = page_max - page_min;

  void* mmap_result = mmap(NULL, phdr_size_, PROT_READ, MAP_PRIVATE, fd_, page_min);  //将程序头映射到内存

  if (mmap_result == MAP_FAILED) {

    DL_ERR("\"%s\" phdr mmap failed: %s", name_, strerror(errno));

    return false;

  }

  phdr_mmap_ = mmap_result;

  phdr_table_ = reinterpret_cast<Elf32_Phdr*>(reinterpret_cast<char*>(mmap_result) + page_offset);  //程序头表在内存中的地址

  return true;

}

<center><font color=#006666 size=3 face="黑体">读ELF程序头,并映射到内存</font></center>

// Reserve a virtual address range big enough to hold all loadable

// segments of a program header table. This is done by creating a

// private anonymous mmap() with PROT_NONE.

bool ElfReader::ReserveAddressSpace() {

  Elf32_Addr min_vaddr;

  load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);  //根据页对齐来计算Load段所占用的大小

  if (load_size_ == 0) {

    DL_ERR("\"%s\" has no loadable segments", name_);

    return false;

  }

  uint8_t* addr = reinterpret_cast<uint8_t*>(min_vaddr);

  int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS;  //匿名私有

  void* start = mmap(addr, load_size_, PROT_NONE, mmap_flags, -1, 0);  //调用mmap为动态库分配一块内存空间

  if (start == MAP_FAILED) {

    DL_ERR("couldn't reserve %d bytes of address space for \"%s\"", load_size_, name_);

    return false;

  }

  load_start_ = start;

  load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;  //真实的加载地址与计算出来的(读ELF程序头中的p_vaddr)加载地址之差

  return true;

}

<center><font color=#006666 size=3 face="黑体">调用mmap申请一块足够大的内存空间,为后面进行映射Load段的映射做准备</font></center>

// Map all loadable segments in process' address space.

// This assumes you already called phdr_table_reserve_memory to

// reserve the address space range for the library.

// TODO: assert assumption.

bool ElfReader::LoadSegments() {

  for (size_t i = 0; i < phdr_num_; ++i) {

    const Elf32_Phdr* phdr = &phdr_table_[i];

    if (phdr->p_type != PT_LOAD) {

      continue;

    }

    // Segment addresses in memory.

    Elf32_Addr seg_start = phdr->p_vaddr + load_bias_;

    Elf32_Addr seg_end   = seg_start + phdr->p_memsz;

    Elf32_Addr seg_page_start = PAGE_START(seg_start);

    Elf32_Addr seg_page_end   = PAGE_END(seg_end);

    Elf32_Addr seg_file_end   = seg_start + phdr->p_filesz;

    // File offsets.

    Elf32_Addr file_start = phdr->p_offset;

    Elf32_Addr file_end   = file_start + phdr->p_filesz;

    Elf32_Addr file_page_start = PAGE_START(file_start);

    Elf32_Addr file_length = file_end - file_page_start;

    if (file_length != 0) {

      void* seg_addr = mmap((void*)seg_page_start,          //将Load Segment映射到内存,大小为在ELF文件中所占用的长度

                            file_length,

                            PFLAGS_TO_PROT(phdr->p_flags),

                            MAP_FIXED|MAP_PRIVATE,

                            fd_,

                            file_page_start);

      if (seg_addr == MAP_FAILED) {

        DL_ERR("couldn't map \"%s\" segment %d: %s", name_, i, strerror(errno));

        return false;

      }

    }

    // if the segment is writable, and does not end on a page boundary,

    // zero-fill it until the page limit.

    if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) {

      memset((void*)seg_file_end, 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end));  //如果这块Segment是可写的,且在内存中的结束地址不在页的边界上,则将后面的数据都填充0

    }

    seg_file_end = PAGE_END(seg_file_end);

    // seg_file_end is now the first page address after the file

    // content. If seg_end is larger, we need to zero anything

    // between them. This is done by using a private anonymous

    // map for all extra pages.

    if (seg_page_end > seg_file_end) {

      void* zeromap = mmap((void*)seg_file_end,                 //如果seg_end大于它在文件中的长度,则继续为多出的那部分申请内存空间,并填充0。这里应该是主要针对bss段

                           seg_page_end - seg_file_end,

                           PFLAGS_TO_PROT(phdr->p_flags),

                           MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,

                           -1,

                           0);

      if (zeromap == MAP_FAILED) {

        DL_ERR("couldn't zero fill \"%s\" gap: %s", name_, strerror(errno));

        return false;

      }

    }

  }

  return true;

}

<center><font color=#006666 size=3 face="黑体">将类型为Load的Segment映射到内存</font></center>
接下来,soinfo_alloc方法会为该库在共享库链表中分配一个soinfo节点,并初始化其数据结构。

static soinfo* load_library(const char* name) {

    // Open the file.

    int fd = open_library(name);

    if (fd == -1) {

        DL_ERR("library \"%s\" not found", name);

        return NULL;

    }

    // Read the ELF header and load the segments.

    ElfReader elf_reader(name, fd);

    if (!elf_reader.Load()) {

        return NULL;

    }

    const char* bname = strrchr(name, '/');

    soinfo* si = soinfo_alloc(bname ? bname + 1 : name);

    if (si == NULL) {

        return NULL;

    }

    si->base = elf_reader.load_start();

    si->size = elf_reader.load_size();

    si->load_bias = elf_reader.load_bias();

    si->flags = 0;

    si->entry = 0;

    si->dynamic = NULL;

    si->phnum = elf_reader.phdr_count();

    si->phdr = elf_reader.loaded_phdr();

    return si;

}

static soinfo* soinfo_alloc(const char* name) {

  if (strlen(name) >= SOINFO_NAME_LEN) {

    DL_ERR("library name \"%s\" too long", name);

    return NULL;

  }

  if (!ensure_free_list_non_empty()) {

    DL_ERR("out of memory when loading \"%s\"", name);

    return NULL;

  }

  // Take the head element off the free list.

  soinfo* si = gSoInfoFreeList;

  gSoInfoFreeList = gSoInfoFreeList->next;

  // Initialize the new element.

  memset(si, 0, sizeof(soinfo));

  strlcpy(si->name, name, sizeof(si->name));

  sonext->next = si;

  sonext = si;

  TRACE("name %s: allocated soinfo @ %p", name, si);

  return si;

}

再回过头来看下soinfo_link_image这个方法,它主要实现了动态链接库中section信息的解析:

  1. 先解析dynamic section动态节区,进而实现各个Section的定位;
  2. 获取其他Section的信息;
  3. 待所有section信息解析完毕后,对HASH,STRTAB,SYMTAB节是否正常解析做校验;
  4. 若标志位有FLAG_EXE,则表示当前程序执行的是一个可执行文件。到这里可以确定,linker不仅负责加载so,也负责解析加载一个可执行的ELF文件;
  5. 加载所需要的其他共享库,其中find_library会递归调用这个so_link_image函数,直到某个so库没有DT_NEEDED段;
  6. 完成rel节的重定位;

    最后,CallConstructors函数会根据动态节区中的信息,获取该共享库所依赖的所有so文件名,并在已加载的动态链接库链表中进行查找、递归调用它们的初始化函数。当运行所需的依赖库都初始化完成后,再执行init_func、init_array方法初始化该动态库。。

    void soinfo::CallConstructors() {
    
    if (constructors_called) {
    
     return;
    
    }
    
    // We set constructors_called before actually calling the constructors, otherwise it doesn't
    
    // protect against recursive constructor calls. One simple example of constructor recursion
    
    // is the libc debug malloc, which is implemented in libc_malloc_debug_leak.so:
    
    // 1. The program depends on libc, so libc's constructor is called here.
    
    // 2. The libc constructor calls dlopen() to load libc_malloc_debug_leak.so.
    
    // 3. dlopen() calls the constructors on the newly created
    
    //    soinfo for libc_malloc_debug_leak.so.
    
    // 4. The debug .so depends on libc, so CallConstructors is
    
    //    called again with the libc soinfo. If it doesn't trigger the early-
    
    //    out above, the libc constructor will be called again (recursively!).
    
    constructors_called = true;
    
    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);
    
    }
    
    

loadLibrary之加载调用

Java层通过System.load或System.loadLibrary来加载一个so文件,它的定义在Android源码中的路径为/libcore/luni/src/main/java/java/lang/System.java,执行流程如下:

接下来,让我们具体看下System.loadLibrary这个方法的实现。可以发现它实际是先通过VMStack.getCallingClassLoader()获取到ClassLoader,然后调用运行时的loadLibrary。

/**

 * Loads and links the library with the specified name. The mapping of the

 * specified library name to the full path for loading the library is

 * implementation-dependent.

 *

 * @param libName

 *            the name of the library to load.

 * @throws UnsatisfiedLinkError

 *             if the library can not be loaded.

 */

public void loadLibrary(String libName) {

    loadLibrary(libName, VMStack.getCallingClassLoader());

}

/*

 * Searches for a library, then loads and links it without security checks.

 */

void loadLibrary(String libraryName, ClassLoader loader) {

    if (loader != null) {

        String filename = loader.findLibrary(libraryName);

        if (filename == null) {

            throw new UnsatisfiedLinkError("Couldn't load " + libraryName +

                                           " from loader " + loader +

                                           ": findLibrary returned null");

        }

        String error = doLoad(filename, loader);

        if (error != null) {

            throw new UnsatisfiedLinkError(error);

        }

        return;

    }

    String filename = System.mapLibraryName(libraryName);

    List<String> candidates = new ArrayList<String>();

    String lastError = null;

    for (String directory : mLibPaths) {

        String candidate = directory + filename;

        candidates.add(candidate);

        if (IoUtils.canOpenReadOnly(candidate)) {

            String error = doLoad(candidate, loader);

            if (error == null) {

                return; // We successfully loaded the library. Job done.

            }

            lastError = error;

        }

    }

    if (lastError != null) {

        throw new UnsatisfiedLinkError(lastError);

    }

    throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);

}

以上代码块的主要功能为:

  1. 若ClassLoader非空,则利用ClassLoader的findLibrary方法来获取library的path;
  2. 若ClassLoader为空,则根据传递进来的libraryName,获取到library file的name(比如传递“test”进来,经过System.mapLibraryName方法的调用,返回的会是“libtest.so”)。然后再在一个path list(即下面代码截图中的mLibPaths)中查找到这个library file,并最终确定library 的path;
  3. 调用nativeLoad这个jni方法来load library。

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功