Skip to content

APEX Native 库

追踪 Android 17 APEX native 库的挂载路径、PMS ABI 分支、linker namespace 边界和运行时加载入口。

基于android-17.0.0_r1
AndroidPMSAPEXNativeLinker

APEX Native 库 ​

本文承接 APEX 架构、ApexManager 管理 和 APEX 中的 Java 库,聚焦 APEX 内 .so 库如何被使用。核心结论是:PMS 不从 APEX 中提取 native 库,也不负责构建 linker namespace;PMS 把 active mount path 注册为包路径,apexd/linkerconfig/bionic 再完成运行时可见性。

1. 安装分叉 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:preparePackage

java
final boolean isApex =
        (installFlags & PackageManager.INSTALL_APEX) != 0;
@ScanFlags int scanFlags =
        SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (isApex) {
    scanFlags |= SCAN_AS_APEX;
}

final File tmpPackageFile = new File(
        isApex ? request.getApexInfo().modulePath
                : request.getCodePath());

APEX 分支使用 apexd 返回的 modulePath;普通 APK 使用安装请求的 code path。SCAN_AS_APEX 影响后续包解析、ABI 和代码路径处理,不能仅依据文件后缀判断。

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:native library 分支

java
scanFlags |= SCAN_NO_DEX;
if (!isApex) {
    Pair<PackageAbiHelper.Abis,
            PackageAbiHelper.NativeLibraryPaths> derivedAbi =
            mPackageAbiHelper.derivePackageAbi(
                    parsedPackage, systemApp,
                    isUpdatedSystemApp,
                    abiOverride,
                    ScanPackageUtils
                            .getAppLib32InstallDir());
    derivedAbi.first.applyTo(parsedPackage);
    derivedAbi.second.applyTo(parsedPackage);
}

if (isApex) {
    parsedPackage.setPath(
            request.getApexInfo().modulePath);
    parsedPackage.setBaseApkPath(
            request.getApexInfo().modulePath);
}

普通 APK 需要由 PMS 推导 ABI、native library root 和提取路径;APEX 跳过这套 derivePackageAbi,因为 .so 已在 payload 镜像中,运行时路径来自 active mount。

2. Active mount ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:ActiveApexInfo、getBackingApexFile

java
public ActiveApexInfo(ApexInfo apexInfo) {
    this(apexInfo.moduleName,
            new File(Environment.getApexDirectory()
                    + File.separator
                    + apexInfo.moduleName),
            new File(apexInfo.preinstalledModulePath),
            apexInfo.isFactory,
            new File(apexInfo.modulePath),
            apexInfo.activeApexChanged);
}

public File getBackingApexFile(File file) {
    Path path = file.toPath();
    if (!path.startsWith(
            Environment.getApexDirectory().toPath())) {
        return null;
    }
    if (path.getNameCount() < 2) {
        return null;
    }
    String moduleName = path.getName(1).toString();
    for (ActiveApexInfo apex : getActiveApexInfos()) {
        if (apex.apexModuleName.equals(moduleName)) {
            return apex.apexFile;
        }
    }
    return null;
}

/apex/<module> 是挂载视图,apexFile 是实际 APEX 文件;二者不能互换。PMS 通过 active mount path 解析包和定位 native library,调试时如果只看 /data/apex/active 文件而不看 /apex/<module>,会漏掉当前运行时路径。

3. Manifest 声明 ​

APEX manifest 中的 native library 声明由 apexd/linkerconfig 使用:provider 声明可导出的库,consumer 声明依赖,JNI 库用于 Java native loader 的 namespace 关联。PMS 的 Java 代码不解析这些字段来提取 .so;它接收扫描后的 package/path 元数据。

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:APEX ABI 跳过分支

java
// The native libs of Apex are located in apex_payload.img.
// They do not need to be parsed from the original apex file.
if (!isApex) {
    derivePackageAbi(parsedPackage, systemApp,
            isUpdatedSystemApp, abiOverride,
            ScanPackageUtils.getAppLib32InstallDir());
}

