详解Android动态库的加载原理

1、概述

源码版本:android-13.0.0_r41

1.1 Java 加载动态库

Android 应用层加载动态库,只需要执行一行代码即可:

System.load("/data/data/libnative-lib.so");
System.loadLibrary("native-lib");

上面两种方式都是可以加载动态库的,底层还是调用 C++ 加载动态库的函数。其区别:

  • 加载的路径不同:System.load(soPath)是指定动态库的完整路径名(soPath为文件完整路径);而System.loadLibrary(soName)则只会从指定的lib目录下查找,并加上lib前缀和.so后缀(libnative-lib.so);
  • 自动加载库的依赖库不同:System.load(soPath)不会自动加载依赖库;而System.loadLibrary(soName)会自动加载依赖库。

1.2 C++ 加载动态库

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jclass caller_class,
                                  std::string* error_msg)

加载动态库的步骤:

  1. 通过 void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo) 打开;
  2. 再通过 void* sym = library->FindSymbol("JNI_OnLoad", nullptr, android::kJNICallTypeRegular) 查找 "JNI_OnLoad" 函数,对动态库中的函数与Java中的函数关联;
  3. 然后 dlclose(handle) 关闭动态库;

2、动态库加载过程

2.1 System.loadLibrary("native-lib");

@CallerSensitive
public static void loadLibrary(String libname) {
	Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}

2.2 Runtime.loadLibrary0()

 private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {

        String libraryName = libname;
        if (loader != null && !(loader instanceof BootClassLoader)) {
            // 通过动态库名称查找动态库对应的文件路径
            // 这里的loader一般为 PathClassLoader,但是 PathClassLoader 没有重写 findLibrary
            // 方法,直接看它的父类 BaseDexClassLoader
            String filename = loader.findLibrary(libraryName);
            if (filename == null &&
                    (loader.getClass() == PathClassLoader.class ||
                     loader.getClass() == DelegateLastClassLoader.class)) {
                // 如果找不到,则给动态库名加上前缀和后缀,如:“lib” + libraryName + “.so”
                filename = System.mapLibraryName(libraryName);
            }
            // 核心方法
            String error = nativeLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }
        getLibPaths();
        String filename = System.mapLibraryName(libraryName);
        String error = nativeLoad(filename, loader, callerClass);
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
    }

这里分两种情况:

  • loader 为空时,直接调用 nativeLoad 方法,并需要把 callerClass 传入;
  • loader 不为空时,先从 loader 即 BaseDexClassLoader 获取动态库的文件路径,再调用 nativeLoad 方法,且不需要把 callerClass 传入;

2.3 BaseDexClassLoader.findLibrary

    @UnsupportedAppUsage
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
            ClassLoader[] sharedLibraryLoadersAfter,
            boolean isTrusted) {
        super(parent);
    	// dexPath 通常指的是 app 路径,
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
    }

    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }

2.4 DexPathList.findLibrary

public String findLibrary(String libraryName) {
        String fileName = System.mapLibraryName(libraryName);

        for (NativeLibraryElement element : nativeLibraryPathElements) {
            String path = element.findNativeLibrary(fileName);

            if (path != null) {
                return path;
            }
        }

        return null;
    }

nativeLibraryPathElements 中包含了app和系统所有的native动态库。然后遍历查找,遇到匹配的则返回动态库的路径。

2.5 Runtime::Runtime_nativeLoad

JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jclass caller)
{
    return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}

2.6 OpenjdkJvm::JVM_NativeLoad

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jclass caller) {
    ScopedUtfChars filename(env, javaFilename);
    std::string error_msg;
    {
        art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
        bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         caller,
                                         &error_msg);
        if (success) {
            return nullptr;
        }
    }
    return env->NewStringUTF(error_msg.c_str());
}

最终的核心方法,在 vm->LoadNativeLibrary 方法中,在 java_vm_ext.cc 中。

