【译】在 Android 上加载原生so库的风险

3,892 阅读7分钟
  • 原标题: The Perils of Loading Native Libraries on Android
  • 原文地址: medium.com/keepsafe-en…
  • 原文作者: Hilal Alsibai
  • 原文发布时间:2015年11月7日

笔者在去年遇到一个问题:项目打包成apk文件后安装正常,打包成aab文件后,通过bundleTool解析成apk文件后MMKV初始化崩溃,但是将aab文件上传到googlePlay后,解析成apk文件下载安装又正常。

通过阅读本文,你可以了解到为什么会出现这个问题,以及这个问题的解决思路。

译文

早在2012年,KeepSafe初期,我们试图为我们的Android应用程序实施加密方案。通过多次迭代和原型,我们通过利用JNI(Java 原生接口)的强大功能找到了一个最佳点。我们决定将接口写入我们在Java中使用的加密库中,通过JNI调用该库仅用于加密和解密的目的。我们选择了即时解决方案,尽可能减少对用户体验的影响。一旦我们对我们的解决方案感到满意,我们决定将其部署到我们的生产应用程序中。我们严格测试了我们的代码,并相信一切都会顺利进行,但是,事情最后超出了我们的控制。

UnsatisfiedLinkError初现

当我们在发布后焦急地更新崩溃报告时,我们开始注意到一个反复出现的错误。用户遇到了UnsatisfiedLinkError,这意味着有两种可能:(1)我们调用的本机库不存在;(2)我们调用的本机方法不存在。由于第二种几乎总是通过编译和基本测试被捕获,此刻我们对用户的安装没有我们在APK中提供的so本地库这一事实感到困惑。

以下是一些日志文件:

java.lang.UnsatisfiedLinkError: Couldnt load stlport_shared from loader dalvik.system.PathClassLoader[dexPath=/data/app/com.kii.safe-1.apk,libraryPath=/data/app-lib/com.kii.safe-1]: findLibrary returned null\
at java.lang.Runtime.loadLibrary(Runtime.java:365)\
at java.lang.System.loadLibrary(System.java:535)\
at com.kii.safe.Native.<clinit>(Native.java:16)\
… 63 more

Caused by: java.lang.UnsatisfiedLinkError: Library stlport_shared not found\
at java.lang.Runtime.loadLibrary(Runtime.java:461)\
at java.lang.System.loadLibrary(System.java:557)\
at com.kii.safe.Native.<clinit>(Native.java:16)\
… 5 more

Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: get_lib_extents[760]: 1305 — /mnt/asec/com.kii.safe-1/lib/libstlport_shared.so is not a valid ELF object\
at java.lang.Runtime.loadLibrary(Runtime.java:434)\
at java.lang.System.loadLibrary(System.java:554)\
at com.kii.safe.Native.<clinit>(Native.java:15)

Caused by: java.lang.UnsatisfiedLinkError: Library cryptopp not found\
at java.lang.Runtime.loadLibrary(Runtime.java:461)\
at java.lang.System.loadLibrary(System.java:557)\
at com.kii.safe.Native.<clinit>(Native.java:17)

