原生程序开发
JNI
JNI方法都在jni.h中定义(JNINativeInterface),该结构体中保存的是一些列JNI方法的指针,第一个参数为JNIEnv结构体指令,
该指令的第一个参数就是一个名为funiction的JNINativeInterface。
JNI编程中,所有Java层数据类型都可以用jvalue表示,定义如下:
除了jobject,其他所有类型都在原生程序中有对应数据类型。
jobject在JNI的实现被定义为C++类,有多个子类,,分类包含jstring,jthrowable,jXXXarray(xxxx为所有的基础数据类型)
JNI方法按功能可以分为:
原生函数入口函数
会分别执行preinit_array_,init_array,程序执行结束,执行fini_array。如果是so只有后两个。
linker的call_constructors如下:
因此在分析so动态库,看i恶意通过查看.init_array是否有函数指针确定是否含初始化代码。如果通过dlopen()方法加载动态库,工作就完成了;如果在Java层通过System.loadLibrary()方法加载动态库,连接器在加载动态库时会查找动态库中是否有JNI初始化函数JNI_Onload(),有就调用。所有对于so动态库首先执行.init_array指定初始化函数执行函数指针数组,再执行JNI_OnLoad()函数。
原生程序文件格式
Android原生程序的编译工具链使用的是gcc和Clang,Clang使用的连接器是gcc的后端,因此所有原生程序的链接工作最终由编译工具链中ld命令和响应脚本完成的。
elf格式文档讲原生程序分成3类:
Relocatable File:可重定位文件,“.o”结尾。
Executable File:可执行文件。
Shared Object File:共享的目标文件。
不加-fPIE -pie参数编译出来是可执行文件,加上为共享的目标文件。
$cc在执行链接时会调用链接器来链接目标文件生成程序,传递参数会确定链接脚本,如-fPIE -pie,如果传递该参数会在链接时使用aarch64linux.xde链接脚本,没指定参数使用arrch64linux.x链接脚本(-T参数为连接器指定使用的链接脚本)。
–verbose参数可以产科支持链接的·1文件类型和默认链接脚本。
LSB是小端,MSB是大端。
AArch64 ELF文件格式
一个ELF文件分为3部分:
- ELF Header,文件头。
- Program Header Table:程序头表,包含多个Program Header与Segment段数据。
- Section Header Table:节区头表,包含多个Section Header与Section(节区)数据。
ELF Header描述了基本文件信息。有上面两个表在文件中偏移。Program Header Table定义的是Segment信息。米哦啊书一个地址段数据的读写执行属性及是否加载到内存。Program Header Table可以有多个Program Header,每个Program Header对应要给Segment,Segment个数由ELF Header给出。
Section Header Table定义Section信息,Section与Segment关系为:一个Segment对应多个Section,每个Section通常只属于一个Segment,特殊情况可属于多个Segment,且Segment之间数据可以重叠。如图:
可看出segment包含运行时需要的信息,section包含链接时需要的信息。ELF在链接时通过Section Header Table寻找节区信息。运行时加载器通过Program Header Table寻找Segment并加载。
.o文件通常不包括Program Header Table。
ELF64_Ehdr与ELF32_Ehdr字段基本一样,判断是32还是64通过抵押给字段e_ident字段。EI_NIDENT的值16,其中EI_MAG0到3为0x7F、E、L、F。EI_CLASS表示ELF的文件类别:1为ELFCLASS32,是32为,2为ELFCLASS64,为64位。
e_type字段表示ELF文件类型,如下:
e_machine是机器架构。e_version描述文件版本,Android上有效值为1的EV_CURRENT。
e_entry指明ELF入口函数,指向ELF的_start导出符号地址。
e_phoff字段指明了Program Header Table在文件中的偏移。e_shoff为Section Header Table在文件中的偏移。
e_ehsize表示Elf64_Ehdr或Elf32_Ehdr结构体占用的字节数。
e_phentsize与e_phnum自动分别指定了Program Header Table每一项占用字节数与个数。e_shentize与e_shnum指定 Section Header Table每一项占用字节数与个数。
e_shstrndx是索引值,表示.shstrtab字符串表是第几个Section Header Table。
Program Header Table
p_type字段描述段类型(Segment Type)。
可执行与共享文件包括如下部分:
多个(一般2个)PT_LOAD类型的Segment。原生程序运行时,Segment会加载到内存中。
一个PT_DYNAMIC类型Segment。记录非常重要的动态链接信息,包括原生程序以来的动态链接库列表,初始化结构数组init_array的地址与大小,字符串表,地址重定位等。
一个PT_GNU_STACK类型的Segment,决定运行时栈是否可执行,默认可读写,不可执行。
一个PT_GNU_RELRO类型Segment,决定连接器执行重定位操作后,哪块内存区域被设置为只读。
一个PT_PHDR类型Segment,指向Program Header Table自身。
一个PT_INITERP类型Segment,指向可执行程序加载器路径。
如果使用-fexceptions开启异常支持,还会使用一个PT_ARM_EXIDX的Segment。
p_flags指定segment,rwx。
p_offset指定Segment在文件偏移。p_vaddr指定Segment在内存虚拟地址,p_paddr指定该Segment采用物理内存寻址的系统中加载后的物理地址。
p_filesz指定Segment在当前文件所占字节数。
p_memsz指定segment在内存字节数。
p_align指定Segment的内存对齐约束,64位原生程序:PT_LOAD指定可执行代码的Segment,内存对齐为65536。PT_DYNAMIC指定数据的Segment,对齐值8。
Section Header Table
Section Header如下:
sh_flags为Section标志信息,如SHF_ALLOC表示Section可分配内存,SHF_EXECINSTR表示该Section包含可执行机器指令。可以同时多个标志。
.dynamic
.dynamic节区调用soinfo::prelink_image()进行预链接处理,使用特定数据结构存放Section条目信息。
d_tag字段描述了区节的类型。影响d_un字段的类型与值。如下:
字符串表
##符号表
通常包含.symtab和.dynsym两个符号表,后者时所有需要分配内存的Section。所以可以优化掉前者。
got表和plt表
.got和.got.plt标志为3,表示会加载到内存并且可写,.plt标志位6,表示会加载到内存并且可执行。
这表的作用就是代码段不允许修改,只能先从代码段plt跳到.got,通过修改稿got实现调用真正函数。
参考:https://www.zhihu.com/question/21249496