本文记录项目中遇到的一个CTS的问题,然后解决此问题的过程
问题背景
CtsJniTestCases模块android.jni.cts.JniStaticTest#test_linker_namespaces测试失败
通过run cts -m android.jni.cts.JniStaticTest -t android.jni.cts.JniStaticTest#test_linker_namespaces 运行
失败的log如下:
junit.framework.AssertionFailedError: The library "/vendor/lib64/libdirect-coredump.so" is not a public library but it loaded.
at junit.framework.Assert.fail(Assert.java:50)
at android.jni.cts.JniStaticTest.test_linker_namespaces(JniStaticTest.java:41)
at java.lang.reflect.Method.invoke(Native Method)
at junit.framework.TestCase.runTest(TestCase.java:168)
测试的结果是/vendor/lib64/libdirect-coredump.so不是public库但是却允许被应用使用,这个违反了GoogleCDD中的违反CDD-3.3.1
[C-0-9] MUST list additional non-AOSP libraries exposed directly to third-party apps in /vendor/etc/public.libraries.txt.
问题分析
查看CTS测试代码发现
cts/tests/tests/jni/src/android/jni/cts/JniStaticTest.java cts/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java cts/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
->test_linker_names[cts/tests/tests/jni/src/android/jni/cts/JniStaticTest.java]
-> LinkerNamespacesHelper.runAccessibilityTest [LinkerNamespacesHelper.java]
-> runAccessibilityTestImpl [LinkerNamespacesHelper.java]
将system/etc/public.libraries.txt和vendor/etc/public.libraries.txt以及公开的systemlib加入对应的venderlib和systemlib数组中
private final static String VENDOR_CONFIG_FILE = "/vendor/etc/public.libraries.txt";
private final static String[] PUBLIC_SYSTEM_LIBRARIES = {
"libaaudio.so",
"libandroid.so",
"libc.so",
"libcamera2ndk.so",
"libdl.so",
"libEGL.so",
"libGLESv1_CM.so",
"libGLESv2.so",
"libGLESv3.so",
"libicui18n.so",
"libicuuc.so",
"libjnigraphics.so",
"liblog.so",
"libmediandk.so",
"libm.so",
"libnativewindow.so",
"libOpenMAXAL.so",
"libOpenSLES.so",
"libRS.so",
"libstdc++.so",
"libsync.so",
"libvulkan.so",
"libz.so"
};
public static String runAccessibilityTest() throws IOException {
...
return runAccessibilityTestImpl(systemLibs.toArray(new String[systemLibs.size()]),
vendorLibs.toArray(new String[vendorLibs.size()]));
}
->Java_android_jni_cts_LinkerNamespacesHelper_runAccessibilityTestImpl [android_jni_cts_LinkerNamespacesTest.cpp]
通过遍历system/lib(64)?和vendor/lib(64)?然后比对上面传进来的属于public的so的列表,如果发现so库能被APP访问但是不在对应的public.libraries.txt列表中的,就会Fail
问题解决
所以这边参考system/core/rootdir/etc/public.libraries.txt中创建了含有libdirect-coredump.so的device/xx/xx/etc/public.libraries.txt
通过在device/xx/xx/device.mk中添加copy语句,使得这个文件在编译时候能够copy到/vendor/etc/public.libraries.txt
PRODUCT_COPY_FILES += $(LOCAL_PATH)/etc/public.libraries.txt:$(TARGET_COPY_OUT_VENDOR)/etc/public.libraries.txt
思考-问题1
public.libraries.txt是如何生效的?
参考LinkerNamespacesHelper中的实现,满足下面两个条件的都算是public的so,能够被普通app访问
1)在/etc/public.libraries.txt出现
2)在/vender/etc/public.libraries.txt出现
那么这两个文件是什么时候被加载的呢?
/system/core/libnativeloader/native_loader.cpp /art/runtime/java_vm_ext.cc /system/core/libnativeloader/native_loader.cpp
系统在初始化的时候就会通过LibraryNamespaces#Initialize去加载这两个文件
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;
const char* android_root_env = getenv("ANDROID_ROOT");
std::string root_dir = android_root_env != nullptr ? android_root_env : "/system";
std::string public_native_libraries_system_config =
root_dir + kPublicNativeLibrariesSystemConfigPathFromRoot;
std::string error_msg;
LOG_ALWAYS_FATAL_IF(!ReadConfig(public_native_libraries_system_config, &sonames, &error_msg),
"Error reading public native library list from \"%s\": %s",
public_native_libraries_system_config.c_str(), error_msg.c_str());
...
ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames);
vendor_public_libraries_ = base::Join(sonames, ':');
}
而这个方法是在创建一个ART虚拟机的时候(也就是应用进程被创建的时候),初始化NativeLoader,这个NativeLoader就是用来装载so库的
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();
.......
下面的g_namespaces->Initialize调用上面介绍的LibraryNamespaces#Initialize完成公共so库的赋值
native_loader.cpp
void InitializeNativeLoader() {
#if defined(__ANDROID__)
std::lock_guard<std::mutex> guard(g_namespaces_mutex);
g_namespaces->Initialize();
#endif
}
思考-问题2
为什么普通应用无法引用非public的库?
普通的应用不能直接引用系统的一些so库,这是在Andrid N之后作出的一下变化
https://source.android.com/devices/tech/config/namespaces_libraries
问题1介绍到public的so库会在APP进程创建的时候将访问权限加入到虚拟机可访问的环境中,但是非public的so库却只有systemapp才有权限访问。
通过下面的代码可以获知,如果是系统应用且不是更新过的App,就会把system/lib(64)?路径加上去,所以系统应用有权限访问/system/lib(64)?下的so库,普通的应用无法访问.
frameworks/base/core/java/android/app/LoadedApk.java
final boolean isBundledApp = mApplicationInfo.isSystemApp()
&& !mApplicationInfo.isUpdatedSystemApp();
makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
String libraryPermittedPath = mDataDir;
if (isBundledApp) {
// This is necessary to grant bundled apps access to
// libraries located in subdirectories of /system/lib
libraryPermittedPath += File.pathSeparator +
System.getProperty("java.library.path");
}