之前学习了ELF文件格式,然后就想分析分析Android是如何对so文件进行加载的,有助于理解linker机制以及so的加固
Java层调用
调用System.loadLibrary对so文件进行加载:
1 | /** |
在这个函数的上面还有一个load函数也是用来加载so文件的,区别在于load需要传入一个绝对路径,所以它可以从外部进行加载
1 | /** |
先分析loadLibrary,进行跟进
这里跟进findLibrary看看是怎样找到so路径的
居然是返回空???是这样的,在Android里ClassLoader只是一个抽象类,部分的实现机制在子类BaseDexClassLoader中,那么跳过去看看
此处呢,调用了DexPathList类的findlibrary方法,跟进查看
/**
* Finds the named native code library on any of the library
* directories pointed at by this instance. This will find the
* one in the earliest listed directory, ignoring any that are not
* readable regular files.
*
* @return the complete path to the library or {@code null} if no
* library was found
*/
public String findLibrary(String libraryName) {
String fileName = System.mapLibraryName(libraryName);
for (File directory : nativeLibraryDirectories) {
String path = new File(directory, fileName).getPath();
if (IoUtils.canOpenReadOnly(path)) {
return path;
}
}
return null;
}
呐,这里其实就是根据传进来的libName,返回内部SO库文件的完整路径filename。再回到Runtime类,获取filename后调用了doLoad方法
再来看看System.load方法
跟进Runtime类
呐,这里就比较直接了,直接调用了doLoad方法
,可见loadLibrary和load的后面的调用是一样的,前者虽然传入的只是库文件名字,但是后面调用了findLibrary对库文件路径进行了获取,load则直接传入库文件路径即可那么,继续跟进doLoad,看看是如何加载的
这里可以发现doLoad调用了native函数nativeLoad加载so文件
Native层调用
跟进java_lang_runtime.cpp
那么跟进dvmLoadNativeCode函数查看
findSharedLibEntry会返回一个SharedLib对象pEntry,此处他会进行一个判断,pEntry对象里保留了so文件的加载信息,如果so以及加载过,上次用来加载的类加载器和当前使用的不一致,或者上次加载失败,都会返回false
接下来会调用dlopen来在当前进程中加载so文件,并且返回一个handle变量
跟进dlopen函数
1 | void* dlopen(const char* filename, int flags) { |
继续跟进do_dlopen
1 | 823soinfo* do_dlopen(const char* name, int flags) { |
find_library会判断so是否加载过,跟进看一下
1 | static soinfo* find_library(const char* name) { |
跟进load_library
看一下load函数
1 | bool ElfReader::Load() { |
一些校验啊,段映射之类的操作,这里就不跟进了
我们回到前面的do_dlopen,加载完后会调用一个CallConstructors()函数
1 | void soinfo::CallConstructors() { |
来看下注释,大致的意思就是会在这调用一些so的初始化函数,比如在这会调用init_func,init_array,init_array_count中的函数
(那么这里我们如果动态调试so时,我们就可以尝试将断点下在CallConstructors,来跟踪一些初始化函数,在脱so壳时很关建)
再回到这来
此处调用了一个关键的JNI_Onload函数,这个函数可以动态注册一些native函数之类的操作
并且跟踪源码后发现.init段的初始化函数是早于JNI_Onload函数执行的
最后
加载过程大致如此,中间还有一些so怎样链接以及链接后填充soinfo结构体的细节没有去仔细分析,这些对于自己实现一套Linker机制还是很有帮助的