Android Hook - 动态链接器命名空间机制

本文介绍了链接器命名空间机制,结合Android源码进行分析,帮助读者了解动态链接器命名空间(Namespace)机制的原理,从而理解该机制对用户进程加载动态库造成的限制

在文章Android Hook - 动态加载so库中,已经介绍了动态链接器Namespace机制的一些规则,本文将描述实现的具体细节。

一、简述动态链接器Namespace机制

1、Namespace机制的目的

正如链接器命名空间所述,此机制由动态链接器提供,目的是:

  1. 隔离不同链接器命名空间中的动态库,以确保具有相同库名称和不同符号的库 不会发生冲突。
    • 例如,/system/lib[64]/libcutils.so/system/lib[64]/vndk-sp-${VER}/libcutils.so 是两个动态库。这两个库可以有不同的符号。它们会加载到不同的链接器命名空间中,以便框架模块可以依赖于 /system/lib[64]/libcutils.so,而 SP-HAL 动态库则可以依赖于/system/lib[64]/vndk-sp-${VER}/libcutils.so
  2. 一个链接器命名空间可导出某些动态库用于另一个链接器命名空间 。这些导出的动态库可以成为对其他程序公开的应用编程接口,同时在其链接器命名空间中隐藏实现细节。
    • 例如/system/lib[64]/libc.so 是由一个链接器命名空间导出而后又被导入到许多链接器命名空间中的公共库。
    • /system/lib[64]/libc.so 的依赖项(例如 libnetd_client.so)将被加载到 /system/lib[64]/libc.so 所在的命名空间中。其他命名空间将无法访问这些依赖项。这种机制会在提供公共接口的同时封装实现细节。

总的来说,Namespace机制的存在,使得每个命名空间可以引入同名动态库而不产生冲突,另外还提供了命名空间有选择的对外暴露动态库的机制。

二、Namespace机制源码分析

1、动态链接器初始化

Namespace机制的实现需要动态链接器 的支持,动态链接器首先会根据系统配置文件来创建各个系统命名空间,这些命名空间主要负责加载各自的系统动态库。

当然,最终的加载还是由动态链接器完成,但再次之前,动态链接器会根据命名空间的规则,判断是否允许加载对应的动态库。

因此,首先让我们来了解动态链接器创建各个系统命名空间的过程,这个过程在动态链接器初始化时发生。

1.1、namespaces初始化

bionic/linker/linker_main.cpp

c++ 复制代码
//1、动态链接器的入口,由begin.S调用
extern "C" ElfW(Addr) __linker_init(void* raw_args) {
  ...   
  //2、调用__linker_init_post_relocation()
  return __linker_init_post_relocation(args, tmp_linker_so);
  ...
}

static ElfW(Addr) __attribute__((noinline))
__linker_init_post_relocation(KernelArgumentBlock& args, soinfo& tmp_linker_so) {
  ...
  //3、调用linker_main(),exe_to_load是目标进程路径,对于应用进程来说就是app_process(64)
  ElfW(Addr) start_address = linker_main(args, exe_to_load);
  ...
}

static ElfW(Addr) linker_main(KernelArgumentBlock& args, const char* exe_to_load) {
  ...
  //4、初始化默认namespaces
  std::vector<android_namespace_t*> namespaces = init_default_namespaces(exe_info.path.c_str()); 
  ...
}

