平台
RK3288 + Android 9
问题
官方原文:
"从 Android 7.0 开始,系统将阻止应用动态链接非公开 NDK 库,这种库可能会导致您的应用崩溃。此行为变更旨在为跨平台更新和不同设备提供统一的应用体验。即使您的代码可能不会链接私有库,但您的应用中的第三方静态库可能会这么做。因此,所有开发者都应进行相应检查,确保他们的应用不会在运行 Android 7.0 的设备上崩溃。如果您的应用使用原生代码,则只能使用公开 NDK API。"
这样会有什么问题?
很多使用JNI的APP将无法像低版本SDK一样, 正常使用, 在使用7.1的时候, 系统会有相关的提示, 到使用9.0时, 直接崩溃报错.
java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib/libserial_port.so" needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace "classloader-namespace" at java.lang.Runtime.loadLibrary0(Runtime.java:1016) at java.lang.System.loadLibrary(System.java:1669)
常见:UnsatisfiedLinkError
解决方案
方案 1修改APP端
见下文记录7.0加载so文件失败:java.lang.UnsatisfiedLinkError: dlopen failed: library “libsqlite.so” not found
方案 2 修改系统SDK
|-- 错误输出部分代码: bionic/linker/linker.cpp
static bool load_library(android_namespace_t* ns, LoadTask* task, LoadTaskList* load_tasks, int rtld_flags, const std::string& realpath, bool search_linked_namespaces) { off64_t file_offset = task->get_file_offset(); const char* name = task->get_name(); const android_dlextinfo* extinfo = task->get_extinfo(); if ((file_offset % PAGE_SIZE) != 0) { DL_ERR("file offset for the library \"%s\" is not page-aligned: %" PRId64, name, file_offset); return false; } if (file_offset < 0) { DL_ERR("file offset for the library \"%s\" is negative: %" PRId64, name, file_offset); return false; } struct stat file_stat; if (TEMP_FAILURE_RETRY(fstat(task->get_fd(), &file_stat)) != 0) { DL_ERR("unable to stat file for the library \"%s\": %s", name, strerror(errno)); return false; } if (file_offset >= file_stat.st_size) { DL_ERR("file offset for the library \"%s\" >= file size: %" PRId64 " >= %" PRId64, name, file_offset, file_stat.st_size); return false; } // Check for symlink and other situations where // file can have different names, unless ANDROID_DLEXT_FORCE_LOAD is set if (extinfo == nullptr || (extinfo->flags & ANDROID_DLEXT_FORCE_LOAD) == 0) { soinfo* si = nullptr; if (find_loaded_library_by_inode(ns, file_stat, file_offset, search_linked_namespaces, &si)) { TRACE("library \"%s\" is already loaded under different name/path \"%s\" - " "will return existing soinfo", name, si->get_realpath()); task->set_soinfo(si); return true; } } if ((rtld_flags & RTLD_NOLOAD) != 0) { DL_ERR("library \"%s\" wasn't loaded and RTLD_NOLOAD prevented it", name); return false; } struct statfs fs_stat; if (TEMP_FAILURE_RETRY(fstatfs(task->get_fd(), &fs_stat)) != 0) { DL_ERR("unable to fstatfs file for the library \"%s\": %s", name, strerror(errno)); return false; } // do not check accessibility using realpath if fd is located on tmpfs // this enables use of memfd_create() for apps if ((fs_stat.f_type != TMPFS_MAGIC) && (!ns->is_accessible(realpath))) { // TODO(dimitry): workaround for http://b/26394120 - the grey-list // TODO(dimitry) before O release: add a namespace attribute to have this enabled // only for classloader-namespaces const soinfo* needed_by = task->is_dt_needed() ? task->get_needed_by() : nullptr; if (is_greylisted(ns, 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_documented_change(__ANDROID_API_N__, "private-api-enforced-for-api-level-24", "library \"%s\" (\"%s\") needed or dlopened by \"%s\" " "is not accessible by namespace \"%s\"", 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()); // do not print this if a library is in the list of shared libraries for linked namespaces if (!maybe_accessible_via_namespace_links(ns, 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; } } soinfo* si = soinfo_alloc(ns, realpath.c_str(), &file_stat, file_offset, rtld_flags); if (si == nullptr) { return false; } task->set_soinfo(si); // Read the ELF header and some of the segments. if (!task->read(realpath.c_str(), file_stat.st_size)) { soinfo_free(si); task->set_soinfo(nullptr); return false; } // find and set DT_RUNPATH and dt_soname // Note that these field values are temporary and are // going to be overwritten on soinfo::prelink_image // with values from PT_LOAD segments. const ElfReader& elf_reader = task->get_elf_reader(); for (const ElfW(Dyn)* d = elf_reader.dynamic(); d->d_tag != DT_NULL; ++d) { if (d->d_tag == DT_RUNPATH) { si->set_dt_runpath(elf_reader.get_string(d->d_un.d_val)); } if (d->d_tag == DT_SONAME) { si->set_soname(elf_reader.get_string(d->d_un.d_val)); } } for_each_dt_needed(task->get_elf_reader(), [&](const char* name) { load_tasks->push_back(LoadTask::create(name, si, ns, task->get_readers_map())); }); return true; }
错误输出:
DL_ERR("library \"%s\" needed or dlopened by \"%s\" is not accessible for the namespace \"%s\"", name, needed_or_dlopened_by, ns->get_name());
修改的思路是, 不走报错这部分分支的代码, 切入点 is_greylisted
diff --git a/bionic/linker/linker.cpp b/bionic/linker/linker.cpp old mode 100644 new mode 100755 index c78b9ab..0aa83bc --- a/bionic/linker/linker.cpp +++ b/bionic/linker/linker.cpp @@ -200,12 +200,14 @@ static bool is_greylisted(android_namespace_t* ns, const char* name, const soinf "libui.so", "libutils.so", "libvorbisidec.so", + "libserial_port.so", nullptr }; // If you're targeting N, you don't get the greylist. if (g_greylist_disabled || get_application_target_sdk_version() >= __ANDROID_API_N__) { - return false; + //make system lib can be loaded. + //return false; } // if the library needed by a system library - implicitly assume it
修改点:
将调用的SO加入灰名单列表, 这里加了一个libserial_port.so
注释判断SDK和灰名单使能判断的返回, 当然, 也可以强制返回 true, 看需求办事
修改后有Warning提示, 忽略:
Warning: library "/system/lib/libserial_port.so" ("/system/lib/libserial_port.so") needed or dlopened by "/system/lib/libnativeloader.so" is not accessible by namespace "classloader-namespace" and will not work when the app moves to API level 24 or later (https://android.googlesource.com/platform/bionic/+/master/private-api-enforced-for-api-level-24) (allowing for now because this app's target API level is still 26)
已上传资源: linker
使用:
adb root adb remount adb push linker /system/bin/ adb reboot
随着而来的第二个问题:
java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++.so" not found at java.lang.Runtime.loadLibrary0(Runtime.java:1016) at java.lang.System.loadLibrary(System.java:1669)
|–bionic/linker/linker.cpp
@@ -2342,6 +2344,10 @@ android_namespace_t* create_namespace(const void* caller_addr, parse_path(ld_library_path, ":", &ld_library_paths); parse_path(default_library_path, ":", &default_library_paths); + if(strcmp(name, "classloader-namespace") == 0){ + parse_path("/system/lib", ":", &default_library_paths); + } parse_path(permitted_when_isolated_path, ":", &permitted_paths); android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t();
解决!!
完整补丁: linker_patch.tar.gz
相关
Android 7.0 NDK 行为变更
记录7.0加载so文件失败:java.lang.UnsatisfiedLinkError: dlopen failed: library “libsqlite.so” not found