2.7 art::JavaVMExt::LoadNativeLibrary

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jclass caller_class,
                                  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);
  }
  // 判断当前动态库是否已经被加载
  if (library != nullptr) {
    // Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.
    if (library->GetClassLoaderAllocator() != class_loader_allocator) {
      // 如果 classloader 不一样,则加载失败,一个动态库只能被一个classloader加载
      ...
      return false;
    }
    
    if (!library->CheckOnLoadResult()) {
      // 判断之前加载时,执行 JNI_OnLoad 方法的结果
      return false;
    }
    return true;
  }

  // Retrieve the library path from the classloader, if necessary.
  ScopedLocalRef<jstring> library_path(env, GetLibrarySearchPath(env, class_loader));

  Locks::mutator_lock_->AssertNotHeld(self);
  const char* path_str = path.empty() ? nullptr : path.c_str();
  bool needs_native_bridge = false;
  char* nativeloader_error_msg = nullptr;
  // 真正加载动态库的函数,返回一个 handle,被下方 SharedLibrary 持有  
  void* handle = android::OpenNativeLibrary(
      env,
      runtime_->GetTargetSdkVersion(),
      path_str,
      class_loader,
      (caller_location.empty() ? nullptr : caller_location.c_str()),
      library_path.get(),
      &needs_native_bridge,
      &nativeloader_error_msg);

  // 异常检查 
  if (env->ExceptionCheck() == JNI_TRUE) {
    LOG(ERROR) << "Unexpected exception:";
    env->ExceptionDescribe();
    env->ExceptionClear();
  }
  
  bool created_library = false;
  {
    // 创建一个 SharedLibrary,持有上方的 handle ,在 ~SharedLibrary() 函数中,
    // 通过 handle 关闭动态库
    std::unique_ptr<SharedLibrary> new_library(
        new SharedLibrary(env,
                          self,
                          path,
                          handle,
                          needs_native_bridge,
                          class_loader,
                          class_loader_allocator));

    MutexLock mu(self, *Locks::jni_libraries_lock_);
    library = libraries_->Get(path);
    if (library == nullptr) {
      library = new_library.release();
      libraries_->Put(path, library);
      created_library = true;
    }
  }
  
  bool was_successful = false;
  // 查找动态库是否存在 "JNI_OnLoad" 方法
  void* sym = library->FindSymbol("JNI_OnLoad", nullptr, android::kJNICallTypeRegular);
  if (sym == nullptr) {
    // 如果没找到,则没有要绑定的自定义 native 方法,直接返回true
    was_successful = true;
  } else {
    using JNI_OnLoadFn = int(*)(JavaVM*, void*);
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
    // 执行 "JNI_OnLoad" 方法
    int version = (*jni_on_load)(this, nullptr);
	// 判断返回的版本是否满足条件
    if (version == JNI_ERR) {
      StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in "%s"", path.c_str());
    } else if (JavaVMExt::IsBadJniVersion(version)) {
      StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in "%s": %d",
                    path.c_str(), version);
    } else {
      was_successful = true;
    }
  }
  // 设置加载结果,上面的 library->CheckOnLoadResult() 用到
  library->SetResult(was_successful);
  return was_successful;
}

总结一下这里:

  • 判断动态库是否已经被加载;
  • 如果被加载,再判断是否是同一个 classloader,如果不是,则返回false,否则返回true;
  • 如果没被加载,通过 android::OpenNativeLibrary 加载,返回一个 handler;
  • 新建一个 SharedLibrary,保存到 libraries_ 中,SharedLibrary 持有上面的 handler;
  • 通过 FindSymbol 查找 "JNI_OnLoad" 函数,没找到就返回 true,加载动态库成功;
  • 找到了 "JNI_OnLoad" 函数后,执行 "JNI_OnLoad" 函数,将自定义的 native 函数与 java 的 native 函数进行关联,并且返回一个 version ;
  • 判断 version 是否符合条件;
  • 给 SharedLibrary 设置是否加载成功;

2.8 android::OpenNativeLibrary