std::vector<android_namespace_t*> init_default_namespaces(const char* executable_path) {
  ...
  const Config* config = nullptr;

  {
    //5、ld.config.txt文件路径,例如"/linkerconfig/ld.config.txt"
    std::string ld_config_file_path = get_ld_config_file_path(executable_path);
    ...
    //6、读取ld.config.txt中的配置到config中,并且创建配置中的命名空间namespace
    if (!Config::read_binary_config(ld_config_file_path.c_str(), executable_path, g_is_asan, g_is_hwasan,
                                    &config, &error_msg)) {
      ...
      config = nullptr;
    }

    //7、初始化默认namespace
  const NamespaceConfig* default_ns_config = config->default_namespace_config();
  ...
  namespaces[default_ns_config->name()] = &g_default_namespace;
  if (default_ns_config->visible()) {
    //重要!!如果可见,添加到g_exported_namespaces
    g_exported_namespaces[default_ns_config->name()] = &g_default_namespace;
  }
    
  //8、初始化其他namespace
  for (auto& ns_config : namespace_configs) {
    ...
    //构造android_namespace_t实例,设置从配置文件中读取的各种值
    android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t();
    s->set_name(ns_config->name());
    ns->set_isolated(ns_config->isolated());
    ns->set_default_library_paths(ns_config->search_paths());
    ns->set_permitted_paths(ns_config->permitted_paths());
    ns->set_allowed_libs(ns_config->allowed_libs());
    ...
    namespaces[ns_config->name()] = ns;
    if (ns_config->visible()) {
      //重要!!如果可见,添加到g_exported_namespaces
      g_exported_namespaces[ns_config->name()] = ns;
    }
  }

  //9、初始化namespace之间的Link,即委托关系
  for (auto& ns_config : namespace_configs) {
    //9.1、遍历namespace
    auto it_from = namespaces.find(ns_config->name());
    CHECK(it_from != namespaces.end());
    android_namespace_t* namespace_from = it_from->second;    
    for (const NamespaceLinkConfig& ns_link : ns_config->links()) {
      //9.2、根据配置中的link关系,找到要link的namespace
      auto it_to = namespaces.find(ns_link.ns_name());
      CHECK(it_to != namespaces.end());
      android_namespace_t* namespace_to = it_to->second;
      if (ns_link.allow_all_shared_libs()) {
        //9.3、配置允许link所有的动态库
        link_namespaces_all_libs(namespace_from, namespace_to);
      } else {
        //9.4、配置只允许link部分动态库
        link_namespaces(namespace_from, namespace_to, ns_link.shared_libs().c_str());
      }
    }
  }
}

bionic/linker/linker_config.cpp

c++ 复制代码
static constexpr const char* kDefaultConfigName = "default";
static constexpr const char* kPropertyAdditionalNamespaces = "additional.namespaces";

//读取ld.config.txt中的配置到config中,并且创建配置中的命名空间namespace
bool Config::read_binary_config(const char* ld_config_file_path,
                                      const char* binary_realpath,
                                      bool is_asan,
                                      bool is_hwasan,
                                      const Config** config,
                                      std::string* error_msg) {
  g_config.clear();
  ...
  //1、创建default命名空间
  namespace_configs[kDefaultConfigName] = g_config.create_namespace_config(kDefaultConfigName);

  //根据additional.namespaces创建命名空间
  std::vector<std::string> additional_namespaces = properties.get_strings(kPropertyAdditionalNamespaces);
  for (const auto& name : additional_namespaces) {
    namespace_configs[name] = g_config.create_namespace_config(name);
  }
  ...
}

总的来说:

  1. 动态链接器启动时 ,会读取配置ld.config.txt ,并根据配置创建命名空间(namespace)对象android_namespace_t和设置它的各种属性。
  2. 其中default命名空间的默认都需要创建的,其他命名空间则根据配置创建。
  3. 这些命名空间,如果对外可见,则会被添加到**g_exported_namespaces**这个map容器中。
  4. 根据配置建立命名空间(namespace)之间的Link 关系,即如果在当前命名空间中找不到动态库,动态链接器会尝试委托给和它有Link关系的命名空间(后文称为Link命名空间)来加载动态库。

1.2、ld.config.txt格式解析

接下来,具体看一个ld.config.txt文件的例子,从而了解Namespace具有哪些属性/信息。

这些属性在官方文档链接器命名空间都有详细介绍,但配置文件比较复杂,这里直接展示配置文件来说明可能会比较直观。

以下按照文件内容,从上到下的顺序来介绍。

1.2.1、分区
shell 复制代码
# 表示指定目录下的进程,属于哪个分区。
# 这里表示/system/bin、/system/xbin下可执行文件都属于system标签;/vendor/bin则属于vendor标签
dir.system = /system/bin/
dir.system = /system/xbin/
dir.system = /system_ext/bin/
dir.product = /product/bin/
dir.vendor = /odm/bin/
...

即Android操作系统中的几个重要分区和目录,旨在使系统的开发和维护更加模块化和灵活。

  1. 根据配置可以看出,主要的分区有systemproductvendor等。
  2. 一个分区关联可以关联多个目录 。例如dir.system = /system/bin/dir.system = /system/xbin/这意味着这些目录下的进程遵循这个分区对应的命名空间规则 。其中应用进程app_process(64)通常在/system/bin/目录下。
1.2.2、additional.namespaces
shell 复制代码
# 标志system 分区的开始
[system]
# 分区对应的命名空间,其中default是默认的,不需要列出。这里额外列出了com_android_adbd等命名空间
additional.namespaces = com_android_adbd,com_android_adservices,com_android_appsearch,com_android_art,com_android_btservices,com_android_conscrypt,com_android_extservices,com_android_i18n,com_android_media,com_android_neuralnetworks,com_android_os_statsd,com_android_resolv,com_android_runtime,com_android_tethering,com_android_uwb,com_android_virt,product,rs,sphal,vndk,vndk_product
  • [system]标志着sytem分区的开始。
  • 一个分区包含多个命名空间配置 。例如system,除了默认的default命名空间以外,还通过**additional.namespaces**声明了多个命名空间。
  • additional.namespaces记录了该分区下的所有命名空间,其中default是默认的,不需要列出。
1.2.3、namespace.${name}.isolated

接下来开始对命名空间进行逐个配置,例子中是在配置system分区的default分组。

shell 复制代码
# 首先是default这个namespace
# 如果 isolated 为 true,系统只能加载某个 search.paths 目录(不包含子目录)中的动态库或某个 permitted.paths 目录(包含子目录)下的动态库。
# 如果 isolated 为 false(默认值),动态链接器不会检查动态库的路径。
namespace.default.isolated = true
  • namespace.default意味着是在配置default这个命名空间。
  • 如果 isolated 为 true,系统只能加载某个 search.paths 目录(不包含子目录)中的动态库或某个 permitted.paths 目录(包含子目录)下的动态库。
  • 如果 isolated 为 false(默认值),动态链接器不会检查动态库的路径。
1.2.4、namespace.${name}.visible
shell 复制代码
# 这表示 android_get_exported_namespace("default") 可以返回有效的链接器命名空间句柄
namespace.default.visible = true
  • visible为true,表示该命名空间将加入到exported_namespace中,从而能够通过将此句柄传递到 android_dlopen_ext() 来打开链接器命名空间中的共享库。
1.2.5、search.paths和permitted.paths
shell 复制代码
# 如果 isolated 为 true,动态链接器会在search.paths目录中搜索动态库
namespace.default.search.paths = /system/${LIB}
namespace.default.search.paths += /system_ext/${LIB}
# 如果 isolated 为 true,动态链接器会在permitted.paths目录及其子目录中搜索动态库
namespace.default.permitted.paths = /system/${LIB}/drm
namespace.default.permitted.paths += /system/${LIB}/extractors
  • ${LIB}是内置占位符。对于 32 位进程,此占位符将替换为 lib;对于 64 位进程,此占位符将替换为 lib64
  • search.paths非常重要,当isolated为true时,表示动态链接器可以加载**其目录(不包含子目录)**下的动态库。也就是说如果调用dlopen()或者解析动态库DT_NEEDED条目时未指定完整路径,在 search.paths 中指定的目录将附加到请求的库名称前面
  • permitted.pathssearch.paths类似,当isolated为true时生效,但是permitted.paths子目录下的动态库也可以加载 。但是,在动态链接器搜索动态库时,permitted.paths 不会附加到请求的库名称前面
shell 复制代码
...
# 如果某个动态库或可执行文件请求另一个动态库,而后者无法加载到 default 命名空间,动态链接器就会尝试从 com_android_adbd 命名空间加载此动态库。然后,如果此动态库也无法从 com_android_adbd 命名空间加载,动态链接器就会尝试从 com_android_i18n 命名空间加载此动态库,依次类推。
namespace.default.links = com_android_adbd,com_android_i18n,default,com_android_tethering,com_android_art,com_android_resolv,com_android_neuralnetworks,com_android_os_statsd
# 表示com_android_adbd命名空间只接受libadb_pairing_auth.so等动态库的委托。此属性无法和allow_all_shared_libs一起使用。
namespace.default.link.com_android_adbd.shared_libs = libadb_pairing_auth.so:libadb_pairing_connection.so:libadb_pairing_server.so
...
# 配置下一个namespace,即com_android_adbd
namespace.com_android_adbd.isolated = true
....
# 这表示所有库名称都可以遍历从vndk 到 defualt 命名空间的委托加载
namespace.vndk.link.default.allow_all_shared_libs = true
...
# 配置其他namespace
[vendor]
...
[product]
...
[unrestricted]
...
[postinstall]
...
[isolated]
...
  • namespace.default.links。表示命名空间的委托关系,即当前命名空间加载不了的动态库可以委托给links指定的命名空间依次尝试加载 。但是需要注意links没有传递性,即当default委托给com_android_adbd加载失败,com_android_adbd不会委托给和它links的其他命名空间。
  • namespace.default.link.com_android_adbd.shared_libs指定哪些动态库可以委托给该命名空间加载
  • namespace.vndk.link.default.allow_all_shared_libs所有动态库都可以委托给links的命名空间加载

1.3、android_namespace_t数据结构

正如namespaces初始化源码中介绍的一样,每个命名空间回对应一个android_namespace_t结构体。

加下来看看这个结构体成员属性和配置的对应关系:

bionic/linker/linker_namespaces.h

cpp 复制代码
struct android_namespace_link_t {
private:
  //命名空间名
  android_namespace_t* const linked_namespace_;
  //对应namespace.${name1}.link.${name2}.shared_libs
  const std::unordered_set<std::string> shared_lib_sonames_;
  //对应namespace.${name1}.link.${name2}.allow_all_shared_libs
  bool allow_all_shared_libs_;
}

struct android_namespace_t {
  private:
  //命名空间名
  std::string name_;
  //对应isolated属性
  bool is_isolated_;
  bool is_exempt_list_enabled_;
  bool is_also_used_as_anonymous_;
  //特定于命名空间的路径列表,用于告诉加载器在特定路径中查找库,优先级比default_library_paths_高
  std::vector<std::string> ld_library_paths_;
  //对应search.paths
  std::vector<std::string> default_library_paths_;
  //对应permitted.paths
  std::vector<std::string> permitted_paths_;
  //这是一个白名单,列出了当前命名空间中允许加载的共享库。它限制了哪些库可以被该命名空间访问。不同于 permitted_paths_,这个列表直接限制库的名称。
  std::vector<std::string> allowed_libs_;
  //和当前命名空间有Link关系的命名空间,都会生成一个android_namespace_link_t结构
  std::vector<android_namespace_link_t> linked_namespaces_;
  //soinfo 是 Android 链接器中表示已加载共享库的结构体。该列表保存了当前命名空间内所有已加载的共享库信息
  soinfo_list_t soinfo_list_; 
}

typedef LinkedList<soinfo, SoinfoListAllocator> soinfo_list_t;
class SoinfoListAllocator {
  ...
}

struct soinfo {
}

从代码可以看出,数据结构和配置是一致的,但有几个属性例外,需要额外说明:

  • ld_library_paths_。表示特定命名空间对象的路径列表,用于告诉加载器在特定路径中查找库,优先级比default_library_paths_高。
  • allowed_libs_。这是一个白名单,列出了当前命名空间中允许加载的共享库。它限制了哪些库可以被该命名空间访问。不同于 permitted_paths_,这个列表直接限制库的名称。
  • soinfo_list_。soinfo 是 Android 链接器中表示已加载共享库的结构体。该列表保存了当前命名空间内所有已加载的共享库信息

最后namespace.${name}.visible字段,标志着android_namespace_t是否加入g_exported_namespaces中,从而可以被外部找到。

bionic/linker/linker.cpp

c++ 复制代码
static std::unordered_map<std::string, android_namespace_t*> g_exported_namespaces;

// This function finds a namespace exported in ld.config.txt by its name.
// A namespace can be exported by setting .visible property to true.
android_namespace_t* get_exported_namespace(const char* name) {
  if (name == nullptr) {
    return nullptr;
  }
  //1、g_exported_namespaces在Linker初始化的时候,从/linkerconfig/ld.config.txt
  auto it = g_exported_namespaces.find(std::string(name));
  if (it == g_exported_namespaces.end()) {
    return nullptr;
  }
  return it->second;
}

g_exported_namespaces的使用逻辑很简单,就是从一个map 中根据命名空间的名称,找到对应的**android_namespace_t**。

2、libnativeloader

2.1、介绍

根据libnativeloader官方文档中的介绍,Libnativeloader 负责在 Android 运行时(ART)中 加载本机动态库(*. so 文件)。本机动态库可以是应用程序提供的 JNI 库公共本机库,如平台提供的 libc.so

该库最典型的用例是调用System.loadLibrary(name),其调用流程如下:

  1. 当调用该方法时,ART运行时将调用委托给该库 ,以及传入对进行调用的类加载器的引用。
  2. 然后该库找到与给定类加载器关联的链接器命名空间 (通常名称为clns,后跟一个数字以使其唯一),并尝试从该命名空间加载请求的库。
  3. 库的实际搜索、加载和链接由动态链接器执行。

应用命名空间 是在APK加载到进程中时创建的,并与加载APK的类加载器相关联。链接器命名空间被配置为只有嵌入在APK中的JNI库可以从命名空间访问,从而防止APK加载其他APK的JNI库。

公共原生库 (如libc.so)的列表不是静态的。默认库集在AOSP中定义,但合作伙伴可以扩展它以包含他们自己的库。例如system分区的列表在/system/etc/public.libraries.txt中。

下面是public.libraries.txt的一个例子:

shell 复制代码
# See https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md
libandroid.so
libaaudio.so
libamidi.so
libbinder_ndk.so
libc.so
libcamera2ndk.so
libclang_rt.hwasan-aarch64-android.so 64 nopreload
libdl.so
libEGL.so
libGLESv1_CM.so
libGLESv2.so
libGLESv3.so
libicu.so
libicui18n.so
libicuuc.so
libjnigraphics.so
liblog.so
libmediandk.so
libm.so
libnativehelper.so
libnativewindow.so
libneuralnetworks.so nopreload
libOpenMAXAL.so
libOpenSLES.so
libRS.so
libstdc++.so
libsync.so
libvulkan.so
libwebviewchromium_plat_support.so
libz.so

2.2、初始化

接下来介绍一下libnativeloader的初始化过程,在这个过程中可以了解到前面动态链接器初始各种namespace后,这些namespace是怎么被使用的。

art/runtime/jni/java_vm_ext.cc

c++ 复制代码
extern "C" EXPORT jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  ...
  //1、初始化libnativeloader,必须保证在调用JNI前加载
  android::InitializeNativeLoader();
}

