缘由:android限制了app加载so库,从 7.1.2 源码来看,在加载so库的时候会检查 加载者的 权限,对于部分常用的库比如:libssl.so libsqlite.so libutils.so libstagefright.so libmedia.so libbinder.so libandroid_runtime.so 等,不限制。
问题: 1.0 在androidstudio 上写了个简单的app,直接点run 安装到连接的设备上。(作为一个第三方app,在 android_studio上 minSdkVersion 14; tartgetSdkVersion 28;)
2.0 在源码上写jni库, android.mk编译出 .so push到设备system/lib64/ (具体环境 android 7.1.2 对应api 25)
#Android.mk for make test .so,
ROOT_DIR := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PATH := $(ROOT_DIR)
LOCAL_MODULE := libwangtest
LOCAL_SHARED_LIBRARIES += libc
#option 表示该模块在所有模式下 user release ,都编译
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := test_jni.c
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
#define MY_LOG_TAG "from-jni" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,MY_LOG_TAG,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,MY_LOG_TAG,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,MY_LOG_TAG,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,MY_LOG_TAG,__VA_ARGS__) // 定义LOGF类型
#define TRACK(...) __android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,"[%d %s]",__LINE__,__FUNCTION__);__android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,__VA_ARGS__)
JNIEXPORT jint JNICALL Java_com_example_wangxiancan_as_1test_TestA_NativeHello
(JNIEnv *env, jobject cls)
{
TRACK("Natieve Hello!");
return 0;
}
3.0 app上加载了对应库,
public class TestA {
static {
Log.i("TestA","start loadLibrary!");
try {
System.loadLibrary("wangtest");
} catch (UnsatisfiedLinkError ule) {
Log.e("TestA", "Can't load wangtest library: " + ule);
System.exit(1);
} catch (SecurityException se) {
Log.e("TestA", "Encountered a security issue when loading wangtest lib rary: " + se);
System.exit(1);
}
Log.i("TestA","loadLibrary ok !");
}
....略
}
4.0 把编译的libwangtest.so push 到设备上/system/lib64/ 运行app,报错
2019-09-11 09:42:39.353 21260-21260/com.example.wangxiancan.as_test I/TestA: start loadLibrary!
2019-09-11 09:42:39.355 21260-21260/com.example.wangxiancan.as_test E/linker: library "/system/lib64/libwangtest.so" ("/system/lib64/libwangtest.so") needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="", permitted_paths="/data:/mnt/expand:/data/data/com.example.wangxiancan.as_test"]
2019-09-11 09:42:39.355 21260-21260/com.example.wangxiancan.as_test E/TestA: Can't load wangtest library: java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib64/libwangtest.so" needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
具体的操作,还是结合实际的错误提示来分析,贴个自己的调试过程:
现在在第三方app加载刚编译的so库,该so库已经push到 /system/lib64/目录下。
运行app:
错误 1
2019-09-11 09:42:39.353 21260-21260/com.example.wangxiancan.as_test I/TestA: start loadLibrary!
2019-09-11 09:42:39.355 21260-21260/com.example.wangxiancan.as_test E/linker: library "/system/lib64/libwangtest.so" ("/system/lib64/libwangtest.so") needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="", permitted_paths="/data:/mnt/expand:/data/data/com.example.wangxiancan.as_test"]
2019-09-11 09:42:39.355 21260-21260/com.example.wangxiancan.as_test E/TestA: Can't load wangtest library: java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib64/libwangtest.so" needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
"/system/lib64/libnativeloader.so" is not accessible for the namespace: 无法访问。。。据说是android7 限制了普通app加载系统私有库(一些共用的库还是可以)
解决方法:既然无法加载system目录下的库,就把这个库拷贝到app目录下吧,(拷贝后为实验的准确性还是把system/lib目录下的那个库删掉确保只有一个吧。不然可能还是去system/lib下找到了这个库,结果加载不成功,不会再去app目录找了,关于这个查找的顺序,应该是先app私有目录再system目录的)既然有错误提示,不妨利用一下,直接把/system/lib64 下刚刚报错的库删除,这样在运行就会报错提示找不到这个库。
错误2
019-09-11 10:29:14.621 6205-6205/? I/TestA: start loadLibrary!
2019-09-11 10:29:14.622 6205-6205/? E/TestA: Can't load wangtest library: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.wangxiancan.as_test-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.wangxiancan.as_test-1/lib/arm64, /system/lib64, /vendor/lib64]]] couldn't find "libwangtest.so"
提示查找的路径是 app私有目录下的 lib/arm64/
哪我们就把测试用的so拷贝到这个目录。(没有arm64目录就自己mkdir 一个)
再运行:
错误3
019-09-11 10:30:41.310 7529-7529/com.example.wangxiancan.as_test I/TestA: start loadLibrary!
2019-09-11 10:30:41.312 7529-7529/com.example.wangxiancan.as_test E/TestA: Can't load wangtest library: java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++.so" not found
还需要libc++? 可以在设备上看到 /system/lib64/下面是存在libc++.so的,既然又报错找不到,也一并把这个/system/lib64/libc++.so 拷贝一份到 app私有目录 */lib/arm64/
再运行:成功加载并调用
2019-09-11 10:32:29.630 8109-8109/? I/TestA: start loadLibrary!
2019-09-11 10:32:29.634 8109-8109/? I/TestA: loadLibrary ok !
2019-09-11 10:32:29.635 8109-8109/? I/from-jni: [19 Java_com_example_wangxiancan_as_1test_TestA_NativeHello]
2019-09-11 10:32:29.635 8109-8109/? I/from-jni: Natieve Hello!
继续分析为何限制?如何限制?看android源码。源码工程中查找 is not accessible for the namespace,在/bionic/linker.cpp 中有加载库的函数。加载的时候先判断对应库是否存在,然后检查“限制条件”
if (!ns->is_accessible(realpath)) {//判断是否可以访问,文件是否存在
// TODO(dimitry): workaround for http://b/26394120 - the grey-list
const soinfo* needed_by = task->is_dt_needed() ? task->get_needed_by() : nullptr;
if (is_greylisted(name, needed_by)) {//这里有一个灰色表,表里面列举了部分库文件,如果
//要加载的库是在这个表里面或者加载库的是"系统"用户,就通过检查,否则拒绝加载,被限制。
// print warning only if needed by non-system library
if (needed_by == nullptr || !is_system_library(needed_by->get_realpath())) {
const soinfo* needed_or_dlopened_by = task->get_needed_by();
const char* sopath = needed_or_dlopened_by == nullptr ? "(unknown)" :
needed_or_dlopened_by->get_realpath();
DL_WARN("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the namespace \"%s\""
" - the access is temporarily granted as a workaround for http://b/26394120, note that the access"
" will be removed in future releases of Android.",
name, realpath.c_str(), sopath, ns->get_name());
add_dlwarning(sopath, "unauthorized access to", name);
}
} else {
// do not load libraries if they are not accessible for the specified namespace.
const char* needed_or_dlopened_by = task->get_needed_by() == nullptr ?
"(unknown)" :
task->get_needed_by()->get_realpath();
DL_ERR("library \"%s\" needed or dlopened by \"%s\" is not accessible for the namespace \"%s\"",
name, needed_or_dlopened_by, ns->get_name());
PRINT("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the"
" namespace: [name=\"%s\", ld_library_paths=\"%s\", default_library_paths=\"%s\","
" permitted_paths=\"%s\"]",
name, realpath.c_str(),
needed_or_dlopened_by,
ns->get_name(),
android::base::Join(ns->get_ld_library_paths(), ':').c_str(),
android::base::Join(ns->get_default_library_paths(), ':').c_str(),
android::base::Join(ns->get_permitted_paths(), ':').c_str());
return false;
}
}
所以这个函数 is_greylisted() 是关键的条件,其中就
// TODO(dimitry): The grey-list is a workaround for http://b/26394120 ---
// gradually remove libraries from this list until it is gone.
static bool is_greylisted(const char* name, const soinfo* needed_by) {
static const char* const kLibraryGreyList[] = {
"libandroid_runtime.so",
"libbinder.so",
"libcrypto.so",
"libcutils.so",
"libexpat.so",
"libgui.so",
"libmedia.so",
"libnativehelper.so",
"libskia.so",
"libssl.so",
"libstagefright.so",
"libsqlite.so",
"libui.so",
"libutils.so",
"libvorbisidec.so",
nullptr
};
// limit greylisting to apps targeting sdk version 23 and below
if (get_application_target_sdk_version() > 23) {
return false;
}
// if the library needed by a system library - implicitly assume it
// is greylisted
if (needed_by != nullptr && is_system_library(needed_by->get_realpath())) {
return true;
}
// if this is an absolute path - make sure it points to /system/lib(64)
if (name[0] == '/' && dirname(name) == kSystemLibDir) {
// and reduce the path to basename
name = basename(name);
}
for (size_t i = 0; kLibraryGreyList[i] != nullptr; ++i) {
if (strcmp(name, kLibraryGreyList[i]) == 0) {
return true;
}
}
return false;
}