您好,登錄后才能下訂單哦!
Native庫的裝載過程
我們從一個簡單的NDK Demo開始分析。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method final TextView tv = (TextView) findViewById(R.id.sample_text); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { tv.setText(stringFromJNI()); } }); } /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI(); // Used to load the 'native-lib' library on application startup. // 動態庫的裝載及鏈接 static { System.loadLibrary("native-lib"); } }
Android 鏈接器Linker之前的工作
下面從 System.loadLibrary()
開始分析。
public static void loadLibrary(String libname) { 這里VMStack.getCallingClassLoader()返回應用的類加載器 Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname); }
下面看 loadLibrary0()
synchronized void loadLibrary0(ClassLoader loader, String libname) { if (libname.indexOf((int)File.separatorChar) != -1) { throw new UnsatisfiedLinkError( "Directory separator should not appear in library name: " + libname); } String libraryName = libname; if (loader != null) { // findLibrary()返回庫的全路徑名 String filename = loader.findLibrary(libraryName); if (filename == null) { // It's not necessarily true that the ClassLoader used // System.mapLibraryName, but the default setup does, and it's // misleading to say we didn't find "libMyLibrary.so" when we // actually searched for "liblibMyLibrary.so.so". throw new UnsatisfiedLinkError(loader + " couldn't find \"" + System.mapLibraryName(libraryName) + "\""); } // 裝載動態庫 String error = doLoad(filename, loader); if (error != null) { throw new UnsatisfiedLinkError(error); } return; } ...... }
參數 loader 為Android的應用類加載器,它是 PathClassLoader 類型的對象,繼承自 BaseDexClassLoader 對象,下面看 BaseDexClassLoader 的 findLibrary()
方法。
public String findLibrary(String name) { // 調用DexPathList的findLibrary方法 return pathList.findLibrary(name); }
下面看 DexPathList 的 findLibrary() 方法
public String findLibrary(String libraryName) { // 產生平臺相關的庫名稱這里返回libxxx.so String fileName = System.mapLibraryName(libraryName); for (Element element : nativeLibraryPathElements) { // 查找動態庫返回庫的全路徑名 String path = element.findNativeLibrary(fileName); if (path != null) { return path; } } return null; }
回到 loadLibrary0() ,有了動態庫的全路徑名就可以裝載庫了,下面看 doLoad() 。
private String doLoad(String name, ClassLoader loader) { ...... // 獲取應用類加載器的Native庫搜索路徑 String librarySearchPath = null; if (loader != null && loader instanceof BaseDexClassLoader) { BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader; librarySearchPath = dexClassLoader.getLdLibraryPath(); } // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized // internal natives. synchronized (this) { return nativeLoad(name, loader, librarySearchPath); } }
nativeLoad() 最終調用 LoadNativeLibrary() ,下面直接分析 LoadNativeLibrary() 。
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, jstring library_path, std::string* error_msg) { ...... SharedLibrary* library; Thread* self = Thread::Current(); { // TODO: move the locking (and more of this logic) into Libraries. // 檢查動態庫是否已經裝載過,如果已經裝載過(類加載器也匹配)直接返回。 MutexLock mu(self, *Locks::jni_libraries_lock_); library = libraries_->Get(path); } ...... // 沒有裝載過,裝載鏈接動態庫 // 參數patch_str傳遞的是動態庫的全路徑,之所以還要傳遞搜索路徑,是因為可能包含它的依賴庫 void* handle = android::OpenNativeLibrary(env, runtime_->GetTargetSdkVersion(), path_str, class_loader, library_path); ...... // 查找動態庫中的"JNI_OnLoad"函數 sym = library->FindSymbol("JNI_OnLoad", nullptr); if (sym == nullptr) { VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]"; was_successful = true; } else { // Call JNI_OnLoad. We have to override the current class // loader, which will always be "null" since the stuff at the // top of the stack is around Runtime.loadLibrary(). (See // the comments in the JNI FindClass function.) ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride())); self->SetClassLoaderOverride(class_loader); VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]"; typedef int (*JNI_OnLoadFn)(JavaVM*, void*); JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym); // 調用庫的JNI_OnLoad函數注冊JNI, 本文暫不討論 int version = (*jni_on_load)(this, nullptr); ...... } ...... }
對于JNI注冊,這里暫不討論,下面看 OpenNativeLibrary() 的實現。
void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path, jobject class_loader, jstring library_path) { #if defined(__ANDROID__) UNUSED(target_sdk_version); if (class_loader == nullptr) { return dlopen(path, RTLD_NOW); } std::lock_guard<std::mutex> guard(g_namespaces_mutex); // 找到類加載器映射的命名空間(Android應用類加載器創建時創建) // 關于命名空間的動態鏈接請參考http://jackwish.net/namespace-based-dynamic-linking-chn.html android_namespace_t* ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader); ....... android_dlextinfo extinfo; // 在一個不同的命名空間中裝載 extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE; extinfo.library_namespace = ns; // RILD_NOW表示重定位在dlopen返回前完成,不會延遲到第一次執行(RTLD_LAZY) return android_dlopen_ext(path, RTLD_NOW, &extinfo); ...... }
下面看 android_dlopen_ext()
的實現
void* android_dlopen_ext(const char* filename, int flags, const android_dlextinfo* extinfo) { // __builtin_return_address是編譯器的內建函數,__builtin_return_address(0)表示當前函數的返回地址 void* caller_addr = __builtin_return_address(0); return dlopen_ext(filename, flags, extinfo, caller_addr); }
接下來就Android鏈接器linker的工作了。
Android 鏈接器Linker的裝載工作
下面從 do_dlopen() 開始分析。
void* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo, void* caller_addr) { // caller_addr在libnativeloader.so中 // 查找地址所在的動態庫(采用遍歷查找,可以優化查找) soinfo* const caller = find_containing_library(caller_addr); // ns為調用庫所在命名空間 android_namespace_t* ns = get_caller_namespace(caller); ...... if (extinfo != nullptr) { ...... // extinfo->flags為ANDROID_DLEXT_USE_NAMESPACE if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0) { if (extinfo->library_namespace == nullptr) { DL_ERR("ANDROID_DLEXT_USE_NAMESPACE is set but extinfo->library_namespace is null"); return nullptr; } // 命名空間使用應用自身類加載器-命名空間 ns = extinfo->library_namespace; } } ...... // 在命名空間ns中裝載庫 soinfo* si = find_library(ns, translated_name, flags, extinfo, caller); ...... }
find_library() 當參數translated_name不為空時,直接調用 find_libraries() ,這是裝載鏈接的關鍵函數,下面看它的實現。
static bool find_libraries(android_namespace_t* ns, soinfo* start_with, const char* const library_names[], size_t library_names_count, soinfo* soinfos[], std::vector<soinfo*>* ld_preloads, size_t ld_preloads_count, int rtld_flags, const android_dlextinfo* extinfo, bool add_as_children) { // ns為應用類加載器-命名空間 // 這里start_with為libnativeloader.so的soinfo // library_names為需要裝載的動態庫,不包含依賴庫 // library_names_count需要裝載的動態庫的數量,這里為1 // ld_preloads為nullptr // add_as_children為false ...... // 為需要裝載的動態庫創建LoadTask添加到load_tasks // LoadTask用于管理動態庫的裝載 for (size_t i = 0; i < library_names_count; ++i) { const char* name = library_names[i]; load_tasks.push_back(LoadTask::create(name, start_with, &readers_map)); } // Construct global_group. // 收集命名空間ns中設置了DF_1_GLOBAL(RTLD_GLOBAL:共享庫中的符號可被后續裝載的庫重定位)標志的動態庫 soinfo_list_t global_group = make_global_group(ns); ...... // Step 1: expand the list of load_tasks to include // all DT_NEEDED libraries (do not load them just yet) // load_tasks以廣度優先遍歷的順序存儲動態庫依賴樹 // 例如依賴樹: 1 // / \ // 2 3 // / \ // 4 5 // load_tasks: 1->2->3->4->5 for (size_t i = 0; i<load_tasks.size(); ++i) { LoadTask* task = load_tasks[i]; soinfo* needed_by = task->get_needed_by(); bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children); task->set_extinfo(is_dt_needed ? nullptr : extinfo); task->set_dt_needed(is_dt_needed); // 收集動態庫的信息以及它的依賴庫 if(!find_library_internal(ns, task, &zip_archive_cache, &load_tasks, rtld_flags)) { return false; } soinfo* si = task->get_soinfo(); if (is_dt_needed) { // si添加到needed_by的依賴中 needed_by->add_child(si); } if (si->is_linked()) { // 已經鏈接過的庫增加引用計數 si->increment_ref_count(); } ...... if (soinfos_count < library_names_count) { soinfos[soinfos_count++] = si; } } // Step 2: Load libraries in random order (see b/24047022) LoadTaskList load_list; // 需要裝載的庫放到load_list中 for (auto&& task : load_tasks) { soinfo* si = task->get_soinfo(); auto pred = [&](const LoadTask* t) { return t->get_soinfo() == si; }; if (!si->is_linked() && std::find_if(load_list.begin(), load_list.end(), pred) == load_list.end() ) { load_list.push_back(task); } } // 隨機化load_list中庫的順序 shuffle(&load_list); for (auto&& task : load_list) { // 裝載動態庫 if (!task->load()) { return false; } } // Step 3: pre-link all DT_NEEDED libraries in breadth first order. // 預鏈接load_tasks中沒有鏈接過的庫,見下文 for (auto&& task : load_tasks) { soinfo* si = task->get_soinfo(); if (!si->is_linked() && !si->prelink_image()) { return false; } } // Step 4: Add LD_PRELOADed libraries to the global group for // future runs. There is no need to explicitly add them to // the global group for this run because they are going to // appear in the local group in the correct order. if (ld_preloads != nullptr) { for (auto&& si : *ld_preloads) { si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL); } } // Step 5: link libraries. // 鏈接過程,見下文 soinfo_list_t local_group; // 廣度優先遍歷添加動態庫依賴圖到local_group中 walk_dependencies_tree( (start_with != nullptr && add_as_children) ? &start_with : soinfos, (start_with != nullptr && add_as_children) ? 1 : soinfos_count, [&] (soinfo* si) { local_group.push_back(si); return true; }); // We need to increment ref_count in case // the root of the local group was not linked. bool was_local_group_root_linked = local_group.front()->is_linked(); bool linked = local_group.visit([&](soinfo* si) { if (!si->is_linked()) { if (!si->link_image(global_group, local_group, extinfo)) { return false; } } return true; }); if (linked) { // 設置鏈接標志 local_group.for_each([](soinfo* si) { if (!si->is_linked()) { si->set_linked(); } }); failure_guard.disable(); } ...... }
find_libraries() 中動態庫的裝載可以分為兩部分
下面從 find_library_internal() 開始分析。
static bool find_library_internal(android_namespace_t* ns, LoadTask* task, ZipArchiveCache* zip_archive_cache, LoadTaskList* load_tasks, int rtld_flags) { soinfo* candidate; // 在應用類加載器-命名空間中查找動態庫是否已經裝載過 if (find_loaded_library_by_soname(ns, task->get_name(), &candidate)) { task->set_soinfo(candidate); return true; } // 在默認命名空間中查找動態庫是否已經裝載過 if (ns != &g_default_namespace) { // check public namespace candidate = g_public_namespace.find_if([&](soinfo* si) { return strcmp(task->get_name(), si->get_soname()) == 0; }); ...... } ...... // 裝載庫 if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags)) { return true; } else { // In case we were unable to load the library but there // is a candidate loaded under the same soname but different // sdk level - return it anyways. if (candidate != nullptr) { task->set_soinfo(candidate); return true; } } return false; }
下面分析 load_library()
static bool load_library(android_namespace_t* ns, LoadTask* task, ZipArchiveCache* zip_archive_cache, LoadTaskList* load_tasks, int rtld_flags) { ...... // 打開庫文件返回文件描述符 int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath); if (fd == -1) { DL_ERR("library \"%s\" not found", name); return false; } task->set_fd(fd, true); task->set_file_offset(file_offset); // 裝載庫 return load_library(ns, task, load_tasks, rtld_flags, realpath); }
下面看另一個 load_library() 的實現
static bool load_library(android_namespace_t* ns, LoadTask* task, LoadTaskList* load_tasks, int rtld_flags, const std::string& realpath) { off64_t file_offset = task->get_file_offset(); const char* name = task->get_name(); const android_dlextinfo* extinfo = task->get_extinfo(); ...... // 為動態庫創建soinfo,用于記錄動態鏈接信息等 soinfo* si = soinfo_alloc(ns, realpath.c_str(), &file_stat, file_offset, rtld_flags); if (si == nullptr) { return false; } task->set_soinfo(si); // Read the ELF header and some of the segments. // 讀取ELF文件頭以及一些段信息 if (!task->read(realpath.c_str(), file_stat.st_size)) { soinfo_free(si); task->set_soinfo(nullptr); return false; } ...... // 查找依賴庫,創建LoadTask添加到load_tasks for_each_dt_needed(task->get_elf_reader(), [&](const char* name) { load_tasks->push_back(LoadTask::create(name, si, task->get_readers_map())); }); return true; }
下面分析ELF文件頭以及段信息的讀取過程,也就是LoadTask的 read() ,它直接調用ElfReader的 Read() 方法。
bool ElfReader::Read(const char* name, int fd, off64_t file_offset, off64_t file_size) { CHECK(!did_read_); CHECK(!did_load_); name_ = name; fd_ = fd; file_offset_ = file_offset; file_size_ = file_size; if (ReadElfHeader() && VerifyElfHeader() && ReadProgramHeaders() && ReadSectionHeaders() && ReadDynamicSection()) { did_read_ = true; } return did_read_; } ReadElfHeader() : 讀取ELF文件頭信息 VerifyElfHeader() : 校驗ELF(文件類型等) ReadProgramHeaders() : 根據ELF文件頭信息獲取程序頭表 ReadSectionHeaders() : 根據ELF文件頭信息獲取段頭表 ReadDynamicSection() : 獲取Dynamic Section的信息 裝載動態庫 動態庫的裝載在LoadTask的 load() 中實現。 bool load() { ElfReader& elf_reader = get_elf_reader(); // 映射動態庫的可加載Segment到進程的虛擬地址空間中 if (!elf_reader.Load(extinfo_)) { return false; } // 保存裝載信息 // 動態庫裝載的起始地址 si_->base = elf_reader.load_start(); // 可裝載的Segment大小之和 si_->size = elf_reader.load_size(); si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller()); // 動態庫裝載的期望起始地址,通常si_->load_bias = si_->base si_->load_bias = elf_reader.load_bias(); // 動態庫程序頭表項數 si_->phnum = elf_reader.phdr_count(); // 動態庫程序頭表的地址 si_->phdr = elf_reader.loaded_phdr(); return true; }
在實際的地址計算中,使用si_->load_bias,不使用si_->base。
下面看ElfReader的 Load() 方法
bool ElfReader::Load(const android_dlextinfo* extinfo) { CHECK(did_read_); CHECK(!did_load_); if (ReserveAddressSpace(extinfo) && LoadSegments() && FindPhdr()) { did_load_ = true; } return did_load_; }
ReserveAddressSpace(): 保留虛擬地址空間為動態庫(裝載地址隨機化)
LoadSegments() : 裝載ELF文件中可裝載的Segments
FindPhdr() : 確保程序頭表包含在一個可加載的Segment中
動態庫的裝載已經完成,下面看鏈接過程。
Native庫動態鏈接的過程
預鏈接
下面看 prelink_image()
bool soinfo::prelink_image() { /* Extract dynamic section */ ElfW(Word) dynamic_flags = 0; // 根據程序頭表的地址計算dynamic section的地址 phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags); ...... uint32_t needed_count = 0; // 解析dynamic section獲取動態鏈接信息 for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) { DEBUG("d = %p, d[0](tag) = %p d[1](val) = %p", d, reinterpret_cast<void*>(d->d_tag), reinterpret_cast<void*>(d->d_un.d_val)); switch (d->d_tag) { ...... case DT_STRTAB: // 動態字符串表的地址 strtab_ = reinterpret_cast<const char*>(load_bias + d->d_un.d_ptr); break; case DT_STRSZ: strtab_size_ = d->d_un.d_val; break; case DT_SYMTAB: // 動態符號表的地址 symtab_ = reinterpret_cast<ElfW(Sym)*>(load_bias + d->d_un.d_ptr); break; ...... case DT_JMPREL: // 需重定位的函數表(.rela.plt)的地址 #if defined(USE_RELA) plt_rela_ = reinterpret_cast<ElfW(Rela)*>(load_bias + d->d_un.d_ptr); #else plt_rel_ = reinterpret_cast<ElfW(Rel)*>(load_bias + d->d_un.d_ptr); #endif break; ...... case DT_RELA: // 需重定位的數據表(.rela.dyn)的地址 rela_ = reinterpret_cast<ElfW(Rela)*>(load_bias + d->d_un.d_ptr); break; ...... case DT_NEEDED: // 依賴的動態庫 ++needed_count; break; } } ...... // Sanity checks. // 檢查動態鏈接信息 if (relocating_linker && needed_count != 0) { DL_ERR("linker cannot have DT_NEEDED dependencies on other libraries"); return false; } if (nbucket_ == 0 && gnu_nbucket_ == 0) { DL_ERR("empty/missing DT_HASH/DT_GNU_HASH in \"%s\" " "(new hash type from the future?)", get_realpath()); return false; } if (strtab_ == 0) { DL_ERR("empty/missing DT_STRTAB in \"%s\"", get_realpath()); return false; } if (symtab_ == 0) { DL_ERR("empty/missing DT_SYMTAB in \"%s\"", get_realpath()); return false; } ...... }
鏈接
鏈接主要完成符號重定位工作,下面從 link_image() 開始分析
bool soinfo::link_image(const soinfo_list_t& global_group, const soinfo_list_t& local_group, const android_dlextinfo* extinfo) { ...... #if defined(USE_RELA) // rela_為重定位數據表的地址 if (rela_ != nullptr) { DEBUG("[ relocating %s ]", get_realpath()); // 數據引用重定位 if (!relocate(version_tracker, plain_reloc_iterator(rela_, rela_count_), global_group, local_group)) { return false; } } // plt_rela_為重定位函數表的地址 if (plt_rela_ != nullptr) { DEBUG("[ relocating %s plt ]", get_realpath()); // 函數引用重定位 if (!relocate(version_tracker, plain_reloc_iterator(plt_rela_, plt_rela_count_), global_group, local_group)) { return false; } } #else ...... }
下面以函數引用重定位為例分析 relocate() 方法
template<typename ElfRelIteratorT> bool soinfo::relocate(const VersionTracker& version_tracker, ElfRelIteratorT&& rel_iterator, const soinfo_list_t& global_group, const soinfo_list_t& local_group) { for (size_t idx = 0; rel_iterator.has_next(); ++idx) { const auto rel = rel_iterator.next(); if (rel == nullptr) { return false; } // rel->r_info的低32位 ElfW(Word) type = ELFW(R_TYPE)(rel->r_info); // rel->r_info的高32位 ElfW(Word) sym = ELFW(R_SYM)(rel->r_info); // 重定位地址的存儲位置 ElfW(Addr) reloc = static_cast<ElfW(Addr)>(rel->r_offset + load_bias); ElfW(Addr) sym_addr = 0; const char* sym_name = nullptr; ElfW(Addr) addend = get_addend(rel, reloc); ...... if (sym != 0) { // sym為動態符號表項的索引 // symtab_[sym].st_name為符號在動態字符串表的索引 // sysm_name為需重定位的符號名 sym_name = get_string(symtab_[sym].st_name); const version_info* vi = nullptr; if (!lookup_version_info(version_tracker, sym, sym_name, &vi)) { return false; } // 查找符號返回符號表項的地址 if (!soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)) { return false; } if (s == nullptr) { ...... } else { ...... // 根據符號表項計算符號地址 sym_addr = lsi->resolve_symbol_address(s); ...... } ...... } switch (type) { // ELF64中R_GENERIC_JUMP_SLOT = R_AARCH64_JUMP_SLOT case R_GENERIC_JUMP_SLOT: count_relocation(kRelocAbsolute); MARK(rel->r_offset); TRACE_TYPE(RELO, "RELO JMP_SLOT %16p <- %16p %s\n", reinterpret_cast<void*>(reloc), reinterpret_cast<void*>(sym_addr + addend), sym_name); // 符號地址更新到reloc(GOT表)中 *reinterpret_cast<ElfW(Addr)*>(reloc) = (sym_addr + addend); break; ...... } } return true; }
參考
Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification
總結
以上所述是小編給大家介紹的Android Native庫的加載及動態鏈接的過程,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。