art/libnativeloader/native_loader.cpp

c++ 复制代码
LibraryNamespaces* g_namespaces GUARDED_BY(g_namespaces_mutex) = new LibraryNamespaces;

void InitializeNativeLoader() {
#if defined(ART_TARGET_ANDROID)
  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  //1、调用LibraryNamespaces的初始化方法,初始化只做一次
  g_namespaces->Initialize();
#endif
}

art/libnativeloader/library_namespaces.cpp

c++ 复制代码
void LibraryNamespaces::Initialize() {  
  //1、只初始化一次
  if (initialized_) {
    return;
  }
 
  //2、从public.libraries.txt中读取需要预加载的动态库
  for (const std::string& soname : android::base::Split(preloadable_public_libraries(), ":")) {
    //3、加载需要预加载的公共动态库。由于libnativeloader在com_android_art这个命名空间中,使用OpenSystemLibrary加载而不是dlopen,以保证这些库加载到system命名空间
    void* handle = OpenSystemLibrary(soname.c_str(), RTLD_NOW | RTLD_NODELETE);
    ...
  }
}

//预加载动态库
const std::string& preloadable_public_libraries() { 
  static std::string list = InitDefaultPublicLibraries(/*for_preload*/ true);
  return list;
}

constexpr const char* kDefaultPublicLibrariesFile = "/etc/public.libraries.txt";
static std::string InitDefaultPublicLibraries(bool for_preload) {
  //1、config_file路径为/system/etc/public.libraries.txt
  std::string config_file = root_dir() + kDefaultPublicLibrariesFile;
  //2、从配置读取需要预加载的动态库
  Result<std::vector<std::string>> sonames =
      ReadConfig(config_file, [&for_preload](const struct ConfigEntry& entry) -> Result<bool> {
        if (for_preload) {
          //3、除非有nopreload,否则都需要预加载
          return !entry.nopreload;
        } else {
          return true;
        }
      }); 
  ...

  std::string libs = android::base::Join(*sonames, ':');
  ...
  return libs;
}

std::string root_dir() {
  //ANDROID_ROOT默认读取到的就是/system
  static const char* android_root_env = getenv("ANDROID_ROOT");
  return android_root_env != nullptr ? android_root_env : "/system";
}

从上面源码可以看出:

  • libnativeloader负责应用所有JNI库的加载,因此它在虚拟机创建时初始化,并且在此之前不能调用JNI库。
  • libnativeloader初始化的主要工作,就是读取public.libraries.txt中的公共动态库并调用OpenSystemLibrary()进行预加载

art/libnativebridge/native_bridge.cc

c++ 复制代码
void* OpenSystemLibrary(const char* path, int flags) {
#ifdef ART_TARGET_ANDROID
    ...
  //1、获取system对应的android_namespace_t
  android_namespace_t* system_ns = android_get_exported_namespace("system");
  if (system_ns == nullptr) {
    system_ns = android_get_exported_namespace("default");
    LOG_ALWAYS_FATAL_IF(system_ns == nullptr,
                        "Failed to get system namespace for loading %s", path);
  }
  //2、指定加载的namspace为system
  const android_dlextinfo dlextinfo = {
      .flags = ANDROID_DLEXT_USE_NAMESPACE,
      .library_namespace = system_ns,
  };
  //3、使用android_dlopen_ext加载
  return android_dlopen_ext(path, flags, &dlextinfo);
#else
  return dlopen(path, flags);
#endif
}

可以看出:

  • OpenSystemLibrary()意味着使用system这个命名空间来加载公共的动态库。
  • 其中android_get_exported_namespace("system")和前文动态链接器初始化的介绍一致,通过这个方法就可以获取指定命名空间对象。
  • 使用android_dlopen_ext()来真正加载动态库,并且传入了关键参数 ANDROID_DLEXT_USE_NAMESPACEsystem_ns,来指定动态库所在的命名空间。
  • 最终的加载还是由动态链接器完成,libnativeloader的主要作用就是给动态链接器传入正确的命名空间

3、应用命名空间创建

正如前文所述,应用进程的Namespace会和类加载器关联,即每个类加载器对应一个命名空间。

接下来,介绍应用进程的Namespace的两个创建时机,以及怎么和类加载器关联上的。

  1. 应用启动构造LoadedApk和PathClassLoader 后,主动 调用createClassloaderNamespace()创建与Classloader关联的命名空间。
  2. ClassLoader首次加载动态库时 ,会调用createClassloaderNamespace()创建与Classloader关联的命名空间。

3.1、PathClassLoader主动创建命名空间

3.1.1、应用初始化,创建PathClassLoader

frameworks/base/core/java/android/app/ActivityThread.java

java 复制代码
public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {
  ...
  private void handleBindApplication(AppBindData data) {
    ...
    //1、创建LoadedApk
    data.info = getPackageInfo(data.appInfo, mCompatibilityInfo, null /* baseLoader */,
                false /* securityViolation */, true /* includeCode */,
                false /* registerPackage */, isSdkSandbox);
    ...
  }
  ...
}

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage, boolean isSdkSandbox) {
  ...
  //2、调用LoadedApk构造方法
  packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode
                            && (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
  ...
}

frameworks/base/core/java/android/app/LoadedApk.java

java 复制代码
private ClassLoader mDefaultClassLoader;

//1、LoadedApk构造方法
public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
        CompatibilityInfo compatInfo, ClassLoader baseLoader,
        boolean securityViolation, boolean includeCode, boolean registerPackage) {

    mActivityThread = activityThread;
    setApplicationInfo(aInfo);
    mPackageName = aInfo.packageName;
    mBaseClassLoader = baseLoader;
    mSecurityViolation = securityViolation;
    mIncludeCode = includeCode;
    mRegisterPackage = registerPackage;
    mDisplayAdjustments.setCompatibilityInfo(compatInfo);
    mAppComponentFactory = createAppFactory(mApplicationInfo, mBaseClassLoader);
}

//2、获取ClassLoader
public ClassLoader getClassLoader() {
  synchronized (mLock) {
      if (mClassLoader == null) {
          createOrUpdateClassLoaderLocked(null /*addedPaths*/);
      }
      return mClassLoader;
  }
}

