一、问题与场景
起因是在开发过程中遇到的问题:
- 应用内部使用了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"...
作为系统应用集成的方式,可以查看另外一遍博文 《预置第三方apk到MTK项目相关问题总结》
二、初步分析
- 首先为了方便,应用内集成的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的加载流程; 可以参考这篇文章,作者着重介绍了应用中so加载流程及目录的生成,对分析有很大的参考作用 《Android的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]
┆
├─ <strong>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)</strong> [libnativeloader/native_loader.cpp]
┆
├─ <strong>void* dlopen(const char* filename, int flag)</strong> [bionic/libdl/libdl.cpp]
┆
├─ <strong>void* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo, const void* caller_addr)</strong> [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 ,
true )) {
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) {
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();
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);
}
if (ld_preloads != nullptr && soinfos_count < ld_preloads_count) {
ld_preloads->push_back(si);
}
if (soinfos_count < library_names_count) {
soinfos[soinfos_count++] = si;
}
}
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;
}
}
...
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) {
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;
}
}
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;
}
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) {
DlErrorRestorer dlerror_restorer;
for (auto& linked_namespace : ns->linked_namespaces()) {
if (find_library_in_linked_namespace(linked_namespace,
task)) {
if (task->get_soinfo() == nullptr) {
if (load_library(linked_namespace.linked_namespace(), task, zip_archive_cache, load_tasks, rtld_flags, false)) {
return true;
}
} else {
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]
static bool find_loaded_library_by_soname(android_namespace_t* ns,
const char* name,
bool search_linked_namespaces,
soinfo** candidate) {
*candidate = nullptr;
if (strchr(name, '/') != nullptr) {
return false;
}
bool found = find_loaded_library_by_soname(ns, name, candidate);
if (!found && search_linked_namespaces) {
for (auto& link : ns->linked_namespaces()) {
if (!link.is_accessible(name)) {
continue;
}
android_namespace_t* linked_ns = link.linked_namespace();
if (find_loaded_library_by_soname(linked_ns, name, candidate)) {
return true;
}
}
}
return found;
}
static bool find_loaded_library_by_soname(android_namespace_t* ns,
const char* name,
soinfo** candidate) {
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);
}
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);
}
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);
if (!task->read(realpath.c_str(), file_stat.st_size)) {
soinfo_free(si);
task->set_soinfo(nullptr);
return false;
}
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 (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;
}
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);
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);
}
if (fd == -1 && ns->is_greylist_enabled() && is_greylisted(ns, name, needed_by)) {
fd = open_library_on_paths(zip_archive_cache, name, file_offset,
g_default_namespace.get_default_library_paths(), realpath);
}
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) {
...
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) {
...
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);
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);
}
}
...
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 ,
true ,
&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;
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 的解释》];(https://blog.csdn.net/dielucui7698/article/details/101400429))
- 调用
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)) {
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 ,
false ,
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;
...
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;
}
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()) {
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) {
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) {
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));
add_soinfos_to_namespace(parent_namespace->soinfo_list(), ns);
for (auto& link : parent_namespace->linked_namespaces()) {
ns->add_linked_namespace(link.linked_namespace(), link.shared_lib_sonames(),
link.allow_all_shared_libs());
}
} else {
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")) {
if (mClassLoader != null) {
return;
}
if (mBaseClassLoader != null) {
mClassLoader = mBaseClassLoader;
} else {
mClassLoader = ClassLoader.getSystemClassLoader();
}
mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
return;
}
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();
}
}
final List<String> zipPaths = new ArrayList<>(10);
final List<String> libPaths = new ArrayList<>(10);
boolean isBundledApp = mApplicationInfo.isSystemApp()
&& !mApplicationInfo.isUpdatedSystemApp();
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) {
libraryPermittedPath += File.pathSeparator
+ Paths.get(getAppDir()).getParent().toString();
libraryPermittedPath += File.pathSeparator + defaultSearchPaths;
}
final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
if (!mIncludeCode) {
if (mClassLoader == null) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(
"" , mApplicationInfo.targetSdkVersion, isBundledApp,
librarySearchPath, libraryPermittedPath, mBaseClassLoader,
null );
StrictMode.setThreadPolicy(oldPolicy);
mAppComponentFactory = AppComponentFactory.DEFAULT;
}
return;
}
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) {
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);
needToSetupJitProfiles = true;
}
if (!libPaths.isEmpty() && SystemProperties.getBoolean(PROPERTY_NAME_APPEND_NATIVE, true)) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
ApplicationLoaders.getDefault().addNative(mClassLoader, libPaths);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
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);
needToSetupJitProfiles = true;
}
if (needToSetupJitProfiles && !ActivityThread.isSystem()) {
setupJitProfileSupport();
}
}
可以看到,当应用为系统应用时,isBundledApp 为true,即当加载的应用为系统应用时,可以共享系统进程的本地共享库命名空间。
四、结论与解决方案
根据如上分析,Android的本地共享库命名空间默认设计为,当应用为系统应用时,优先共享链接使用系统内置的本地动态链接库,而作为三方应用时,仅允许共享有限的本地动态链接库(空);
这个问题中,应用加载的libmydemo.so依赖的libcurl.so和libcrypto.so与系统内置库同名,当应用预置为系统APP启动时,libcrypto.so作为已加载的系统内置库被允许应用共享,从而不再加载应用目录下的同名库导致符号表匹配错误,为了解决问题,将与系统重名的libcurl.so、libcrypto.so重新编译为其他的名字,从而问题顺利解决。
|