记录动态加载需要解决的问题和涉及的技术原理
学要解决的问题
安全性问题
MD5校验so文件完整性版本控制问题
1)宿主API兼容
2)可规避有bug的so文件abi兼容问题
根据设备架构识别不同的so文件代码侵入问题
自身和第三方sdk的load方法无法统一
1)采用ASM全部修改成system.load(“ ”)
2)so路径注入nativeLibraryDirectories数组(classloader内部字段),然后采用system.loadlibrary(“ ”)
涉及的技术原理
so加载流程
1)安装时,pms根据设备abi拷贝对应的so文件
2)启动时,so文件路径注入nativeLibraryDirectories数组
3)从classloader内字段nativeLibraryDirectories查找并加载对应so文件(最后涉及到dlopen)JNI独立模块
插件apk内创建新的classloader,成本较大,但收益高JNI内置宿主
插件使用宿主的classloader,成本较小,但有明显缺点:nativeLibraryDirectories数组并发问题dlopen问题
so内部依赖问题(涉及Linker相关改动),解决方案:
1)分析so依赖,然后递归加载依赖(Soloader)
Linker 里检索的路径在创建 ClassLoader 实例后就被系统通过 Namespace 机制绑定了,当我们注入新的路径之后,虽然 ClassLoader 里的路径增加了,但是 Linker 里 Namespace 已经绑定的路径集合并没有同步更新,所以出现了 libxxx.so 文件(当前的so)能找到,而依赖的so 找不到的情况
2)自定义Linker检索逻辑(Relinker,成本较低)
3)直接替换classloader,规避linker改动 (成本较大)
注:
SD卡等外部存储路径是一种可拆卸的(已安装)不可执行(noexec)的存储介质,不能直接用作作为替换文件的运行目录,使用前应该把文件复制到APP内部存储下再运行。所以使用 System.load 加载so时要注意把so拷贝至 /data/data/package-name/ 下
Android应用所需要的So文件一般被放在/data/data/package_name/lib目录中
(其实这里已经可以用system.load(path)来调用了)so库,本质就是一个elf文件,那么so库也符合elf文件的格式,ELF文件由4部分组成,分别是:
- ELF头(ELF header)
- 程序头表(Program header table)
- 节(Section)
- 节头表(Section header table)
实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
依赖其他so的信息存在dynamic字段中,可以用readelf(ndk中就有toolchains目录后) 查看,readelf -d nativecpptwo.so 这里的 -d 就是查看dynamic段的意思
参考链接
https://juejin.cn/post/7107958280097366030 (字节pika文章)
https://github.com/TestPlanB/dyso (字节pika github)
https://cloud.tencent.com/developer/article/1592672 (bugly官方干货)
https://cloud.tencent.com/developer/article/1643819 (作者已经删除相关代码,原因不详)
https://github.com/facebook/SoLoader (递归加载依赖的 so 文件)
https://github.com/google/android-classyshark (so 依赖分析工具)
https://community.jiguang.cn/article/127460 (so相关文章)