android 动态库加载机制

省流:android 不兼容 glibc,而是写了一套独立的 c 运行时库 (bionic libc),为移动设备和 google 自己推的东西做了大量优化。在这套工具链里,aosp 实现了一个兼容 bionic libc 的链接器,放到系统中代替 ld。

这个链接器叫 linker,二进制放在 /bin/system/bin 目录下。

好像有点跑题了,我都忘了在哪看到的 linker 才写到这里的。可能是 frida 用了 Interceptor hook 的 linker

扯回来。在 Java 的应用层代码里写 System.loadLibrary 后,会经历如下调用栈

cpp 复制代码
// java
System.loadLibrary
Runtime.loadLibrary0
// jni
// c
Runtime_nativeLoad
JVM_NativeLoad
// cc
art::JavaVMExt::LoadNativeLibrary // 检查是否是要 load 已有 library,不是的话调用 OpenNativeLibrary(dlopen) 获取 handle、创建 library 对象、查找 JNI_Onload 的 sym,没有的话直接 return true.
android::OpenNativeLibrary
OpenSystemLibrary ...(android_dlopen_ext/dlopen)

其中,从 nativeLoad 开始,执行流通过 jni 跳转到了 c 函数 Runtime_nativeLoad。可以查看 Runtime.c - Android Code Search ,往里跟踪的其他 c 文件

看样子最后还是用的 dlopen

详解每一轮调用:

​​​Android动态库的加载原理,真的理解了吗?一文带你游览动态库的加载流程,从 Java 到 C++,一步一步探索知识的 - 掘金

上面这篇把无关代码删了,很方便阅读。但是在 /bionic/linker/linker.cppfind_libraries 函数删的太多了,有点难以理解中间变量的意义

