声明:原创文章,转载请备注来源: https://shuwoom.com/?p=351

1 JAVA层函数调用关系

本文通过分析Android 4.4版本的源码,了解linker是如何加载并链接SO文件。在阅读本文之前,读者最好阅读有关ELF的文件格式,可以阅读《ELF文件结构学习》深入了解ELF的文件结构。

so加载的全局流程函数关系如下图所示:

 

Java层的函数调用关系图如下:

 

我们从下述JAVA层中加载so函数开始作为入口点进行追踪。

System.loadLibrary(“libxxx.so”);

该函数位置:libcore\luni\src\main\java\java\lang\System.java。函数定义如下:

public static void loadLibrary(String libName) {

Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());

}

loadLibrary函数内调用了Runtime类里的loadLibrary函数。该函数主要是搜索so库,加载并链接so文件。

该函数位置:libcore\luni\src\main\java\java\lang\Runtime.java。函数关键代码如下:

void loadLibrary(String libraryName, ClassLoader loader) {

        if (loader != null) {

            //1.获取SO路径

            String filename = loader.findLibrary(libraryName);

            if (filename == null) {

                throw new UnsatisfiedLinkError(“Couldn’t load ” + libraryName + ” from loader ” + loader +

                                               “: findLibrary returned null”);

            }

            //2.加载SO文件

            String error = doLoad(filename, loader);

            if (error != null) {

                throw new UnsatisfiedLinkError(error);

            }

            return;

        }

    ……

}

我们接着跟进doLoad函数。该函数位置:libcore\luni\src\main\java\java\lang\Runtime.java。函数定义如下:

private String doLoad(String name, ClassLoader loader) {

        String ldLibraryPath = null;

        if (loader != null && loader instanceof BaseDexClassLoader) {

            ldLibraryPath = ((BaseDexClassLoader) loader).getLdLibraryPath();

        }

                synchronized (this) {

            //调用native函数加载SO

            return nativeLoad(name, loader, ldLibraryPath);

        }

    }

可以看到,doLoad调用了native函数nativeLoad加载SO,下一节我们从该函数继续追踪分析。

 

 

2 Native层函数调用关系

 

 

Native层函数调用关系如上图。

继续上一节的分析,查看Android源码,nativeLoad的函数位置:dalvik\vm\native\java_lang_Runtime.cpp。

其具体函数定义如下:

static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args, JValue* pResult)

{

    StringObject* fileNameObj = (StringObject*) args[0];

    Object* classLoader = (Object*) args[1];

    char* fileName = NULL;

    StringObject* result = NULL;

    char* reason = NULL;

    bool success;

    assert(fileNameObj != NULL);

    fileName = dvmCreateCstrFromString(fileNameObj);

    success = dvmLoadNativeCode(fileName, classLoader, &reason);

    if (!success) {

        const char* msg = (reason != NULL) ? reason : “unknown failure”;

        result = dvmCreateStringFromCstr(msg);

        dvmReleaseTrackedAlloc((Object*) result, NULL);

    }

    free(reason);

    free(fileName);

    RETURN_PTR(result);

}

Dalvik_java_lang_Runtime_nativeLoad调用了dvmLoadNativeCode函数来加载so文件,fileName是so文件的路径。dvmLoadNativeCode函数位置:dalvik\vm\Native.cpp。

关键代码如下:

bool dvmLoadNativeCode(const char* pathName, Object* classLoader, char** detail)

