08-SO加载解析过程
Posted Domefy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了08-SO加载解析过程相关的知识,希望对你有一定的参考价值。
1 JAVA层函数调用关系
本文通过分析Android 4.4版本的源码,了解linker是如何加载并链接SO文件。在阅读本文之前,读者最好阅读有关ELF的文件格式,可以阅读《ELF文件结构学习》深入了解ELF的文件结构。
so加载的全局流程函数关系如下图所示:
Java层的函数调用关系图如下:
我们从下述JAVA层中加载so函数开始作为入口点进行追踪。
System.loadLibrary(“libxxx.so”);
public static void loadLibrary(String libName) Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
该函数位置:libcore\\luni\\src\\main\\java\\java\\lang\\System.java。函数定义如下:
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; ......
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结构体的定义:查看dlopen函数,位置:bionic\\linker\\dlfcn.c。函数定义如下:
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); ;
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); ;
接下来查看do_dlopen函数的定义,其位置:bionic\\linker\\linker.cpp。soinfo结构体的理解结合ELF文件格式学习。
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函数位置:bionic\\linker\\linker.cpp。函数定义如下:do_dlopen调用find_library函数加载并链接so文件,然后返回soinfo指针对象,如果加载链接成功,则调用CallConstructors进行初始化工作,跟”.init_array”节区相关。
static soinfo* find_library(const char* name) soinfo* si = find_library_internal(name); if (si != NULL) si->ref_count++; return si;
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;
接下来,我们继续跟踪分析find_library_internal,其位置:bionic\\linker\\linker.cpp。函数定义如下:
该函数的主要实现加载和链接的过程,是最关键的代码部分,其函数调用关系的全局图如下:
3Native层-加载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属性的segment为writable,因为重定位要修正引用位置 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:扩展
以上是关于08-SO加载解析过程的主要内容,如果未能解决你的问题,请参考以下文章
Cesium源码解析一(terrain文件的加载解析与渲染全过程梳理)