so加载 - Linker跟NameSpace知识 (上篇)_dlopen namespace(1),java 面试基础题

109 阅读5分钟

struct android_namespace_link_t

private: std::string name_; namespace名称 bool is_isolated_; 是否隔离(大部分是true) std::vectorstd::string ld_library_paths_; 链接路径 std::vectorstd::string default_library_paths_;默认可访问路径 std::vectorstd::string permitted_paths_;已允许访问路径 ....

我们来看一看,这个数据结构在哪里会被使用到,其实就是so库加载过程。当我们调用System.loadLibrary的时候,其实最终调用的是

private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) { 文件名校验 if (libname.indexOf((int)File.separatorChar) != -1) { throw new UnsatisfiedLinkError( "Directory separator should not appear in library name: " + libname); } String libraryName = libname; // Android-note: BootClassLoader doesn't implement findLibrary(). http://b/111850480 // Android's class.getClassLoader() can return BootClassLoader where the RI would // have returned null; therefore we treat BootClassLoader the same as null here. if (loader != null && !(loader instanceof BootClassLoader)) { String filename = loader.findLibrary(libraryName); if (filename == null && (loader.getClass() == PathClassLoader.class || loader.getClass() == DelegateLastClassLoader.class)) { // Don't give up even if we failed to find the library in the native lib paths. // The underlying dynamic linker might be able to find the lib in one of the linker // namespaces associated with the current linker namespace. In order to give the // dynamic linker a chance, proceed to load the library with its soname, which // is the fileName. // Note that we do this only for PathClassLoader and DelegateLastClassLoader to // minimize the scope of this behavioral change as much as possible, which might // cause problem like b/143649498. These two class loaders are the only // platform-provided class loaders that can load apps. See the classLoader attribute // of the application tag in app manifest. filename = System.mapLibraryName(libraryName); } if (filename == null) { // It's not necessarily true that the ClassLoader used // System.mapLibraryName, but the default setup does, and it's // misleading to say we didn't find "libMyLibrary.so" when we // actually searched for "liblibMyLibrary.so.so". throw new UnsatisfiedLinkError(loader + " couldn't find "" + System.mapLibraryName(libraryName) + """); } String error = nativeLoad(filename, loader); if (error != null) { throw new UnsatisfiedLinkError(error); } return; }

// We know some apps use mLibPaths directly, potentially assuming it's not null. // Initialize it here to make sure apps see a non-null value. getLibPaths(); String filename = System.mapLibraryName(libraryName); 最终调用nativeLoad String error = nativeLoad(filename, loader, callerClass); if (error != null) { throw new UnsatisfiedLinkError(error); } }

这里我们注意到,抛出UnsatisfiedLinkError的时机,要么so文件名加载不合法,要么就是nativeLoad方法返回了错误信息,这里是需要我们注意的,我们如果出现这个异常,可以从这里排查,nativeLoad方法最终通过LoadNativeLibrary,在native层真正进入so的加载过程

LoadNativeLibrary 非常长,我们截取部分 bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader, jclass caller_class, std::string* error_msg) {

会判断是否已经加载过当前so,同时也要加锁,因为存在多线程加载的情况

SharedLibrary* library; Thread* self = Thread::Current(); { // TODO: move the locking (and more of this logic) into Libraries. MutexLock mu(self, *Locks::jni_libraries_lock_); library = libraries_->Get(path); }

调用OpenNativeLibrary加载 void* handle = android::OpenNativeLibrary( env, runtime_->GetTargetSdkVersion(), path_str, class_loader, (caller_location.empty() ? nullptr : caller_location.c_str()), library_path.get(), &needs_native_bridge, &nativeloader_error_msg);

这里又是漫长的native方法,OpenNativeLibrary,在这里我们终于见到namespace了

void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path, jobject class_loader, const char* caller_location, jstring library_path, bool* needs_native_bridge, char** error_msg) { #if defined(ART_TARGET_ANDROID) UNUSED(target_sdk_version);

if (class_loader == nullptr) { needs_native_bridge = false; if (caller_location != nullptr) { android_namespace_t boot_namespace = FindExportedNamespace(caller_location); if (boot_namespace != nullptr) { const android_dlextinfo dlextinfo = { .flags = ANDROID_DLEXT_USE_NAMESPACE, .library_namespace = boot_namespace, }; 最终调用android_dlopen_ext打开 void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo); if (handle == nullptr) { *error_msg = strdup(dlerror()); } return handle; } }

// Check if the library is in NATIVELOADER_DEFAULT_NAMESPACE_LIBS and should // be loaded from the kNativeloaderExtraLibs namespace. { Result<void*> handle = TryLoadNativeloaderExtraLib(path); if (!handle.ok()) { *error_msg = strdup(handle.error().message().c_str()); return nullptr; } if (handle.value() != nullptr) { return handle.value(); } }

// Fall back to the system namespace. This happens for preloaded JNI // libraries in the zygote. // TODO(b/185833744): Investigate if this should fall back to the app main // namespace (aka anonymous namespace) instead. void* handle = OpenSystemLibrary(path, RTLD_NOW); if (handle == nullptr) { *error_msg = strdup(dlerror()); } return handle; }

std::lock_guardstd::mutex guard(g_namespaces_mutex); NativeLoaderNamespace* ns; 涉及到了namespace,如果当前classloader没有,则创建,但是这属于异常情况 if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) { // This is the case where the classloader was not created by ApplicationLoaders // In this case we create an isolated not-shared namespace for it. Result<NativeLoaderNamespace*> isolated_ns = CreateClassLoaderNamespaceLocked(env, target_sdk_version, class_loader, /is_shared=/false, /dex_path=/nullptr, library_path, /permitted_path=/nullptr, /uses_library_list=/nullptr); if (!isolated_ns.ok()) { *error_msg = strdup(isolated_ns.error().message().c_str()); return nullptr; } else { ns = *isolated_ns; } }

return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);

这里我们打断一下,我们看到上面代码分析,如果当前classloader的namespace如果为null,则创建,这里我们也知道一个信息,namespace是跟classloader绑定的。同时我们也知道,classloader在创建的时候,其实就会绑定一个namespace。我们在app加载的时候,就会通过LoadedApk这个class去加载一个pathclassloader

frameworks/base/core/java/android/app/LoadedApk.java

if (!mIncludeCode) { if (mDefaultClassLoader == null) { StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads(); mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader( "" /* codePath /, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader, null / classLoaderName */); setThreadPolicy(oldPolicy); mAppComponentFactory = AppComponentFactory.DEFAULT; }

if (mClassLoader == null) { mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader, new ApplicationInfo(mApplicationInfo)); }

return; }