{

   SharedLib* pEntry;

    ……

   //检查so是否已经加载过

   pEntry = findSharedLibEntry(pathName);

    if (pEntry != NULL) {

        if (pEntry->classLoader != classLoader) {

            ALOGW(“Shared lib ‘%s’ already opened by CL %p; can’t open in %p”,

                pathName, pEntry->classLoader, classLoader);

            return false;

        }

        if (verbose) {

            ALOGD(“Shared lib ‘%s’ already loaded in same CL %p”,

                pathName, classLoader);

        }

        if (!checkOnLoadResult(pEntry))

            return false;

        return true;

    }

   …

    //第一次加载so文件

    handle = dlopen(pathName, RTLD_LAZY);

    ……

    /* create a new entry */

    SharedLib* pNewEntry;

    pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));

    pNewEntry->pathName = strdup(pathName);

    pNewEntry->handle = handle;

    pNewEntry->classLoader = classLoader;

    dvmInitMutex(&pNewEntry->onLoadLock);

    pthread_cond_init(&pNewEntry->onLoadCond, NULL);

    pNewEntry->onLoadThreadId = self->threadId;

    //添加到lib动态列表中

    SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);

    if (pNewEntry != pActualEntry) {

        LOGI(“WOW: we lost a race to add a shared lib (%s CL=%p)”,

            pathName, classLoader);

        freeSharedLibEntry(pNewEntry);

        return checkOnLoadResult(pActualEntry);

    } else {

        ……

        bool result = false;

        void* vonLoad;

        int version;

         //获取“JNI_OnLoad”函数的符号地址

        vonLoad = dlsym(handle, “JNI_OnLoad”);

       if (vonLoad == NULL) {

            ALOGD(“No JNI_OnLoad found in %s %p, skipping init”, pathName,    classLoader);

            result = true;

        } else {

                //调用JNI_OnLoad函数

                OnLoadFunc func = (OnLoadFunc)vonLoad;

                ……

                version = (*func)(gDvmJni.jniVm, NULL);

        }

      ……

 }

        if (result)

            pNewEntry->onLoadResult = kOnLoadOkay;

        else

            pNewEntry->onLoadResult = kOnLoadFailed;

        pNewEntry->onLoadThreadId = 0;

      ……

}

 

查看dlopen函数,位置:bionic\linker\dlfcn.c。函数定义如下:

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;

}

dlopen调用do_dlopen函数打开so文件并返回soinfo结构指针对象。我们在linker.h文件查看soinfo结构体的定义:

struct soinfo {

 public:

  char name[SOINFO_NAME_LEN];

  const Elf32_Phdr* phdr; //program header table

  size_t phnum; //program header table表项个数

  Elf32_Addr entry;  //程序入口对于可执行文件

  Elf32_Addr base;  

  unsigned size;

 

  uint32_t unused1;  // DO NOT USE, maintained for compatibility.

 

  Elf32_Dyn* dynamic; //dynamic link table

 

  uint32_t unused2; // DO NOT USE, maintained for compatibility

  uint32_t unused3; // DO NOT USE, maintained for compatibility

 

  soinfo* next;

  unsigned flags;

 

  const char* strtab;  //对应”.shstrtab”节区

  Elf32_Sym* symtab;  //对应”.dynsym”节区

 

  //跟hash表相关,参考”.hash”哈希表结构

  size_t nbucket;

  size_t nchain;

  unsigned* bucket;

  unsigned* chain;

 

  unsigned* plt_got;

 

  Elf32_Rel* plt_rel;

  size_t plt_rel_count;

 

  Elf32_Rel* rel;

  size_t rel_count;

 

  linker_function_t* preinit_array;

  size_t preinit_array_count;

 

  linker_function_t* init_array;

  size_t init_array_count;

  linker_function_t* fini_array;

  size_t fini_array_count;

 

  linker_function_t init_func;

  linker_function_t fini_func;

 

  unsigned* ARM_exidx;

  size_t ARM_exidx_count;

 

  size_t ref_count;

  link_map_t link_map;

 

  bool constructors_called;

 

  // When you read a virtual address from the ELF file, add this

  // value to get the corresponding address in the process’ address space.

  Elf32_Addr load_bias;

 

  bool has_text_relocations;

  bool has_DT_SYMBOLIC;

 

  void CallConstructors();

  void CallDestructors();

  void CallPreInitConstructors();

 

 private:

  void CallArray(const char* array_name, linker_function_t* functions, size_t count, bool reverse);

  void CallFunction(const char* function_name, linker_function_t function);

};

soinfo结构体的理解结合ELF文件格式学习。

接下来查看do_dlopen函数的定义,其位置:bionic\linker\linker.cpp。

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;

}

 

do_dlopen调用find_library函数加载并链接so文件,然后返回soinfo指针对象,如果加载链接成功,则调用CallConstructors进行初始化工作,跟”.init_array”节区相关。