@GuardedBy("mLock")
//3、创建ClassLoader
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
  ...
  ///1、通常是system/lib64:/system_ext/lib64
  final String defaultSearchPaths = System.getProperty("java.library.path");
  //2、PermittedPath是应用包名下data目录
  String libraryPermittedPath = canAccessDataDir() ? mDataDir : "";
  ...    
  //3、apk文件所在路径,例如/data/app/~~Zy2LWl-7kqC78FKz4Dcexw==/com.muye.dl_iterate_phdr_enhance-_EoEKwR2vKlqUDIG0zW3Fg==/base.apk   
  final List<String> zipPaths = new ArrayList<>(10);
   //4、动态库文件所在目录,例如/data/app/~~Zy2LWl-7kqC78FKz4Dcexw==/com.muye.dl_iterate_phdr_enhance-_EoEKwR2vKlqUDIG0zW3Fg==/base.apk!/lib/arm64-v8a
  ///data/app/~~Zy2LWl-7kqC78FKz4Dcexw==/com.muye.dl_iterate_phdr_enhance-_EoEKwR2vKlqUDIG0zW3Fg==/lib/arm64
  final List<String> libPaths = new ArrayList<>(10);
  ...
  //5、使用ApplicationLoaders创建ClassLoader,其中mApplicationInfo.classLoaderName为null
  mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
                    zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                    libraryPermittedPath, mBaseClassLoader,
                    mApplicationInfo.classLoaderName, sharedLibraries.first, nativeSharedLibraries,
                    sharedLibraries.second);
  //6、给ClassLoader添加动态库路径
  ApplicationLoaders.getDefault().addNative(mDefaultClassLoader, libPaths);
  ...
   if (mClassLoader == null) {
    //7、mAppComponentFactory是CoreComponentFactory,
    mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
            new ApplicationInfo(mApplicationInfo));
  }
}

frameworks/base/core/java/android/app/ApplicationLoaders.java

java 复制代码
ClassLoader getClassLoaderWithSharedLibraries(
        String zip, int targetSdkVersion, boolean isBundled,
        String librarySearchPath, String libraryPermittedPath,
        ClassLoader parent, String classLoaderName,
        List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
        List<ClassLoader> sharedLibrariesLoadedAfterApp) {
    // For normal usage the cache key used is the same as the zip path.
    return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
                          libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
                          nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
}

private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                       String librarySearchPath, String libraryPermittedPath,
                                       ClassLoader parent, String cacheKey,
                                       String classLoaderName, List<ClassLoader> sharedLibraries,
                                       List<String> nativeSharedLibraries,
                                       List<ClassLoader> sharedLibrariesLoadedAfterApp) {
  ...
  //1、最终调用ClassLoaderFactory
  ClassLoader loader = ClassLoaderFactory.createClassLoader(
    zip, null, parent, classLoaderName, sharedLibraries,
    null /*sharedLibrariesLoadedAfterApp*/);
  ...
}

frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java

java 复制代码
 public static ClassLoader createClassLoader(String dexPath,
      String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
      int targetSdkVersion, boolean isNamespaceShared, String classLoaderName,
      List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
      List<ClassLoader> sharedLibrariesAfter) {
    //1、创建ClassLoader
  final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
          classLoaderName, sharedLibraries, sharedLibrariesAfter);

  String sonameList = "";
  if (nativeSharedLibraries != null) {
      sonameList = String.join(":", nativeSharedLibraries);
  }

  //2、主动创建ClassLoader对应的Namespace。createClassloaderNamespace()是一个JNI方法,最终将调用Native层的CreateClassLoaderNamespaceLocked()
  String errorMessage = createClassloaderNamespace(classLoader,
                                                   targetSdkVersion,
                                                   librarySearchPath,
                                                   libraryPermittedPath,
                                                   isNamespaceShared,
                                                   dexPath,
                                                   sonameList);
  ...
  return classLoader;
  }

/**
 * Same as {@code createClassLoader} below, except that no associated namespace
 * is created.
 */
public static ClassLoader createClassLoader(String dexPath,
        String librarySearchPath, ClassLoader parent, String classloaderName,
        List<ClassLoader> sharedLibraries, List<ClassLoader> sharedLibrariesLoadedAfter) {
    ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
            ? null
            : sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
    ClassLoader[] arrayOfSharedLibrariesLoadedAfterApp = (sharedLibrariesLoadedAfter == null)
            ? null
            : sharedLibrariesLoadedAfter.toArray(
                    new ClassLoader[sharedLibrariesLoadedAfter.size()]);
    //创建PathClassLoader
    if (isPathClassLoaderName(classloaderName)) {
        return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries,
                arrayOfSharedLibrariesLoadedAfterApp);
    } 
    ...
}

private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName();
private static final String DEX_CLASS_LOADER_NAME = DexClassLoader.class.getName();
public static boolean isPathClassLoaderName(String name) {
  // For null values we default to PathClassLoader. This cover the case when packages
  // don't specify any value for their class loaders.
  return name == null || PATH_CLASS_LOADER_NAME.equals(name) ||
          DEX_CLASS_LOADER_NAME.equals(name);
}

上述流程的总结为:

  1. LoadedApk.getClassLoader()方法会创建PathClassLoader ,创建完成后,还会主动调用createClassloaderNamespace()创建与其关联的应用命名空间(ClassloaderNamespace)。
  2. 创建PathClassLoader时,会传入dexPath(应用程序所在目录)、librarySearchPath(应用动态库所在目录)。
  3. 创建ClassloaderNamespace时,会传入ClassLoaderdexPath(应用程序所在目录)、librarySearchPath(应用动态库所在目录)和libraryPermittedPath(包名/data目录)。

这里解释了Android Hook - 动态加载so库SO库依赖 一节提到的,为什么修改ClassLoader的DexPathList后,还是无法使用System.loadLibrary()来加载有其他依赖项的动态库

因为ClassLoader关联的命名空间 被创建后,其librarySearchPath就没有被修改,当动态链接器根据DT_NEEDED条目来加载动态库依赖时,就会找不到其依赖的动态库了(因为依赖项所在的目录,不在命名空间的librarySearchPath中)。

上面的JAVA层的流程,正如介绍libnativeloader时所述,最终ART虚拟机 将加载动态库的工作委托给libnativeloader来完成。

3.1.2、libnativeloader创建应用命名空间

art/libnativeloader/native_loader.cpp

c++ 复制代码
jstring CreateClassLoaderNamespace(JNIEnv* env,
                                   int32_t target_sdk_version,
                                   jobject class_loader,
                                   bool is_shared,
                                   jstring dex_path_j,
                                   jstring library_path_j,
                                   jstring permitted_path_j,
                                   jstring uses_library_list_j) {
#if defined(ART_TARGET_ANDROID)
  std::string dex_path;
  ...

  //1、根据dex文件路径,来获取对应的ApiDomain。
  Result<nativeloader::ApiDomain> api_domain = nativeloader::GetApiDomainFromPathList(dex_path);
  ...

  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  //2、加锁并调用CreateClassLoaderNamespaceLocked方法
  Result<NativeLoaderNamespace*> ns = CreateClassLoaderNamespaceLocked(env,
                                                                       target_sdk_version,
                                                                       class_loader,
                                                                       api_domain.value(),
                                                                       is_shared,
                                                                       dex_path,
                                                                       library_path_j,
                                                                       permitted_path_j,
                                                                       uses_library_list_j);
  ...
}

art/libnativeloader/native_loader.cpp

c++ 复制代码
LibraryNamespaces* g_namespaces GUARDED_BY(g_namespaces_mutex) = new LibraryNamespaces;

//创建和ClassLoader关联的Namespace
Result<NativeLoaderNamespace*> CreateClassLoaderNamespaceLocked(JNIEnv* env,
                                                                int32_t target_sdk_version,
                                                                jobject class_loader,
                                                                nativeloader::ApiDomain api_domain,
                                                                bool is_shared,
                                                                const std::string& dex_path,
                                                                jstring library_path_j,
                                                                jstring permitted_path_j,
                                                                jstring uses_library_list_j)
    REQUIRES(g_namespaces_mutex) {
  //1、创建和classLoader关联的命名空间,传入了动态库路径等参数
  Result<NativeLoaderNamespace*> ns = g_namespaces->Create(env,
                                                           target_sdk_version,
                                                           class_loader,
                                                           api_domain,
                                                           is_shared,
                                                           dex_path,
                                                           library_path_j,
                                                           permitted_path_j,
                                                           uses_library_list_j);
  if (!ns.ok()) {
    return ns;
  }
  //2、将这个命名空间,和默认命名空间关联。
  Result<void> linked = CreateNativeloaderDefaultNamespaceLibsLink(*ns.value());
  if (!linked.ok()) {
    return linked.error();
  }
  return ns;
}

