ART世界探险(11) - OAT文件格式分析(1) - ELF文件头(上)

ART世界探险(11) - OAT文件格式分析(1) - ELF文件头(上)

既然是要探险,咱们就保持一定的深度,起码将来可以做个基于ART的黑客之类的。
所以我们针对细节多下一些工夫,先仔细分析一下OAT文件的格式。
ART的本质上是一个编译器,所以我们需要对编译、链接的主要环节都有一个比较深入的了解。想要绕过编译原理去学好ART,是不太现实的一件事情,我们选择可以让这个过程有趣和好玩。

闲扯不多说了,言归正传。

可执行文件

OAT是一种可执行文件,所以封装在一个ELF格式的可执行文件中。
可执行文件,我们可以借大家熟悉的Windows操作系统理解,就是Windows中的exe文件和dll文件。

  • Windows下的exe,dll文件的格式是PE(Portable Executable)
  • Linux/Android下的可执行格式是ELF(Executable Linkable Format)
  • Mac/iPhone下的可执行文件的格式是Mach-O(Mach Object)

PE和ELF都是COFF格式(Common file format)的变种。

ELF文件的分类

ELF文件可以分为以下4种:

  • 可重定位文件:Relocatable File,就像Windows下的.obj文件和Linux下的.o文件一样的目标文件,需要链接才可以执行。
  • 真正的可执行文件:Executable File,如Windows的exe一样,是可以直接运行的程序。
  • 共享目标文件:Shared Object File,像Windows的dll和Linux下的so一些,动态链接库。
  • 核心转储文件:Core dump,用于意外时的地址空间的转储。

这个分类信息,在ELF文件头里面有描述,我们后面会讲到。

ELF文件头

我们把ELF文件头分成两部分来分析,我们先来看前半部分:

Magic Number

所谓的Magic Number,其实就是文件的一个标记。
ELF的Magic Number一共占4个字节,首字节为0x7f,后面分别为'E','L','F'.
只支持大写,必须是0x7f,0x45,0x4c,0x46

我们以Go语言为例,写一段解析Magic Number的代码来作为示例吧:

    buf, err := ioutil.ReadFile(elfFile.elfFileName)
    if err != nil {
        fmt.Println("Error reading ELF:", err)
    }

    magic := [...]byte{0x7f, 'E', 'L', 'F'}
    magic2 := buf[:4]
    if (magic[0] == magic2[0]) && (magic[1] == magic2[1]) && (magic[2] == magic2[2]) && (magic[3] == magic2[3]) {
        fmt.Println("It is an ELF")
    } else {
        fmt.Println("It is not an ELF")
        return
    }

位宽

  • 1-代表32位
  • 2-代表64位

大端还是小端

这个是在多字节的情况下,是高位在前还是低位在前。

  • 1-Little Endian,小端
  • 2-Big Endian,大端

操作系统

操作系统
0x00 System V
0x03 Linux
0x06 Solaris
0x09 FreeBSD

在Android上肯定是0x03了。

ELF子类型

这个就是我们上面画图的那4大类型

ELF子类型
0x01 可重定位文件
0x02 可执行文件
0x03 动态链接库
0x04 core dump

芯片架构

芯片架构
0x00 未知
0x03 x86
0x08 MIPS
0x28 ARM
0x3e x86_64
0xb7 AArch64

ELF头分析示例

ELF文件头的结构(第一部分)

偏移量 长度 描述 备注
0x00 4 Magic Number
0x04 1 位数 32位还是64位
0x05 1 大端还是小端
0x06 1 版本号 1
0x07 1 操作系统
0x08 8 暂时未用到
0x10 2 ELF文件的子类型
0x12 2 芯片架构
0x14 4 又一个版本号 设成1

我们下面解析一个实际的OAT文件看看

下面是我从一个64位arm的OAT文件截取的一部分,我们来分析一下:

7F 45 4C 46  02 01 01 03  00 00 00 00  00 00 00 00
03 00 B7 00  01 00 00 00  00 00 00 00  00 00 00 00
40 00 00 00  00 00 00 00  E0 3A AA 00  00 00 00 00
00 00 00 00  40 00 38 00  07 00 40 00  12 00 11 00
06 00 00 00  04 00 00 00  40 00 00 00  00 00 00 00
40 00 00 00  00 00 00 00
  • 7F 45 4C 46: Magic Number,就不多说了
  • 02:64位
  • 01:Little Endian,小端
  • 01:版本号1
  • 03:Linux
  • 8个0:没用到
  • 03 00:动态链接库。注意了,我们的OAT文件是so!
  • B7 00: AArch64,arm64-v8a在AArch64状态下
  • 01 00 00 00: 版本号1

