iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >so加载Linker跟NameSpace机制详解
  • 917
分享到

so加载Linker跟NameSpace机制详解

so加载LinkerNameSpace机制so加载 2023-01-15 12:01:40 917人浏览 泡泡鱼
摘要

目录前言LinkerNameSpace总结前言 so库的加载可是我们日常开发都会用到的,因此系统也提供了非常方便的api给我们进行调用 System.loadLibrary(xxx

前言

so库的加载可是我们日常开发都会用到的,因此系统也提供了非常方便的api给我们进行调用

System.loadLibrary(xxxso);

当然,随着版本的变化,loadLibrary也是出现了非常大的变化,最重要的是分水岭是AndroidN加入了namespace机制,可能很多人都是一头雾水噢!这是个啥?我们在动态so加载方案中,会频繁出现这个名词,同时还有一个高频的词就是Linker,本期不涉及复杂的技术方案,我们就来深入聊聊,Linker的概念,与namespace机制的加入,希望能帮助更多开发者去了解so的加载过程。

Linker

我们都知道,linux平台下有动态链接文件(.so)与静态链接文件(.a),两者其实都是一种ELF文件(相关的文件格式我们不赘述)。为什么会有这么两种文件呢?我们就从简单的角度来想一次,其实就是为了更多的代码复用,比如程序1,程序2都用到了同一个东西,比如xx.so

此时就会出现,程序1与程序2中调用fun common的地方,在没有链接之前,调用处的地址,我们以“stub”,表示这其实是一个未确定的东西,而后续的这个地址填充(写入正确的common地址),其实就是Linker的职责。

我们通过上面的例子,其实就可以明白,Linker,主要的职责,就是帮助查找当前程序所依赖的动态库文件(ELF文件)。那么Linker本身是个什么呢,其实他跟.so文件都是同一种格式,也是ELF文件,那么Linker又由谁帮助加载启动呢,这里就会出现存在一个(鸡生蛋,蛋生鸡)的问题,而ELF文件给出的答案就是,设立一个:interp 的段,当一个进程启动的时候(linux中通过execv启动),此时就会通过load_elf_binary函数,先加载ELF文件,然后再调用load_elf_interp方法,直接加载了:interp 段地址的起点,从而能够构建我们的大管家Linker,当然,Linker本身就不能像普通的so文件一样,去依赖另一个so,其实原因也很简单,没人帮他初始化呀!因此Linker是采用配置的方式先启动起来了!

当然,我们主要的目标是建立概念,Linker本身涉及的复杂加载,我们也不继续贴出来了

NameSpace

在以往的anroidN以下版本中,加载so库通常是直接采用dlopen的方式去直接加载的,对于非公开的符号,如果被使用,就容易在之后迭代出现问题,(类似java,使用了一个三方库的private方法,如果后续变更方法含义,就会出现问题),因此引入了NameSpace机制

Android 7.0 为原生库引入了命名空间,以限制内部 API 可见性并解决应用意外使用平台库而不是自己的平台库的情况。

我们说的NameSpace,主要对应着一个数据结构android_namespace_link_t

linker_namespaces.h 
struct android_namespace_link_t 
private:
        std::string name_; namespace名称
        bool is_isolated_; 是否隔离(大部分是true)
        std::vector<std::string> ld_library_paths_; 链接路径
        std::vector<std::string> default_library_paths_;默认可访问路径
        std::vector<std::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_guard<std::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,
            false,
            nullptr,
            library_path,
            nullptr,
            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(
            "" , mApplicationInfo.targetSdkVersion, isBundledApp,
            librarySearchPath, libraryPermittedPath, mBaseClassLoader,
            null );
        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<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries,
                                            List<ClassLoader> 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);
        ... 
        ProtectedDataGuard guard;
        之后就是在namespace的加载列表找library的过程了
        soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
       ....
        return nullptr;
        }

总结

最后我们先总结一下,Linker作用跟NameSpace的调用流程,可以发现其实内部非常复杂,但是我们抓住主干去看,NameSpace其实作用的功能,也就是规范了查找so的过程,需要在指定列表查找。

以上就是so加载Linker跟NameSpace机制详解的详细内容,更多关于so加载Linker NameSpace机制的资料请关注编程网其它相关文章!

--结束END--

本文标题: so加载Linker跟NameSpace机制详解