void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
jobject class_loader, const char* caller_location, jstring library_path,
bool* needs_native_bridge, char** error_msg) {
    if (class_loader == nullptr) {
        if (caller_location != nullptr) {
            if (boot_namespace != nullptr) {
                // 如果 class_loader 为空并且 caller_location 不为空,直接调用 android_dlopen_ext
                void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
                if (handle == nullptr) {
                    *error_msg = strdup(dlerror());
                }
                return handle;
            }
        }
    	// 从系统中查找打开
        void* handle = OpenSystemLibrary(path, RTLD_NOW);
        if (handle == nullptr) {
            *error_msg = strdup(dlerror());
        }
        return handle;
    }
    NativeLoaderNamespace* ns;
    // 通过 classloader 查找其命名空间,没找到就会创建一个
  	if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
        Result<NativeLoaderNamespace*> isolated_ns =
        CreateClassLoaderNamespaceLocked(env,
                                         target_sdk_version,
                                         class_loader,
                                         /*is_shared=*/false,
                                         /*dex_path=*/nullptr,
                                         library_path,
                                         /*permitted_path=*/nullptr,
                                         /*uses_library_list=*/nullptr);
        if (!isolated_ns.ok()) {
      		*error_msg = strdup(isolated_ns.error().message().c_str());
      		return nullptr;
    	} else {
      		ns = *isolated_ns;
    	}
    }
      
    // 通过命名空间打开动态库
    return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
}
2.8.1 OpenSystemLibrary
void* OpenSystemLibrary(const char* path, int flags) {
  android_namespace_t* system_ns = android_get_exported_namespace("system");
  if (system_ns == nullptr) {
    system_ns = android_get_exported_namespace("default");
  }
  const android_dlextinfo dlextinfo = {
      .flags = ANDROID_DLEXT_USE_NAMESPACE,
      .library_namespace = system_ns,
  };
  // 命名空间使用的是系统的或者是默认的,最终还是调用   android_dlopen_ext 
  return android_dlopen_ext(path, flags, &dlextinfo);
2.8.2 OpenNativeLibraryInNamespace
void* OpenNativeLibraryInNamespace(NativeLoaderNamespace* ns, const char* path,
                                   bool* needs_native_bridge, char** error_msg) {
  // 调用的 native_loader_namespace::Load
  auto handle = ns->Load(path);
  return handle.ok() ? *handle : nullptr;
}
2.8.3 native_loader_namespace::Load
Result<void*> NativeLoaderNamespace::Load(const char* lib_name) const {
  	android_dlextinfo extinfo;
    extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
    extinfo.library_namespace = this->ToRawAndroidNamespace();
    // 命名空间使用的是当前 classloader 的命名空间,最终还是调用 android_dlopen_ext 
    void* handle = android_dlopen_ext(lib_name, RTLD_NOW, &extinfo);
    if (handle != nullptr) {
      return handle;
    }
}

这里需要说明一下,Android 7.0 开始,禁止 app 加载非NDK库,也就是说系统禁止了应用去链接系统的私有库(libandroid_runtime.so,libart.so)。为了实现禁止 app 访问私有库,在 NativeLoader 加载动态库时,新增了一种 链接器命名空间 机制。一个命名空间下有若干个动态库的搜索路径,每个命名空间也只能在自己的搜索路径下搜索动态库。当然,一个命名空间可以链接到其他的命名空间并设置共享,也可以访问到其下的搜索路径。

总结下上面的几个小节:最终都会调用到 libdl::android_dlopen_ext ,只不过传入的命名空间 android_dlextinfo 不一样。

2.9 libdl::android_dlopen_ext

// bionic/libdl/libdl.cpp
void* android_dlopen_ext(const char* filename, int flag, const android_dlextinfo* extinfo) {
  const void* caller_addr = __builtin_return_address(0);
  return __loader_android_dlopen_ext(filename, flag, extinfo, caller_addr);
}

// bionic/linker/dlfcn.cpp
void* __loader_android_dlopen_ext(const char* filename,
                           int flags,
                           const android_dlextinfo* extinfo,
                           const void* caller_addr) {
  return dlopen_ext(filename, flags, extinfo, caller_addr);
}

static void* dlopen_ext(const char* filename,
                        int flags,
                        const android_dlextinfo* extinfo,
                        const void* caller_addr) {
  ScopedPthreadMutexLocker locker(&g_dl_mutex);
  g_linker_logger.ResetState();
  // 最终调用到 linker::do_dlopen
  void* result = do_dlopen(filename, flags, extinfo, caller_addr);
  return result;
}

2.10 linker::do_dlopen

void* do_dlopen(const char* name, int flags,
                const android_dlextinfo* extinfo,
                const void* caller_addr) {
  soinfo* const caller = find_containing_library(caller_addr);
  android_namespace_t* ns = get_caller_namespace(caller);

  ProtectedDataGuard guard;
  soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
  loading_trace.End();

  if (si != nullptr) {
    void* handle = si->to_handle();
    si->call_constructors();
    failure_guard.Disable();
    return handle;
  }
  return nullptr;
}

static soinfo* find_library(android_namespace_t* ns,
                            const char* name, int rtld_flags,
                            const android_dlextinfo* extinfo,
                            soinfo* needed_by) {
  soinfo* si = nullptr;
  if (name == nullptr) {
    si = solist_get_somain();
  } else if (!find_libraries(ns,
                             needed_by,
                             &name,
                             1,
                             &si,
                             nullptr,
                             0,
                             rtld_flags,
                             extinfo,
                             false /* add_as_children */)) {
  }
  si->increment_ref_count();

  return si;
}

最终调用到 linker::find_libraries,也是最核心的方法,下面我们来看看。

2.11 linker::find_libraries

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,
                    std::vector<android_namespace_t*>* namespaces) {
  // Step 0: prepare.
  // 准备工作,为需要装载的动态库创建 LoadTask 保存在到 load_tasks
  std::unordered_map<const soinfo*, ElfReader> readers_map;
  LoadTaskList load_tasks;

  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, ns, &readers_map));
  }

  // Step 1: expand the list of load_tasks to include
  // all DT_NEEDED libraries (do not load them just yet)
  for (size_t i = 0; i<load_tasks.size(); ++i) {
    LoadTask* task = load_tasks[i];
    soinfo* needed_by = task->get_needed_by();

	// 在 start_ns 命名空间以及它链接的命名空间下查找动态库以及它的依赖库的信息,
    // 如果没找到,直接返回 false
    // 找到了就 打开动态库 并保存 fd  
    if (!find_library_internal(start_ns, task, &zip_archive_cache, &load_tasks, rtld_flags)) {
      return false;
    }
    soinfo* si = task->get_soinfo();
    if (is_dt_needed) {
      needed_by->add_child(si);
    }
    if (soinfos_count < library_names_count) {
      soinfos[soinfos_count++] = si;
    }
  }

  // Step 2: Load libraries in random order (see b/24047022)
  // 需要装载的库放到 load_list
  LoadTaskList 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);
    }
  }
	
  for (auto&& task : load_list) {
    address_space_params* address_space =
        (reserved_address_recursive || !task->is_dt_needed()) ? &extinfo_params : &default_params;
    // 装载动态库,把动态库 mmap 到内存中,获取虚拟地址空间和一些头部信息、segment大小等
    // 2.13  
    if (!task->load(address_space)) {
      return false;
    }
  }

  // Step 3: pre-link all DT_NEEDED libraries in breadth first order.
  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    // prelink_image() 预链接:通过程序头表获取 dynamic section 的地址,
	// 解析 dynamic section 中各个表的地址以及 size
    // 2.14  
    if (!si->is_linked() && !si->prelink_image()) {
      return false;
    }
    register_soinfo_tls(si);
  }

  // Step 4: Construct the global group. DF_1_GLOBAL bit is force set for LD_PRELOADed libs because
  // they must be added to the global group. Note: The DF_1_GLOBAL bit for a library is normally set
  // in step 3.
  // 构建全局组  
  if (ld_preloads != nullptr) {
    for (auto&& si : *ld_preloads) {
      si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL);
    }
  }

  // Step 5: Collect roots of local_groups.
  // 按照依赖顺序构建动态库链接树
  std::vector<soinfo*> local_group_roots;
  if (start_with != nullptr && add_as_children) {
    local_group_roots.push_back(start_with);
  } else {
    CHECK(soinfos_count == 1);
    local_group_roots.push_back(soinfos[0]);
  }

  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    soinfo* needed_by = task->get_needed_by();
    bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);
    android_namespace_t* needed_by_ns =
        is_dt_needed ? needed_by->get_primary_namespace() : ns;

    if (!si->is_linked() && si->get_primary_namespace() != needed_by_ns) {
      auto it = std::find(local_group_roots.begin(), local_group_roots.end(), si);
      if (it == local_group_roots.end()) {
        local_group_roots.push_back(si);
      }
    }
  }

  // Step 6: Link all local groups
  // 链接动态库
  for (auto root : local_group_roots) {
    soinfo_list_t local_group;
    android_namespace_t* local_group_ns = root->get_primary_namespace();

    walk_dependencies_tree(root,
      [&] (soinfo* si) {
        if (local_group_ns->is_accessible(si)) {
          local_group.push_back(si);
          return kWalkContinue;
        } else {
          return kWalkSkip;
        }
      });

    soinfo_list_t global_group = local_group_ns->get_global_group();
    SymbolLookupList lookup_list(global_group, local_group);
    soinfo* local_group_root = local_group.front();

    bool linked = local_group.visit([&](soinfo* si) {
      if (!si->is_linked() && si->get_primary_namespace() == local_group_ns) {
        const android_dlextinfo* link_extinfo = nullptr;
        if (si == soinfos[0] || reserved_address_recursive) {
          link_extinfo = extinfo;
        }
        lookup_list.set_dt_symbolic_lib(si->has_DT_SYMBOLIC ? si : nullptr);
        // 链接动态库,进行符号解析与重定位
        if (!si->link_image(lookup_list, local_group_root, link_extinfo, &relro_fd_offset) ||
            !get_cfi_shadow()->AfterLoad(si, solist_get_head())) {
          return false;
        }
      }
      return true;
    });
    if (!linked) {
      return false;
    }
  }

  // Step 7: Mark all load_tasks as linked and increment refcounts
  // for references between load_groups (at this point it does not matter if
  // referenced load_groups were loaded by previous dlopen or as part of this
  // one on step 6)
  // 将所有 load_tasks 标记为已链接  
  if (start_with != nullptr && add_as_children) {
    start_with->set_linked();
  }

  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    si->set_linked();
  }
  // 增加 load_groups 间的引用数	
  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    soinfo* needed_by = task->get_needed_by();
    if (needed_by != nullptr &&
        needed_by != start_with &&
        needed_by->get_local_group_root() != si->get_local_group_root()) {
      si->increment_ref_count();
    }
  }
  return true;
}

