文章目录
- 一、问题与场景
- 二、初步分析
- 三、详细分析
- do_dlopen() [linker.cpp]
- find_library() [linker.cpp]
- find_libraries() [linker.cpp]
- find_library_internal() [linker.cpp]
- find_loaded_library_by_soname() [linker.cpp]
- load_library() [linker.cpp]
- open_library() [linker.cpp]
- do_dlopen() [Linker.cpp]
- solist_get_head() [bionic/linker/linker_main.cpp]
- solist_add_soinfo() [bionic/linker/linker.cpp]
- __linker_init() [bionic/linker/linker_main.cpp]
- __linker_init_post_relocation [bionic/linker/linker_main.cpp]
- init_default_namespaces() [bionic/linker/linker.cpp]
- FindNamespaceByClassLoader() [system/core/libnativeloader/native_loader.cpp]
- class LibraryNamespaces::Create() [system/core/libnativeloader/native_loader.cpp]
- class LibraryNamespaces::CreateClassLoaderNamespace() [system/core/libnativeloader/native_loader.cpp]
- createClassloaderNamespace_native() [frameworks/base/core/jni/com_android_internal_os_ClassLoaderFactory.cpp]
- ClassLoaderFactory.createClassLoader() [rameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java]
- LoadApked.createOrUpdateClassLoaderLocked() [frameworks/base/core/java/android/app/LoadedApk.java]
- 四、结论与解决方案
一、问题与场景
起因是在开发过程中遇到的问题:
- 应用内部使用了jni加载自研的so模块,该so又依赖了libcurl.so,libcurl.so又依赖了libcrypto.so;
┏ MyDemo
┣━ jniLib
┣━━━ libmydemo.so
┣━━━ libcurl.so
┣━━━ libcrypto.so
- 测试中当应用作为第三方应用安装到设备时(Android P),运行正常;当应用作为系统应用集成至
system/app
目录下时,出现如下错误(找不到对应的符号sk_pop_free_ex)
java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "sk_pop_free_ex" referenced by "/system/app/MyDemo/lib/arm64/libcurl.so"...
二、初步分析
- 首先为了方便,应用内集成的libcurl.so、libcrypto.so直接拷贝自Android Q系统源码工程;
- 分析libcrypto.so的符号表,Android P版本确实比Android Q版本少了sk_pop_free_ex接口;
- 结合现象,推测作为系统应用集成的方式,应该加载的是系统的libcrypto.so而非应用内包含的so;
三、详细分析
分析思路
- Android APP调用jni接口之前,通常要先在静态代码块中加载指定的so,加载的代码如下,使用了
System.loadLibrary("soname")
,那就以System.loadLibrary作为起点,分析内部so的加载流程;
- 本文涉及/bionic/linker模块调试,调试日志的开启方式(需要根据包名设置)和日志输出格式参考如下:
adb shell setprop debug.ld.app.com.example.mydemo dlopen,dlerror
LD_LOG(kLogDlopen, "log is %s", %s);
- 本文的分析基于Android P平台系统代码;
- 根据参考文档,直接梳理出System.loadLibrary的调用流程:
┌─ void loadLibrary(String libname) [java/java/lang/System.java]
┆
├─ void loadLibrary0(ClassLoader loader, String libname) [java/java/lang/Runtime.java]
┆
├─ String nativeLoad(String filename, ClassLoader loader) [java/java/lang/Runtime.java]
┆
├─ jstring Runtime_nativeLoad((JNIEnv* env, jclass ignored, jstring javaFilename, jobject javaLoader)[main/native/Runtime.c]
┆
├─ jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename, jobject javaLoader) [art/openjdkjvm/OpenjdkJvm.cc]
┆
├─ bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, std::string* error_msg) [art/runtime/java_vm_ext.cc]
┆
├─ void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path, jobject class_loader, jstring library_path, bool* needs_native_bridge, std::string* error_msg) [libnativeloader/native_loader.cpp]
┆
├─ void* dlopen(const char* filename, int flag) [bionic/libdl/libdl.cpp]
┆
├─ void* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo, const void* caller_addr) [bionic/linker/linker.cpp
- 可以看到最终调用到linker.cpp内的do_dlopen()函数.
★我们再来分析do_dlopen()的调用流程:
do_dlopen() [linker.cpp]
void* do_dlopen(const char* name, int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
std::string trace_prefix = std::string("dlopen: ") + (name == nullptr ? "(nullptr)" : name);
...
soinfo* const caller = find_containing_library(caller_addr);
android_namespace_t* ns = get_caller_namespace(caller);
LD_LOG(kLogDlopen,
"dlopen(name=\"%s\", flags=0x%x, extinfo=%s, caller=\"%s\", caller_ns=%s@%p) ...",
name,
flags,
android_dlextinfo_to_string(extinfo).c_str(),
caller == nullptr ? "(null)" : caller->get_realpath(),
ns == nullptr ? "(null)" : ns->get_name(),
ns);
...
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
...
return nullptr;
}
这里根据日志可以看出,当name为libmydemo.so时/system/app/MyDemo/lib/arm64/libmydemo.so
,caller是调用者so库/system/lib64/libnativeloader.so
,这里还涉及另外一个变量caller_ns(android_namespace_t),是Android O开始专门为so动态链接加载设计的命名空间(参考《基于命名空间的动态链接—— 隔离 Android 中应用程序和系统的本地库》),这是这个问题中分析的重点对象;
★do_dlopen
调用了find_library
方法,查看find_library
方法:
find_library() [linker.cpp]
static soinfo* find_library(android_namespace_t* ns,
const char* name, int rtld_flags,
const android_dlextinfo* extinfo,
soinfo* needed_by) {
soinfo* si = nullptr;
if (name == nullptr) {
si = solist_get_somain();
} else if (!find_libraries(ns,
needed_by,
&name,
1,
&si,
nullptr,
0,
rtld_flags,
extinfo,
false /* add_as_children */,
true /* search_linked_namespaces */)) {
if (si != nullptr) {
soinfo_unload(si);
}
return nullptr;
}
si->increment_ref_count();
return si;
}
find_library
中又调用了find_libraries
方法,注意这里传入的name
参数即为do_dlopen
中要加载的libcurl.so, library_names_count
参数是1,add_as_children
为false,search_linked_namespaces
为true;
★继续查看find_libraries
方法:
find_libraries() [linker.cpp]
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,
bool search_linked_namespaces,
std::vector<android_namespace_t*>* namespaces) {
// Step 0: prepare.
std::unordered_map<const soinfo*, ElfReader> readers_map;
LoadTaskList load_tasks;
for (size_t i = 0; i < library_names_count; ++i) {
const char* name = library_names[i];
load_tasks.push_back(LoadTask::create(name, start_with, ns, &readers_map));
}
...
for (size_t i = 0; i<load_tasks.size(); ++i) {
LoadTask* task = load_tasks[i];
soinfo* needed_by = task->get_needed_by();
// Note: start from the namespace that is stored in the LoadTask. This namespace
// is different from the current namespace when the LoadTask is for a transitive
// dependency and the lib that created the LoadTask is not found in the
// current namespace but in one of the linked namespace.
if (!find_library_internal(const_cast<android_namespace_t*>(task->get_start_from()),
task,
&zip_archive_cache,
&load_tasks,
rtld_flags,
search_linked_namespaces || is_dt_needed)) {
return false;
}
soinfo* si = task->get_soinfo();
if (is_dt_needed) {
needed_by->add_child(si);
}
// When ld_preloads is not null, the first
// ld_preloads_count libs are in fact ld_preloads.
if (ld_preloads != nullptr && soinfos_count < ld_preloads_count) {
ld_preloads->push_back(si);
}
if (soinfos_count < library_names_count) {
soinfos[soinfos_count++] = si;
}
}
// Step 2: Load libraries in random order (see b/24047022)
LoadTaskList load_list;
for (auto&& task : load_tasks) {
soinfo* si = task->get_soinfo();
auto pred = [&](const LoadTask* t) {
return t->get_soinfo() == si;
};
if (!si->is_linked() &&
std::find_if(load_list.begin(), load_list.end(), pred) == load_list.end() ) {
load_list.push_back(task);
}
}
shuffle(&load_list);
for (auto&& task : load_list) {
if (!task->load()) {
return false;
}
}
...
// Step 6: Link all local groups
for (auto root : local_group_roots) {
soinfo_list_t local_group;
android_namespace_t* local_group_ns = root->get_primary_namespace();
walk_dependencies_tree(root,
[&] (soinfo* si) {
if (local_group_ns->is_accessible(si)) {
local_group.push_back(si);
return kWalkContinue;
} else {
return kWalkSkip;
}
});
soinfo_list_t global_group = local_group_ns->get_global_group();
bool linked = local_group.visit([&](soinfo* si) {
// Even though local group may contain accessible soinfos from other namesapces
// we should avoid linking them (because if they are not linked -> they
// are in the local_group_roots and will be linked later).
if (!si->is_linked() && si->get_primary_namespace() == local_group_ns) {
if (!si->link_image(global_group, local_group, extinfo) ||
!get_cfi_shadow()->AfterLoad(si, solist_get_head())) {
return false;
}
}
return true;
});
if (!linked) {
return false;
}
}
// Step 7: Mark all load_tasks as linked and increment refcounts
// for references between load_groups (at this point it does not matter if
// referenced load_groups were loaded by previous dlopen or as part of this
// one on step 6)
if (start_with != nullptr && add_as_children) {
start_with->set_linked();
}
for (auto&& task : load_tasks) {
soinfo* si = task->get_soinfo();
si->set_linked();
}
for (auto&& task : load_tasks) {
soinfo* si = task->get_soinfo();
soinfo* needed_by = task->get_needed_by();
if (needed_by != nullptr &&
needed_by != start_with &&
needed_by->get_local_group_root() != si->get_local_group_root()) {
si->increment_ref_count();
}
}
return true;
}
这里有三个重要的阶段,第一是创建了一个加载任务列表LoadTaskList load_tasks
,里面仅包含了一个加载libmydemo的任务;第二是遍历load_tasks
,继续调用find_library_internal
实现加载;第三是遍历load_list
(load_list
是load_tasks
的去重集合)并调用task->load()
对task中指定的so进行真正的加载;
★继续往下查看find_library_internal
方法:
find_library_internal() [linker.cpp]
static bool find_library_internal(android_namespace_t* ns,
LoadTask* task,
ZipArchiveCache* zip_archive_cache,
LoadTaskList* load_tasks,
int rtld_flags,
bool search_linked_namespaces) {
soinfo* candidate;
if (find_loaded_library_by_soname(ns, task->get_name(), search_linked_namespaces, &candidate)) {
task->set_soinfo(candidate);
return true;
}
// Library might still be loaded, the accurate detection
// of this fact is done by load_library.
TRACE("[ \"%s\" find_loaded_library_by_soname failed (*candidate=%s@%p). Trying harder...]",
task->get_name(), candidate == nullptr ? "n/a" : candidate->get_realpath(), candidate);
if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags, search_linked_namespaces)) {
return true;
}
if (search_linked_namespaces) {
// if a library was not found - look into linked namespaces
// preserve current dlerror in the case it fails.
DlErrorRestorer dlerror_restorer;
for (auto& linked_namespace : ns->linked_namespaces()) {
if (find_library_in_linked_namespace(linked_namespace,
task)) {
if (task->get_soinfo() == nullptr) {
// try to load the library - once namespace boundary is crossed
// we need to load a library within separate load_group
// to avoid using symbols from foreign namespace while.
//
// However, actual linking is deferred until when the global group
// is fully identified and is applied to all namespaces.
// Otherwise, the libs in the linked namespace won't get symbols from
// the global group.
if (load_library(linked_namespace.linked_namespace(), task, zip_archive_cache, load_tasks, rtld_flags, false)) {
return true;
}
} else {
// lib is already loaded
return true;
}
}
}
}
return false;
}
如上代码分四个步骤:
第一步通过find_loaded_library_by_soname()
判断传入task中指定的so是否已加载过;
如果没有加载过,第二步通过load_library()
加载task中指定的so;
第三、四步和一、二步类似,区别是传入的namespace从当前ns->linked_namespaces()即当前命名空间链接的命名空间中遍历得到;
通过增加日志调试发现,在作为系统APP集成的问题场景下,find_loaded_library_by_soname
返回的是true,而作为三方APP安装的场景下返回的是false;
★首先看一下find_loaded_library_by_soname
对应的实现:
find_loaded_library_by_soname() [linker.cpp]
// Returns true if library was found and false otherwise
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;
}
bool found = find_loaded_library_by_soname(ns, name, candidate);
if (!found && search_linked_namespaces) {
// if a library was not found - look into linked namespaces
for (auto& link : ns->linked_namespaces()) {
if (!link.is_accessible(name)) {
continue;
}
android_namespace_t* linked_ns = link.linked_namespace();
if (find_loaded_library_by_soname(linked_ns, name, candidate)) {
return true;
}
}
}
return found;
}
// 真正的find_loaded_library_by_soname逻辑
static bool find_loaded_library_by_soname(android_namespace_t* ns,
const char* name,
soinfo** candidate) {
return !ns->soinfo_list().visit([&](soinfo* si) {
const char* soname = si->get_soname();
if (soname != nullptr && (strcmp(name, soname) == 0)) {
*candidate = si;
return false;
}
return true;
});
}
逻辑其实并不复杂:
- 首先排除name中包含’/'的情况,即name为一个so文件路径,例如从
do_dlopen
调用传过来的name为/system/app/MyDemo/lib/arm64/libmydemo.so
,则find_loaded_library_by_soname
直接返回false; - 然后调用真正逻辑的
find_loaded_library_by_soname
方法,该方法从传入的命名空间ns中获取一个关联的so信息列表并遍历,检查是否包含当前要加载的soname; - 如果没有找到,则获取命名空间ns链接的命名空间linked_ns,遍历检查linked_ns观察的so中是否包含soname;
★对于从find_libraries
调用过来,并且遍历第一个load_task
来讲,这里直接返回了false,那么libmydemo.so关联的libcurl.so及libcrypto.so又是怎样关联并加载的,具体的调用流程又是怎样的呢?不要忘了find_loaded_library_by_soname
返回false之后,还有一个load_library
方法:
load_library() [linker.cpp]
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();
off64_t file_offset;
std::string realpath;
if (extinfo != nullptr && (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) != 0) {
file_offset = 0;
if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) {
file_offset = extinfo->library_fd_offset;
}
if (!realpath_fd(extinfo->library_fd, &realpath)) {
PRINT("warning: unable to get realpath for the library \"%s\" by extinfo->library_fd. "
"Will use given name.", name);
realpath = name;
}
task->set_fd(extinfo->library_fd, false);
task->set_file_offset(file_offset);
return load_library(ns, task, load_tasks, rtld_flags, realpath, search_linked_namespaces);
}
// Open the file.
int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath);
if (fd == -1) {
DL_ERR("library \"%s\" not found", name);
return false;
}
task->set_fd(fd, true);
task->set_file_offset(file_offset);
return load_library(ns, task, load_tasks, rtld_flags, realpath, search_linked_namespaces);
}
// 真正的load_library逻辑
static bool load_library(android_namespace_t* ns,
LoadTask* task,
LoadTaskList* load_tasks,
int rtld_flags,
const std::string& realpath,
bool search_linked_namespaces) {
...
soinfo* si = soinfo_alloc(ns, realpath.c_str(), &file_stat, file_offset, rtld_flags);
if (si == nullptr) {
return false;
}
...
task->set_soinfo(si);
// Read the ELF header and some of the segments.
if (!task->read(realpath.c_str(), file_stat.st_size)) {
soinfo_free(si);
task->set_soinfo(nullptr);
return false;
}
// find and set DT_RUNPATH and dt_soname
// 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();
for (const ElfW(Dyn)* d = elf_reader.dynamic(); d->d_tag != DT_NULL; ++d) {
if (d->d_tag == DT_RUNPATH) {
si->set_dt_runpath(elf_reader.get_string(d->d_un.d_val));
}
if (d->d_tag == DT_SONAME) {
si->set_soname(elf_reader.get_string(d->d_un.d_val));
}
}
for_each_dt_needed(task->get_elf_reader(), [&](const char* name) {
load_tasks->push_back(LoadTask::create(name, si, ns, task->get_readers_map()));
});
return true;
}
load_library
对应的两个方法也执行了两个阶段: 打开so库以及载入so库
打开so这一步,如果传入的extinfo->library_fd
参数有效,则直接使用该文件句柄fd,否则通过调用open_library
方法获取当前so的文件句柄fd;
读取so库这一步在真正逻辑的load_library
方法内,调用task->read()
实现读取task指定so内部信息,在这里获取到当前so依赖的so名称,并创建LoadTask最终添加到load_tasks列表中;
★看一下open_library
的逻辑:
open_library() [linker.cpp]
static int open_library(android_namespace_t* ns,
ZipArchiveCache* zip_archive_cache,
const char* name, soinfo *needed_by,
off64_t* file_offset, std::string* realpath) {
TRACE("[ opening %s at namespace %s]", name, ns->get_name());
// If the name contains a slash, we should attempt to open it directly and not search the paths.
if (strchr(name, '/') != nullptr) {
int fd = -1;
if (strstr(name, kZipFileSeparator) != nullptr) {
fd = open_library_in_zipfile(zip_archive_cache, name, file_offset, realpath);
}
if (fd == -1) {
fd = TEMP_FAILURE_RETRY(open(name, O_RDONLY | O_CLOEXEC));
if (fd != -1) {
*file_offset = 0;
if (!realpath_fd(fd, realpath)) {
PRINT("warning: unable to get realpath for the library \"%s\". Will use given path.", name);
*realpath = name;
}
}
}
return fd;
}
// Otherwise we try LD_LIBRARY_PATH first, and fall back to the default library path
int fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_ld_library_paths(), realpath);
if (fd == -1 && needed_by != nullptr) {
fd = open_library_on_paths(zip_archive_cache, name, file_offset, needed_by->get_dt_runpath(), realpath);
// Check if the library is accessible
if (fd != -1 && !ns->is_accessible(*realpath)) {
fd = -1;
}
}
if (fd == -1) {
fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_default_library_paths(), realpath);
}
// TODO(dimitry): workaround for http://b/26394120 (the grey-list)
if (fd == -1 && ns->is_greylist_enabled() && is_greylisted(ns, name, needed_by)) {
// try searching for it on default_namespace default_library_path
fd = open_library_on_paths(zip_archive_cache, name, file_offset,
g_default_namespace.get_default_library_paths(), realpath);
}
// END OF WORKAROUND
return fd;
}
open_library()
方法传入命名空间ns,so文件名称name
,通过string* realpath返回一个字符串路径;
- 如果
name
是一个地址,直接返回-1; - 如果
name
是一个zip文件的地址,直接在zip文件中打开并返回fd; - 尝试从
ns->get_ld_library_paths()
中打开so文件,根据调试日志,这个列表为空; - 尝试从
needed_by->get_dt_runpath()
中打开so文件,need_by
即此so的调用者so,根据调试日志,这个列表也为空; - 尝试从
ns->get_default_library_paths()
中打开so文件,根据调试日志这个列表为[/system/app/MyDemo/lib/arm64, /system/app/MyDemo/MyDemo.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64]
; - 尝试从
g_default_namespace.get_default_library_paths()
中打开so,根据调试日志,这个列表为[/system/lib64, /vendor/lib64]
;
所以这里的逻辑解释了之前的两个疑问:
- 在执行
load_library(libmydemo, ...)
之后,通过task->read()
读取到其关联的libcurl等库名称;find_library_internal
调用了load_library(libmydemo, ...)
内部为关联的libcurl等库创建了LoadTask
对象,并添加到load_tasks
;find_library_internal(libmydemo, ...)
执行返回到find_libraries()
方法之后,for循环内继续执行find_library_internal(libcurl, ...)
、find_library_internal(libcrypto, ...)
.等,do_dlopen()
方法也正是通过这样的方式递归加载顶层so所依赖的所有的so库的;
现在回过头来看一下之前的问题对应的逻辑:
do_dlopen()
调用find_libraries()
通过find_library_internal()
加载libmydemo.so成功;- for循环执行
find_library_internal()
加载libcurl.so成功;- for循环执行
find_library_internal(libcurl)
加载libcrypto之后报错(找不到对应的符号sk_pop_free_ex);
★定位一下dlopen failed: cannot locate symbol
,这个错误代码在linker.cpp的bool soinfo::relocate()
方法中,
唯一调用该方法的地方在bool soinfo::link_image()
方法中,而唯一调用link_image
方法的地方在find_libraries
方法中,具体在注释// Step 6: Link all local groups
的段落,在循环执行find_library_internal
方法结束之后,看来问题就出现在find_library_internal
方法对libcrypto.so的加载;
★根据前面的逻辑分析,添加调试日志,很快发现,当传入的name是libcrypto.so时, find_library_internal()
方法第一阶段的find_loaded_library_by_soname
返回了false;
而在find_loaded_library_by_soname
中添加的日志显示,传入的ns及关联so信息列表ns->soinfo_list如下:
ns->get_name(): classloader-namespace;
ns->soinfo_list(): [
ld-android.so
linux-vdso.so.1
(null)
ld-android.so
linux-vdso.so.1
libandroid_runtime.so
libbase.so
libbinder.so
libcutils.so
libhwbinder.so
liblog.so
libnativeloader.so
libutils.so
libwilhelm.so
libcpp.so
libc.so
libm.so
libdl.so
libbpf.so
libnetdutils.so
libmemtrack.so
libandroidfw.so
libappfuse.so
libcrypto.so
libnativehelper.so
libdebuggerd_client.so
libui.so
libgraphicsenv.so
libgui.so
libsensor.so
libinput.so
libcamera_client.so
libcamera_metadata.so
libsqlite.so
libEGL.so
libGLESv1_CM.so
libGLESv2.so
libvulkan.so
libziparchive.so
libETC1.so
libhardware.so
libhardware_legacy.so
libselinux.so
libicuuc.so
libmedia.so
libmediametrics.so
libaudioclient.so
libjpeg.so
libusbhost.so
libharfbuzz_ng.so
libz.so
libpdfium.so
libimg_utils.so
libnetd_client.so
libsoundtrigger.so
libminikin.so
libprocessgroup.so
libnativebridge.so
libmemunreachable.so
libhidlbase.so
libhidltransport.so
libvintf.so
libnativewindow.so
libhwui.so
libstatslog.so
libdiagnostic.so
libvndksupport.so
libmedia_omx.so
libmediaextractor.so
libaudiomanager.so
libstagefright.so
libstagefright_foundation.so
libstagefright_http_support.so
android.hardware.memtrack@1.0.so
android.hardware.graphics.allocator@2.0.so
android.hardware.graphics.common@1.1.so
android.hardware.graphics.mapper@2.0.so
android.hardware.graphics.mapper@2.1.so
android.hardware.configstore@1.0.so
android.hardware.configstore-utils.so
libsync.so
libutilscallstack.so
libbufferhubqueue.so
libpdx_default_transport.so
android.hidl.token@1.0-utils.so
android.hardware.graphics.bufferqueue@1.0.so
libclang_rt.ubsan_standalone-aarch64-android.so
libicui18n.so
libbacktrace.so
android.hardware.graphics.common@1.0.so
libpcre2.so
libpackagelistparser.so
libsonivox.so
libexpat.so
libaudioutils.so
libmedia_helper.so
libaudioservice.so
libft2.so
libhidl-gen-utils.so
libtinyxml2.so
libdng_sdk.so
libheif.so
libpiex.so
libpng.so
libprotobuf-cpp-lite.so
libRScpp.so
android.hardware.media.omx@1.0.so
libdrmframework.so
libion.so
libmediautils.so
libstagefright_codecbase.so
libstagefright_omx_utils.so
libstagefright_xmlparser.so
libhidlallocatorutils.so
libhidlmemory.so
android.hidl.allocator@1.0.so
android.hidl.memory@1.0.so
android.hardware.cas.native@1.0.so
android.hardware.configstore@1.1.so
android.hidl.token@1.0.so
android.hardware.media@1.0.so
libunwind.so
libunwindstack.so
libdexfile.so
libstdcpp.so
libspeexresampler.so
android.hidl.memory.token@1.0.so
android.hardware.cas@1.0.so
liblzma.so
libavenhancements.so
libstagefright_httplive.so
libmediaplayerservice.so
android.hidl.base@1.0.so
libstagefright_omx.so
libmediadrm.so
libpowermanager.so
libstagefright_bufferqueue_helper.so
libmediadrmmetrics_lite.so
android.hardware.drm@1.0.so
android.hardware.drm@1.1.so
libart.so
liblz4.so
libmetricslogger.so
libtombstoned_client.so
libsigchain.so
boot.oat
boot-QPerformance.oat
boot-UxPerformance.oat
boot-core-oj.oat
boot-core-libart.oat
boot-conscrypt.oat
boot-okhttp.oat
boot-bouncycastle.oat
boot-apache-xml.oat
boot-ext.oat
boot-framework.oat
boot-telephony-common.oat
boot-voip-common.oat
boot-ims-common.oat
boot-android.hidl.base-V1.0-java.oat
boot-android.hidl.manager-V1.0-java.oat
boot-framework-oahl-backward-compatibility.oat
boot-android.test.base.oat
boot-android.car.oat
boot-tcmiface.oat
boot-WfdCommon.oat
boot-telephony-ext.oat
boot-bmmcamera.oat
libadbconnection.so
libandroid.so
libaaudio.so
libcamera2ndk.so
libmediandk.so
libmedia_jni.so
libmidi.so
libmtp.so
libexif.so
libasyncio.so
libGLESv3.so
libjnigraphics.so
libneuralnetworks.so
libtextclassifier_hash.so
android.hardware.neuralnetworks@1.0.so
android.hardware.neuralnetworks@1.1.so
libOpenMAXAL.so
libOpenSLES.so
libRS.so
android.hardware.renderscript@1.0.so
libwebviewchromium_plat_support.so
libcarvehiclemanager.so
android.hardware.automotive.vehicle@2.0.so
libjavacore.so
libopenjdk.so
libcurl.so
libopenjdkjvm.so
libart-compiler.so
libvixl-arm.so
libvixl-arm64.so
libqti-at.so
libxml2.so
libqti-perfd-client_system.so
vendor.qti.hardware.perf@1.0.so
libcompiler_rt.so
libwebviewchromium_loader.so
libjavacrypto.so
system@app@MyDemo@MyDemo.apk@classes.dex
libmydemo.so
]
可以看出名为classloader-namespace
的命名空间ns对应的soinfo_list里,包含了很多来来自于system/lib64
中的so,其中包括libcrypto
!!!继而在通过find_library_internal
方法时加载libcrypto时,被内部find_loaded_library_by_soname
方法判定为已加载过的so从而直接跳过加载;
★对比一下作为三方APP应用时,此处的日志信息:
ns->get_name(): classloader-namespace;
ns->soinfo_list(): [
(null),
libcurl.so
libmydemo.so
]
可以看出名为classloader-namespace
的命名空间ns对应的soinfo_list里,只包含了前面的libcurl;
这也验证了前文的猜测,果然作为系统应用,加载到libcrypto时,并未按照设想加载应用内的libcrypto.so,而是使用的是已经加载过的系统的同名libcrypto.so;
★那为什么作为系统应用加载libcurl时没有报错?继续对比一下:
作为系统应用,find_loaded_library_by_soname
传入libcurl
时的日志信息:
ns->get_name(): classloader-namespace;
ns->soinfo_list(): [
ld-android.so
linux-vdso.so.1
(null)
ld-android.so
linux-vdso.so.1
libandroid_runtime.so
libbase.so
libbinder.so
libcutils.so
libhwbinder.so
liblog.so
libnativeloader.so
libutils.so
libwilhelm.so
libcpp.so
libc.so
libm.so
libdl.so
libbpf.so
libnetdutils.so
libmemtrack.so
libandroidfw.so
libappfuse.so
libcrypto.so
libnativehelper.so
libdebuggerd_client.so
libui.so
libgraphicsenv.so
libgui.so
libsensor.so
libinput.so
libcamera_client.so
libcamera_metadata.so
libsqlite.so
libEGL.so
libGLESv1_CM.so
libGLESv2.so
libvulkan.so
libziparchive.so
libETC1.so
libhardware.so
libhardware_legacy.so
libselinux.so
libicuuc.so
libmedia.so
libmediametrics.so
libaudioclient.so
libjpeg.so
libusbhost.so
libharfbuzz_ng.so
libz.so
libpdfium.so
libimg_utils.so
libnetd_client.so
libsoundtrigger.so
libminikin.so
libprocessgroup.so
libnativebridge.so
libmemunreachable.so
libhidlbase.so
libhidltransport.so
libvintf.so
libnativewindow.so
libhwui.so
libstatslog.so
libdiagnostic.so
libvndksupport.so
libmedia_omx.so
libmediaextractor.so
libaudiomanager.so
libstagefright.so
libstagefright_foundation.so
libstagefright_http_support.so
android.hardware.memtrack@1.0.so
android.hardware.graphics.allocator@2.0.so
android.hardware.graphics.common@1.1.so
android.hardware.graphics.mapper@2.0.so
android.hardware.graphics.mapper@2.1.so
android.hardware.configstore@1.0.so
android.hardware.configstore-utils.so
libsync.so
libutilscallstack.so
libbufferhubqueue.so
libpdx_default_transport.so
android.hidl.token@1.0-utils.so
android.hardware.graphics.bufferqueue@1.0.so
libclang_rt.ubsan_standalone-aarch64-android.so
libicui18n.so
libbacktrace.so
android.hardware.graphics.common@1.0.so
libpcre2.so
libpackagelistparser.so
libsonivox.so
libexpat.so
libaudioutils.so
libmedia_helper.so
libaudioservice.so
libft2.so
libhidl-gen-utils.so
libtinyxml2.so
libdng_sdk.so
libheif.so
libpiex.so
libpng.so
libprotobuf-cpp-lite.so
libRScpp.so
android.hardware.media.omx@1.0.so
libdrmframework.so
libion.so
libmediautils.so
libstagefright_codecbase.so
libstagefright_omx_utils.so
libstagefright_xmlparser.so
libhidlallocatorutils.so
libhidlmemory.so
android.hidl.allocator@1.0.so
android.hidl.memory@1.0.so
android.hardware.cas.native@1.0.so
android.hardware.configstore@1.1.so
android.hidl.token@1.0.so
android.hardware.media@1.0.so
libunwind.so
libunwindstack.so
libdexfile.so
libstdcpp.so
libspeexresampler.so
android.hidl.memory.token@1.0.so
android.hardware.cas@1.0.so
liblzma.so
libavenhancements.so
libstagefright_httplive.so
libmediaplayerservice.so
android.hidl.base@1.0.so
libstagefright_omx.so
libmediadrm.so
libpowermanager.so
libstagefright_bufferqueue_helper.so
libmediadrmmetrics_lite.so
android.hardware.drm@1.0.so
android.hardware.drm@1.1.so
libart.so
liblz4.so
libmetricslogger.so
libtombstoned_client.so
libsigchain.so
boot.oat
boot-QPerformance.oat
boot-UxPerformance.oat
boot-core-oj.oat
boot-core-libart.oat
boot-conscrypt.oat
boot-okhttp.oat
boot-bouncycastle.oat
boot-apache-xml.oat
boot-ext.oat
boot-framework.oat
boot-telephony-common.oat
boot-voip-common.oat
boot-ims-common.oat
boot-android.hidl.base-V1.0-java.oat
boot-android.hidl.manager-V1.0-java.oat
boot-framework-oahl-backward-compatibility.oat
boot-android.test.base.oat
boot-android.car.oat
boot-tcmiface.oat
boot-WfdCommon.oat
boot-telephony-ext.oat
boot-bmmcamera.oat
libadbconnection.so
libandroid.so
libaaudio.so
libcamera2ndk.so
libmediandk.so
libmedia_jni.so
libmidi.so
libmtp.so
libexif.so
libasyncio.so
libGLESv3.so
libjnigraphics.so
libneuralnetworks.so
libtextclassifier_hash.so
android.hardware.neuralnetworks@1.0.so
android.hardware.neuralnetworks@1.1.so
libOpenMAXAL.so
libOpenSLES.so
libRS.so
android.hardware.renderscript@1.0.so
libwebviewchromium_plat_support.so
libcarvehiclemanager.so
android.hardware.automotive.vehicle@2.0.so
libjavacore.so
libopenjdk.so
libcurl.so
libopenjdkjvm.so
libart-compiler.so
libvixl-arm.so
libvixl-arm64.so
libqti-at.so
libxml2.so
libqti-perfd-client_system.so
vendor.qti.hardware.perf@1.0.so
libcompiler_rt.so
libwebviewchromium_loader.so
libjavacrypto.so
system@app@MyDemo@MyDemo.apk@classes.dex
libmydemo.so
]
作为三方应用,find_loaded_library_by_soname
传入libcurl
时的日志信息:
ns->get_name(): classloader-namespace;
ns->soinfo_list(): [
(null)
libmydemo.so
]
可以看出虽然名为classloader-namespace
的命名空间ns对应的soinfo_list里包含了很多来来自于system/lib64
中的so,但是并没有包含系统的libcurl
,所以会继续从应用安装路径加载该so文件(逃过一劫–!);
★到这里问题就变成了:为什么系统应用和三方应用在加载so库时,classloader-namespace
的命名空间对应的soinfo_list不同?
要搞清楚造成soinfo_list差异的原因,需要回到do_dlopen()
方法,查看这个ns的来源,参考前文do_dlopen()
方法的代码,或者看这里的摘要:
do_dlopen() [Linker.cpp]
void* do_dlopen(const char* name, int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
std::string trace_prefix = std::string("dlopen: ") + (name == nullptr ? "(nullptr)" : name);
...
soinfo* const caller = find_containing_library(caller_addr);
android_namespace_t* ns = get_caller_namespace(caller);
...
if (extinfo != nullptr) {
if (extinfo->flags ...) {
...
return nullptr;
}
...
if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0) {
if (extinfo->library_namespace == nullptr) {
return nullptr;
}
ns = extinfo->library_namespace;
}
}
...
}
static android_namespace_t* get_caller_namespace(soinfo* caller) {
return caller != nullptr ? caller->get_primary_namespace() : g_anonymous_namespace;
}
soinfo* find_containing_library(const void* p) {
ElfW(Addr) address = reinterpret_cast<ElfW(Addr)>(p);
for (soinfo* si = solist_get_head(); si != nullptr; si = si->next) {
if (address >= si->base && address - si->base < si->size) {
return si;
}
}
return nullptr;
}
当caller不为空时,ns来自于caller->get_primary_namespace()
,查看这个类方法内部,返回的是class soinfo
的属性变量primary_namespace_
,而根据class soinfo
的代码[bionic/linker/linker_soinfo.cpp]
(这里就不贴了),可知,primary_namespace_
在class soinfo
的构造函数中赋值;当extinfo部位空时,且extinfo的条件满足前置条件时,ns被覆盖为extinfo->library_namespace
;
caller从find_containing_library(void *p)
方法中获取,传入的p调用者的地址,类似于一个索引,find_containing_library
方法内部根据这个地址在solist_get_head
起始的链表中查找符合的soinfo
对象指针并返回;
★solist_get_head()
方法声明在linker_main.cpp中,内部直接返回了一个全局变量static soinfo* solist
,查看solist链表的相关代码:
solist_get_head() [bionic/linker/linker_main.cpp]
soinfo* solist_get_head() {
return solist;
}
ElfW(Addr) __linker_init(void* raw_args) {
...
// Initialize static variables. Note that in order to
// get correct libdl_info we need to call constructors
// before get_libdl_info().
sonext = solist = get_libdl_info(kLinkerPath, linker_so, linker_link_map);
g_default_namespace.add_soinfo(solist);
...
}
void solist_add_soinfo(soinfo* si) {
sonext->next = si;
sonext = si;
}
从如上代码及注释中可以看出,solist在__linker_init
的时候被初始化并在头部节点保存libdl的信息(debug后得知是ld-android.so),调用solist_add_soinfo
方法可以在solist链表中追加节点;
★继续追溯solist_add_soinfo
这个方法,在soinfo_alloc
方法中找到唯一被调用的地方:
solist_add_soinfo() [bionic/linker/linker.cpp]
soinfo* soinfo_alloc(android_namespace_t* ns, const char* name,
struct stat* file_stat, off64_t file_offset,
uint32_t rtld_flags) {
if (strlen(name) >= PATH_MAX) {
async_safe_fatal("library name \"%s\" too long", name);
}
TRACE("name %s: allocating soinfo for ns=%p", name, ns);
soinfo* si = new (g_soinfo_allocator.alloc()) soinfo(ns, name, file_stat,
file_offset, rtld_flags);
solist_add_soinfo(si);
si->generate_handle();
ns->add_soinfo(si);
TRACE("name %s: allocated soinfo @ %p", name, si);
return si;
}
★而又在load_library
中找到soinfo_alloc
方法被调用,参考前文的代码,这里的逻辑之前并没有关注到,这里与新创建的soinfo绑定的ns正是传入给load_library
的ns;
重新梳理一下整个逻辑:
do_dlopen
方法接收一个so的name
和一个地址指针caller_addr
,从caller_addr
中获取soinfo *caller
,从caller
中获取android_namespace_t ns
(日志显示当name
为libcurl时,caller->relpath
为/system/lib64/libnativeloader.so
,当前ns->getname()
为classloader-namespace
,该ns后面也用于加载依赖的libcrypto),后面ns根据exinfo内容被覆盖为extinfo->library_namespace
;do_dlopen
方法调用find_library
、find_libraries
方法,find_libraries
内维护一个LoadTask列表
(初始内容为当前soname创建的task)并遍历其中的任务, 如果ns所关联的已加载过的soinfo_list中未包含当前的待加载的so的name,则最终调用到load_library
来从正确的路径读取name指定的so(例如作为三方应用时,从/data/app/com.example.mydemo-XTSDKLLYT78HGF9G6DF==/base.apk!/lib/arm64-v8a/
路径加载libcurl和libcrypto);load_library
内部首先通过open_library
方法获取对应name的so正确的路径,然后通过soinfo_alloc
方法创建对应name的so的soinfo并将其添加到linker_main.cpp中的solist
全局列表中,最后在load_library
内将创建的soinfo绑定到load_task
内部,并通过task->read()
读取当前so的内部信息,继而获取当前so依赖的soname并创建task
追加到LoadTask列表
;- 在
find_libraries
方法中,继续遍历LoadTask列表
直到所有的依赖都加载完毕,最后遍历去重后的LoadTask列表
并使用task->load()
执行真正的载入;
★根据重新梳理的逻辑,接下来分析的重点应该是当caller的relpath为/system/lib64/libnativeloader.so
时caller以及ns的来源;caller的来源这里涉及到linux进程内加载器/连接器
的启动与执行逻辑,涉及到比较专业的linux系统知识,具体可以参考《bionic linker代码分析》,这里仅关注以下几个点,加上分析调试日志的推测得出结论(可能存在不严谨或错误的阐述):
__linker_init() [bionic/linker/linker_main.cpp]
ElfW(Addr) __linker_init(void* raw_args) {
...
// Initialize static variables. Note that in order to
// get correct libdl_info we need to call constructors
// before get_libdl_info().
sonext = solist = get_libdl_info(kLinkerPath, linker_so, linker_link_map);
PRINT("__linker_init: solist=%s", solist->get_soname());
g_default_namespace.add_soinfo(solist);
// We have successfully fixed our own relocations. It's safe to run
// the main part of the linker now.
args.abort_message_ptr = &g_abort_message;
ElfW(Addr) start_address = __linker_init_post_relocation(args);
...
}
__linker_init
方法在进程初始化并启动程序之前,由内核调用,这里会调用__linker_init_post_relocation
方法;
★__linker_init_post_relocation
这个方法中有如下逻辑:
__linker_init_post_relocation [bionic/linker/linker_main.cpp]
static ElfW(Addr) __linker_init_post_relocation(KernelArgumentBlock& args) {
...
const char* executable_path = get_executable_path();
soinfo* si = soinfo_alloc(&g_default_namespace, executable_path, &file_stat, 0, RTLD_GLOBAL);
...
std::vector<android_namespace_t*> namespaces = init_default_namespaces(executable_path);
...
for (auto linked_ns : namespaces) {
if (linked_ns != &g_default_namespace) {
linked_ns->add_soinfo(somain);
somain->add_secondary_namespace(linked_ns);
}
}
...
// Load ld_preloads and dependencies.
std::vector<const char*> needed_library_name_list;
size_t ld_preloads_count = 0;
for (const auto& ld_preload_name : g_ld_preload_names) {
needed_library_name_list.push_back(ld_preload_name.c_str());
++ld_preloads_count;
}
for_each_dt_needed(si, [&](const char* name) {
needed_library_name_list.push_back(name);
});
const char** needed_library_names = &needed_library_name_list[0];
size_t needed_libraries_count = needed_library_name_list.size();
if (needed_libraries_count > 0 &&
!find_libraries(&g_default_namespace,
si,
needed_library_names,
needed_libraries_count,
nullptr,
&g_ld_preloads,
ld_preloads_count,
RTLD_GLOBAL,
nullptr,
true /* add_as_children */,
true /* search_linked_namespaces */,
&namespaces)) {
__linker_cannot_link(g_argv[0]);
}
...
}
首先通过调用init_default_namespaces
初始化一系列默认的命名空间,接着调用find_libraries
方法加载 si, si初始化时有两个关键参数:
-
&g_default_namespace
与si->primary_namespace_
绑定,在后面的init_default_namespaces
中被初始化; -
executable_path
从get_executable_path
方法获取,为一个指向当前进程程序
的链接文件地址/proc/self/exe
;
★init_default_namespaces
里面涉及到通过读取配置文件初始指定的命名空间,这里只关注g_default_namespace
的初始化,:
init_default_namespaces() [bionic/linker/linker.cpp]
std::vector<android_namespace_t*> init_default_namespaces(const char* executable_path) {
g_default_namespace.set_name("(default)");
...
const Config* config = nullptr;
...
if (!Config::read_binary_config(ld_config_file_path.c_str(),
executable_path,
g_is_asan,
&config,
&error_msg)) {
if (!error_msg.empty()) {
DL_WARN("Warning: couldn't read \"%s\" for \"%s\" (using default configuration instead): %s",
ld_config_file_path.c_str(),
executable_path,
error_msg.c_str());
}
config = nullptr;
}
if (config == nullptr) {
return init_default_namespace_no_config(g_is_asan);
}
const auto& namespace_configs = config->namespace_configs();
std::unordered_map<std::string, android_namespace_t*> namespaces;
// 1. Initialize default namespace
const NamespaceConfig* default_ns_config = config->default_namespace_config();
g_default_namespace.set_isolated(default_ns_config->isolated());
g_default_namespace.set_default_library_paths(default_ns_config->search_paths());
g_default_namespace.set_permitted_paths(default_ns_config->permitted_paths());
namespaces[default_ns_config->name()] = &g_default_namespace;
if (default_ns_config->visible()) {
g_exported_namespaces[default_ns_config->name()] = &g_default_namespace;
}
...
g_default_namespace
在这里被命名为"(default)",并通过其set_default_library_paths
和set_permitted_paths
接口设置了默认的路径和豁免路径;
★回到__linker_init_post_relocation
代码继续分析逻辑:
在之后构建了列表needed_library_name_list
,该列表的内容来自两个地方:
- g_ld_preload_names, 这个列表的值从环境变量中解析"LD_PRELOAD";
- 来自si的dt_needed(即si所依赖的so,参考[《DT_NEEDED 的解释》];())
- 调用
find_libraries()
方法,传入g_default_namespace
和needed_library_name_list
递归加载当前进程程序的所有依赖库(search_linked_namespaces
为true),该调用过程中会调用solist_add_soinfo
方法,将已加载的库添加到linker_main.cpp里的solist
链表中;
★通过增加调试日志并测试,发现启动应用的时候并不会触发linker程序的__linker_init
逻辑,这里推测是因为应用进程由zygote进程fork出来,共享了zygote进程的linker资源,zygote进程对应的程序为/system/bin/app_process
,查看app_process
程序的依赖库,这也解释了为什么System.loadLibrary(libcurl)
调用到do_dlopen()
时,caller->get_relpath()
的是libnativeloader
,应该是在zygote启动时通过__linker_init
加载了进来, caller_ns
显示为(default)
:
★接下来问题转变为分析当caller
为libnativeloader
时,namespace
(classloader-namespace
)的来源,及其soinfo_list
的来源和应用作为系统和三方时造成差异的原因;★首先定位到do_dlopen()
方法的上一级,即caller_addr
对应的libnativeloader源码位置:####OpenNativeLibrary() [system/core/libnativeloader/native_loader.cpp]
void* OpenNativeLibrary(JNIEnv* env,
int32_t target_sdk_version,
const char* path,
jobject class_loader,
jstring library_path,
bool* needs_native_bridge,
std::string* error_msg) {
#if defined(__ANDROID__)
...
NativeLoaderNamespace ns;
if (!g_namespaces->FindNamespaceByClassLoader(env, class_loader, &ns)) {
// This is the case where the classloader was not created by ApplicationLoaders
// In this case we create an isolated not-shared namespace for it.
ALOGD("OpenNativeLibrary: Create class_loader[%s] g_namespaces is_shared=%d", _css_cp, false);
free(_css_cp);
if (!g_namespaces->Create(env,
target_sdk_version,
class_loader,
false /* is_shared */,
false /* is_for_vendor */,
library_path,
nullptr,
&ns,
error_msg)) {
return nullptr;
}
}
if (ns.is_android_namespace()) {
android_dlextinfo extinfo;
extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE;
extinfo.library_namespace = ns.get_android_ns();
void* handle = android_dlopen_ext(path, RTLD_NOW, &extinfo);
if (handle == nullptr) {
*error_msg = dlerror();
}
*needs_native_bridge = false;
return handle;
} else {
void* handle = NativeBridgeLoadLibraryExt(path, RTLD_NOW, ns.get_native_bridge_ns());
if (handle == nullptr) {
*error_msg = NativeBridgeGetError();
}
*needs_native_bridge = true;
return handle;
}
#else
...
}
根据调试日志分析如上代码:
首先创建局部对象NativeLoaderNamespace ns
并通过g_namespaces->FindNamespaceByClassLoader(...,&ns)
尝试获取NativeLoaderNamespace ns
,g_namespaces
是一个全局LibraryNamespaces
对象,封装了namespace有关的一些列函数动作,调试日志显示这里g_namespaces->FindNamespaceByClassLoader(...,&ns)
返回的是true,表明ns已被找到;
然后判断ns.is_android_namespace()
是否满足,创建android_dlextinfo extinfo
局部对象,并对extinfo
的flags和library_namespace属性赋值;
之后调用android_dlopen_ext()
方法,并传入path(so_filepath)
和extinfo
;注意这里的flags
赋值为ANDROID_DLEXT_USE_NAMESPACE
,满足前文分析do_dlopen
时所述的条件,而extinfo.library_namespace
赋值为ns.get_android_ns()
,根据代码和调试日志,最终在do_dlopen
中被作为ns
使用;
★查看class LibraryNamespaces
的FindNamespaceByClassLoader
方法:
FindNamespaceByClassLoader() [system/core/libnativeloader/native_loader.cpp]
bool FindNamespaceByClassLoader(JNIEnv* env, jobject class_loader, NativeLoaderNamespace* ns) {
auto it = std::find_if(namespaces_.begin(), namespaces_.end(),
[&](const std::pair<jweak, NativeLoaderNamespace>& value) {
return env->IsSameObject(value.first, class_loader);
});
if (it != namespaces_.end()) {
if (ns != nullptr) {
*ns = it->second;
}
return true;
}
return false;
}
分析代码逻辑,在pair<jweak, NativeLoaderNamespace> namespaces_
中查找匹配的class_loader
并将对应的value
赋值给ns返回指针;
★namespaces_
是一个全局PariMap变量,在class LibraryNamespaces
的Create
方法中有push的动作:
class LibraryNamespaces::Create() [system/core/libnativeloader/native_loader.cpp]
bool Create(JNIEnv* env,
uint32_t target_sdk_version,
jobject class_loader,
bool is_shared,
bool is_for_vendor,
jstring java_library_path,
jstring java_permitted_path,
NativeLoaderNamespace* ns,
std::string* error_msg) {
...
uint64_t namespace_type = ANDROID_NAMESPACE_TYPE_ISOLATED;
if (is_shared) {
namespace_type |= ANDROID_NAMESPACE_TYPE_SHARED;
}
if (target_sdk_version < 24) {
namespace_type |= ANDROID_NAMESPACE_TYPE_GREYLIST_ENABLED;
}
...
const char* namespace_name = kClassloaderNamespaceName; // classloader-namespace
...
NativeLoaderNamespace native_loader_ns;
if (!is_native_bridge) {
android_namespace_t* ns = android_create_namespace(namespace_name,
nullptr,
library_path.c_str(),
namespace_type,
permitted_path.c_str(),
parent_ns.get_android_ns());
if (ns == nullptr) {
*error_msg = dlerror();
return false;
}
// Note that when vendor_ns is not configured this function will return nullptr
// and it will result in linking vendor_public_libraries_ to the default namespace
// which is expected behavior in this case.
android_namespace_t* vendor_ns = android_get_exported_namespace(kVendorNamespaceName);
if (!android_link_namespaces(ns, nullptr, system_exposed_libraries.c_str())) {
*error_msg = dlerror();
return false;
}
if (vndk_ns != nullptr && !system_vndksp_libraries_.empty()) {
// vendor apks are allowed to use VNDK-SP libraries.
if (!android_link_namespaces(ns, vndk_ns, system_vndksp_libraries_.c_str())) {
*error_msg = dlerror();
return false;
}
}
if (!vendor_public_libraries_.empty()) {
if (!android_link_namespaces(ns, vendor_ns, vendor_public_libraries_.c_str())) {
*error_msg = dlerror();
return false;
}
}
native_loader_ns = NativeLoaderNamespace(ns);
} else {
...
}
namespaces_.push_back(std::make_pair(env->NewWeakGlobalRef(class_loader), native_loader_ns));
*ns = native_loader_ns;
return true;
}
is_native_bridge
表示是否在其他平台(比如x86)上加载arm程序和库,此处不考虑,关注为值false的逻辑:这里调用android_create_namespace
方法创建了名为classloader-namespace
的android_namespace_t* ns
,并在最后用这个android_namespace_t* ns
初始化了NativeLoaderNamespace native_loader_ns
赋值给ns并将指针返回;这里创建的android_namespace_t* ns
即为OpenNativeLibrary
方法中extinfo.library_namespace
,并在调用android_dlopen_ext
方法是传递给了do_dlopen
方法;
★android_create_namespace
方法最终调用到linker.cpp
中的create_namespace
方法:
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) {
if (parent_namespace == nullptr) {
// if parent_namespace is nullptr -> set it to the caller namespace
soinfo* caller_soinfo = find_containing_library(caller_addr);
parent_namespace = caller_soinfo != nullptr ?
caller_soinfo->get_primary_namespace() :
g_anonymous_namespace;
}
ProtectedDataGuard guard;
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);
android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t();
ns->set_name(name);
ns->set_isolated((type & ANDROID_NAMESPACE_TYPE_ISOLATED) != 0);
ns->set_greylist_enabled((type & ANDROID_NAMESPACE_TYPE_GREYLIST_ENABLED) != 0);
if ((type & ANDROID_NAMESPACE_TYPE_SHARED) != 0) {
// append parent namespace paths.
std::copy(parent_namespace->get_ld_library_paths().begin(),
parent_namespace->get_ld_library_paths().end(),
back_inserter(ld_library_paths));
std::copy(parent_namespace->get_default_library_paths().begin(),
parent_namespace->get_default_library_paths().end(),
back_inserter(default_library_paths));
std::copy(parent_namespace->get_permitted_paths().begin(),
parent_namespace->get_permitted_paths().end(),
back_inserter(permitted_paths));
// If shared - clone the parent namespace
add_soinfos_to_namespace(parent_namespace->soinfo_list(), ns);
// and copy parent namespace links
for (auto& link : parent_namespace->linked_namespaces()) {
ns->add_linked_namespace(link.linked_namespace(), link.shared_lib_sonames(),
link.allow_all_shared_libs());
}
} else {
// If not shared - copy only the shared group
add_soinfos_to_namespace(parent_namespace->get_shared_group(), ns);
}
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;
}
这个函数首先创建一个名为name
的android_namespace_t* ns
,然后根据参数设置其属性;
其中有一个比较关键的参数type
, 当type
设置中包含ANDROID_NAMESPACE_TYPE_SHARED
时,注释有如下三个操作(parent_namespace
即libnativeloader
的ns即前文__linker_init
方法创建的g_default_namespace
):
a) append parent namespace paths.
:添加parent_namespace
的ld_library_paths
、default_library_paths
、permitted_paths
到ns的对应属性列表中;
b) If shared - clone the parent namespace
:如果type
为"SHARED",则将parent_namespace
的soinfo_list
添加到ns(这里和系统应用的ns->soinfo_list
对应),同时将parent_namespace
的linked_namespaces
添加到ns中;
c) If not shared - copy only the shared group
:如果type
不为"SHARED",则仅添加parent_namespace
的shared_group
到ns中(根据调试日志这个列表为空,和三方应用的ns->soinfo_list
对应);
从LibraryNamespaces::Create
方法中可以看出,影响ns
classloader-namespace的soinfo_list
内容的正是传入的is_shared
这个参数;
★class LibraryNamespaces Create()
方法的另外一处调用CreateClassLoaderNamespace()
方法里面了:
class LibraryNamespaces::CreateClassLoaderNamespace() [system/core/libnativeloader/native_loader.cpp]
jstring CreateClassLoaderNamespace(JNIEnv* env,
int32_t target_sdk_version,
jobject class_loader,
bool is_shared,
bool is_for_vendor,
jstring library_path,
jstring permitted_path) {
#if defined(__ANDROID__)
std::lock_guard<std::mutex> guard(g_namespaces_mutex);
std::string error_msg;
NativeLoaderNamespace ns;
bool success = g_namespaces->Create(env,
target_sdk_version,
class_loader,
is_shared,
is_for_vendor,
library_path,
permitted_path,
&ns,
&error_msg);
if (!success) {
return env->NewStringUTF(error_msg.c_str());
}
#else
UNUSED(env, target_sdk_version, class_loader, is_shared, is_for_vendor,
library_path, permitted_path);
#endif
return nullptr;
}
创建NativeLoaderNamespace
的代码和OpenNativeLibrary
中的一致,调试日志显示该方法在OpenNativeLibrary
调用之前被调用,即在System.loadLibrary()
之前已经调用了,而is_shared
这个参数继续从外部传进来;
★追溯CreateClassLoaderNamespace
方法,对应JAVA层ClassLoaderFactory.java
中的createClassloaderNamespace接口,通过JNI调用com_android_internal_os_ClassLoaderFactory.cpp
中的createClassloaderNamespace_native
方法调进来;
createClassloaderNamespace_native() [frameworks/base/core/jni/com_android_internal_os_ClassLoaderFactory.cpp]
static jstring createClassloaderNamespace_native(JNIEnv* env,
jobject clazz,
jobject classLoader,
jint targetSdkVersion,
jstring librarySearchPath,
jstring libraryPermittedPath,
jboolean isShared,
jboolean isForVendor) {
return android::CreateClassLoaderNamespace(env, targetSdkVersion,
classLoader, isShared == JNI_TRUE,
isForVendor == JNI_TRUE,
librarySearchPath, libraryPermittedPath);
}
static const JNINativeMethod g_methods[] = {
{ "createClassloaderNamespace",
"(Ljava/lang/ClassLoader;ILjava/lang/String;Ljava/lang/String;ZZ)Ljava/lang/String;",
reinterpret_cast<void*>(createClassloaderNamespace_native) },
};
★ClassLoaderFactory.createClassloaderNamespace
接口仅在ClassLoaderFactory.createClassLoader
方法中有调用:
ClassLoaderFactory.createClassLoader() [rameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java]
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
int targetSdkVersion, boolean isNamespaceShared, String classloaderName) {
final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent,
classloaderName);
boolean isForVendor = false;
for (String path : dexPath.split(":")) {
if (path.startsWith("/vendor/")) {
isForVendor = true;
break;
}
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace");
String errorMessage = createClassloaderNamespace(classLoader,
targetSdkVersion,
librarySearchPath,
libraryPermittedPath,
isNamespaceShared,
isForVendor);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
if (errorMessage != null) {
throw new UnsatisfiedLinkError("Unable to create namespace for the classloader " +
classLoader + ": " + errorMessage);
}
return classLoader;
}
private static native String createClassloaderNamespace(ClassLoader classLoader,
int targetSdkVersion,
String librarySearchPath,
String libraryPermittedPath,
boolean isNamespaceShared,
boolean isForVendor);
}
isShared
在ClassLoader.java
的createClassLoader
方法中来自boolean isNamespaceShared
,同样从外部传入;
★ClassLoaderFactory.createClassLoader
的调用者有两个:
a) ZygoteInit.createPathClassLoader
方法,这显然不在APP的启动流程中;
b) ApplicationLoaders.getClassLoader
方法,这个方法里isNamespaceShared
来自isBundled
,接下来重点追溯这个方法,在LoadApked.java
的createOrUpdateClassLoaderLocked
方法中找到调用:
LoadApked.createOrUpdateClassLoaderLocked() [frameworks/base/core/java/android/app/LoadedApk.java]
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
if (mPackageName.equals("android")) {
// Note: This branch is taken for system server and we don't need to setup
// jit profiling support.
if (mClassLoader != null) {
// nothing to update
return;
}
if (mBaseClassLoader != null) {
mClassLoader = mBaseClassLoader;
} else {
mClassLoader = ClassLoader.getSystemClassLoader();
}
mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
return;
}
// Avoid the binder call when the package is the current application package.
// The activity manager will perform ensure that dexopt is performed before
// spinning up the process.
if (!Objects.equals(mPackageName, ActivityThread.currentPackageName()) && mIncludeCode) {
try {
ActivityThread.getPackageManager().notifyPackageUse(mPackageName,
PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
if (mRegisterPackage) {
try {
ActivityManager.getService().addPackageDependency(mPackageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
// Lists for the elements of zip/code and native libraries.
//
// Both lists are usually not empty. We expect on average one APK for the zip component,
// but shared libraries and splits are not uncommon. We expect at least three elements
// for native libraries (app-based, system, vendor). As such, give both some breathing
// space and initialize to a small value (instead of incurring growth code).
final List<String> zipPaths = new ArrayList<>(10);
final List<String> libPaths = new ArrayList<>(10);
boolean isBundledApp = mApplicationInfo.isSystemApp()
&& !mApplicationInfo.isUpdatedSystemApp();
// Vendor apks are treated as bundled only when /vendor/lib is in the default search
// paths. If not, they are treated as unbundled; access to system libs is limited.
// Having /vendor/lib in the default search paths means that all system processes
// are allowed to use any vendor library, which in turn means that system is dependent
// on vendor partition. In the contrary, not having /vendor/lib in the default search
// paths mean that the two partitions are separated and thus we can treat vendor apks
// as unbundled.
final String defaultSearchPaths = System.getProperty("java.library.path");
final boolean treatVendorApkAsUnbundled = !defaultSearchPaths.contains("/vendor/lib");
if (mApplicationInfo.getCodePath() != null
&& mApplicationInfo.isVendor() && treatVendorApkAsUnbundled) {
isBundledApp = false;
}
makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
String libraryPermittedPath = mDataDir;
if (isBundledApp) {
// For bundled apps, add the base directory of the app (e.g.,
// /system/app/Foo/) to the permitted paths so that it can load libraries
// embedded in module apks under the directory. For now, GmsCore is relying
// on this, but this isn't specific to the app. Also note that, we don't
// need to do this for unbundled apps as entire /data is already set to
// the permitted paths for them.
libraryPermittedPath += File.pathSeparator
+ Paths.get(getAppDir()).getParent().toString();
// This is necessary to grant bundled apps access to
// libraries located in subdirectories of /system/lib
libraryPermittedPath += File.pathSeparator + defaultSearchPaths;
}
final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
// If we're not asked to include code, we construct a classloader that has
// no code path included. We still need to set up the library search paths
// and permitted path because NativeActivity relies on it (it attempts to
// call System.loadLibrary() on a classloader from a LoadedApk with
// mIncludeCode == false).
if (!mIncludeCode) {
if (mClassLoader == null) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(
"" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
librarySearchPath, libraryPermittedPath, mBaseClassLoader,
null /* classLoaderName */);
StrictMode.setThreadPolicy(oldPolicy);
mAppComponentFactory = AppComponentFactory.DEFAULT;
}
return;
}
/*
* With all the combination done (if necessary, actually create the java class
* loader and set up JIT profiling support if necessary.
*
* In many cases this is a single APK, so try to avoid the StringBuilder in TextUtils.
*/
final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
TextUtils.join(File.pathSeparator, zipPaths);
if (DEBUG) Slog.v(ActivityThread.TAG, "Class path: " + zip +
", JNI path: " + librarySearchPath);
boolean needToSetupJitProfiles = false;
if (mClassLoader == null) {
// Temporarily disable logging of disk reads on the Looper thread
// as this is early and necessary.
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader,
mApplicationInfo.classLoaderName);
mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
StrictMode.setThreadPolicy(oldPolicy);
// Setup the class loader paths for profiling.
needToSetupJitProfiles = true;
}
if (!libPaths.isEmpty() && SystemProperties.getBoolean(PROPERTY_NAME_APPEND_NATIVE, true)) {
// Temporarily disable logging of disk reads on the Looper thread as this is necessary
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
ApplicationLoaders.getDefault().addNative(mClassLoader, libPaths);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
// /vendor/lib, /odm/lib and /product/lib are added to the native lib search
// paths of the classloader. Note that this is done AFTER the classloader is
// created by ApplicationLoaders.getDefault().getClassLoader(...). The
// reason is because if we have added the paths when creating the classloader
// above, the paths are also added to the search path of the linker namespace
// 'classloader-namespace', which will allow ALL libs in the paths to apps.
// Since only the libs listed in <partition>/etc/public.libraries.txt can be
// available to apps, we shouldn't add the paths then.
//
// However, we need to add the paths to the classloader (Java) though. This
// is because when a native lib is requested via System.loadLibrary(), the
// classloader first tries to find the requested lib in its own native libs
// search paths. If a lib is not found in one of the paths, dlopen() is not
// called at all. This can cause a problem that a vendor public native lib
// is accessible when directly opened via dlopen(), but inaccesible via
// System.loadLibrary(). In order to prevent the problem, we explicitly
// add the paths only to the classloader, and not to the native loader
// (linker namespace).
List<String> extraLibPaths = new ArrayList<>(3);
String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
if (!defaultSearchPaths.contains("/vendor/lib")) {
extraLibPaths.add("/vendor/lib" + abiSuffix);
}
if (!defaultSearchPaths.contains("/odm/lib")) {
extraLibPaths.add("/odm/lib" + abiSuffix);
}
if (!defaultSearchPaths.contains("/product/lib")) {
extraLibPaths.add("/product/lib" + abiSuffix);
}
if (!extraLibPaths.isEmpty()) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
ApplicationLoaders.getDefault().addNative(mClassLoader, extraLibPaths);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
if (addedPaths != null && addedPaths.size() > 0) {
final String add = TextUtils.join(File.pathSeparator, addedPaths);
ApplicationLoaders.getDefault().addPath(mClassLoader, add);
// Setup the new code paths for profiling.
needToSetupJitProfiles = true;
}
// Setup jit profile support.
//
// It is ok to call this multiple times if the application gets updated with new splits.
// The runtime only keeps track of unique code paths and can handle re-registration of
// the same code path. There's no need to pass `addedPaths` since any new code paths
// are already in `mApplicationInfo`.
//
// It is NOT ok to call this function from the system_server (for any of the packages it
// loads code from) so we explicitly disallow it there.
if (needToSetupJitProfiles && !ActivityThread.isSystem()) {
setupJitProfileSupport();
}
}
可以看到,当应用为系统应用时,isBundledApp
为true,即当加载的应用为系统应用时,可以共享系统进程的本地共享库命名空间。
四、结论与解决方案
根据如上分析,Android的本地共享库命名空间默认设计为,当应用为系统应用时,优先共享链接使用系统内置的本地动态链接库,而作为三方应用时,仅允许共享有限的本地动态链接库(空);
这个问题中,应用加载的libmydemo.so依赖的libcurl.so和libcrypto.so与系统内置库同名,当应用预置为系统APP启动时,libcrypto.so作为已加载的系统内置库被允许应用共享,从而不再加载应用目录下的同名库导致符号表匹配错误,为了解决问题,将与系统重名的libcurl.so、libcrypto.so重新编译为其他的名字,从而问题顺利解决。