之后ApplicationLoaders.getDefault().getClassLoader会调用createClassLoader

public static ClassLoader createClassLoader(String dexPath, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, int targetSdkVersion, boolean isNamespaceShared, String classLoaderName, List sharedLibraries, List nativeSharedLibraries, List sharedLibrariesAfter) {

final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent, classLoaderName, sharedLibraries, sharedLibrariesAfter);

String sonameList = ""; if (nativeSharedLibraries != null) { sonameList = String.join(":", nativeSharedLibraries); }

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace"); 这里就讲上述的属性传入,创建了一个属于该classloader的namespace String errorMessage = createClassloaderNamespace(classLoader, targetSdkVersion, librarySearchPath, libraryPermittedPath, isNamespaceShared, dexPath, sonameList); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

if (errorMessage != null) { throw new UnsatisfiedLinkError("Unable to create namespace for the classloader " + classLoader + ": " + errorMessage); }

return classLoader; }

这里我们得到的主要消息是,我们的classloader的namespace,里面的so检索路径,其实都在创建的时候就被定下来了(这个也是,为什么想要实现so动态加载,其中的一个方案就是替换classloader的原因,因为我们当前使用的classloader的namespace检索路径,已经是固定了,后续对classloader本身的检索路径添加,是不会同步给namespace的,只有创建的时候才会同步)

好了,我们继续回到OpenNativeLibrary,内部其实调用android_dlopen_ext打开

void* android_dlopen_ext(const char* filename, int flag, const android_dlextinfo* extinfo) { const void* caller_addr = __builtin_return_address(0); return __loader_android_dlopen_ext(filename, flag, extinfo, caller_addr); }

这里不知道大家有没有觉得眼熟,这里肯定最终调用就是dlopen,只不过谷歌为了限制dlopen的调起方,采用了__builtin_return_address 内建函数作为卡口,限制了普通app调哟dlopen(这里也是有破解方法的)

之后的经历android_dlopen_ext -> dlopen_ext ->do_dlopen,最终到了最后加载的方法了

void* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo, const void* caller_addr) { std::string trace_prefix = std::string("dlopen: ") + (name == nullptr ? "(nullptr)" : name); ScopedTrace trace(trace_prefix.c_str()); ScopedTrace loading_trace((trace_prefix + " - loading and linking").c_str()); soinfo* const caller = find_containing_library(caller_addr); // 找到调用者,属于哪个namespace android_namespace_t* ns = get_caller_namespace(caller);

img img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!