? 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 文件。
必须将
作为 AOSP 一部分的原生库不得公开(默认情况下公开的标准公共原生库除外)。只有芯片供应商或设备制造商添加的其他库可供应用访问。
从 Android 8.0 开始,供应商的公共库需要遵循以下额外限制,并且需要进行相应的设置:
-
供应商的原生库必须添加适当的标签,这样才能供应用访问。如有任何应用(包括第三方应用)要求访问原生库,则该库必须在供应商专用的file_contexts文件中标记为 same_process_hal_file,具体如下所示:
1/vendor/lib(64)?/libnative.so u:object_r:same_process_hal_file:s0其中,libnative.so为原生库的名称。
-
该库不得依赖(无论是直接依赖,还是通过其依赖关系间接依赖)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