总结一下这里:

  1. 准备工作,为需要装载的动态库创建 LoadTask 保存在到 load_tasks;
  2. 在 start_ns 命名空间以及它链接的命名空间下查找动态库以及它的依赖库的信息,如果没找到,直接返回 false。找到了就 打开动态库 并保存 fd;
  3. 把 load_tasks mmap 到内存中,读取虚拟地址和一些头部信息,然后存放到 load_list;
  4. prelink_image() 预链接:通过程序头表获取 dynamic section 的地址,解析 dynamic section 中各个表的地址以及 size;
  5. 对预链接的库构建全局组,再按照依赖顺序构建动态库链接树;
  6. 链接动态库,进行符号解析与重定位
  7. 将所有 load_tasks 标记为已链接,并增加 load_groups 间的引用数;

对于链接,这里引用下书里的流程总结(程序员的自我修养):

第一步、空间与地址分配:

扫描所有的输入目标文件,并且获得它们的各个段的长度、 属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一 放到一个全局符号表。这一步中,链接器将能够获得所有输入目标文件的段长度,并且将它 们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。

第二步、符号解析与重定位:

使用上面第一步中收集到的所有信息,读取输入文件中段 的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。事实上第二步是 链接过程的核心,特别是重定位过程。

2.12 linker::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;
  // 从类加载器的命名空间里查找动态库是否有已经被加载,找到则返回 true   2.12.1
  if (find_loaded_library_by_soname(ns, task->get_name(), true /* search_linked_namespaces */,
                                    &candidate)) {
    task->set_soinfo(candidate);
    return true;
  }
  // 从命名空间及其链接的命名空间中加载动态库,
  // 最后一个参数 true 表示需要查找链接的命名空间的 soinfo_list()   2.12.2
  if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags,
                   true /* search_linked_namespaces */)) {
    return true;
  }

  if (ns->is_exempt_list_enabled() && is_exempt_lib(ns, task->get_name(), task->get_needed_by())) {
    ns = &g_default_namespace;
    // 如果动态库在白名单里,则从默认命名空间及其链接的命名空间中加载动态库  
    if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags,
                     true /* search_linked_namespaces */)) {
      return true;
    }
  }

  for (auto& linked_namespace : ns->linked_namespaces()) {
    // 从 ns 里链接的命名空间查找动态库
    if (find_library_in_linked_namespace(linked_namespace, task)) {
      // 动态库是否已加载
      if (task->get_soinfo() != nullptr) {
        return true;
      }
      // 没加载的话则加载到内存中	
      if (load_library(linked_namespace.linked_namespace(), task, zip_archive_cache, load_tasks,
                       rtld_flags, false /* search_linked_namespaces */)) {
        return true;
      }
    }
  }

  return false;
}
2.12.1 linker::find_loaded_library_by_soname
static bool find_loaded_library_by_soname(android_namespace_t* ns,
                                         const char* name,
                                         bool search_linked_namespaces,
                                         soinfo** candidate) {
  *candidate = nullptr;

  // 如果是绝对路径,直接返回 false
  if (strchr(name, '/') != nullptr) {
    return false;
  }
  // 先在当前 命名空间 中查找 
  bool found = find_loaded_library_by_soname(ns, name, candidate);
  // 如果当前的 ns 没找到,且 ns 还有关联的其他 ns
  if (!found && search_linked_namespaces) {
    for (auto& link : ns->linked_namespaces()) {
      // 如果当前的命名空间不允许被访问,则到下一个  
      if (!link.is_accessible(name)) {
        continue;
      }
      android_namespace_t* linked_ns = link.linked_namespace();
	  // 然后在 关联的命名空间 中查找
      if (find_loaded_library_by_soname(linked_ns, name, candidate)) {
        return true;
      }
    }
  }
  return found;
}