cpp 复制代码
// 该函数用于查找并加载多个共享库,处理库之间的依赖关系
// add_as_children - 是否将一级加载的库(library_names[]中的库,不包括它们的传递依赖)
//                   作为start_with库的子节点。当为dlopen()调用时设为false,
//                   此时新加载的库必须形成独立的树结构
bool find_libraries(android_namespace_t* ns,          // 使用的命名空间
                    soinfo* start_with,                // 起始库(父节点)
                    const char* const library_names[], // 要加载的库名数组
                    size_t library_names_count,        // 库名数组长度
                    soinfo* soinfos[],                 // 输出加载的soinfo指针数组
                    std::vector<soinfo*>* ld_preloads, // 预加载库列表
                    size_t ld_preloads_count,          // 预加载库数量
                    int rtld_flags,                    // 加载标志(如RTLD_LAZY等)
                    const android_dlextinfo* extinfo,  // 扩展加载信息
                    bool add_as_children,              // 是否作为子节点添加
                    std::vector<android_namespace_t*>* namespaces) { // 相关命名空间列表
  // 步骤0:准备工作
  std::unordered_map<const soinfo*, ElfReader> readers_map; // 用于缓存ELF读取器
  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));
  }

  // 如果soinfos数组为空,在栈上分配临时空间
  // 需要这个数组来处理加载部分失败的情况(例如加载多个库时部分成功)
  if (soinfos == nullptr) {
    size_t soinfos_size = sizeof(soinfo*)*library_names_count;
    soinfos = reinterpret_cast<soinfo**>(alloca(soinfos_size)); // 栈上分配临时内存
    memset(soinfos, 0, soinfos_size); // 初始化为空指针
  }

  size_t soinfos_count = 0; // 已加载的库数量

  // 使用范围守卫确保加载任务会被清理
  auto scope_guard = android::base::make_scope_guard([&]() {
    for (LoadTask* t : load_tasks) {
      LoadTask::deleter(t); // 退出时清理加载任务
    }
  });

  ZipArchiveCache zip_archive_cache; // 用于APK内文件的缓存
  soinfo_list_t new_global_group_members; // 新的全局组成员

  // 步骤1:扩展加载任务列表,包含所有DT_NEEDED依赖(暂时不实际加载)
  for (size_t i = 0; i<load_tasks.size(); ++i) {
    LoadTask* task = load_tasks[i];
    soinfo* needed_by = task->get_needed_by(); // 获取依赖该库的父库

    // 判断是否是DT_NEEDED依赖(非直接加载的依赖)
    bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);
    task->set_extinfo(is_dt_needed ? nullptr : extinfo); // 设置扩展信息
    task->set_dt_needed(is_dt_needed); // 标记是否为DT_NEEDED依赖

    // 从任务指定的命名空间开始查找(可能不同于当前命名空间)
    android_namespace_t* start_ns = const_cast<android_namespace_t*>(task->get_start_from());

    // 实际查找库的实现
    if (!find_library_internal(start_ns, task, &zip_archive_cache, &load_tasks, rtld_flags)) {
      return false; // 查找失败直接返回
    }

    soinfo* si = task->get_soinfo(); // 获取找到的soinfo对象

    // 如果是DT_NEEDED依赖,添加到父库的子节点
    if (is_dt_needed) {
      needed_by->add_child(si);
    }

    // 处理预加载库(LD_PRELOAD)
    bool is_ld_preload = false;
    if (ld_preloads != nullptr && soinfos_count < ld_preloads_count) {
      ld_preloads->push_back(si);
      is_ld_preload = true;
    }

    // 填充输出数组
    if (soinfos_count < library_names_count) {
      soinfos[soinfos_count++] = si;
    }

    // 处理全局符号可见性(DF_1_GLOBAL标志)
    if (is_ld_preload || (si->get_dt_flags_1() & DF_1_GLOBAL) != 0) {
      if (!si->is_linked() && namespaces != nullptr && !new_global_group_members.contains(si)) {
        new_global_group_members.push_back(si);
        // 将新全局成员添加到所有初始命名空间
        for (auto linked_ns : *namespaces) {
          if (si->get_primary_namespace() != linked_ns) {
            linked_ns->add_soinfo(si);     // 添加到命名空间
            si->add_secondary_namespace(linked_ns); // 添加二级命名空间
          }
        }
      }
    }
  }

  // 步骤2:随机顺序加载库(防止地址预测攻击)
  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);
    }
  }

  // 处理地址保留标志
  bool reserved_address_recursive = false;
  if (extinfo) {
    reserved_address_recursive = extinfo->flags & ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE;
  }
  if (!reserved_address_recursive) {
    shuffle(&load_list); // 随机打乱加载顺序
  }

  // 设置地址空间参数
  address_space_params extinfo_params, default_params;
  size_t relro_fd_offset = 0;
  if (extinfo) {
    // 处理保留地址相关的标志
    if (extinfo->flags & ANDROID_DLEXT_RESERVED_ADDRESS) {
      extinfo_params.start_addr = extinfo->reserved_addr;
      extinfo_params.reserved_size = extinfo->reserved_size;
      extinfo_params.must_use_address = true;
    } else if (extinfo->flags & ANDROID_DLEXT_RESERVED_ADDRESS_HINT) {
      extinfo_params.start_addr = extinfo->reserved_addr;
      extinfo_params.reserved_size = extinfo->reserved_size;
    }
  }

  // 实际加载库到内存
  for (auto&& task : load_list) {
    address_space_params* address_space =
        (reserved_address_recursive || !task->is_dt_needed()) ? &extinfo_params : &default_params;
    if (!task->load(address_space)) {
      return false;
    }
  }

  // 步骤3:预链接所有依赖库(广度优先顺序)
  bool any_memtag_stack = false;
  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    if (!si->is_linked() && !si->prelink_image()) { // 执行预链接
      return false;
    }
    // 检查内存标签(MTE)相关设置
    if (si->has_min_version(7) && si->memtag_stack()) {
      any_memtag_stack = true;
    }
    register_soinfo_tls(si); // 注册TLS信息
  }

  // 处理内存标签回调
  if (any_memtag_stack) {
    if (auto* cb = __libc_shared_globals()->memtag_stack_dlopen_callback) {
      cb();
    } else {
      __libc_shared_globals()->initial_memtag_stack = true;
    }
  }

  // 步骤4:强制设置LD_PRELOAD库的DF_1_GLOBAL标志
  if (ld_preloads != nullptr) {
    for (auto&& si : *ld_preloads) {
      si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL);
    }
  }

  // 步骤5:收集本地组的根节点(处理跨命名空间的情况)
  std::vector<soinfo*> local_group_roots;
  if (start_with != nullptr && add_as_children) {
    local_group_roots.push_back(start_with); // 起始库作为根
  } else {
    CHECK(soinfos_count == 1);
    local_group_roots.push_back(soinfos[0]); // 第一个加载的库作为根
  }

  // 遍历加载任务查找跨命名空间的边界
  for (auto&& task : load_tasks) {
    soinfo* si = task->get_soinfo();
    soinfo* needed_by = task->get_needed_by();
    bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);
    android_namespace_t* needed_by_ns = is_dt_needed ? needed_by->get_primary_namespace() : ns;

    // 如果库未链接且跨命名空间
    if (!si->is_linked() && si->get_primary_namespace() != needed_by_ns) {
      auto it = std::find(local_group_roots.begin(), local_group_roots.end(), si);
      if (it == local_group_roots.end()) {
        local_group_roots.push_back(si); // 添加为新的本地组根
      }
    }
  }

  // 步骤6:链接所有本地组
  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();
    SymbolLookupList lookup_list(global_group, local_group);
    soinfo* local_group_root = local_group.front(); // 本地组根节点

    // 遍历并链接本地组成员
    bool linked = local_group.visit([&](soinfo* si) {
      if (!si->is_linked() && si->get_primary_namespace() == local_group_ns) {
        // 处理扩展信息
        const android_dlextinfo* link_extinfo = nullptr;
        if (si == soinfos[0] || reserved_address_recursive) {
          link_extinfo = extinfo;
        }
        // 调用加载hook(如果有)
        if (__libc_shared_globals()->load_hook) {
          __libc_shared_globals()->load_hook(si->load_bias, si->phdr, si->phnum);
        }
        // 处理符号查找
        lookup_list.set_dt_symbolic_lib(si->has_DT_SYMBOLIC ? si : nullptr);
        if (!si->link_image(lookup_list, local_group_root, link_extinfo, &relro_fd_offset) ||
            !get_cfi_shadow()->AfterLoad(si, solist_get_head())) {
          return false;
        }
      }
      return true;
    });

    if (!linked) {
      return false;
    }
  }

  // 步骤7:标记所有加载任务为已链接,并更新引用计数
  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; // 全部加载成功
}
相关推荐
雨白3 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹4 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空6 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭7 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日8 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安8 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑8 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟12 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡13 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0013 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体