本文链接: https://www.lsjlt.com/news/177771.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • so加载Linker跟NameSpace机制详解
    目录前言LinkerNameSpace总结前言 so库的加载可是我们日常开发都会用到的,因此系统也提供了非常方便的api给我们进行调用 System.loadLibrary(xxx...
    99+
    2023-01-15
    so加载Linker NameSpace机制 so加载
  • Android 动态加载 so实现示例详解
    目录背景so动态加载介绍从一个例子出发so库检索与删除动态加载so结束了吗?ELF文件扩展总结背景 对于一个普通的android应用来说,so库的占比通常都是巨高不下的,因为我们无可...
    99+
    2024-04-02
  • jvm虚拟机类加载机制详解
    目录1 概述2 类的加载时机3 类的加载过程3.1 加载3.2 验证3.3 准备3.4 解析3.5 初始化4 类加载器4.1 双亲委派模型4.2 破坏双亲委派模型1 概述 ​ Jav...
    99+
    2024-04-02
  • JVM分析之类加载机制详解
    目录1、前言2、类加载是什么3、类加载过程3.1 加载3.2 链接3.3 初始化4、总结1、前言 JVM内部架构包含类加载器、内存区域、执行引擎等。日常开发中,我们编写的java文件...
    99+
    2022-11-13
    JVM类加载机制 JVM类加载
  • 一文详解Java中的类加载机制
    目录一、前言二、类加载的时机2.1 类加载过程2.2 什么时候类初始化2.3 被动引用不会初始化三、类加载的过程3.1 加载3.2 验证3.3 准备3.4 解析3.5 初始化四、父类...
    99+
    2024-04-02
  • Java 类加载机制详细介绍
    一、类加载器  类加载器(ClassLoader),顾名思义,即加载类的东西。在我们使用一个类之前,JVM需要先将该类的字节码文件(.class文件)从磁盘、网络或其他来源加载到内存中,并对字节码进行解析生成对应的Class对象,这就是类加...
    99+
    2023-05-31
    java 加载机制
  • 详细分析JVM类加载机制
    目录前言1. jvm 的组成2. 类加载1. 加载  2. 链接3. 初始化3. 类加载器引导类加载器(启动类加载器)扩展类加载器应用程序类加载器4. 双亲委派机...
    99+
    2024-04-02
  • Android虚拟机与类加载机制详情
    目录JVM与Dalvik基于栈的虚拟机字节码指令执行过程基于寄存器的虚拟机寄存器基于寄存器的虚拟机ART与Dalvikdex2aotdexopt与dexaotAndroid N的运作...
    99+
    2024-04-02
  • Java JVM类加载机制解读
    目录1.什么是类加载2.类加载的过程2.1加载2.2验证2.3准备2.4解析2.5初始化【重中之重之重中重】第一段代码:第二段代码:第三段代码:最后一段代码:总结1.什么是类加载 首...
    99+
    2024-04-02
  • 怎么理解类加载机制
    本篇内容主要讲解“怎么理解类加载机制”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解类加载机制”吧!我们写的类,在编译完成后,究竟是怎么加载进虚拟机的虚拟...
    99+
    2024-04-02
  • JVM加载class文件的原理机制实例详解
    目录一、JVM简介二、JVM的组成部分三、JVM加载class文件的原理机制一、JVM简介 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于...
    99+
    2024-04-02
  • 面试必时必问的JVM 类加载机制详解
    目录前言正文1、类加载的过程。1)加载2)验证3)准备4)解析5)初始化2、Java 虚拟机中有哪些类加载器?1)启动类加载器(Bootstrap ClassLoader):2)扩展...
    99+
    2024-04-02
  • Mybatis基于MapperScan注解的动态代理加载机制详解
    1.如下图在代码开发中使用mybatis时,通过一个接口UserDao对应的方法selectUserNameById执行xml里配置的selectUserNameById查...
    99+
    2023-01-28
    Mybatis动态代理加载 Mybatis MapperScan注解动态代理加载
  • 详解Java的类加载机制及热部署的原理
    目录一、什么是类加载二、类的生命周期2.1 加载2.2 连接2.3 初始化2.4 结束生命周期三、类加载器四、Java类加载机制五、类的加载六、双亲委派模型七、自定义加载器的应用7....
    99+
    2024-04-02
  • Java之类加载机制案例讲解
    1.类加载 <1>.父子类执行的顺序 1.父类的静态变量和静态代码块(书写顺序) 2.子类的静态变量和静态代码块(书写顺序) 3.父类的实例代码块(书写顺序) 4.父类...
    99+
    2024-04-02
  • SpringMVC bean实现加载控制方法详解
    目录1、Spring配置类排除加载SpringMVC的bean2、Servlet容器配置类简洁开发1、Spring配置类排除加载SpringMVC的bean SpringMVC 通常...
    99+
    2024-04-02
  • Java中类加载机制的实例讲解
    这篇文章主要介绍“Java中类加载机制的实例讲解”,在日常操作中,相信很多人在Java中类加载机制的实例讲解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中类加载机制的实例讲解”的疑惑有所帮助!接下来...
    99+
    2023-06-20
  • Mysql事务以及加锁机制详解
    这篇文章主要讲解了“Mysql事务以及加锁机制详解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Mysql事务以及加锁机制详解”吧!事务的特征ACID,即原...
    99+
    2024-04-02
  • AQS加锁机制Synchronized相似点详解
    目录正文1. Synchronized加锁流程2. AQS加锁原理3. 总结正文 在并发多线程的情况下,为了保证数据安全性,一般我们会对数据进行加锁,通常使用Synchronized...
    99+
    2022-11-13
    AQS加锁机制Synchronized AQS Synchronized
  • 怎么理解Flutter图片加载与缓存机制
    本篇内容主要讲解“怎么理解Flutter图片加载与缓存机制”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解Flutter图片加载与缓存机制”吧!前言今天来学习一下 Flutter 自身是如...
    99+
    2023-06-25
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作