static bool find_loaded_library_by_soname(android_namespace_t* ns,
                                          const char* name,
                                          soinfo** candidate) {
  // 在 ns 下查找是否有相同的已加载的动态库  
  return !ns->soinfo_list().visit([&](soinfo* si) {
    if (strcmp(name, si->get_soname()) == 0) {
      *candidate = si;
      return false;
    }
    return true;
  });
}
2.12.2 linker::load_library
static bool load_library(android_namespace_t* ns,
                         LoadTask* task,
                         ZipArchiveCache* zip_archive_cache,
                         LoadTaskList* load_tasks,
                         int rtld_flags,
                         bool search_linked_namespaces) {
  const char* name = task->get_name();
  soinfo* needed_by = task->get_needed_by();
  const android_dlextinfo* extinfo = task->get_extinfo();

  // java 层调用 System.LoadLibrary 加载时,extinfo 不为空  
  if (extinfo != nullptr && (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) != 0) {
    off64_t file_offset = 0;
    if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) {
      file_offset = extinfo->library_fd_offset;
    }
    std::string realpath;
    // 获取真实的文件路径
    if (!realpath_fd(extinfo->library_fd, &realpath)) {
      realpath = name;
    }

    task->set_fd(extinfo->library_fd, false);
    task->set_file_offset(file_offset);
    // 通过重载的 load_library 加载动态库 2.12.4  
    return load_library(ns, task, load_tasks, rtld_flags, realpath, search_linked_namespaces);
  }
  // 在 native 层直接通过 dlopen 或者 android_dlopen_ext 加载, 则 extinfo 为空
  off64_t file_offset;
  std::string realpath;
  // 打开动态库,从磁盘加载到内存中,获取 fd  2.12.3
  int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath);
  if (fd == -1) {
    return false;
  }

  task->set_fd(fd, true);
  task->set_file_offset(file_offset);
  // 通过重载的 load_library 加载动态库 2.12.4	
  return load_library(ns, task, load_tasks, rtld_flags, realpath, search_linked_namespaces);
}
2.12.3 linker::open_library
static int open_library(android_namespace_t* ns,
                        ZipArchiveCache* zip_archive_cache,
                        const char* name, soinfo *needed_by,
                        off64_t* file_offset, std::string* realpath) {
  TRACE("[ opening %s from namespace %s ]", name, ns->get_name());

  // 如果是绝对路径,则直接通过文件路径打开动态库
  if (strchr(name, '/') != nullptr) {
    // 通过 open() 函数打开文件,返回一个文件句柄 fd  
    return open_library_at_path(zip_archive_cache, name, file_offset, realpath);
  }

  // 尝试从命名空间下的 LD_LIBRARY_PATH 动态链接库搜索路径列表中打开
  int fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_ld_library_paths(), realpath);

  // Try the DT_RUNPATH, and verify that the library is accessible.
  if (fd == -1 && needed_by != nullptr) {
    // 尝试从依赖的动态库下的 DT_RUNPATH 路径列表中打开 
    fd = open_library_on_paths(zip_archive_cache, name, file_offset, needed_by->get_dt_runpath(), realpath);
    if (fd != -1 && !ns->is_accessible(*realpath)) {
      close(fd);
      fd = -1;
    }
  }

  // Finally search the namespace's main search path list.
  if (fd == -1) {
    // 尝试从命名空间下的 default_library_paths 默认动态库路径列表中打开  
    fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_default_library_paths(), realpath);
  }

  return fd;
}

