so 介绍

基本介绍

  • 为什么会用到 Shared Object(SO)

    • 开发效率

    • 快速移植

  • so 的版本

    • 根据 CPU 平台有所不一样

加载方法

  • System.loadLibrary

    • 如果加载的文件名是 xxx ,那么其实加载的是项目中 libs 目录下的 libxxx.so文件。

  • System.load

    • 对应 lib 的绝对路径。

主要使用第一种方式,第二种方式主要用于在插件中加载 so 文件。

loadLibrary 加载流程

根据官方 API 介绍

The call System.loadLibrary(name) is effectively equivalent to the call

可以看出该函数其实调用的是 Runtime.java( libcore/luni/src/main/java/java/lang/Runtime.java )中的函数 loadLibrary,继而会继续调用 loadLibrary 另一个重载函数,它包含两个参数

  • libame,我们传入的库名字

  • VMStack.getCallingClassLoader(),类加载器 ClassLoader,方便于去寻找相应的 library。

可以看出,程序主要的功能正如注释所说

Searches for a library, then loads and links it without security checks.

而其中所采用的加载函数是 doLoad 函数。在这里,我们先不继续分析,我们来看看 load 函数如何。

load 加载流程

根据官方 API 说明,如下

The call System.load(name) is effectively equivalent to the call:

其同样也是调用 Runtime.java 中的函数,如下

其同样也会调用load 的两个参数的重载函数,继而会调用doLoad函数。

无论是上面的哪一种加载方法,最后都会调用Runtime.java中的doLoad函数。

核心加载流程

doLoad

下面我们来分析一下 doLoad 函数,如下

虽然源代码很长,但是很多部分都是注释,也说明了为什么要使用这样的一个函数的原因,主要有以下原因

  • Android App 都是由 zygote fork 生成的,因此他们的 LD_LIBRARY_PATH 就是 zygote 的LD_LIBRARY_PATH,这也说明 apk 中的 so 文件不在这个路径下。

  • so 文件之间可能存在相互依赖,我们需要按照其按依赖关系的逆方向进行加载。

函数的基本思想就是找到库文件的路径,然后使用 synchronized 方式调用了 nativeLoad 函数。

nativeload

而 nativeload 函数其实就是一个原生层的函数

相应的文件路径为 dalvik/vm/native/java_lang_Runtime.cpp ,具体的 nativeLoad 函数如下

可以看出在 native 层对应的函数是 Dalvik_java_lang_Runtime_nativeLoad,如下

根据注释,我们可以确定关键的代码在

这一行执行后会告诉我们加载对应的 so 是否成功。

dvmLoadNativeCode

其基本的代码如下,我们可以根据注释来简单判断一下该函数的功能:

  • 程序根据指定的绝对路径加载相应的 native code,但是如果该 library 已经加载了,那么就不会再次进行加载。

此外,正如 JNI 中所说,我们不能将一个库加载到多个 class loader 中,也就是说,一个 library 只会和一个 class loader 关联。

函数的基本执行流程如下

  1. 利用 findSharedLibEntry 判断是否已经加载了这个库,以及如果已经加载的话,是不是采用的是同一个class loader。

  1. 如果没有加载的话,就会利用 dlopen 打开该共享库。

其中的 dlopen 函数(bionic/linker/dlfcn.cpp)如下

其会调用 do_dlopen 函数(bionic/linker/linker.cpp),如下

在找到对应的库之后,会使用 si->CallConstructors(); 来构造相关信息,如下

可以看出,正如注释所写的,如说 .init 函数与 init_array 存在的话,程序会依次调用 .init 函数与.init_array 中对应位置的代码。相关说明如下

  1. 建立一个打开的共享库的 entry,并试图其加入到对应的 list 中,方便管理。如果加入失败的话,就会对其进行释放。

  1. 如果加载成功,就会利用 dlsym 来获取对应 so 文件中的 JNI_OnLoad 函数,如果存在该函数的话,就进行调用,否则,就会直接返回。

总结

这说明加载 .so 文件时,会按照执行如下顺序的函数(如果不存在的话,就会跳过)

  • .init 函数

  • .init_array 中的函数

  • JNI_OnLoad 函数

Last updated