android的链接器命名空间

android 7.0加入了对私有系统so库API调用的限制(因为私有系统库会在任意时刻更新,使用这些系统库的应用有可能会在更新后崩溃),android 8.0引入了project Treble实现框架与供应商解耦合(解决系统碎片化,升级慢),这都需要依赖链接器命名空间机制。这里主要关注的重点是链接器命名空间在android 7.0引入后对System.loadLibrary,dlopen和dlsym函数的限制。

链接器命名空间

当链接器收到一个库加载的命令时会遍历库搜索路径(Library Search Path)LSPath 去寻找文件并将其加载到内存中,并通过已加载库列表(Alread Load Library list)ALList去跟踪已加载的所有库,当需要加载的库已经在ALList中时将不会再进行重复加载。android 7.0之前就是使用这种方式,所有的库都被加载到同一个ALList列表中。android7.0之后引入了链接器命名空间,每个命名空间都有自己的LSPach和ALList。每个命名空间只能在自己的LSPath中加载库,在进行库搜索时也只能再自己的ALList中进行搜索。

除此之外命名空间可以链接到其他命名空间并设置此链接的共享库范围。例如一个命名空间B链接到命名空间A,此链接设置允许所有的库都可以在无法加载到命名空间B的时候去链接的命名空间A中搜索加载(allow_all_shared_libs = true)。一个命名空间C也链接到命名空间A,而此链接设置只允许部分库(liba.so,libb.so,libc.so)在无法加载到命名空间A的时候去链接的命名空间B中搜索加载(设置shared_libs列表),需要注意allow_all_shared_libs = true与shared_libs无法同时设置。(特别注意共享库范围是对某个链接有效的,不同的链接其设置的共享库范围可能不同)

可执行文件的链接器命名空间初始化

一个需要动态链接的可执行文件在由内核加载后,并且在代码执行之前,linker链接程序会通过配置文件初始化其链接器命名空间(纯静态链接不调用任何外部库导出函数的可执行文件自然不需要设置命名空间)。配置文件选择有一定的规则,一般情况下都是使用/system/etc/ld.config.{abi}.txt作为配置文件。我的手机是android 10所以对应的配置文件为/system/etc/ld.config.29.txt,此文件中含有多个标签针对不同路径的可执行文件进行不同的配置。配置文件中信息的含义可参考google官方解释https://source.android.google.cn/docs/core/architecture/vndk/linker-namespace

例如对于app进程而言native入口实际是/system/bin/app_process的main函数,所以在第一个app进程zygote的app_process文件执行之前链接器linker会解析ld.config.29.txt文件,因为app_process文件在/system/bin目录中,所以会根据system标签中的命名空间配置进行命名空间初始化。

查看system标签下的命名空间配置,additional.namespaces = runtime,conscrypt,media,resolv,sphal,vndk,rs表示除了default默认命名空间外还需要创建runtime,conscrypt,media,resolv,sphal,vndk,rs这7个命名空间。

查看default命名空间的初始化配置(中间有些信息删除了),会设置default命名空间为严格隔离,设置库加载的搜索路径,特权路径。还会设置default命名空间链接到runtime和resolv,并设置链接到runtime和resolv的共享库范围。

查看runtime命名空间的初始化配置,同样会设置runtime命名空间为严格隔离,设置库加载的搜索路径。同时设置了runtime命名空间链接到默认命名空间default中,设置链接范围为allow_all_shared_libs = true即允许runtime命名空间无法加载的库都可以去default中去搜索加载。

app进程链接器命名空间

为了实现禁止app访问私有库(libandroid_runtime.so,libart.so),andoird系统通过NativeLoader(libnativeloader.so)组件使用命名空间机制的接口将类加载器映射到一个关联的命名空间中。app加载so库有两种方式:java中调用System.loadLibrary()和native中调用dlopen()或android_dlopen_ext()。java调用System.loadLibrary()加载库最后会来到NativeLoader中,NativeLoader把库加载到System.loadLibrary()调用上下文的类加载器所映射的命名空间中,如果类加载器是第一次加载so库就会创建一个与类加载器对应的新命名空间。native调用dlopen同样会获取dlopen调用上下文so文件所对应的类加载器,并将库加载到此类加载器映射的命名空间中。

NativeLoader维护从java类加载器到命名空间的映射,在app启动时ApplicationLoader加载器Path CL会创建第一个命名空间CL NS 0,之后创建的类加载器只要调用System.loadLibrary加载原生库都会在第一次调用的时候创建一个对应的命名空间,如果类加载器不需要调用System.loadLibrary加载原生库则就没有对应的命名空间App CL 3。这些创建的命名空间被命名为classloader-namespace,默认链接到defult命名空间中并设置链接的共享库范围shared_libs为/system/etc/public.libraries.txt/vendor/etc/public.libraries.txt文件中列举的库。https://source.android.google.cn/docs/core/permissions/namespaces_libraries

查看/system/etc/public.libraries.txt/vendor/etc/public.libraries.txt文件中列举的so库,根据链接器命名空间的限制规则APP进程默认只允许打开这些系统so库(通过java调用System.loadLibrary和native调用dlopen)。

参考链接:
https://source.android.google.cn/docs/core/permissions/namespaces_libraries
https://source.android.google.cn/docs/core/architecture/vndk/linker-namespace
https://zhenhuaw.me/blog/2017/namespace-based-dynamic-linking-chn.html

posted @ 2023-01-02 00:41  怎么可以吃突突  阅读(853)  评论(0编辑  收藏  举报