这个注释是 PMS/native 库边界的直接证据:APEX 的 .so 不复制到 APK 的 lib/<abi> 目录,解析器也不通过 APK zip 抽取它们。

4. linker namespace ​

APEX native 库是否能被某个进程加载,取决于运行时 linker namespace 和链接关系。APEX 的 provideNativeLibs、requireNativeLibs、jniLibs 由 linkerconfig 与 libnativeloader 消费;PMS 只保证 active APEX 的包路径和元数据被正确注册。

源码文件:system/apex/apexd/apex_constants.h,符号:APEX 路径常量

cpp
static constexpr const char* kApexRoot = "/apex";
static constexpr const char* kApexDataDir = "/data/apex";
static constexpr const char* kActiveApexPackagesDataDir =
    "/data/apex/active";
static constexpr const char* kApexPackageSystemDir =
    "/system/apex";

系统分区的预装 APEX、data 分区的 active APEX 和运行时 /apex mount 是不同层次。linker namespace 使用 active mount,回滚/更新管理使用 data/apex session 和备份目录。

5. JNI 库加载 ​

APEX 中的 Java 类调用 System.loadLibrary 时,libnativeloader 会根据 classloader 关联的 namespace 搜索对应 APEX 的 lib/lib64。jniLibs 声明决定哪些库对该 Java 运行环境可见;它不等于 provideNativeLibs 的全局导出。

运行时路径大致是:

text
Java classloader
  -> NativeLoaderNamespace
  -> linker namespace links
  -> /apex/<module>/lib 或 lib64
  -> dlopen(lib*.so)

如果 native library 文件存在但 dlopen 失败,优先检查 namespace links、ABI、jniLibs 声明和 active mount,不要先回到 PMS 的 APK ABI 提取逻辑。

6. 路径写回 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:APEX path 设置

java
if (isApex) {
    parsedPackage.setPath(
            request.getApexInfo().modulePath);
    parsedPackage.setBaseApkPath(
            request.getApexInfo().modulePath);
}

PMS 将 active module path 写入 parsed package 的 path/baseApkPath,使后续包查询、系统包映射和 APK-in-APEX 判断指向挂载视图。native library 的真正搜索仍由 linker 处理,但 PMS 必须先提供正确的 active path。

7. Backing file ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ApexManager.java,符号:getBackingApexFile

java
if (!path.startsWith(
        Environment.getApexDirectory().toPath())) {
    return null;
}
String moduleName = path.getName(1).toString();
for (ActiveApexInfo apex : getActiveApexInfos()) {
    if (apex.apexModuleName.equals(moduleName)) {
        return apex.apexFile;
    }
}
return null;

调试工具或系统服务可以把 /apex/<module>/... 文件反查到对应 .apex 文件。路径不在 /apex 根下、层级不足或 module 不在 active 列表时返回 null;这不是 native loader 的错误,而是 backing-file 查询边界。

8. 失败定位 ​

现象应检查
APK 的 .so 正常,APEX 的 .so 不可见active mount、linker namespace、provide/require
PMS 报 ABI/库提取错误是否误走了非 APEX derivePackageAbi 分支
JNI loadLibrary 失败jniLibs、classloader namespace、ABI 路径
module path 指向旧版本ApexInfo.modulePath、active cache、apexd 激活状态
文件能看到但跨模块加载失败linker namespace link,而非 Linux 文件存在性
backing APEX 查询为空路径是否位于 /apex/<module>,module 是否 active

9. 阅读检查 ​

复述这条路径:apexd 激活 payload → /apex/<module>/lib* 挂载 → PMS setPath(modulePath) → linkerconfig 建立 namespace → native process/JNI 通过 namespace 加载 .so。

然后回答:PMS 是否把 APEX .so 解压到 APK lib 目录?provideNativeLibs 是否由 PackageManagerService 直接创建 linker namespace?active mount path 和 .apex 文件路径是否等价?答案分别是“不是”“不是”“不等价”。