Android添加自定义公共so库

? Android对应用应用的系统库限制越来越严格,上层应用包括(apk、jar包)不能直接引用系统的一些so库了。如果需要使用,只能使用,系统申明的公共库。如果使用非系统申明的公共库,apk运行后调用该so库时,app会直接挂掉。

1、错误信息

具体报错形式如下:

1
2
3
4
5
6
01-01 02:17:24.222  7475  7475 E linker  : library "/system/lib/libhalloworld.so" ("/system/lib/libhalloworld.so") needed or dl
opened by "/system/lib/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths
="", default_library_paths="/system/fake-libs:/data/app/com.example.halloworld-1/base.apk!/lib/", permitted_paths="/dat
a:/mnt/expand:/data/data/com.example.halloworld"]
01-01 02:17:24.223  7475  7475 E System  : java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib/libhalloworld.so"
 needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace "classloader-namespace"

? 这个报错的意思是说,apk通过dlopen打开一个native库时,Nativeloader打不开libhalloworld.so。并且提示了apk能够访问的具体路径:

  • ld_library_paths="",
  • default_library_paths="/system/fake-libs64:/data/app/com.example.haha-1/base.apk!/lib/",
  • permitted_paths="/data:/mnt/expand:/data/data/com.example.haha

? 上面这些路径是apk可以访问的路径,除之之外的其它路径是不能访问的,由于我们要访问的so库位于/system/lib,因此apk不能访问。

2、升级后apk奔溃问题

? 存在出厂预制的apk能正常使用,但是升级之后运行奔溃的问题。先来看看源码的处理:

1
2
3
4
5
6
7
8
9
10
11
final boolean isBundledApp = mApplicationInfo.isSystemApp() //如果是系统apk
                && !mApplicationInfo.isUpdatedSystemApp(); //apk没有升级过

String libraryPermittedPath = mDataDir;
if (isBundledApp) {  
    //permitted_paths就增加system/lib
    // This is necessary to grant bundled apps access to
    // libraries located in subdirectories of /system/lib
    libraryPermittedPath += File.pathSeparator +
            System.getProperty("java.library.path");
}

? 因此,如果是系统apk并且没有升级过,so库的搜索路径就会增加一个system/lib。而apk升级过之后,就不满足条件了。

3、解决方法

3.1、符合google要求的改法