art/libnativeloader/library_namespaces.cpp

c++ 复制代码
constexpr const char* kClassloaderNamespaceName = "clns";
std::list<std::pair<jweak, NativeLoaderNamespace>> namespaces_;
//创建与ClassLoader关联的命名空间。
Result<NativeLoaderNamespace*> LibraryNamespaces::Create(JNIEnv* env,
                                                         uint32_t target_sdk_version,
                                                         jobject class_loader,
                                                         ApiDomain api_domain,
                                                         bool is_shared,
                                                         const std::string& dex_path,
                                                         jstring library_path_j,
                                                         jstring permitted_path_j,
                                                         jstring uses_library_list_j) {
  ...
  std::string namespace_name = kClassloaderNamespaceName;
  ...
  static int clns_count = 0;
  //1、从这里看出,应用进程创建的namespace名称格式为`clns-n`
  namespace_name = android::base::StringPrintf("%s-%d", namespace_name.c_str(), ++clns_count);
  ...
  //2、即找到父ClassLoader对应的命名空间,作为即将创建的当前命名空间的父亲。FindParentNamespaceByClassLoader()具体流程不介绍,源码很简单。
  NativeLoaderNamespace* parent_ns = FindParentNamespaceByClassLoader(env, class_loader);
  ...
   //3、创建NativeLoaderNamespace。这个是核心逻辑。
  Result<NativeLoaderNamespace> app_ns =
      NativeLoaderNamespace::Create(namespace_name,
                                    library_path,
                                    permitted_path,
                                    parent_ns,
                                    is_shared,
                                    target_sdk_version < 24 /* is_exempt_list_enabled */,
                                    also_used_as_anonymous);
  ...
   //4、查找到system对应Namespace
  Result<NativeLoaderNamespace> system_ns = NativeLoaderNamespace::GetSystemNamespace(is_bridged);
  ...
    //5、Link应用namespace和system Namespace。Link过程不详细介绍。
  Result<void> linked = app_ns->Link(&system_ns.value(), system_exposed_libraries);
  ...
  //6、关联ClassLoader和NativeLoaderNamespace,即存到map中
  std::pair<jweak, NativeLoaderNamespace>& emplaced =
      namespaces_.emplace_back(std::make_pair(env->NewWeakGlobalRef(class_loader), *app_ns));
  ...
}

art/libnativeloader/native_loader_namespace.cpp

c++ 复制代码
Result<NativeLoaderNamespace> NativeLoaderNamespace::Create(
    const std::string& name, const std::string& search_paths, const std::string& permitted_paths,
    const NativeLoaderNamespace* parent, bool is_shared, bool is_exempt_list_enabled,
    bool also_used_as_anonymous) {
  bool is_bridged = false;
  ...

  // Fall back to the system namespace if no parent is set.
  //1、获取system对应的命名空间对象,最终肯定是通过android_get_exported_namespace()从动态链接器获取的,因此不深入GetSystemNamespace()。
  Result<NativeLoaderNamespace> system_ns = GetSystemNamespace(is_bridged);
  ...
  const NativeLoaderNamespace& effective_parent = parent != nullptr ? *parent : *system_ns;

  // All namespaces for apps are isolated
  //2、所有的应用命名空间都是isolated,这个属性在解析ld.config.txt配置时已经介绍过了
  uint64_t type = ANDROID_NAMESPACE_TYPE_ISOLATED;

  //设置一些其他属性
  ...

  if (!is_bridged) {
    //3、调用android_create_namespace(),这里传入了search_paths和permitted_paths两类用于搜索动态库的目录
    android_namespace_t* raw =
        android_create_namespace(name.c_str(), nullptr, search_paths.c_str(), type,
                                 permitted_paths.c_str(), effective_parent.ToRawAndroidNamespace());
    if (raw != nullptr) {
      return NativeLoaderNamespace(name, raw);
    }
  } else {
    ...
  }
  ...
}

bionic/libdl/libdl_android.cpp

c++ 复制代码
__attribute__((__weak__))
struct android_namespace_t* android_create_namespace(const char* name,
                                                     const char* ld_library_path,
                                                     const char* default_library_path,
                                                     uint64_t type,
                                                     const char* permitted_when_isolated_path,
                                                     struct android_namespace_t* parent) {  
  const void* caller_addr = __builtin_return_address(0);
  //1、android_create_namespace()会调用__loader_android_create_namespace(),这个方法在动态链接器中实现
  return __loader_android_create_namespace(name,
                                           ld_library_path,
                                           default_library_path,
                                           type,
                                           permitted_when_isolated_path,
                                           parent,
                                           caller_addr);
}

上述流程的重点为:

  1. libnativeloader最终调用android_create_namespace()方法让动态链接器创建android_namespace_t对象。
  2. libnativeloader会把android_namespace_t对象进一步封装成NativeLoaderNamespace ,然后使用一个Map记录ClassLoader和NativeLoaderNamespace的对应关系
  3. 新建的命名空间还默认和system namespace和default namespace关联,从而使用应用进程动态库可以依赖Android提供的一些系统动态库。
  4. 应用的命名空间的名称格式为clns-n。例如clns-0clns-1,以此类推。
3.1.3、动态链接负责最后的创建

bionic/linker/dlfcn.cpp

c++ 复制代码
android_namespace_t* __loader_android_create_namespace(const char* name,
                                                const char* ld_library_path,
                                                const char* default_library_path,
                                                uint64_t type,
                                                const char* permitted_when_isolated_path,
                                                android_namespace_t* parent_namespace,
                                                const void* caller_addr) {
  ScopedPthreadMutexLocker locker(&g_dl_mutex);
    //1、继续调用create_namespace()
  android_namespace_t* result = create_namespace(caller_addr,
                                                 name,
                                                 ld_library_path,
                                                 default_library_path,
                                                 type,
                                                 permitted_when_isolated_path,
                                                 parent_namespace);

  ...
  return result;
}

bionic/linker/linker.cpp

c++ 复制代码
android_namespace_t* create_namespace(const void* caller_addr,
                                      const char* name,
                                      const char* ld_library_path,
                                      const char* default_library_path,
                                      uint64_t type,
                                      const char* permitted_when_isolated_path,
                                      android_namespace_t* parent_namespace) {
  ...
  //1、由于传入的default_library_path、permitted_when_isolated_path都是用:来拼接多个目录路径的,因此这里要分割开
  std::vector<std::string> ld_library_paths;
  std::vector<std::string> default_library_paths;
  std::vector<std::string> permitted_paths;

  parse_path(ld_library_path, ":", &ld_library_paths);
  parse_path(default_library_path, ":", &default_library_paths);
  parse_path(permitted_when_isolated_path, ":", &permitted_paths);

  //2、创建android_namespace_t对象,这里和动态链接器解析配置后,创建命名空间对象的方式一致
  android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t();
  //3、给命名空间设置各种属性
  ns->set_name(name);
  ns->set_isolated((type & ANDROID_NAMESPACE_TYPE_ISOLATED) != 0);
  ns->set_exempt_list_enabled((type & ANDROID_NAMESPACE_TYPE_EXEMPT_LIST_ENABLED) != 0);
  ns->set_also_used_as_anonymous((type & ANDROID_NAMESPACE_TYPE_ALSO_USED_AS_ANONYMOUS) != 0);

  ...

  //4、关键!给命名空间设置动态库的搜索目录
  ns->set_ld_library_paths(std::move(ld_library_paths));
  ns->set_default_library_paths(std::move(default_library_paths));
  ns->set_permitted_paths(std::move(permitted_paths));

  ...
  return ns;
}

和动态连接器解析ld.config.txt的过程类似,使用android_namespace_t()方法创建命名空间对象后,设置各种属性,主要是动态库的搜索目录

3.2、ClassLoader首次加载动态库时创建

除了主动调用CreateClassLoaderNamespaceLocked()方法为ClassLoader创建命名空间,在ClassLoader首次加载动态库时,也会默认创建其对应的命名空间。

