Android对应用应用的系统库限制越来越严格,上层应用包括(apk、jar包)不能直接引用系统的一些so库了。如果需要使用,只能使用,系统申明的公共库。如果使用非系统申明的公共库,apk运行后调用该so库时,app会直接挂掉。
具体报错形式如下:
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能够访问的具体路径:
上面这些路径是apk可以访问的路径,除之之外的其它路径是不能访问的,由于我们要访问的so库位于/system/lib,因此apk不能访问。
存在出厂预制的apk能正常使用,但是升级之后运行奔溃的问题。先来看看源码的处理:
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升级过之后,就不满足条件了。
具体介绍和处理查看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 开始,供应商的公共库需要遵循以下额外限制,并且需要进行相应的设置:
供应商的原生库必须添加适当的标签,这样才能供应用访问。如有任何应用(包括第三方应用)要求访问原生库,则该库必须在供应商专用的file_contexts文件中标记为 same_process_hal_file,具体如下所示:
/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-
。
对于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同样会奔溃。
谷歌原生提供的公共库是放在/system/etc/public.libraries.txt里面,引用这里的库不会存在上述的问题。但是引用这个文件可能存在cts测试的问题(暂时没有验证过),因此我们可以在在device或者vendor目录下,在编译时覆盖掉该文件,或者直接修改该文件来达到添加库的目的。参考修改可以看看(https://android-review.googlesource.com/#/c/209029/)
public.libraries.txt相关的处理逻辑如下,在LibraryNamespaces类的Initialize()会读取这个文件,将so库设置为公共so库。
//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
//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,进行初始化
//system/core/libnativeloader/native_loader.cpp
static LibraryNamespaces* g_namespaces = new LibraryNamespaces;
void InitializeNativeLoader() {
g_namespaces->Initialize();
}
初始化是调用LibraryNamespaces类的Initialize完成公共so库的赋值.
void Initialize() {
..................
std::vector<std::string> sonames;
ReadConfig(public_native_libraries_system_config, &sonames, &error_msg),
ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames);
public_libraries_ = base::Join(sonames, ':');
.............
}
1、Android N 之后,Google对调用公共系统库的限制越来越严格,只有public.libraries.txt相关的文件申明后的so才能使用。
2、如果声明的public.libraries.txt和so库在vendor目录下,那么system下的apk和jar是不能使用的,同样会造成apk奔溃。
3、使用以上的解决方法,必须严格按照规定来使用,有对不上的地方apk就会奔溃。
库的命名空间: 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