令人十分气愤的是

Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: reloc_library[1286]: 1748 cannot locate ‘쯰ҷЦf1Ϙ˗˞ք᣼0Ⱉض夘Ϛ.͏闑㥁ج뭫ර⓻в^ӎ3c`+W#Ҽ?-Bַˌ֕꼠’…\
at java.lang.Runtime.loadLibrary(Runtime.java:370)\
at java.lang.System.loadLibrary(System.java:535)\
at com.kii.safe.Native.<clinit>(Native.java:17)

Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: reloc_library[1312]: 1327 cannot locatePܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭXߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭXߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#\
at java.lang.Runtime.loadLibrary(Runtime.java:434)\
at java.lang.System.loadLibrary(System.java:554)\
at com.kii.safe.Native.<clinit>(Native.java:17)

没有明确的模式说明哪个库有问题,因为似乎所有so库都在抛出异常。它不是特定于Android版本的,它不仅发生在特定设备上。此外,在某些情况下,一些本机库被正确加载,但不是全部。在这一点上,我们在互联网上疯狂地寻找答案或帮助,结果却空手而归。我们开始发布修补程序,其中大部分是推测性修补程序、额外的跟踪数据,以便我们更好地了解崩溃发生时的确切环境,以及专门检查预期安装位置中的本机库的一段代码。

此外,在某些情况下,一些本机库被正确加载,但不是全部。

我们收集到的信息显示本地库不存在。系统类或文件系统错误并不是什么奇怪的侥幸,但是该用户的设备看起来很正常。 我们有了以下几点思路:

(1) 用户设备空间不足

当我们考虑到这个异常的可能原因时,我们开始认为也许人们只是没有空间来正确安装本机库,因此它从未安装过。在尝试加载库之前进行快速诊断检查很快就证明了这个想法是错误的。用户在他们的设备上有足够的空间来存放我们正在运送的库。

(2)so库未包含在更新中

造成我们问题的第二个可能原因是Google Play在向用户设备提供APK时破坏了我们的APK。在阅读了诸如此类的报告后,我们对这个想法有了一些支持,详细说明Google Play应该向所有受问题影响的应用程序开发人员发出通知,该问题导致用户在更新后无法启动他们的应用程序,因为本地库安装不正确。唯一的问题是这份报告是在8月发布的,而我们在几个月后才开始处理这个问题。我们也从未收到来自Google Play的通知,提到他们有任何错误。当然,这是很难验证的。

(3)直接与真实用户一起调试问题

由于我们无法在手头的10多种不同设备上重现该问题,因此我们决定联系遇到问题的用户。一位用户慷慨地决定帮助我们,并指出该应用程序在最新更新之前运行良好。这里的问题是,用户注意到的应用程序版本是一个包含我们的加密代码和本机库的版本,这只是为了增加我们的困惑和总体难题。我们决定直接向用户提供一个APK文件,我们在其中验证了所有本机库都存在于APK文件中。用户安装了APK,启动了应用程序,然后又直接遇到了同样的UnsatisfiedLinkError异常。这证实了不是Google Play的问题,问题在于AndroidPackageManager安装过程

找到解决方案

由于我们发现问题出在安装过程中,因此我们决定复制安装过程中提取应用程序代码中的本机库的部分。幸运的是,您可以通过以下方式轻松获取设备上应用程序APK文件的引用

Context.getApplicationInfo().sourceDir;

然后我们将使用它来将本机库提取到内部存储位置。由于APK文件只是ZIP文件,因此只需编写一些ZIP提取代码即可。我们迅速实现了提取代码并发货,导致崩溃数量大幅下降!每天抛出的 UnsatisfiedLinkError异常总数如图:

1_QDcE8GYO_YVJrzrjBUg94A.png

为了减少我们的APK大小并确保我们的应用程序可以在所有可能的设备上运行,我们为x86Armv7Arm架构设计了我们的应用程序。每种风格只包含与其各自架构对应的本机库,因此完全有可能有人可以安装不是为他们的设备架构制作的APK

我们开始在崩溃中记录安装程序包名称,并很快发现,是的,用户正在从各种来源安装应用程序,并且每个新的UnsatisfiedLinkError都来自手动安装的应用程序,用户错误地为他们的设备安装了错误的abi架构。这是最后的“陷阱”,我们松了一口气,因为它有一个非常简单的解释。

介绍ReLinker

我们决定将提取代码打包成一个大家都可以使用的小库。任何人都不应该经历我们所经历的调试过程,尤其是当它涉及到一个非常基本的Android功能时,它不在App开发人员的控制范围内。

使用ReLinker就像替换标准一样简单

System.loadLibrary(“mylibrary”);

使用回调

ReLinker.loadLibrary(context, “mylibrary”)

ReLinker 源码

结语

在发布修复程序时,遇到此崩溃的人数持续达到约100,000名用户。我们希望您会发现ReLinker很有用,并且永远不会再随机遇到UnsatisfiedLinkerError

译者话

其实框架做的事情很简单,在我们需要加载so的时候,找到apk文件内指定位置的so文件加载,并且优化了soABI适配,进而解决了UnsatisfiedLinkError。不过这种偏底层的问题确实是我们平时不容易遇到的。