Android Hook - 动态加载so库SO库加载原理 一节中,我们跟踪源码介绍了动态库加载从JAVA层 的**System.load()/System.loadLibray()到调用libnativeloaderOpenNativeLibrary()**方法的流程。

但是没有分析OpenNativeLibrary()的在ART虚拟机下的具体实现,而是直接使用了非ART虚拟机下的情况的结论。

在有了前面知识铺垫的前提下,就可以具体来看ART虚拟机的实现了。

art/libnativeloader/native_loader.cpp

c++ 复制代码
LibraryNamespaces* g_namespaces GUARDED_BY(g_namespaces_mutex) = new LibraryNamespaces;

void* OpenNativeLibrary(JNIEnv* env,
                        int32_t target_sdk_version,
                        const char* path,
                        jobject class_loader,
                        const char* caller_location,
                        jstring library_path_j,
                        bool* needs_native_bridge,
                        char** error_msg) {
#if defined(ART_TARGET_ANDROID)//说明是ART虚拟机
  ...
  
  NativeLoaderNamespace* ns;
  const char* ns_descr;
  {
    std::lock_guard<std::mutex> guard(g_namespaces_mutex);
       //1、根据classloader找到对应命名空间。g_namespaces在前面已经介绍过了。
    ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader);
    ns_descr = "class loader";

    if (ns == nullptr) {
      // This is the case where the classloader was not created by ApplicationLoaders
      // In this case we create an isolated not-shared namespace for it.
      //2、重点!!如果ClassLoader没有对应的命名空间,那么创建一个。这种情况就是非PathClassLoader的情况。
      const std::string empty_dex_path;
      Result<NativeLoaderNamespace*> res =
          CreateClassLoaderNamespaceLocked(env,
                                           target_sdk_version,
                                           class_loader,
                                           nativeloader::API_DOMAIN_DEFAULT,
                                           /*is_shared=*/false,
                                           empty_dex_path,
                                           library_path_j,
                                           /*permitted_path_j=*/nullptr,
                                           /*uses_library_list_j=*/nullptr);
      ...
      ns = res.value();
      ns_descr = "isolated";
    }
  }
    //3、命名空间调用Load()方法来加载动态库
  Result<void*> handle = ns->Load(path);
  ...
  return handle.value();

#else   // !ART_TARGET_ANDROID
  ...
#endif  // !ART_TARGET_ANDROID
}

上面的代码很简单,主要是:

  • 如果ClassLoader没有对应的命名空间,那么创建一个。这种情况就是非PathClassLoader的情况
  • 命名空间调用Load()方法来加载动态库。

4、动态库加载过程

当ClassLoader对应的命名空间创建后,我们就可以使用它来加载动态库。因此我们继续来NativeLoaderNamespace::Load()方法的具体实现。

4.1、入口链路

art/libnativeloader/native_loader_namespace.cpp

c++ 复制代码
Result<void*> NativeLoaderNamespace::Load(const char* lib_name) const {
  if (!IsBridged()) {
    android_dlextinfo extinfo;
    //1、extinfo标志使用Namespace来加载
    extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
    //2、这里需要把NativeLoaderNamespace转回android_namespace_t
    extinfo.library_namespace = this->ToRawAndroidNamespace();
    //3、调用android_dlopen_ext
    void* handle = android_dlopen_ext(lib_name, RTLD_NOW, &extinfo);
    if (handle != nullptr) {
      return handle;
    }
  } else {
    ...
  }
  ...
}

bionic/libdl/libdl.cpp

c++ 复制代码
__attribute__((__weak__))
void* android_dlopen_ext(const char* filename, int flag, const android_dlextinfo* extinfo) {
  //1、重要!!__builtin_return_address(0)是获取LR寄存器的值,LR 寄存器存储函数的返回地址,即调用者的地址。
  const void* caller_addr = __builtin_return_address(0);
  //2、继续调用__loader_android_dlopen_ext
  return __loader_android_dlopen_ext(filename, flag, extinfo, caller_addr);
}

bionic/linker/dlfcn.cpp

c++ 复制代码
void* __loader_android_dlopen_ext(const char* filename,
                           int flags,
                           const android_dlextinfo* extinfo,
                           const void* caller_addr) {
  //调用dlopen_ext
  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);
  ...
   //调用do_dlopen
  void* result = do_dlopen(filename, flags, extinfo, caller_addr);
  ...
  return result;
}

bionic/linker/linker.cpp

c++ 复制代码
void* do_dlopen(const char* name, int flags,
                const android_dlextinfo* extinfo,
                const void* caller_addr) {
  ...
  //1、根据调用者的内存地址,找到调用者所在动态库。每个加载到内存的动态库,在linker中都会有记录(毕竟是它加载的),因此,通过比较caller_addr内存地址,是否在某个动态库内存地址范围内,就可以找到调用者所在的动态库,find_containing_library()正是这样实现的。
  soinfo* const caller = find_containing_library(caller_addr);
  //2、根据soinfo获取namespace。soinfo代表是内存中动态库的信息,因此也记录着它是被哪个命名空间加载的。
  android_namespace_t* ns = get_caller_namespace(caller);

    ...  

  if (extinfo != nullptr) {
    ...
    if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0) {
      ...
      //3、如果外部有传namespace,那么这个优先级更高。即应用命名空间加载的情况。
      ns = extinfo->library_namespace;
    }
  }
  
  ...  
  //4、继续调用find_library()来最终加载动态库
  soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);

  if (si != nullptr) {
    //5、返回结果
    void* handle = si->to_handle();
    ...
    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;

  ...
   //6、继续调用find_libraries()
  find_libraries(ns,
                 needed_by,
                 &name,
                 1,
                 &si,
                 nullptr,
                 0,
                 rtld_flags,
                 extinfo,
                 false /* add_as_children */)
  ...

  return si;
}

上面代码的重点是:

libdl会根据当前调用者所在的动态库,获取其对应的命名空间,只是这个命名空间的优先级 比外部传入的

  1. 根据前文得知,从JAVA层开始到这里的链路,会构造一个应用命名空间传入。因此当前分析的case是外部传入命名空间的情况。
  2. 另外一种则是在JNI调用dlopen()的情况,此时会根据调用者地址获取命名空间,这是在Native层也能保证命名空间机制生效的原因。而这使得我们无法主动加载一些系统动态库

4.2、核心流程

bionic/linker/linker.cpp

c 复制代码
//核心流程
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.
  //1、load_tasks存着需要加载的动态库列表
  LoadTaskList load_tasks;
    //2、此时library_names_count为1,因此表示只需要加载当前动态库。每有一个待加载的动态库,都会构造一个LoadTask。
  for (size_t i = 0; i < library_names_count; ++i) {
    const char* name = library_names[i];
    //3、创建LoadTask,其中start_with就是后面task->get_needed_by(),表示加载当前动态库的动态库。
    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)
  /**
   * 4、遍历所有的加载任务(开始只有一个),递归收集它的依赖。
   * 这里为什么等于递归收集,原因是find_library_internal()会收集当前动态库的依赖,并且加入到load_tasks,因此继续遍历,就会遍历到刚加入的库,进而继续收集它们的依赖。
   */ 
  for (size_t i = 0; i<load_tasks.size(); ++i) {
    LoadTask* task = load_tasks[i];
    //needed_by表示加载当前动态库的动态库。
    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);
    //5、继续调用find_library_internal()。这个方法的作用是收集动态库依赖,为这些需要加载动态库也构造LoadTask,并加入到load_list列表。
    //注意,find_library_internal()中会打开动态库文件,读取ELF头和段表信息。
    if (!find_library_internal(start_ns, task, &zip_archive_cache, &load_tasks, rtld_flags)) {
      return false;
    }

    soinfo* si = task->get_soinfo();
  }
  ...
  // Step 2: Load libraries in random order (see b/24047022)
  LoadTaskList load_list;
  //6、到了这一步,load_tasks中就不止是1个LoadTask,而是存储着所有有依赖关系的动态库的LoadTask。由于有些动态库可能同时被多个其他动态库依赖,例如libc.so,因此这里需要去重,使得load_list中每个动态库只有一项。
  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    auto pred = [&](const LoadTask* t) {
      return t->get_soinfo() == si;
    };
       //7、遍历这些动态库,加入到load_list中,这一步的目的对load_tasks进行去重
    if (!si->is_linked() &&
        std::find_if(load_list.begin(), load_list.end(), pred) == load_list.end() ) {
      load_list.push_back(task);
    }
  }
  
  //8、依次加载load_list中记录的动态库,这里的顺序不重要,因为动态库之间还没有实际的联系(即还没有进行重定位)
  for (auto&& task : load_list) {
    address_space_params* address_space =
        (reserved_address_recursive || !task->is_dt_needed()) ? &extinfo_params : &default_params;
    //8.1、重要!!依次执行加载任务,这里会使用mmap把动态库加载到内存中,
    if (!task->load(address_space)) {
      return false;
    }
  }
  
  //Step 3: pre-link all DT_NEEDED libraries in breadth first order.
  //9、预链接所有 DT_NEEDED 库(按广度优先顺序)。所谓的预链接就是对动态库进行动态重定位,Android不支持延迟绑定。
  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    //9.1、prelink_image()的作用是进行ELF文件的解析和重定位。
    if (!si->is_linked() && !si->prelink_image(dlext_use_relro)) {
      return false;
    }
    ..
  }
  ...
  
  //10、遍历所有加载任务,将库标记为已链接。
  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    si->set_linked();
  }
}

