Android so文件浅析

一. 简述
Android中的so文件是动态链接库,是二进制文件,即ELF文件。多用于NDK开发中。

二. 基础知识

三. so文件格式解析
so文件即ELF文件,是一个二进制文件,我们可以用UltraEdit打开查看。如下:
这里写图片描述
上面有一处很明显看到,在so文件解析出来的头文件字段是ELF,也印证.so是一个ELF格式的问题。
ELF文件中各个结构体的内容,我们可以看源码中如下路径:
platform/external/kernel-headers /original/uapi/linux/elf.h
这里写图片描述

下面来逐步解析这个ELF文件

1.  ELF 头部(32bit/64bit)

这里写图片描述
可以看到e_ident[EI_NIDENT] 这个就是ELF 魔术数字(ELF magic number)

几个字段需要关注下,在so加固中修改so会用到:
E_phoff:注释写的很明确,这是程序表头的偏移值;
E_shoff:段表头的偏移值;
E_shstrndx:

2. ELF 段头(32bit/64bit)

这里写图片描述
p_offset:段文件偏移
p_vaddr:段虚拟地址
p_paddr:段物理地址
p_filesz:段大小在文件中

 3. ELF 程序头(32bit/64bit)

这里写图片描述
4. 工具解析ELF
这边常用的是readelf,这个工具运行在linux下的。一般运行的时候readelf –help,就可
以看到命令可以带什么参数,参数的含义等,
这里写图片描述
以下列出常用的几个:
A. readelf –h xxx.so
查看elf的头部信息
这里写图片描述
B. readelf –S xxx.so
查看elf节头信息
这里写图片描述

C. readelf –l xxx.so
查看elf段头信息
这里写图片描述

四. so文件加载
1. 加载方法
so文件的加载有且仅有两种方式:一个是load(),另一个是loadLibrary()
这里写图片描述

A. load
void load (String filename)
这里写图片描述

这个方法其实是直接由库导出被调用,并不是加载动态库。方法中传参为是一个String类型,不过内容是有要求的,是要so文件的绝对路径,,比如说:/system/lib64/libc++.so 。

B. loadLibrary
void loadLibrary (String libname)
这里写图片描述
loadLibrary方法和load方法的区别主要在于传参,此方法的传参也是一个String类型的值,不过这个值也有要求:比如我们需要加载的是libc++.so文件,那么这个libname需要携程c++即可。因为代码中是有实现的,会在前缀加上lib,后缀加上.so。
代码实现路径:/dalvik/vm/native/java_lang_System.c  /dalvik/tree/vm/Native.c
这里写图片描述
这里写图片描述

2.  加载流程

A. Load的加载流程:

这里写图片描述

B. LoadLibrary的加载流程:

这里写图片描述

最终还是调用Runtime类中的doLoad()方法,后续的实现其实和load()方法的一致。


3.  加载中注意

A. 常见的错误:

a. 加载so文件的时候无权限
首先你要看下so文件的绝对路径的权限是什么?外卡路径是没有权限的。Android O上,
对于第三方的apk,一般so文件生成的nativeLibraryPath是在/data/app-lib/XXX/ 下的。

b. 加载so文件的时候文件不存在
请check路径下是否有so文件。

c. ELF had a bad magic number
这里是so文件损坏了,需要check损坏的原因做处理。

B. 目前常用的是使用loadLibrary来动态加载库文件。

五. 扩展知识
1. Android NDK开发
(1).环境搭建
Eclipse的环境搭建在网上很多可以搜搜。这边主要讲下AS的搭建。
A. 首先需要去下个NDK工具包(如果不下载,在创建jni目录的时候AS也会提示NDK not configured的,直接install也行的):
这里写图片描述

B. 在创建项目的时候新建一个jni目录,如下图:
这里写图片描述

C. 配置NDK的路径,如下图:
这里写图片描述
D. 配置Grade中的NDK,如下图:
这里写图片描述

如上步骤基本就已经对于NDK的配置环境搭建完成了,下面开始具体实现啦!

(2).简单案例
对于JNI技术来说:主要是在java中我们定义方法,而在C++中实现这个方法,最后再回到java中进行调用。

注意:
A.javah 命令的使用【附录1】
a.首先要确保本地的java环境变量配置ok,不然无法用javah命令
b.首先先进入到写的java的目录下,比如说:
C:\Users\XXX\AndroidStudioProjects\NDKDemo\app\src\main\java\r\demo\com\ndkdemo

然后终端中输入:javac JNIDemo.java

此时该目录下会生成JNIDemo.class。

c.最为关键的是.h文件的生成,
这里经常出现的错误为:错误: 找不到 ‘r.demo.com.ndkdemo.JNIDemo’ 的类文件。

给出一个方法:
cd C:\Users\XXX\AndroidStudioProjects\NDKDemo\app\src\main\java
javah –d ../jni r.demo.com.ndkdemo.JNIDemo

此时在/jni 目录下就会生成:r_demo_com_ndkdemo_JNIDemo.h

(3).JNI类型
看我们第二第二步生成的c++文件内容,如下:
这里写图片描述

这里看到方法为r_demo_com.ndkdemo_JNIDemo_setjni。这个名字的命名还是很有规律的,前面r_demo_com.ndkdemo 是你当前project的包名,JNIDemo是你java类名,setjni 是java类中具体的方法。
接着看方法中的参数:JNIEnv类型和jobject类型。
A. JNIEnv类型:这是一个指针,主要是对java端的代码进行操作,比如创建java类中的对象,调用java对象的方法等。
常用的函数有:NewObject 、NewString 、Get Field等
B. Jobject类型。

六.后续
【附录1】:
用法:
javah [options]
其中, [options] 包括:
-o 输出文件 (只能使用 -d 或 -o 之一)
-d

输出目录
-v -verbose 启用详细输出
-h –help -? 输出此消息
-version 输出版本信息
-jni 生成 JNI 样式的标头文件 (默认值)
-force 始终写入输出文件
-classpath 从中加载类的路径
-cp 从中加载类的路径
-bootclasspath 从中加载引导类的路径

是使用其全限定名称指定的
(例如, java.lang.Object)。

猜你喜欢

转载自blog.csdn.net/zplxl99/article/details/80376298