android加载so库时会调用loadLibrary来完成加载操作。一般情况下,我们将JNI_OnLoad看作so库的入口,但是很多加壳过的so根本无法从JNI_OnLoad处开始分析,那JNI_OnLoad真的是so的入口吗,从android源码中找到答案如下:

System.loadLibrary其实只是做了一个中转(System.loadLibrary):

实际上调用了Runtime类中的loadLibrary(Runtime.loadLibrary):

从代码可以看出,若load为空的话会调用System.mapLibraryName来映射动态库名(比如“security”映射成”libsecurity.so”)。Runtime.loadLibrary并没有做真实的加载工作,而是将加载工作丢给native层的nativeLoad(/dalvik/vm/native/java_lang_Runtime.cpp),其代码如下:

这个函数将java层的string转成native的string,然后调用dvmLoadNativeCode完成真正的加载,它的代码如下(/dalvik/vm/Native.cpp):

这个函数用dlopen打开一个so,然后用dlsys查找so库中的JNI_OnLoad函数,若有的话就调用。这里要详细看看dlopen这个函数(/bionic/linker/dlfcn.c):

dlopen这个函数首先通过find_library将so库的信息加载进一个soinfo的结构体,这个结构体定义如下:

这个结构体中保存了这个so的入口,大小,符号表等信息,我们关心的是init_array,init_func,继续看dlopen后面调用的 call_constructors_recursive函数( /bionic/linker/linker.c):

看到这里就明白了,在调用JNI_OnLoad前,先会调用init和init_array中保存的函数。接下来做个小测试:

gcc编译后执行结果如下:

before main

main

__attribute__((constructor))就相当于在init段中注册了一个函数,所以会在main函数执行前被执行。

可以通过readelf -e xxx来查看so中init或init_array段的基地址:

init

 

参考文献

so壳入口浅析

Java中System.loadLibrary() 的执行过程

 

观看更多有关 的文章?

*

+
跳转到评论