整个动态库加载的核心流程在find_libraries()方法,即在Linker当中,重点为:

  1. 打开目标动态库文件,读取其中的ELF文件头 和段表,根据DT_NEEDED项找到依赖的其他动态库,为每个动态库创建一个LoadTask。这个过程会递归进行 ,原因是find_library_internal()会收集当前动态库的依赖,并且加入到load_tasks,因此继续遍历,就会遍历到刚加入的库,进而继续收集它们的依赖。

    最终,目标动态的依赖及依赖的所有依赖,都会被加入到load_tasks,这就是本次要加载的所有动态库(有的可能已经加载过)。

  2. LoadTaskList 进行去重 ,原因是有些动态库可能同时被多个其他动态库依赖,例如libc.so,因此这里需要去重,使得load_list中每个动态库只有一项。

  3. 加载load_list中的所有动态库,这里的顺序不重要,因为动态库之间还没有实际的联系(即还没有进行重定位)。

  4. 最后按广度优先顺序,对上述过程中新加载的动态库,进行动态重定位 。动态重定位是动态链接器重要功能,简单来说就是把动态库中的相对地址 替换成绝对地址 ,这是因为动态库在编译时是无法确认一些符号 (在Android Hook - dl_iterate_phdr()增强中有介绍,它是动态库之间连接的桥梁)的真实地址,直到它被加载到内存中后才能确认,此时就需要将这些地址替换成内存中的真实地址了。

  5. 最后,把重定位完成的动态库都标记一下,依次动态库的加载动态链接就结束了。

至此,动态库的核心加载流程就解释清楚了,一句话解释就是解析动态库所有依赖,再进行重定位。

但还有两个过程我们没有具体去看,即:

  1. 如何解析动态库的依赖,并将它们构造成LoadTask 加入到LoadTaskList中。
  2. LoadTask.load()方法具体是怎么把动态库加载到内存的。

更重要的是,命名空间在这个过程中发挥了什么作用?

4.3、解析动态库依赖

bionic/linker/linker.cpp

c 复制代码
static bool find_library_internal(android_namespace_t* ns,
                                  LoadTask* task,
                                  ZipArchiveCache* zip_archive_cache,
                                  LoadTaskList* load_tasks,
                                  int rtld_flags) {
  soinfo* candidate;
    //1、先尝试在缓存中查找
  if (find_loaded_library_by_soname(ns, task->get_name(), true /* search_linked_namespaces */,
                                    &candidate)) {
    ...
    task->set_soinfo(candidate);
    return true;
  }

  ...
  //2、从磁盘中加载
  if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags,
                   true /* search_linked_namespaces */)) {
    return true;
  }

  ...

    //3、如果当前命名空间下的路径找不到动态库,那么委托给Link的命名空间加载
  for (auto& linked_namespace : ns->linked_namespaces()) {
    //3.1、检查委托的命名空间能不能加载该动态库,可以则继续执行
    if (find_library_in_linked_namespace(linked_namespace, task)) {
      // Library is already loaded.
      if (task->get_soinfo() != nullptr) {
        ...
          //3.2、已经加载过了,结束
        return true;
      }
          //3.2、没有加载过,那么尝试加载
      if (load_library(linked_namespace.linked_namespace(), task, zip_archive_cache, load_tasks,
                       rtld_flags, false /* search_linked_namespaces */)) {
        ...
        return true;
      }
    }
  }

  return false;
}

解析依赖分为三步:

  1. 查找缓存 ,即动态库是否已经加载过了,如果没有,那么还会在Link的命名空间缓存中找。
  2. 找不到,那么就从磁盘中按照ELF文件格式读取动态库文件,解析出ELF文件头信息和段表 ,遍历.dynamic节,找到DT_NEEDED项,即动态库的依赖项,使用这些依赖项信息构造LoadTask。
  3. 如果磁盘中也找不到,那么尝试委托给Link的命名空间去磁盘中找。

如果不了ELF文件格式和DT_NEEDED 项的作用,可以参考文章Android Hook - 动态加载so库Android Hook - 解析proc/pid/maps文件

4.3.1、查找缓存

bionic/linker/linker.cpp

c 复制代码
// Returns true if library was found and false otherwise
//有search_linked_namespaces参数的版本
static bool find_loaded_library_by_soname(android_namespace_t* ns,
                                         const char* name,
                                         bool search_linked_namespaces,
                                         soinfo** candidate) {
  *candidate = nullptr;

  // Ignore filename with path.
  if (strchr(name, '/') != nullptr) {
    return false;
  }
    //1、调用同名方法find_loaded_library_by_soname(),但是没有search_linked_namespaces参数
  bool found = find_loaded_library_by_soname(ns, name, candidate);

  //2、如果没有找到动态库,并且search_linked_namespaces为true,表示需要尝试在跟当前命名空Link的其他命名空间中查找。
  if (!found && search_linked_namespaces) {
    // if a library was not found - look into linked namespaces
    //2.1、遍历有Link关系的命名空间。应用命名空间Link了system和default两个命名空间。
    for (auto& link : ns->linked_namespaces()) {
      if (!link.is_accessible(name)) {
        continue;
      }

      android_namespace_t* linked_ns = link.linked_namespace();
          //2.2、递归调用find_loaded_library_by_soname(),注意到此时也没有search_linked_namespaces参数,这就是前文为什么说Link委托加载没有传递性的原因。
      if (find_loaded_library_by_soname(linked_ns, name, candidate)) {
        return true;
      }
    }
  }

  return found;
}

//没有search_linked_namespaces参数的版本
static bool find_loaded_library_by_soname(android_namespace_t* ns,
                                          const char* name,
                                          soinfo** candidate) {
  //1、soinfo_list是命名空间已经加载的动态库的列表,这里相当于在缓存中查找
  return !ns->soinfo_list().visit([&](soinfo* si) {
    if (strcmp(name, si->get_soname()) == 0) {
      *candidate = si;
      return false;
    }

    return true;
  });
}
  • 命名空间加载的动态库都会记录在其soinfo_list中,因此通过soinfo_list可以检查是否已经加载过了。
  • 如果找不到还会在Link的命名空间的缓存中查找,search_linked_namespaces参数的变化,这说明Link委托加载没有传递性
4.3.2、磁盘加载

bionic/linker/linker.cpp

c++ 复制代码
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();

  ... 
  // Open the file.
  off64_t file_offset;
  std::string realpath;
  //1、打开动态库文件
  int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath);
  ...
  task->set_fd(fd, true);
  task->set_file_offset(file_offset);

  //2、进一步加载
  return load_library(ns, task, load_tasks, rtld_flags, realpath, search_linked_namespaces);
}

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();
  ...
  //1、创建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.
  //2、按照ELF文件格式读取动态库文件,解析出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;
  }

  // Find and set DT_RUNPATH, DT_SONAME, and DT_FLAGS_1.
  // Note that these field values are temporary and are
  // going to be overwritten on soinfo::prelink_image
  // with values from PT_LOAD segments.
  const ElfReader& elf_reader = task->get_elf_reader();
  ...
  
  //3、遍历.dynamic节,找到DT_NEEDED项,即动态库的依赖项
  for (const ElfW(Dyn)* d = elf_reader.dynamic(); d->d_tag != DT_NULL; ++d) {
    if (d->d_tag == DT_NEEDED) {
      const char* name = fix_dt_needed(elf_reader.get_string(d->d_un.d_val), elf_reader.name());
      ...
      //4、使用依赖的动态库信息,构造LoadTask,加入队列
      load_tasks->push_back(LoadTask::create(name, si, ns, task->get_readers_map()));
    }
  }

  return true;
}
  • 从磁盘读取动态库文件,解析出ELF文件头信息和段表,进行遍历.dynamic节,找到DT_NEEDED项,即动态库的依赖项,最后使用依赖的动态库信息,构造LoadTask,加入队列。
