文章目录

  • 一、问题与场景
  • 二、初步分析
  • 三、详细分析
  • 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]
  • 四、结论与解决方案


一、问题与场景

起因是在开发过程中遇到的问题:

  1. 应用内部使用了jni加载自研的so模块,该so又依赖了libcurl.so,libcurl.so又依赖了libcrypto.so;
┏ MyDemo
┣━ jniLib
┣━━━ libmydemo.so
┣━━━ libcurl.so
┣━━━ libcrypto.so
  1. 测试中当应用作为第三方应用安装到设备时(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"...


二、初步分析

  1. 首先为了方便,应用内集成的libcurl.so、libcrypto.so直接拷贝自Android Q系统源码工程;
  2. 分析libcrypto.so的符号表,Android P版本确实比Android Q版本少了sk_pop_free_ex接口;
  3. 结合现象,推测作为系统应用集成的方式,应该加载的是系统的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,dlerrorLD_LOG(kLogDlopen, "log is %s", %s);
  • 本文的分析基于Android P平台系统代码;
  1. 根据参考文档,直接梳理出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
  1. 可以看到最终调用到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_listload_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;
  });
}

逻辑其实并不复杂:

  1. 首先排除name中包含’/'的情况,即name为一个so文件路径,例如从do_dlopen调用传过来的name为/system/app/MyDemo/lib/arm64/libmydemo.so,则find_loaded_library_by_soname直接返回false;
  2. 然后调用真正逻辑的find_loaded_library_by_soname方法,该方法从传入的命名空间ns中获取一个关联的so信息列表并遍历,检查是否包含当前要加载的soname;
  3. 如果没有找到,则获取命名空间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返回一个字符串路径;

  1. 如果name是一个地址,直接返回-1;
  2. 如果name是一个zip文件的地址,直接在zip文件中打开并返回fd;
  3. 尝试从ns->get_ld_library_paths()中打开so文件,根据调试日志,这个列表为空;
  4. 尝试从needed_by->get_dt_runpath()中打开so文件,need_by即此so的调用者so,根据调试日志,这个列表也为空;
  5. 尝试从ns->get_default_library_paths()中打开so文件,根据调试日志这个列表为[/system/app/MyDemo/lib/arm64, /system/app/MyDemo/MyDemo.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64];
  6. 尝试从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_libraryfind_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初始化时有两个关键参数:

  1. &g_default_namespacesi->primary_namespace_绑定,在后面的init_default_namespaces中被初始化;
  2. executable_pathget_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_pathsset_permitted_paths接口设置了默认的路径和豁免路径;

★回到__linker_init_post_relocation代码继续分析逻辑:
在之后构建了列表needed_library_name_list,该列表的内容来自两个地方:

  1. g_ld_preload_names, 这个列表的值从环境变量中解析"LD_PRELOAD";
  2. 来自si的dt_needed(即si所依赖的so,参考[《DT_NEEDED 的解释》];())
  3. 调用find_libraries()方法,传入g_default_namespaceneeded_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):

android vendor app加载system的so库 system.loadlibrary android_java


★接下来问题转变为分析当callerlibnativeloader时,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 nsg_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 LibraryNamespacesFindNamespaceByClassLoader方法:

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 LibraryNamespacesCreate方法中有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-namespaceandroid_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;
}

这个函数首先创建一个名为nameandroid_namespace_t* ns,然后根据参数设置其属性;
其中有一个比较关键的参数type, 当type设置中包含ANDROID_NAMESPACE_TYPE_SHARED时,注释有如下三个操作(parent_namespacelibnativeloader的ns即前文__linker_init方法创建的g_default_namespace):
a) append parent namespace paths.:添加parent_namespaceld_library_pathsdefault_library_pathspermitted_paths到ns的对应属性列表中;
b) If shared - clone the parent namespace:如果type为"SHARED",则将parent_namespacesoinfo_list添加到ns(这里和系统应用的ns->soinfo_list对应),同时将parent_namespacelinked_namespaces添加到ns中;
c) If not shared - copy only the shared group:如果type不为"SHARED",则仅添加parent_namespaceshared_group到ns中(根据调试日志这个列表为空,和三方应用的ns->soinfo_list对应);
LibraryNamespaces::Create方法中可以看出,影响nsclassloader-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);
}

isSharedClassLoader.javacreateClassLoader方法中来自boolean isNamespaceShared,同样从外部传入;

ClassLoaderFactory.createClassLoader的调用者有两个:
a) ZygoteInit.createPathClassLoader方法,这显然不在APP的启动流程中;
b) ApplicationLoaders.getClassLoader方法,这个方法里isNamespaceShared来自isBundled,接下来重点追溯这个方法,在LoadApked.javacreateOrUpdateClassLoaderLocked方法中找到调用:

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重新编译为其他的名字,从而问题顺利解决。