天天看點

某些error page不加載_細說So動态庫的加載流程

某些error page不加載_細說So動态庫的加載流程

本文為看雪論壇優秀文章

看雪論壇作者ID:sossai

dlopen之記憶體裝載 dlopen用來打開一個動态連結庫,并将其裝入記憶體。 它的定義在Android源碼中的路徑為/bionic/linker/dlfcn.cpp,執行流程如下:

某些error page不加載_細說So動态庫的加載流程

其核心代碼在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進行初始化。

某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程

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

006666 size=3 >**讀ELF檔案頭**

``` 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(reinterpret_cast<char*>(mmap_result) + page_offset); //程式頭表在記憶體中的位址return true; }

讀ELF程式頭,并映射到記憶體

// 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; }

調用mmap申請一塊足夠大的記憶體空間,為後面進行映射Load段的映射做準備

// 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; }

将類型為Load的Segment映射到記憶體 接下來,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的定位:

某些error page不加載_細說So動态庫的加載流程

2. 擷取其他Section的資訊:

某些error page不加載_細說So動态庫的加載流程

3. 待所有section資訊解析完畢後,對HASH、STRTAB、SYMTAB節是否正常解析做校驗:

某些error page不加載_細說So動态庫的加載流程

4. 若标志位有FLAG_EXE,則表示目前程式執行的是一個可執行檔案。 到這裡可以确定,linker不僅負責加載so,也負責解析加載一個可執行的ELF檔案:

某些error page不加載_細說So動态庫的加載流程

5. 加載所需要的其他共享庫,其中find_library會遞歸調用這個so_link_image函數,直到某個so庫沒有DT_NEEDED段:

某些error page不加載_細說So動态庫的加載流程

6. 完成rel節的重定位:

某些error page不加載_細說So動态庫的加載流程

最後,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,執行流程如下:

某些error page不加載_細說So動态庫的加載流程

接下來,讓我們具體看下System.loadLibrary這個方法的實作。 可以發現它實際是先通過VMStack.getCallingClassLoader()擷取到ClassLoader,然後調用運作時的loadLibrary。

public void loadLibrary(String libName) {     loadLibrary(libName, VMStack.getCallingClassLoader()); }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。   然而,這裡其實又牽扯出了幾個問題: 首先,可用的library path都是哪些? 這實際上也決定了我們的so檔案放在哪些目錄下,才可以真正的被load起來。 其次,在native層的nativeLoad又是如何實作加載的? 下面會對這兩個問題,逐一分析介紹。

>>>>

So的加載路徑

先來看看當傳入的ClassLoader為空的情況(執行System.loadLibrary時并不會發生),那麼就需要關注下mLibPaths的指派,相應代碼如下:

private static final Runtime mRuntime = new Runtime();private final String[] mLibPaths = initLibPaths();private static String[] initLibPaths() {String javaLibraryPath = System.getProperty("java.library.path");if (javaLibraryPath == null) {return EmptyArray.STRING;     }String[] paths = javaLibraryPath.split(":");// Add a '/' to the end of each directory so we don't have to do it every time.for (int i = 0; i < paths.length; ++i) {if (!paths[i].endsWith("/")) {             paths[i] += "/";         }     }return paths; }

這裡library path list實際上讀取自一個system property,直接到System.java下檢視初始化代碼,它其實是LD_LIBRARY_PATH環境變量的值,具體内容可以檢視注釋,為"/vendor/lib:/system/lib"。

某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程

然後再來看下傳入的ClassLoader非空的情況,也就是ClassLoader的findLibrary的執行過程。

protected String findLibrary(String libName) {return null; }

結果發現竟然是一個空函數,而ClassLoader本身也隻是個抽象類,那系統中實際運作的ClassLoader是哪個呢? 這裡可以寫個小程式,将實際運作的ClassLoader輸出:

某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程

于是,得知android系統中ClassLoader真正的實作在dalvik.system.PathClassLoader。 此外,在這條日志中,還順帶将PathClassLoader初始化的參數一同列印了出來。 其中,libraryPath為"/data/app-lib/elf.xuexi-1".. 不過PathClassLoader隻是繼承 BaseDexClassLoader,并沒有實際内容。

某些error page不加載_細說So動态庫的加載流程

繼續到BaseDexClassLoader下看findLibrary的實作。

某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程

可以看到,這裡又是在調用DexPathList類下的findLibrary。 關注splitLibraryPath方法,它傳回了需要加載的動态庫所在目錄。

某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程

這裡簡單說下splitLibraryPath方法的作用,它是根據傳進來的libraryPath和system property中"java.library.path"的屬性值即“/vendor/lib:/system/lib”來構造出要加載的動态庫所在目錄清單。

private static File[] splitLibraryPath(String path) {// Native libraries may exist in both the system and// application library paths, and we use this search order://// 1. this class loader's library path for application libraries// 2. the VM's library path from the system property for system libraries//// This order was reversed prior to Gingerbread; see http://b/2933456.     ArrayList result = splitPaths(path, System.getProperty("java.library.path"), true);return result.toArray(new File[result.size()]); }private static ArrayList splitPaths(String path1, String path2,boolean wantDirectories) {     ArrayList result = new ArrayList();     splitAndAdd(path1, wantDirectories, result);     splitAndAdd(path2, wantDirectories, result);return result; }private static void splitAndAdd(String searchPath, boolean directoriesOnly,         ArrayList resultList) {if (searchPath == null) {return;     }for (String path : searchPath.split(":")) {try {             StructStat sb = Libcore.os.stat(path);if (!directoriesOnly || S_ISDIR(sb.st_mode)) {                 resultList.add(new File(path));             }         } catch (ErrnoException ignored) {         }     } }

現在可以對動态連結庫的加載路徑做個總結了,系統預設的目錄為"/vendor/lib"和"/system/lib"。 當使用System.loadLibrary或System.load來加載一個共享庫的時候,會将VM棧中的ClassLoader傳入。 之後調用findLibrary方法,在兩個目錄中去尋找指定的so檔案: 一個是構造ClassLoader時,傳進來的那個libraryPath; 另一個則是system property中"java.library.path"的屬性值。 也就是說,實際上是會在如下的3個目錄中進行查找: 1. "/vendor/lib" 2. "/system/lib" 3. "/data/app-lib/包名-n"   對于"/data/app-lib/包名-n"這個路徑,大家可能會比較陌生,但應該都知道"/data/data/包名/lib"目錄,這裡就簡單講解下apk安裝過程中的一點細節,以說明二者之間的關系(在Android源碼中的路徑為"/frameworks/native/cmds/installd/commands.c")

int install(const char *pkgname, uid_t uid, gid_t gid, const char *seinfo){char pkgdir[PKG_PATH_MAX];char libsymlink[PKG_PATH_MAX];char applibdir[PKG_PATH_MAX];struct stat libStat;if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) {         ALOGE("invalid uid/gid: %d %d\n", uid, gid);return -1;     }if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) { //建立包路徑,"/data/data/包名"         ALOGE("cannot create package path\n");return -1;     }if (create_pkg_path(libsymlink, pkgname, PKG_LIB_POSTFIX, 0)) { //建立庫路徑,"/data/data/包名/lib"         ALOGE("cannot create package lib symlink origin path\n");return -1;     }if (create_pkg_path_in_dir(applibdir, &android_app_lib_dir, pkgname, PKG_DIR_POSTFIX)) { //建立"/data/app-lib/包名"目錄         ALOGE("cannot create package lib symlink dest path\n");return -1;     }if (mkdir(pkgdir, 0751) < 0) {         ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));return -1;     }if (chmod(pkgdir, 0751) < 0) {         ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));         unlink(pkgdir);return -1;     }if (lstat(libsymlink, &libStat) < 0) {if (errno != ENOENT) {             ALOGE("couldn't stat lib dir: %s\n", strerror(errno));return -1;         }     } else {if (S_ISDIR(libStat.st_mode)) {if (delete_dir_contents(libsymlink, 1, 0) < 0) {                 ALOGE("couldn't delete lib directory during install for: %s", libsymlink);return -1;             }         } else if (S_ISLNK(libStat.st_mode)) {if (unlink(libsymlink) < 0) {                 ALOGE("couldn't unlink lib directory during install for: %s", libsymlink);return -1;             }         }     }if (symlink(applibdir, libsymlink) < 0) {         ALOGE("couldn't symlink directory '%s' -> '%s': %s\n", libsymlink, applibdir,                 strerror(errno));         unlink(pkgdir);return -1;     }if (selinux_android_setfilecon2(pkgdir, pkgname, seinfo, uid) < 0) {         ALOGE("cannot setfilecon dir '%s': %s\n", pkgdir, strerror(errno));         unlink(libsymlink);         unlink(pkgdir);return -errno;     }if (chown(pkgdir, uid, gid) < 0) {         ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));         unlink(libsymlink);         unlink(pkgdir);return -1;     }return 0; }

以上代碼會先構造幾個目錄名: pkgdir為"/data/data/包名",libsymlink為"/data/data/包名/lib",applibdir為"/data/app-lib/包名"。 然後建立相應目錄,并賦權限。 之後,建立"/data/data/包名/lib"指向"/data/app-lib/包名"的符号連結。 現在再回過頭來說明下"/data/app-lib/包名-n"、"/data/data/包名/lib"這二者之間的關系。 在"/data/data/包名/"目錄下執行ls –l指令,就會發現lib是一個連結,So其實是放在"/data/app-lib/包名-n"路徑下的。

某些error page不加載_細說So動态庫的加載流程

>>>>

Native層的加載實作

doLoad實際上是調用本地的nativeLoad方法,nativeLoad會先更新LD_LIBRARY_PATH,然後執行dvmLoadNativeCode函數,真正實作so檔案的加載。

static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,     JValue* pResult){     StringObject* fileNameObj = (StringObject*) args[0];     Object* classLoader = (Object*) args[1];     StringObject* ldLibraryPathObj = (StringObject*) args[2];     assert(fileNameObj != NULL);char* fileName = dvmCreateCstrFromString(fileNameObj);if (ldLibraryPathObj != NULL) {char* ldLibraryPath = dvmCreateCstrFromString(ldLibraryPathObj);void* sym = dlsym(RTLD_DEFAULT, "android_update_LD_LIBRARY_PATH");if (sym != NULL) {typedef void (*Fn)(const char*);             Fn android_update_LD_LIBRARY_PATH = reinterpret_cast(sym);             (*android_update_LD_LIBRARY_PATH)(ldLibraryPath);         } else {             ALOGE("android_update_LD_LIBRARY_PATH not found; .so dependencies will not work!");         }free(ldLibraryPath);     }     StringObject* result = NULL;char* reason = NULL;bool 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); }

dvmLoadNativeCode定義在Android源碼中的路徑為/dalvik/vm/Native.cpp,它的主要功能如下: 1. 調用findSharedLibEntry方法,周遊查找已加載的lib。 具體來說,就是先用待加載的lib路徑名計算出一個32位hash值,然後周遊gDvm中的nativeLibs(其結構為HashTable用來儲存加載的本地庫),如果找到則傳回一個SharedLib結構。 這裡如果LIB已被加載,則會對其加載的ClassLoader進行比較,JNI隻允許同一個LIB隻被一個ClassLoader加載。

某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程

2. 調用dlopen打開一個so。

某些error page不加載_細說So動态庫的加載流程

3. 将新加載的LIB插入到gDvm儲存的連結清單中,執行JNI_OnLoad的調用。

某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程

總結 在了解So在記憶體中的加載原理後,可以得知以下幾點:

  • So的加載路徑為:"/vendor/lib"、"/system/lib"、"/data/app-lib/包名-n"。
  • So的入口為init_array、init_func這些初始化函數。這部分在dlopen的過程中就會執行,再之後的是JNI_Onload方法的調用。這裡面可以注冊一些本地方法,也可以繼續做些變量的初始化等操作。
  • 在So的加載流程中,最終會被存放到SharedLib這個結構體中,并添加到nativeLibs這個hash表下。
某些error page不加載_細說So動态庫的加載流程
某些error page不加載_細說So動态庫的加載流程

- End -

某些error page不加載_細說So動态庫的加載流程

看雪ID:sossai

https://bbs.pediy.com/user-540885.htm 

*本文由看雪論壇  sossai  原創,轉載請注明來自看雪社群

推薦文章++++

某些error page不加載_細說So動态庫的加載流程

* 通過捕獲段錯誤實作的自定義linker

* Xposed進階用法 實作Tinker熱修複

* 連連看逆向

* 從“短信劫持馬”的制作來談App安全

* 入門級加強——3種加強方式學習記錄

進階安全圈,不得不讀的一本書

某些error page不加載_細說So動态庫的加載流程

﹀ ﹀ ﹀

某些error page不加載_細說So動态庫的加載流程

公衆号ID:ikanxue 官方微網誌:看雪安全 商務合作:[email protected]

某些error page不加載_細說So動态庫的加載流程