4.3.3、委托给关联的命名空间

bionic/linker/linker.cpp

c++ 复制代码
static bool find_library_in_linked_namespace(const android_namespace_link_t& namespace_link,
                                             LoadTask* task) {
  //1、获取关联的命名空间
  android_namespace_t* ns = namespace_link.linked_namespace();

  soinfo* candidate;
  bool loaded = false;

  std::string soname;
  //2、从缓存中查找,注意这里search_linked_namespaces传了false,也就是即使找不到,也不会委托给关联的命名空间了
  if (find_loaded_library_by_soname(ns, task->get_name(), false, &candidate)) {
    //2.1、找到了
    loaded = true;
    soname = candidate->get_soname();
  } else {
    //2.2、没有找到,那么任务名就是动态库名
    soname = resolve_soname(task->get_name());
  }

  //3、判断命名空间是否允许加载对应的动态库
  if (!namespace_link.is_accessible(soname.c_str())) {
    ...
    //3.1、如果不允许,那么就结束
    return false;
  }

  // if library is already loaded - return it
  if (loaded) {
     //4、已经找到了,那么将动态库对应的soinfo记录下来
    task->set_soinfo(candidate);
    return true;
  }
  
  //5、找不到也没关系,也返回true,因为命名空间实际可以加载它,但是还没有加载
  task->set_soinfo(nullptr);
  return true;
}

bionic/linker/linker_namespaces.h

c++ 复制代码
  bool is_accessible(const char* soname) const {
    //全部允许委托加载,或者只允许白名单内的加载
    return allow_all_shared_libs_ || shared_lib_sonames_.find(soname) != shared_lib_sonames_.end();
  }

实际find_library_in_linked_namespace()主要作用是判断当前委托的命名空间,能否加载目标动态库

而一个命名空间能否允许被委托加载某些动态库,是由ld.config.txt介绍的allow_all_shared_libs_shared_libs属性决定的。

4.4、为动态库分配内存

bionic/linker/linker.cpp

c++ 复制代码
class LoadTask {
 public:
    bool load(address_space_params* address_space) {
    //1、读取ELF文件头
    ElfReader& elf_reader = get_elf_reader();
    if (!elf_reader.Load(address_space)) {
      return false;
    }
       //2、解析ELF文件头的各种属性
    si_->base = elf_reader.load_start();
    si_->size = elf_reader.load_size();
    si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller());
    si_->load_bias = elf_reader.load_bias();
    si_->phnum = elf_reader.phdr_count();
    si_->phdr = elf_reader.loaded_phdr();
    si_->set_gap_start(elf_reader.gap_start());
    si_->set_gap_size(elf_reader.gap_size());
    si_->set_should_pad_segments(elf_reader.should_pad_segments());
    si_->set_should_use_16kib_app_compat(elf_reader.should_use_16kib_app_compat());
    if (si_->should_use_16kib_app_compat()) {
      si_->set_compat_relro_start(elf_reader.compat_relro_start());
      si_->set_compat_relro_size(elf_reader.compat_relro_size());
    }

    return true;
  }
}

bionic/linker/linker_phdr.cpp

c++ 复制代码
bool ElfReader::Load(address_space_params* address_space) {
  ...
  //1、申请内存
  bool reserveSuccess = ReserveAddressSpace(address_space);
  ...
  return did_load_;
}

bionic/linker/linker_phdr.cpp

c 复制代码
// 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(address_space_params* address_space) {
  //1、min_vaddr是最小的段起始地址
  ElfW(Addr) min_vaddr;
  //2、读取类型为PT_LOAD的段的总大小,只有它们才会被加载到内存
  load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);
  ...
    
  uint8_t* addr = reinterpret_cast<uint8_t*>(min_vaddr);
  void* start;

  if (load_size_ > address_space->reserved_size) {
    ...
    //3、通常reserved_size是0,因此调用ReserveWithAlignmentPadding()申请内存。ReserveWithAlignmentPadding()将处理页对齐和使用匿名内存映射申请内存。
    start = ReserveWithAlignmentPadding(load_size_, kLibraryAlignment, start_alignment, &gap_start_,
                                        &gap_size_);
    ..
  } 
    ...
    
  //4、记录动态库起始地址和ReserveWithAlignmentPadding
  load_start_ = start;  
  load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;

  ...

  return true;
}

static void* ReserveWithAlignmentPadding(size_t size, size_t mapping_align, size_t start_align,
                                         void** out_gap_start, size_t* out_gap_size) {
  int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS;
  // Reserve enough space to properly align the library's start address.
  mapping_align = std::max(mapping_align, start_align);
  if (mapping_align == page_size()) {
    //使用匿名内存映射分配内存
    void* mmap_ptr = mmap(nullptr, size, PROT_NONE, mmap_flags, -1, 0);
    ...
    return mmap_ptr;
  }
    ...
  size_t mmap_size = __builtin_align_up(size, mapping_align) + mapping_align - page_size();
  //使用匿名内存映射分配内存
  uint8_t* mmap_ptr =
      reinterpret_cast<uint8_t*>(mmap(nullptr, mmap_size, PROT_NONE, mmap_flags, -1, 0));
  ...
  return start;
}

总的来说,分配的内存的过程为:

  1. 根据EFL头和段表,计算PT_LOAD的段的总大小,只有它们才会被加载到内存。
  2. 使用匿名内存映射为段分配内存。
  3. 计算起始地址(load_start_)、load_bias_load_bias_的概念在Android Hook - dl_iterate_phdr()增强中有详细描述。

三、总结

命名空间机制主要有三个参与对象,分别是应用程序(Art虚拟机)libnativeloader动态链接器

其中:

  • Art虚拟机负责在启动时创建PathClassLoader 和调用libnativeloader创建对应的命名空间,此外,其他ClassLoader 在它们首次加载动态库时,也会创建对应命名空间。

    虚拟机对于动态库的加载,会委托给libnativeloader进行,加载成功后则负责调用其**JNI_OnLoad()**方法。

  • libnativeloader是虚拟机和动态链接器之间的桥梁。

    1. 负责调用动态链接器为每个ClassLoader创建应用命名空间 ,关键参数是应用动态库目录
    2. 负责维护ClassLoader和应用命名空间直接的映射关系。
    3. 负责通过当前ClassLoader判断库是否已经被加载等,如果没有加载过,则找到其命名空间,交给动态连接器去处理。
  • 动态链接器负责实现加载动态库到内存命名空间委托机制

    1. 递归解析目标动态库的所有依赖,创建LoadTask。这个过程会分别尝试从缓存、磁盘和Link命名空间中查找。
    2. 把上一步中所有未加载到内存的动态库使用匿名内存映射加载到内存。
    3. 为加载的所有动态库进行进一步ELF文件的解析和重定位
相关推荐
鸿蒙布道师7 小时前
鸿蒙NEXT开发Base64工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
jiet_h8 小时前
Android adb 的功能和用法
android·adb
美狐美颜sdk9 小时前
美颜SDK兼容性挑战:如何让美颜滤镜API适配iOS与安卓?
android·深度学习·ios·美颜sdk·第三方美颜sdk·视频美颜sdk
居然是阿宋9 小时前
深入理解 YUV 颜色空间:从原理到 Android 视频渲染
android·音视频
KevinWang_10 小时前
DialogFragment 不适合复用
android
古鸽1008610 小时前
Audio Hal 介绍
android
小叶不焦虑11 小时前
关于 Android 系统回收站的实现
android
木西11 小时前
从0到1搭建一个RN应用从开发测试到上架全流程
android·前端·react native
小橙子207712 小时前
一条命令配置移动端(Android / iOS)自动化环境
android·ios·自动化
和煦的春风12 小时前
案例分析 | SurfaceFlinger Binder RT 被降级到CFS
android