find_library函数位置:bionic\linker\linker.cpp。函数定义如下:

static soinfo* find_library(const char* name) {

  soinfo* si = find_library_internal(name);

  if (si != NULL) {

    si->ref_count++;

  }

  return si;

}

 

接下来,我们继续跟踪分析find_library_internal,其位置:bionic\linker\linker.cpp。函数定义如下:

static soinfo* find_library_internal(const char* name) {

  if (name == NULL) {

    return somain;

  }

  //so列表中查找目标so

  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;

  }

  //加载

  si = load_library(name);

  if (si == NULL) {

    return NULL;

  }

  //链接

  if (!soinfo_link_image(si)) {

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

    soinfo_free(si);

    return NULL;

  }

  return si;

}

 

该函数的主要实现加载和链接的过程,是最关键的代码部分,其函数调用关系的全局图如下:

 

 

3 Native层-加载so到内存

首先观察加载过程,load_library函数位置:bionic\linker\linker.cpp。函数定义如下:

static soinfo* load_library(const char* name) {

    // 1.打开so文件

    int fd = open_library(name);

    if (fd == -1) {

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

        return NULL;

    }

//2.读取ELF头文件并加载segment到内存

    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;

}

 

//3.soinfo对象进行赋值

    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;

}

 

3.1 打开so文件

open_library函数位置:bionic\linker\linker.cpp。其函数定义如下:

static int open_library(const char* name) {

  TRACE(“[ opening %s ]”, name);

 

  // If the name contains a slash, we should attempt to open it directly and not search the paths.

  if (strchr(name, ‘/’) != NULL) {

    int fd = TEMP_FAILURE_RETRY(open(name, O_RDONLY | O_CLOEXEC));

    if (fd != -1) {

      return fd;

    }

    // …but nvidia binary blobs (at least) rely on this behavior, so fall through for now.

  }

 

  // Otherwise we try LD_LIBRARY_PATH first, and fall back to the built-in well known paths.

  int fd = open_library_on_path(name, gLdPaths);

  if (fd == -1) {

    fd = open_library_on_path(name, gSoPaths);

  }

  return fd;

}

3.2读取ELF头文件并加载segment到内存

ElfReader::load函数位置:bionic\linker\linker_phdr.cpp,其函数定义如下:

bool ElfReader::Load() {

  return ReadElfHeader() &&

         VerifyElfHeader() &&

         ReadProgramHeader() &&

         ReserveAddressSpace() &&

         LoadSegments() &&

         FindPhdr();

}

可见Load函数调用了6个子函数,其过程可以分成3部分,如下图所示:

 

 

(1) 读取ELF头部

ReadElfHeader函数定义如下:

bool ElfReader::ReadElfHeader() {

  ssize_t rc = TEMP_FAILURE_RETRY(read(fd_, &header_, sizeof(header_)));

  if (rc < 0) {

    DL_ERR(“can’t read file \”%s\”: %s”, name_, strerror(errno));

    return false;

  }

  if (rc != sizeof(header_)) {

    DL_ERR(“\”%s\” is too small to be an ELF executable”, name_);

    return false;

  }

  return true;

}

如上,通过IO操作和文件符号读取ELF头部。header_是ElfReader类的成员(查看linker_phdr.h投文件)。

(2) 验证ELF头部

VerifyElfHeader函数定义如下:

bool ElfReader::VerifyElfHeader() {

  //检查magic number是否为”\177ELF”

  if (header_.e_ident[EI_MAG0] != ELFMAG0 ||

      header_.e_ident[EI_MAG1] != ELFMAG1 ||

      header_.e_ident[EI_MAG2] != ELFMAG2 ||

      header_.e_ident[EI_MAG3] != ELFMAG3) {

    DL_ERR(“\”%s\” has bad ELF magic”, name_);

    return false;

  }

  //检查其位数是否为32位

  if (header_.e_ident[EI_CLASS] != ELFCLASS32) {

    DL_ERR(“\”%s\” not 32-bit: %d”, name_, header_.e_ident[EI_CLASS]);

    return false;

  }

  //检查so文件是否是小段字节序

  if (header_.e_ident[EI_DATA] != ELFDATA2LSB) {

    DL_ERR(“\”%s\” not little-endian: %d”, name_, header_.e_ident[EI_DATA]);

    return false;

  }

  //检查so文件是否为共享目标文件

  if (header_.e_type != ET_DYN) {

    DL_ERR(“\”%s\” has unexpected e_type: %d”, name_, header_.e_type);

    return false;

  }

  //检查版本号是否为1

  if (header_.e_version != EV_CURRENT) {

    DL_ERR(“\”%s\” has unexpected e_version: %d”, name_, header_.e_version);

    return false;

  }

  //检查是否是ARM、MIPS或386平台

  if (header_.e_machine !=

#ifdef ANDROID_ARM_LINKER

      EM_ARM

#elif defined(ANDROID_MIPS_LINKER)

      EM_MIPS

#elif defined(ANDROID_X86_LINKER)

      EM_386

#endif

  ) {

    DL_ERR(“\”%s\” has unexpected e_machine: %d”, name_, header_.e_machine);

    return false;

  }

 

  return true;

}

该函数主要用于检查ELF头部某些字段是否合法。

(3) 读取程序头部

ReadProgramHeader函数定义如下:

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;

  }

  //获取program header table的大小范围

  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;

}

该函数主要作用是分配足够大的内存空间用于加载program header table。

(4) 分配内存空间

ReserveAddressSpace函数定义如下:

bool ElfReader::ReserveAddressSpace() {

  Elf32_Addr min_vaddr;

  //获取program header table中所有LOAD属性的segment的大小范围

  load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);

  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;

  //给所有LOAD属性的segment分配足够大的内存空间

  void* start = mmap(addr, load_size_, PROT_NONE, mmap_flags, -1, 0);

  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;

  return true;

}

分配足够大的内存空间用于加载program header table中LOAD属性的segment空间。

(5) 加载segments到内存

LoadSegments函数定义如下:

bool ElfReader::LoadSegments() {

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

    const Elf32_Phdr* phdr = &phdr_table_[i];

    //只映射LOAD属性的segment

    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,

                            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));

    }

 

    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_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;

}

映射program header table中所有LOAD属性的segment到之前分配的内存空间中。

(6) 查找程序头部表

FindPhdr函数定义如下:

bool ElfReader::FindPhdr() {

  const Elf32_Phdr* phdr_limit = phdr_table_ + phdr_num_;

 

  //如果有PT_PHDR属性的segment直接使用

  for (const Elf32_Phdr* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {

    if (phdr->p_type == PT_PHDR) {

      return CheckPhdr(load_bias_ + phdr->p_vaddr);

    }

  }

 

  // Otherwise, check the first loadable segment. If its file offset

  // is 0, it starts with the ELF header, and we can trivially find the

  // loaded program header from it.

  for (const Elf32_Phdr* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {

    if (phdr->p_type == PT_LOAD) {

      if (phdr->p_offset == 0) {

        Elf32_Addr  elf_addr = load_bias_ + phdr->p_vaddr;

        const Elf32_Ehdr* ehdr = (const Elf32_Ehdr*)(void*)elf_addr;

        Elf32_Addr  offset = ehdr->e_phoff;

        return CheckPhdr((Elf32_Addr)ehdr + offset);

      }

      break;

    }

  }

 

  DL_ERR(“can’t find loaded phdr for \”%s\””, name_);

  return false;

}

该函数其实主要是检测Program Header是否在LOAD属性的segment范围内。

继续查看CheckPhdr的函数定义:

bool ElfReader::CheckPhdr(Elf32_Addr loaded) {

  const Elf32_Phdr* phdr_limit = phdr_table_ + phdr_num_;

  Elf32_Addr loaded_end = loaded + (phdr_num_ * sizeof(Elf32_Phdr));

  for (Elf32_Phdr* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {

    if (phdr->p_type != PT_LOAD) {

      continue;

    }

    Elf32_Addr seg_start = phdr->p_vaddr + load_bias_;

    Elf32_Addr seg_end = phdr->p_filesz + seg_start;

    if (seg_start <= loaded && loaded_end <= seg_end) {

      loaded_phdr_ = reinterpret_cast<const Elf32_Phdr*>(loaded);

      return true;

    }

  }

  DL_ERR(“\”%s\” loaded phdr %x not in loadable segment”, name_, loaded);

  return false;

}

至此,加载so的过程就完成了,下面开始进行链接。

4 Native层-链接so文件

soinfo_link_image函数位置:bionic\linker\linker.cpp,其关键代码如下:

static int soinfo_link_image(soinfo *si)

{

    unsigned *d;

    /* “base” might wrap around UINT32_MAX. */

    Elf32_Addr base = si->load_bias;

    const Elf32_Phdr *phdr = si->phdr;

    int phnum = si->phnum;

    int relocating_linker = (si->flags & FLAG_LINKER) != 0;

    soinfo **needed, **pneeded;

    size_t dynamic_count;

    /* We can’t debug anything until the linker is relocated */

    if (!relocating_linker) {

        INFO(“[ %5d linking %s ]\n”, pid, si->name);

        DEBUG(“%5d si->base = 0x%08x si->flags = 0x%08x\n”, pid,

            si->base, si->flags);

    }

    //获取loadable segment中的dynamic section的地址和大小

    phdr_table_get_dynamic_section(phdr, phnum, base, &si->dynamic,

                                   &dynamic_count);

    if (si->dynamic == NULL) {

        if (!relocating_linker) {

            DL_ERR(“missing PT_DYNAMIC?!”);

        }

        goto fail;

    } else {

        if (!relocating_linker) {

            DEBUG(“%5d dynamic = %p\n”, pid, si->dynamic);

        }

    }

#ifdef ANDROID_ARM_LINKER

    (void) phdr_table_get_arm_exidx(phdr, phnum, base,

                                    &si->ARM_exidx, &si->ARM_exidx_count);

#endif

//1.“.dynamic ” section中提取有用新消息

for(d = si->dynamic; *d; d++){

        //dynamic section中的信息赋值到si对象中

        switch(*d++){

       //((unsigned *) (si->base + *d)指向dynamic link table中的数组成员

        case DT_HASH:

            //d_tag=DT_HASH,则p_ptr指向哈希表的地址

            si->nbucket = ((unsigned *) (si->base + *d))[0];

            si->nchain = ((unsigned *) (si->base + *d))[1];

            si->bucket = (unsigned *) (si->base + *d + 8);

            si->chain = (unsigned *) (si->base + *d + 8 + si->nbucket * 4);

            break;

        case DT_STRTAB:

            //对应.strtab section

            si->strtab = (const char *) (si->base + *d);

            break;

        case DT_SYMTAB:

             si->symtab = (Elf32_Sym *) (si->base + *d);

            break;

         …

        }

    }

if(si->flags & FLAG_EXE) {

    //如果是可执行文件

    …

}

   /* dynamic_count is an upper bound for the number of needed libs */

    pneeded = needed = (soinfo**) alloca((1 + dynamic_count) * sizeof(soinfo*));

    //根据symbol link table成员中DT_NEEDED属性,获取当前so依赖的其他库文件并进行加载链接

    for(d = si->dynamic; *d; d += 2) {

        if(d[0] == DT_NEEDED){

            DEBUG(“%5d %s needs %s\n”, pid, si->name, si->strtab + d[1]);

            soinfo *lsi = find_library(si->strtab + d[1]);

            if(lsi == 0) {

                strlcpy(tmp_err_buf, linker_get_error(), sizeof(tmp_err_buf));

                DL_ERR(“could not load library \”%s\” needed by \”%s\”; caused by %s”,

                       si->strtab + d[1], si->name, tmp_err_buf);

                goto fail;

            }

            *pneeded++ = lsi;

            lsi->refcount++;

        }

    }

   *pneeded = NULL;

    if (si->has_text_relocations) {

    //如果有DT_TEXTREL节区,重新设置LOAD属性的segmentwritable,因为重定位要修正引用位置

        if (phdr_table_unprotect_segments(si->phdr, si->phnum, si->load_bias) < 0) {

            DL_ERR(“can’t unprotect loadable segments for \”%s\”: %s”,

                   si->name, strerror(errno));

            goto fail;

        }

    }

   

  //符号重定位,对应“.rel.plt”

  if(si->plt_rel) {

        DEBUG(“[ %5d relocating %s plt ]\n”, pid, si->name );

        if(soinfo_relocate(si, si->plt_rel, si->plt_rel_count, needed))

            goto fail;

    }

    //符号重定位,对应“.rel.dyn”

    if(si->rel) {

        DEBUG(“[ %5d relocating %s ]\n”, pid, si->name );

        if(soinfo_relocate(si, si->rel, si->rel_count, needed))

            goto fail;

    }

   si->flags |= FLAG_LINKED;

    DEBUG(“[ %5d finished linking %s ]\n”, pid, si->name);

    if (si->has_text_relocations) {

        /* All relocations are done, we can protect our segments back to read-only. */

        if (phdr_table_protect_segments(si->phdr, si->phnum, si->load_bias) < 0) {

            DL_ERR(“can’t protect segments for \”%s\”: %s”,

                   si->name, strerror(errno));

            goto fail;

        }

    }

    /* We can also turn on GNU RELRO protection */

    if (phdr_table_protect_gnu_relro(si->phdr, si->phnum, si->load_bias) < 0) {

        DL_ERR(“can’t enable GNU RELRO protection for \”%s\”: %s”,

               si->name, strerror(errno));

        goto fail;

    }

    /* If this is a SET?ID program, dup /dev/null to opened stdin,

       stdout and stderr to close a security hole described in:

    ftp://ftp.freebsd.org/pub/FreeBSD/CERT/advisories/FreeBSD-SA-02:23.stdio.asc

     */

    if (program_is_setuid) {

        nullify_closed_stdio();

    }

    notify_gdb_of_load(si);

    return 0;

fail:

    ERROR(“failed to link %s\n”, si->name);

    si->flags |= FLAG_ERROR;

    return -1;

}

 

其中phdr_table_get_dynamic_section定义如下:

void phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,

                               int               phdr_count,

                               Elf32_Addr        load_bias,

                               Elf32_Addr**      dynamic,

                               size_t*           dynamic_count)

{

    const Elf32_Phdr* phdr = phdr_table;

    const Elf32_Phdr* phdr_limit = phdr + phdr_count;

    for (phdr = phdr_table; phdr < phdr_limit; phdr++) {

        if (phdr->p_type != PT_DYNAMIC) {

            continue;

        }

        *dynamic = (Elf32_Addr*)(load_bias + phdr->p_vaddr);

        if (dynamic_count) {

            *dynamic_count = (unsigned)(phdr->p_memsz / 8);

        }

        return;

    }

    *dynamic = NULL;

    if (dynamic_count) {

        *dynamic_count = 0;

    }

}

5 Native层-执行JNI_OnLoad函数

再回到执行完find_library函数后,另一个函数操作:si->CallConstructors

 

CallConstructors函数位置:bionic\linker\linker.cpp。函数定义如下:

void soinfo::CallConstructors() {

  if (constructors_called) {

    return;

  }

  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);

  }

  //调用当前so依赖的第三方库的CallConstructors函数

  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);

}

 

该函数最后调用了两个函数,分别是CallFunction和CallArray。

CallFunction定义如下:

void soinfo::CallFunction(const char* function_name UNUSED, linker_function_t function) {

  if (function == NULL || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {

    return;

  }

  function();

  set_soinfo_pool_protection(PROT_READ | PROT_WRITE);

}

 

CallArray定义如下:

void soinfo::CallArray(const char* array_name UNUSED, linker_function_t* functions, size_t count, bool reverse) {

  if (functions == NULL) {

    return;

  }

  int begin = reverse ? (count – 1) : 0;

  int end = reverse ? -1 : count;

  int step = reverse ? -1 : 1;

  for (int i = begin; i != end; i += step) {

    CallFunction(“function”, functions[i]);

  }

}

 

总结

参考《链接器和加载器》P62 TODO:扩展

参考

闲聊阿里加固(一) https://segmentfault.com/a/1190000007549877

打赏

发表评论

电子邮件地址不会被公开。