本文介绍了链接器命名空间机制,结合Android源码进行分析,帮助读者了解动态链接器命名空间(Namespace)机制的原理,从而理解该机制对用户进程加载动态库造成的限制。
在文章Android Hook - 动态加载so库中,已经介绍了动态链接器Namespace机制的一些规则,本文将描述实现的具体细节。
一、简述动态链接器Namespace机制
1、Namespace机制的目的
正如链接器命名空间所述,此机制由动态链接器提供,目的是:
- 隔离不同链接器命名空间中的动态库,以确保具有相同库名称和不同符号的库 不会发生冲突。
- 例如,
/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
。
- 例如,
- 一个链接器命名空间可导出某些动态库用于另一个链接器命名空间 。这些导出的动态库可以成为对其他程序公开的应用编程接口,同时在其链接器命名空间中隐藏实现细节。
- 例如
/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初始化
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);
}
...
}
总的来说:
- 在动态链接器启动时 ,会读取配置
ld.config.txt
,并根据配置创建命名空间(namespace)对象android_namespace_t
和设置它的各种属性。 - 其中
default
命名空间的默认都需要创建的,其他命名空间则根据配置创建。 - 这些命名空间,如果对外可见,则会被添加到**
g_exported_namespaces
**这个map容器中。 - 根据配置建立命名空间(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操作系统中的几个重要分区和目录,旨在使系统的开发和维护更加模块化和灵活。
- 根据配置可以看出,主要的分区有
system
、product
、vendor
等。 - 一个分区关联可以关联多个目录 。例如
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.paths
与search.paths
类似,当isolated
为true时生效,但是permitted.paths
的子目录下的动态库也可以加载 。但是,在动态链接器搜索动态库时,permitted.paths
不会附加到请求的库名称前面。
1.2.6、关系属性namespace.${name}.links
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
中,从而可以被外部找到。
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)
,其调用流程如下:
- 当调用该方法时,ART运行时将调用委托给该库 ,以及传入对进行调用的类加载器的引用。
- 然后该库找到与给定类加载器关联的链接器命名空间 (通常名称为
clns
,后跟一个数字以使其唯一),并尝试从该命名空间加载请求的库。 - 库的实际搜索、加载和链接由动态链接器执行。
应用命名空间 是在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_NAMESPACE
和system_ns
,来指定动态库所在的命名空间。 - 最终的加载还是由动态链接器完成,
libnativeloader
的主要作用就是给动态链接器传入正确的命名空间。
3、应用命名空间创建
正如前文所述,应用进程的Namespace会和类加载器关联,即每个类加载器对应一个命名空间。
接下来,介绍应用进程的Namespace的两个创建时机,以及怎么和类加载器关联上的。
- 应用启动构造LoadedApk和PathClassLoader 后,主动 调用
createClassloaderNamespace()
创建与Classloader关联的命名空间。 - 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);
}
上述流程的总结为:
- LoadedApk.getClassLoader()方法会创建PathClassLoader ,创建完成后,还会主动调用
createClassloaderNamespace()
来创建与其关联的应用命名空间(ClassloaderNamespace)。 - 创建PathClassLoader时,会传入
dexPath
(应用程序所在目录)、librarySearchPath
(应用动态库所在目录)。 - 创建ClassloaderNamespace时,会传入ClassLoader 、
dexPath
(应用程序所在目录)、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);
}
上述流程的重点为:
libnativeloader
最终调用android_create_namespace()
方法让动态链接器创建android_namespace_t
对象。libnativeloader
会把android_namespace_t
对象进一步封装成NativeLoaderNamespace ,然后使用一个Map记录ClassLoader和NativeLoaderNamespace的对应关系。- 新建的命名空间还默认和system namespace和default namespace关联,从而使用应用进程动态库可以依赖Android提供的一些系统动态库。
- 应用的命名空间的名称格式为
clns-n
。例如clns-0
、clns-1
,以此类推。
3.1.3、动态链接负责最后的创建
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;
}
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()到调用libnativeloader
的OpenNativeLibrary()**方法的流程。
但是没有分析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 {
...
}
...
}
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);
}
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;
}
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
会根据当前调用者所在的动态库,获取其对应的命名空间,只是这个命名空间的优先级 比外部传入的低。
- 根据前文得知,从JAVA层开始到这里的链路,会构造一个应用命名空间传入。因此当前分析的case是外部传入命名空间的情况。
- 另外一种则是在JNI调用
dlopen()
的情况,此时会根据调用者地址获取命名空间,这是在Native层也能保证命名空间机制生效的原因。而这使得我们无法主动加载一些系统动态库。
4.2、核心流程
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当中,重点为:
-
打开目标动态库文件,读取其中的ELF文件头 和段表,根据
DT_NEEDED
项找到依赖的其他动态库,为每个动态库创建一个LoadTask。这个过程会递归进行 ,原因是find_library_internal()
会收集当前动态库的依赖,并且加入到load_tasks
,因此继续遍历,就会遍历到刚加入的库,进而继续收集它们的依赖。最终,目标动态的依赖及依赖的所有依赖,都会被加入到
load_tasks
,这就是本次要加载的所有动态库(有的可能已经加载过)。 -
对LoadTaskList 进行去重 ,原因是有些动态库可能同时被多个其他动态库依赖,例如
libc.so
,因此这里需要去重,使得load_list
中每个动态库只有一项。 -
加载
load_list
中的所有动态库,这里的顺序不重要,因为动态库之间还没有实际的联系(即还没有进行重定位)。 -
最后按广度优先顺序,对上述过程中新加载的动态库,进行动态重定位 。动态重定位是动态链接器重要功能,简单来说就是把动态库中的相对地址 替换成绝对地址 ,这是因为动态库在编译时是无法确认一些符号 (在Android Hook - dl_iterate_phdr()增强中有介绍,它是动态库之间连接的桥梁)的真实地址,直到它被加载到内存中后才能确认,此时就需要将这些地址替换成内存中的真实地址了。
-
最后,把重定位完成的动态库都标记一下,依次动态库的加载 和动态链接就结束了。
至此,动态库的核心加载流程就解释清楚了,一句话解释就是解析动态库所有依赖,再进行重定位。
但还有两个过程我们没有具体去看,即:
- 如何解析动态库的依赖,并将它们构造成LoadTask 加入到LoadTaskList中。
LoadTask.load()
方法具体是怎么把动态库加载到内存的。
更重要的是,命名空间在这个过程中发挥了什么作用?
4.3、解析动态库依赖
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;
}
解析依赖分为三步:
- 先查找缓存 ,即动态库是否已经加载过了,如果没有,那么还会在Link的命名空间 的缓存中找。
- 找不到,那么就从磁盘中按照ELF文件格式读取动态库文件,解析出ELF文件头信息和段表 ,遍历
.dynamic
节,找到DT_NEEDED项,即动态库的依赖项,使用这些依赖项信息构造LoadTask。 - 如果磁盘中也找不到,那么尝试委托给Link的命名空间去磁盘中找。
如果不了ELF文件格式和DT_NEEDED 项的作用,可以参考文章Android Hook - 动态加载so库、Android Hook - 解析proc/pid/maps文件。
4.3.1、查找缓存
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、磁盘加载
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、委托给关联的命名空间
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、为动态库分配内存
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;
}
}
c++
bool ElfReader::Load(address_space_params* address_space) {
...
//1、申请内存
bool reserveSuccess = ReserveAddressSpace(address_space);
...
return did_load_;
}
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;
}
总的来说,分配的内存的过程为:
- 根据EFL头和段表,计算PT_LOAD的段的总大小,只有它们才会被加载到内存。
- 使用匿名内存映射为段分配内存。
- 计算起始地址(
load_start_
)、load_bias_
。load_bias_
的概念在Android Hook - dl_iterate_phdr()增强中有详细描述。
三、总结
命名空间机制主要有三个参与对象,分别是应用程序(Art虚拟机) ,libnativeloader ,动态链接器。
其中:
-
Art虚拟机负责在启动时创建PathClassLoader 和调用libnativeloader创建对应的命名空间,此外,其他ClassLoader 在它们首次加载动态库时,也会创建对应命名空间。
虚拟机对于动态库的加载,会委托给
libnativeloader
进行,加载成功后则负责调用其**JNI_OnLoad()**方法。 -
libnativeloader
是虚拟机和动态链接器之间的桥梁。- 负责调用动态链接器为每个ClassLoader创建应用命名空间 ,关键参数是应用动态库目录。
- 负责维护ClassLoader和应用命名空间直接的映射关系。
- 负责通过当前ClassLoader判断库是否已经被加载等,如果没有加载过,则找到其命名空间,交给动态连接器去处理。
-
动态链接器负责实现加载动态库到内存 和命名空间委托机制。
- 递归解析目标动态库的所有依赖,创建LoadTask。这个过程会分别尝试从缓存、磁盘和Link命名空间中查找。
- 把上一步中所有未加载到内存的动态库使用匿名内存映射加载到内存。
- 为加载的所有动态库进行进一步ELF文件的解析和重定位。