概括一下这里的流程:

  1. 如果是绝对路径,直接打开,返回一个文件句柄 fd;
  2. 如果不是绝对路径,先尝试从命名空间下的 LD_LIBRARY_PATH 动态链接库搜索路径列表中打开;
  3. 再尝试从依赖的动态库下的 DT_RUNPATH 路径列表中打开;
  4. 最后尝试从命名空间下的 default_library_paths 默认动态库路径列表中打开;

而打开的操作,最终都是调用 open() 函数打开,返回一个 fd。

2.12.4 linker::load_library 重载
static bool load_library(android_namespace_t* ns,
                         LoadTask* task,
                         LoadTaskList* load_tasks,
                         int rtld_flags,
                         const std::string& realpath,
                         bool search_linked_namespaces) {
  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);
  task->set_soinfo(si);
  // Read the ELF header and some of the segments.
  // 读取 ELF头部和一些段信息  
  if (!task->read(realpath.c_str(), file_stat.st_size)) {
    task->remove_cached_elf_reader();
    task->set_soinfo(nullptr);
    soinfo_free(si);
    return false;
  }

  // 读取 dynamic 的一些信息
  const ElfReader& elf_reader = task->get_elf_reader();
  for (const ElfW(Dyn)* d = elf_reader.dynamic(); d->d_tag != DT_NULL; ++d) {
    if (d->d_tag == DT_RUNPATH) {
      si->set_dt_runpath(elf_reader.get_string(d->d_un.d_val));
    }
    if (d->d_tag == DT_SONAME) {
      si->set_soname(elf_reader.get_string(d->d_un.d_val));
    }
    // We need to identify a DF_1_GLOBAL library early so we can link it to namespaces.
    if (d->d_tag == DT_FLAGS_1) {
      si->set_dt_flags_1(d->d_un.d_val);
    }
  }

  // 检查是否有依赖的动态库,加入到 load_tasks
  for_each_dt_needed(task->get_elf_reader(), [&](const char* name) {
    load_tasks->push_back(LoadTask::create(name, si, ns, task->get_readers_map()));
  });

  return true;
}

总结一下 find_library_internal :

  1. 从命名空间里查找动态库是否有已经被加载(::find_loaded_library_by_soname),找到则返回 true;
  2. 从命名空间及其链接的命名空间中加载动态库(::load_library),加载成功则返回 true;
  3. 判断动态库是否在白名单中,如果在白名单里,则从默认的命名空间及其链接的命名空间中加载动态库;
  4. 从命名空间里所有链接的命名空间中查找动态库(::find_library_in_linked_namespace),没找到则返回 false;
  5. 如果 4 中找到了,判断是否已经加载,已加载直接返回 true;未加载调用 load_library 加载;

2.13 ElfReader::Load

LoadTask::Load 主要是调用 ElfReader::Load 读取 ELF 文件信息,然后保存在 LoadTask 的 soinfo ,我们直接看 ElfReader::Load。

bool ElfReader::Load(address_space_params* address_space) {
  // mmap 到内存  
  bool reserveSuccess = ReserveAddressSpace(address_space);
  // LoadSegments() : 把类型为 PT_LOAD 的 Segment mmap 到内存中 
  // FindPhdr() : 检查头部是否在可加载段内 
  // FindGnuPropertySection() : 检查 PT_GNU_PROPERTY 中 GNU 的一些属性信息  
  if (reserveSuccess && LoadSegments() && FindPhdr() &&
      FindGnuPropertySection()) {
    did_load_ = true;
    if (note_gnu_property_.IsBTICompatible()) {
      // 恢复所有可加载段的原始保护模式  
      did_load_ = (phdr_table_protect_segments(phdr_table_, phdr_num_, load_bias_,
                                               &note_gnu_property_) == 0);
    }
  }
  if (reserveSuccess && !did_load_) {
    // 如果 mmap 成功了但是读取数据失败,则 munmap 解映射  
	munmap(load_start_, load_size_);
  }
  return did_load_;
}