具体介绍和处理查看Google官方的说明(https://source.android.google.cn/devices/tech/config/namespaces_libraries#adding-additional-native-libraries)添加其他原生库。

? 除了标准的公共原生库之外,芯片供应商(从 Android 7.0 开始)和设备制造商(从 Android 9 开始)还可以选择提供可供应用访问的其他原生库,方法是将它们放在相应的库文件夹中,并在 .txt 文件中明确列出它们。

库文件夹是:

  • /vendor/lib(对于芯片供应商的 32 位库)和 /vendor/lib64(对于芯片供应商的 64 位库)
  • /system/lib(对于设备制造商的 32 位库)和 /system/lib64(对于设备制造商的 64 位库)

.txt 文件是:

  • /vendor/etc/public.libraries.txt(对于芯片供应商的库)
  • /system/etc/public.libraries-COMPANYNAME.txt(对于设备制造商的库),其中 COMPANYNAME 指的是制造商的名称(例如 awesome.company)。COMPANYNAME 应符合 [A-Za-z0-9_.-]+ 格式(字母数字字符、_、.(点)和 -)。如果某些库来自外部解决方案提供商,则可以在设备中包含多个此类 .txt 文件。

必须system 分区内由设备制造商公开提供的原生库命名为 lib*COMPANYNAME.so,例如 libFoo.awesome.company.so。换句话说,没有公司名称后缀的 libFoo.so 不得公开。库文件名中的 COMPANYNAME 必须与列出库名称的 txt 文件名称中的 COMPANYNAME 匹配。

作为 AOSP 一部分的原生库不得公开(默认情况下公开的标准公共原生库除外)。只有芯片供应商或设备制造商添加的其他库可供应用访问。

从 Android 8.0 开始,供应商的公共库需要遵循以下额外限制,并且需要进行相应的设置:

  1. 供应商的原生库必须添加适当的标签,这样才能供应用访问。如有任何应用(包括第三方应用)要求访问原生库,则该库必须在供应商专用的file_contexts文件中标记为 same_process_hal_file,具体如下所示:

    1
    /vendor/lib(64)?/libnative.so u:object_r:same_process_hal_file:s0

    其中,libnative.so为原生库的名称。

  2. 该库不得依赖(无论是直接依赖,还是通过其依赖关系间接依赖)VNDK-SP 库和 LLNDK 库之外的任何系统库。您可以在以下位置找到 VNDK-SP 库和 LLNDK 库的列表:development/vndk/tools/definition/tool/datasets/eligible-list--release.csv

对于system so上面的内容主要有三点:

1、需要添加的so库文件名格式要求:如 libhalloworld.google.so

2、public.libraries.txt文件命名:public.libraries-google.txt

3、public.libraries-google.txt文件内容必须是libhalloworld.google.so,且放在/system/etc下

以上三点有一点不对应,apk同样会奔溃。

3.2 偷懒改法

? 谷歌原生提供的公共库是放在/system/etc/public.libraries.txt里面,引用这里的库不会存在上述的问题。但是引用这个文件可能存在cts测试的问题(暂时没有验证过),因此我们可以在在device或者vendor目录下,在编译时覆盖掉该文件,或者直接修改该文件来达到添加库的目的。参考修改可以看看(https://android-review.googlesource.com/#/c/209029/)

4、相关源码解读

? public.libraries.txt相关的处理逻辑如下,在LibraryNamespaces类的Initialize()会读取这个文件,将so库设置为公共so库。

1
2
3
4
5
6
7
8
9
10
11
//system/core/libnativeloader/native_loader.cpp
static constexpr const char* kPublicNativeLibrariesSystemConfigPathFromRoot = "/etc/public.libraries.txt";
static constexpr const char* kPublicNativeLibrariesVendorConfig = "/vendor/etc/public.libraries.txt";
  void Initialize() {
    ...
    std::vector<std::string> sonames;
    ReadConfig(public_native_libraries_system_config, &sonames, &error_msg),
    ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames);
    public_libraries_ = base::Join(sonames, ':');
    ...
  }

该方法的调用流程,首先在创建一个虚拟机的时候,初始化NativeLoader

1
2
3
4
5
6
7
8
//art/runtime/java_vm_ext.cc
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  ................
  // Initialize native loader. This step makes sure we have
  // everything set up before we start using JNI.
  android::InitializeNativeLoader();
  ...
}

然后进入native_loader,进行初始化

1
2
3
4
5
6
//system/core/libnativeloader/native_loader.cpp
static LibraryNamespaces* g_namespaces = new LibraryNamespaces;

void InitializeNativeLoader() {
  g_namespaces->Initialize();
}

初始化是调用LibraryNamespaces类的Initialize完成公共so库的赋值.

1
2
3
4
5
6
7
8
  void Initialize() {
    ..................
    std::vector<std::string> sonames;
    ReadConfig(public_native_libraries_system_config, &sonames, &error_msg),
    ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames);
    public_libraries_ = base::Join(sonames, ':');
    .............
  }

5、总结

1、Android N 之后,Google对调用公共系统库的限制越来越严格,只有public.libraries.txt相关的文件申明后的so才能使用。

2、如果声明的public.libraries.txt和so库在vendor目录下,那么system下的apk和jar是不能使用的,同样会造成apk奔溃。

3、使用以上的解决方法,必须严格按照规定来使用,有对不上的地方apk就会奔溃。

6、参考文献

库的命名空间: https://source.android.google.cn/devices/tech/config/namespaces_libraries#adding-additional-native-libraries

链接器命名空间: https://source.android.google.cn/devices/architecture/vndk/linker-namespace

VNDK 构建系统支持: https://source.android.google.cn/devices/architecture/vndk/build-system?hl=zh-cn

Platform APIs: https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md

Namespace based Dynamic Linking:https://jackwish.net/2017/namespace-based-dynamic-linking.html

Framework基础:Android N 公共so库怎么定义:https://www.jianshu.com/p/4be3d1dafbec