一些参考资料

  1. 一种ELF参考规范的文档:
    http://www.cs.princeton.edu/courses/archive/fall05/cos318/docs/ELF_Format.pdf
  2. ELF格式的wiki
    https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
时间: 2024-09-24 05:12:47

ART世界探险(11) - OAT文件格式分析(1) - ELF文件头(上)的相关文章

ART世界探险(12) - OAT文件分析(2) - ELF文件头分析(中)

ART世界探险(12) - OAT文件分析(2) - ELF文件头分析(中) 段(section)的概念 一块内存分配给应用程序之后,从代码的组织上,我们就有将它们分段的需求. 比如,可以分为代码段,数据段,只读数据段,堆栈段,未初始化的数据段等等. 在GAS汇编器中,我们通过.section伪指令来指定段名.ARM编译器我买不起,我就忽略它了. 标准section 段的描述 默认段名 代码段 .text 经过初始化的数据段 .data 未经初始化的数据段 .bss BSS是Block Star

ART世界探险(6) - 流程控制指令

ART世界探险(6) - 流程控制指令 分支结构 Java分支结构 我们先来个最简单的,比较大小吧. public static long bigger(long a, long b){ if(a>=b){ return a; }else{ return b; } } public static int less(int a,int b){ if(a<=b){ return a; }else{ return b; } } 看看Java字节码是个什么样子: public static long

ART世界探险(9) - 同步锁

ART世界探险(9) - 同步锁 Java是一种把同步锁写进语言和指令集的语言. 从语言层面,Java提供了synchronized关键字. 从指令集层面,Java提供了monitorenter和monitorexit两条指令. 下面我们就看看它们是如何实现的吧. 三种锁的方式 Java代码 有三种方式来加锁: 直接在函数上加synchronized关键字 在函数内用某Object去做同步 调用concurrent库中的其他工具 public synchronized int newID(){

ART世界探险(7) - 数组

ART世界探险(7) - 数组 Java针对数据是有专门的指令去处理的,这与C/C++有显著的不同. Java字节码对于数组的支持 一个极简的例子 Java源代码 为了简化,我们取一个极简的例子来说明Java的数组指令的用法: 我们new一个长度为1的字节数组,然后返回这个数组的长度. public static int testByteArrayLength(){ byte[] baArray = new byte[1]; return baArray.length; } Java字节码 有几

ART世界探险(5) - 计算指令

ART世界探险(5) - 计算指令 整数运算 Java的整型运算 我们先看看JVM是如何处理这些基本整数运算的吧. public static long add(long a, long b){ return a+b; } public static long sub(long a,long b){ return a-b; } public static long mul(long a, long b){ return a*b; } public static long div(long a,l

ART世界探险(20) - Android N上的编译流程

ART世界探险(20) - Android N上的编译流程 就在我们分析Android M版本的ART还只走出了一小段路的时候,Android N的新ART就问世了. Android N上的ART还是有不小的改进的.不过做为一个关注细节的系列文章,我们还是从Compile的过程说起. 流程概述 在安装的时候,默认情况下,Android N只做interpret-only的编译,如下命令行所示: /system/bin/dex2oat --zip-fd=7 --zip-location=base.

ART世界探险(8) - 面向对象编程

ART世界探险(8) - 面向对象编程 对象和方法调用 接口定义: public interface SampleInterface { void interfaceMethod(); } 给接口做一个实现: public class SampleClass implements SampleInterface{ @Override public void interfaceMethod() { } } 我们先做一个新建对象,并调用这个对象从接口. public void testMethod(

ART世界探险(13) - 初入dex2oat

ART世界探险(13) - 初入dex2oat dex2oat流程分析 进入整个流程之前,我们先看一下地图,大致熟悉一下我们下一步要去哪里: 主函数 dex2oat的main函数,直接是dex2oat工厂函数的封装. int main(int argc, char** argv) { int result = art::dex2oat(argc, argv); // Everything was done, do an explicit exit here to avoid running Ru

ART世界探险(10) - 异常处理

ART世界探险(10) - 异常处理 对于编译Java的话,有一个问题不能不考虑,就是异常处理的问题.异常处理是基于Java的语句块的,翻译成本地代码的话,需要针对这些指令的地址进行一下重排. 我们来看下ART是如何实现异常处理的. Java异常处理 首先复习一下Java. Java有两种Exception,一种是普通Exception,另一种是RuntimeException.非RuntimeException,如果没有处理,就是没有用try...catch块包围或者是throws声明的话,会