2.14 soinfo::prelink_image

bool soinfo::prelink_image() {
  if (flags_ & FLAG_PRELINKED) return true;
  /* Extract dynamic section */
  ElfW(Word) dynamic_flags = 0;
  // 通过程序头表的地址,获取内存中 ELF 文件 .dynamic(type=PT_DYNAMIC) 部分的地址和大小  
  phdr_table_get_dynamic_section(phdr, phnum, load_bias, &dynamic, &dynamic_flags);

  uint32_t needed_count = 0;
  // 解析 .dynamic 节,获取动态链接相关的信息  
  for (ElfW(Dyn)* d = dynamic; d->d_tag != DT_NULL; ++d) {
    switch (d->d_tag) {
      // .dynstr 节(动态链接字符串表)  
      case DT_STRTAB:
        strtab_ = reinterpret_cast<const char*>(load_bias + d->d_un.d_ptr);
        break;
      // .dynstr 节 size
      case DT_STRSZ:
        strtab_size_ = d->d_un.d_val;
        break;
      // .dynsym节(动态链接符号表)  
      case DT_SYMTAB:
        symtab_ = reinterpret_cast<ElfW(Sym)*>(load_bias + d->d_un.d_ptr);
        break;
      // .dynsym 节 size
      case DT_SYMENT:
        if (d->d_un.d_val != sizeof(ElfW(Sym))) {
          return false;
        }
        break;
      // 初始化函数的地址  
      case DT_INIT:
        init_func_ = reinterpret_cast<linker_ctor_function_t>(load_bias + d->d_un.d_ptr);
        break;
      // .rel.*节(重定位表) 
      case DT_RELA:
        rela_ = reinterpret_cast<ElfW(Rela)*>(load_bias + d->d_un.d_ptr);
        break;
      // .rel.*节 size	
      case DT_RELASZ:
        rela_count_ = d->d_un.d_val / sizeof(ElfW(Rela));
        break;  
      // 依赖的动态库  
      case DT_NEEDED:
        ++needed_count;
        break;
    }
  }
  return true;
}

预链接 DT_NEEDED 中所有未被链接的动态库, 通过程序头表获取 dynamic section 的地址,解析 dynamic section 中各个表的地址以及 size。

2.15 soinfo::link_image

bool soinfo::link_image(const SymbolLookupList& lookup_list, soinfo* local_group_root,
                        const android_dlextinfo* extinfo, size_t* relro_fd_offset) {
  //省略了一些检查代码
  ......  
  // 执行重定位 2.16
  if (!relocate(lookup_list)) {
    return false;
  }

  //省略了一些检查代码
  ......

  // 设置为已链接
  set_image_linked();
  return true;
}

主要看 soinfo::relocate() 函数。

2.16 soinfo::relocate

bool soinfo::relocate(const SymbolLookupList& lookup_list) {
  // 初始化数据
  Relocator relocator(version_tracker, lookup_list);
  relocator.si = this;
  relocator.si_strtab = strtab_;
  relocator.si_strtab_size = has_min_version(1) ? strtab_size_ : SIZE_MAX;
  relocator.si_symtab = symtab_;
  relocator.tlsdesc_args = &tlsdesc_args_;
  relocator.tls_tp_base = __libc_shared_globals()->static_tls_layout.offset_thread_pointer();

  if (android_relocs_ != nullptr) {
    // 这里应该是相对寻址修正
    if (android_relocs_size_ > 3 &&
        android_relocs_[0] == 'A' &&
        android_relocs_[1] == 'P' &&
        android_relocs_[2] == 'S' &&
        android_relocs_[3] == '2') {
      const uint8_t* packed_relocs = android_relocs_ + 4;
      const size_t packed_relocs_size = android_relocs_size_ - 4;
      if (!packed_relocate<RelocMode::Typical>(relocator, sleb128_decoder(packed_relocs, packed_relocs_size))) {
        return false;
      }
    } else {
      return false;
    }
  }

  if (relr_ != nullptr) {
    // 对 SHT_RELR section 进行重定位,没查找到这个节的相关信息,可能是新出的  
    if (!relocate_relr()) {
      return false;
    }
  }

#if defined(USE_RELA) // 使用 rela
  if (rela_ != nullptr) {
    if (!plain_relocate<RelocMode::Typical>(relocator, rela_, rela_count_)) {
      return false;
    }
  }
  if (plt_rela_ != nullptr) {
    if (!plain_relocate<RelocMode::JumpTable>(relocator, plt_rela_, plt_rela_count_)) {
      return false;
    }
  }
#else // 使用 rel
  if (rel_ != nullptr) {
    if (!plain_relocate<RelocMode::Typical>(relocator, rel_, rel_count_)) {
      return false;
    }
  }
  if (plt_rel_ != nullptr) {
    if (!plain_relocate<RelocMode::JumpTable>(relocator, plt_rel_, plt_rel_count_)) {
      return false;
    }
  }
#endif
  return true;
}

rela 与 rel 的根本区别,就是 rela 比 rel 多存储了一个 r_addend 值,r_addend 在相对重定位中会被用到。

  • rel : 每次都会读取 r_addend 的值,需要一次额外的访问内存操作;
  • rela:会存放 r_addend 值,不需要额外的访问内存操作,但是需要耗费额外的空间来存储 r_addend,本质上是空间换时间。现在大部分的系统都是使用 rela 的方式;

这里调用两次 plain_relocate 函数,分别对 .rel.plt 节区和 .rel.dyn 节区中指向的重定位数据进行修正。 plain_relocate 就是具体的重定位函数,跟一下,发现核心是 linker_relocate::process_relocation_impl() 函数。

2.17 linker_relocate::process_relocation_impl

template <RelocMode Mode>
__attribute__((always_inline))
static bool process_relocation_impl(Relocator& relocator, const rel_t& reloc) {
  constexpr bool IsGeneral = Mode == RelocMode::General;
  // reloc.r_offset 为对应的需要重定位数据的实际地址(.got 表项的地址)
  // relocator.si->load_bias 为模块实际加载的基地址
  void* const rel_target = reinterpret_cast<void*>(reloc.r_offset + relocator.si->load_bias);
  // 重定位的类型
  const uint32_t r_type = ELFW(R_TYPE)(reloc.r_info);
  // 重定位数据的符号表索引  
  const uint32_t r_sym = ELFW(R_SYM)(reloc.r_info);

  // 对应符号实际内存地址
  ElfW(Addr) sym_addr = 0;
    
  // 利用 r_sym 符号表索引从 .symtab 中获取对应的表项,
  // 并利用表项的 st_name 字段在 .dynstr 中找到对应的重定位符号名
  if (r_sym != 0) {
    sym_name = relocator.get_string(relocator.si_symtab[r_sym].st_name);
  }
#if defined(USE_RELA) // 使用的 rela 
  auto get_addend_rel   = [&]() -> ElfW(Addr) { return reloc.r_addend; };
  auto get_addend_norel = [&]() -> ElfW(Addr) { return reloc.r_addend; };
#else // 使用的 rel
  auto get_addend_rel   = [&]() -> ElfW(Addr) { return *static_cast<ElfW(Addr)*>(rel_target); };
  auto get_addend_norel = [&]() -> ElfW(Addr) { return 0; };
#endif
  if (IsGeneral && is_tls_reloc(r_type)) {
    ......
  } else {
      // lookup_symbol 从 relocator 中 SymbolLookupList 里查找符号的地址
      if (!lookup_symbol<IsGeneral>(relocator, r_sym, sym_name, &found_in, &sym)) return false;
      if (sym != nullptr) {
        // 计算符号地址 Elfw(sym) 加上模块基地址  
        sym_addr = found_in->resolve_symbol_address(sym);
      }
  }
  if constexpr (IsGeneral || Mode == RelocMode::JumpTable) {
    // R_GENERIC_JUMP_SLOT 是外部函数引用的重定位类型
    if (r_type == R_GENERIC_JUMP_SLOT) {
      count_relocation_if<IsGeneral>(kRelocAbsolute);
      // get_addend_norel() 返回的为 r_addend 值
      // result 的值就是需要重定位的数据修正的值  
      const ElfW(Addr) result = sym_addr + get_addend_norel();
      *static_cast<ElfW(Addr)*>(rel_target) = result;
      return true;
    }
  }
  ...... // 省略一些判断
  return true;
}

lookup_symbol 实现符号查找,跟到最后,最终的实现函数是 linker_soinfo::soinfo_do_lookup_impl。linker_soinfo::soinfo_do_lookup_impl 的主要步骤:

  • 先查找 SymbolLookupList 中的库;
  • 再查找 SymbolLookupList 中库的依赖库;
  • 在库中查找时,如果有 hash 表,则查询 hash 表;没有则直接查询符号名称;

3、总结与收获

回顾整个流程,简单的总结下:

  1. 先通过相关的命名空间查找动态库;
  2. 找到后打开动态库;
  3. 把动态库加载到内存中,返回 handler;
  4. 构建SharedLibrary,存有 3 中的 handler,然后保存到 libraries_ 中;
  5. 检查是否有 JNI_OnLoad 函数,有就执行;
  6. 4 中 SharedLibrary 析构的时候,调用 dlclose(handle) 关闭动态库;

收获:

  • 对动态库加载流程有了整体的认知;
  • 对于 ELF 文件结构,认识了一些常见的表和节区;
  • 提升了阅读 netive 层的代码能力;

猜你喜欢

转载自blog.csdn.net/qq_39312146/article/details/134793829
今日推荐