首页 > 其他分享 >Android Dex文件详解

Android Dex文件详解

时间:2023-08-04 11:31:51浏览次数:51  
标签:Dex dex Java 字节 文件 虚拟机 详解 字符串 Android

前言 ==

相信大家都熟悉dex文件,把一个apk给解压缩,就会得到一堆dex文件,但是这些dex文件是怎么来的,又有什么用,为什么这样设计,有进行思考过吗

俗话说知其然,知其所以然,本篇文章开始探究一下这些底层实现细节。

正文 ==

不同的虚拟机

JVM

JVM是Java Virtual Machine的简称,即Java虚拟机,它本质是一层软件抽象,在这之上才可以运行Java程序。Java文件经过编译后会生成JVM字节码,和C语言编译后生成的汇编语言不同,C编译成的汇编语言可以直接在硬件上跑,但是Java编译生成的字节码是在JVM上跑,需要由JVM把字节码翻译成机器指令。

也是由于这个JVM在操作系统上屏蔽了底层实现的差异,从而有了Java的跨平台特性。

DVM

DVM是Dalvik Virtual Machine的简称,是Android4.4及以前使用的虚拟机,所有Android程序都运行在Android系统进程中,每个进程对应着一个Dalvik虚拟机实例。

JVM和DVM都提供了对对象生命周期管理,堆栈管理,安全和异常管理及垃圾回收等重要功能。

但是DVM却不能和JVM一样能直接运行Java字节码,它只能运行.dex文件,而这个.dex文件则是由Java字节码通过Android的dx工具生成的文件。

ART

ART是Android Runtime,在Android5.0开始使用ART虚拟机来替代Dalvik虚拟机,为什么Google要换Android程序运行的虚拟机呢 因为ART虚拟机更优秀。

前面说了Dalvik虚拟机会在APP打开时去运行.dex文件,而这个是实时的,也就是JIT特性(Just In Time),这也就会导致在启动APP时会先将.dex文件转换成机器码,这就导致了APP启动慢的问题。

而ART虚拟机有个很好的特性叫做AOT(ahead of time),这个特性可以在安装APK的时候将dex直接处理成可直接供ART虚拟机使用的机器码,ART虚拟机将.dex文件转换成可直接运行的.oat文件,而且ART虚拟机天生支持多dex,所以ART虚拟机可以很大提升APP的冷启动速度。

除了这个优点外,ART还提升了GC速度,提供功能更全面的Debug特性,但是缺点也就是APK安装速度慢,占用的空间多。

生成和查看dex文件

前面说了dex文件是给Android手机的虚拟机来使用的,所以我们来看看如何生成和查看一个dex文件。

先编写一个简单的.java文件:

public class HelloWorld {  
    int a = 0;  
    static String b = "HelloDalvik";  
  
    public int getNumber(int i, int j) {  
        int e = 3;  
        return e + i + j;  
    }  
  
    public static void main(String[] args) {  
        int c = 1;  
        int d = 2;  
        HelloWorld helloWorld = new HelloWorld();  
        String sayNumber = String.valueOf(helloWorld.getNumber(c, d));  
        System.out.println("HelloDex!" + sayNumber);  
    }  
}

然后使用javac命令来编译.java文件为.class,注意这里必须使用Java 8,而不能使用Java 11,如下图专门使用Java 8编译的结果(原来Windows环境变量是Java 11,后续的dex解析有误):

Android Dex文件详解_偏移量

有了.class文件后,就是Android的dx工具,该工具一般在下面目录:

//也就是sdk目录下的build-tools文件夹中
D:\Users\wayee\AppData\Local\Android\sdk\build-tools\30.0.3\dx.bat

使用dx工具对.class文件进行处理:

Android Dex文件详解_字符串_02

然后会生成一个.dex文件,直接打开这个dex文件它是十六进制编码的文件,看不出任何有用信息,这时就需要一个专门来看这个的工具,这里推荐使用 010 Editor 这个工具,直接把.dex文件拖入工具:

Android Dex文件详解_字符串_03

注意这里选择的模板就是DEX.bt,然后就可以按照DEX的格式来分析这些字节是什么意思了,所以看懂dex文件必须要了解DEX文件的格式。

Dex文件格式

看到这里就必须要清楚一个基本概念了,也就是平时使用Java编写的文件,这里给编译打包成dex文件,那这个dex文件就必须要包含这个Java文件的所有信息,那是按照Java文件顺序一行一行保存为字节码还是其他什么方式呢

所以想知道编译器是如何在编译Java文件后保存信息的,就必须要清楚Dex文件格式。

我们可以直接在刚刚010 Editor软件中看到Dex.bt即Dex文件的格式,其格式如下:

当然也可以去Android源码官网看一下Dex的格式:

看了上面dex文件的格式,其大致可以分为3个区域,分别是文件头、索引区和数据区,那我们就来挨个分析这几个区域有什么作用,以及是如何保存编译后的Java文件。

header 文件头

头文件它包含了这个dex文件的几乎所有信息,所以它的信息非常多,其格式如下:

Android Dex文件详解_偏移量_04

然后这时就直接点击010 Editor下面的dex\_header部分:

Android Dex文件详解_偏移量_05

其中上面的红框就是文件头的数据,而下面的红框就是文件头的格式,我们来挨个分析一下。

1、Magic value,即魔数,这个就是用来失败dex这种文件的,可以判断当前的dex文件是否有效,其值是固定死的:

Android Dex文件详解_字符串_06

转换成ASCII也就是dex.035, 所以凡是dex文件都是这个开头,否则就是错误的dex文件。

2、checksum,dex文件的校验和,它可以判断dex文件是否损坏或者篡改,占用4个字节,注意这里是采用小字节序的编码方式,即低位上存储的就是低字节内容,可以看一下:

Android Dex文件详解_偏移量_07

会发现这里的值和二进制保存是相反的。

3、SHA1签名,也就是把整个dex文件用SHA-1签名得到的一个值,占用20个字节。

4、fileSize,整个文件的大小,占用4个字节,看一下值这里是:

Android Dex文件详解_字符串_08

十进制是1204,换算成16进制就是4B4,我们再来看看这个dex文件的长度:

Android Dex文件详解_Java_09

这里长度也是4B4。

5、headerSize,表示头结构的大小,占用4个字节,这个就不截图看了。

6、endian\_tag,表示字节序,这里具体的值就2个,标准的.dex格式采用小段字节序,但具体实现可能会选择执行字节交换,所以这个改变就由这个tag来判断。

7、linkSize和linkOff,这2个字段指定了链接段的大小和文件偏移,通常情况下他们都是0,linkSize为0表示为静态链接。

从这里开始就会发现有off这个字段,这是啥意思呢,其实也就是文件偏移量,也就是从这个文件第多少位置开始表示的值。

8、mapOff,这个字段表示DexMapList的文件偏移,这里我们先不多介绍,后面再说,这里值是:

Android Dex文件详解_Java_10

换成16进制就是414h。

9、stringIdsSize和stringIdsOff,这2个字段指定了dex文件中所有用到的字符串的个数和位置偏移,注意这里指的是位置偏移,而不是真正的字符串值。

我们来看看size是多少:

Android Dex文件详解_Java_11

会发现一共有28个字符串,而其值的偏移从112开始,而这个112是不是有点熟悉,112是整个dex头的大小,也就说明在头部之后第一部分就是字符串索引,这里之所以叫做索引也很合理,从112开始的n个字节保存的是程序用到的字符串的偏移量,注意这里不是字符串,只是各个字符串的偏移量。

这时你可能会疑惑,这28个字符串的偏移量该如何存放以及值是多少,我们完全不用担心,还是打开010 Editor软件,选中数据结构是dex\_string\_ids即可:

Android Dex文件详解_偏移量_12

会发现从70h开始,开始保存的每个字符串的偏移量,而这个偏移量对应的就是最后面部分的值,我们还是拿第一个字符串来说:

Android Dex文件详解_字符串_13

会发现这里的偏移量,我们转到偏移量会发现:

Android Dex文件详解_Java_14

这里是用了10个字节来保存了一个字符串,这个字符串是"clinit",我们暂时不考虑这个字符串是啥意思,这里采用了一种叫做uleb的数据结构,来动态保存字符串长度,这里我们暂时不考虑细节。

其实从这个字符串保存的方法来看,我们已经能大概看出是如何保存的了。首先在头部保存字符串大小,以及字符串索引的偏移量,然后再遍历索引找到每个字符串。

比如上面我们的代码,在这里保存的字符串是如下:

Android Dex文件详解_偏移量_15

10、typeIds和typeIdsOff,有了上面字符串保存的逻辑,这个就是类的类型的数量和位置偏移,也都是占用4个字节,我们还是来看看值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kW77mc6C-1653642835067)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b0eb703dded4459a2e3a9b282327f8f~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

一共用到了9个类型,但是注意这里就没必要像保存字符串一样了,记录每个类型的偏移量,再去偏移的地方取值,这里类型的描述符已经在前面字符串变量中都进行描述过了,所以这里保存的是字符串的索引,我们来看看:

Android Dex文件详解_Java_16

找到上面对应的偏移位置,我们发现第一个类型值是0x5,然后我们再去前面的字符串索引找到下标为5的字符串:

Android Dex文件详解_偏移量_17

会发现这里的值是I,也就是第一个类型,依次类推,所有的类型如下:

Android Dex文件详解_字符串_18

会发现这几个类型的字符串描述在前面字符串列表中都保存过了,这样设计也可以减小查询操作、节省内存。

11、protoIdSize和protoIdOff,这个表示的是方法原型的个数和位置偏移,会发现上面dex文件中有7个方法原型,这里图就不截了,来看一下这7个方法原型都保存了哪些数据:

Android Dex文件详解_字符串_19

其实不难理解,想表示一个方法原型不外乎就是方法名、返回值和参数,其中参数可能是多个,所以会有多个类型索引,这里具体的数据结构就不细说了,大体意思理解即可,来看看7个方法原型:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9aNGJ56-1653642835081)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0de94620959b4b9e92178e7999594b2b~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]

这里的方法也就是前面java文件中有使用到的。

12、fieldSize和fieldOff,这2个字段就比较简单了,表示java文件中字段的信息,从头部数据结构中会发现有3个字段,我们直接看一下字段索引的数据:

Android Dex文件详解_偏移量_20

会发现到这里时,信息表示就变的简单了,因为你想表示一个字段,不外乎就是类型、其类的类型、以及自己描述的字符串,而由于前面我们已经得到了字符串索引和类型索引,所以这里数据结构中的值直接使用前面定义过的索引即可。

还是看一下定义的所有字段的值:

Android Dex文件详解_Java_21

13、methodSize和methodOff,这2个字段就比较熟悉了,表示了方法,而方法的表示也是需要几个要点,比如方法所在的类、方法的声明以及方法名,而类在之前类型索引定义过了,方法声明也声明过了,以及方法名也就是之前定义的字符串索引,所以这里我们就不细看其数据结构了,直接看一下我们前面写的java文件有多少个方法:

Android Dex文件详解_Java_22

这里一共有10个方法。

14、classDefsSize和classDefsOff,这2个字段表示类定义的相关信息,类的信息就比较多了,包括类的修饰符、父类、接口、注解、静态元素等等,我们也还是通过010 Editor来看一下class都保存了哪些信息:

Android Dex文件详解_偏移量_23

可以发现还是有不少信息的。

到这里我们基本就可以把一个类的信息都整清楚了,我们使用一张图来表示:

Android Dex文件详解_偏移量_24

上图虽然只是表示了文件头的信息,但是我们知道有了这些文件头的信息,根据偏移量便可以获取到其保存的值。

Dex文件格式总结

看了文件头的定义,并且明白其值的意义,便也就熟悉了整个Dex格式的保存原理,我们这里看一张图:

Android Dex文件详解_字符串_25

这里除了文件头还有索引区和数据区,其中索引区的偏移量已经在文件头中定义,而数据区则保存着类的定义以及索引区中的数据,而最下面的链接数据区则是一些静态库或者动态库的链接。

总结==

本篇内容有点多,但是还是很好理解的,首先就是虚拟机,在Android系统的虚拟机需要读取dex文件,而这个dex文件是由我们编写的.java文件编译而来,所以dex文件应当保存.java文件的所有信息。

而保存这些信息的方法就像是文件头保存大致地址,索引区保存具体地址,数据区是真的地方,通过这种方式就可以完整的保存一个java文件的信息。


想要了解更多Anrloid相关知识可以点击下方课堂链接             https://edu.51cto.com/course/32703.html Android课

标签:Dex,dex,Java,字节,文件,虚拟机,详解,字符串,Android
From: https://blog.51cto.com/u_16163480/6959108

相关文章

  • Windows运行命令之netstat命令详解
    一、简介netstat命令显示处于活动状态的TCP连接、计算机正在侦听的端口、以太网统计信息、IP路由表、用于IP、ICMP、TCP和UDP协议的IPv4统计信息和IPv6统计信息(IPv6、ICMPv6、TCPoverIPv6和UDPoverIPv6协议)。使用没有参数的情况下,此命令显示活动TCP连接。 二、命令示例1、nets......
  • awk命令详解
    awk变量1、主要作用用来处理文本,将文本按照指定的格式输出。其中包含了变量,循环以及数组2、格式2.1awk[选项]'匹配规则和处理规则'[处理文本路径][root@localhost~]#awk-F:'{print$1}'/etc/passwd匹配规则主要是:正则表达式处理规则主要是:设置变量......
  • Xcode Snippets 功能详解
    http://nshipster.com/xcode-snippets/iOSdevelopmentallbutrequirestheuseofXcode.Toitscredit,Xcodehasimprovedprettyconsistentlyoverthelastcoupleofyears.Sure, itstillhasits…quirks,buthey—thingscouldbe much,muchworse.Workingi......
  • React Hooks 中的属性详解
    ReactHooks是React16.8版本中新增的特性,允许我们在不编写class的情况下使用state和其他的React特性。Hooks是一种可以让你在函数组件中“钩入”React特性的函数。以下是一些常用的ReactHooks,并附有详细的用法和代码示例。1.useStateuseState是一个Hook函数,让......
  • 第一节:Lvs软件负载技术详解
    一.        二.        三.         !作       者:Yaopengfei(姚鹏飞)博客地址:http://www.cnblogs.com/yaopengfei/声     明1:如有错误,欢迎讨论,请勿谩骂^_^。声     明2:原创博客请在转载......
  • Hive Merge详解
    说明Hive在2.2版本之后开始支持Merge操作,并且Merge只能在支持ACID的表上执行语法MERGEINTO<targettable>ASTUSING<sourceexpression/table>ASSON<booleanexpression1>WHENMATCHED[AND<booleanexpression2>]THENUPDATESET<setclauselist>WHEN......
  • Android 打印调用栈的方法
    转载1.Java层调用栈打印:(1)打印本地调用堆栈Log.i(TAG,Log.getStackTraceString(newThrowable()));//打印本地调用堆栈(2)打印远程调用堆栈importandroid.os.Binder;importandroid.app.IActivityManager;importandroid.util.Log;StringprocessName="";intpid=......
  • 9.2.Config Server 配置规则详解
    9.2.ConfigServer配置规则详解在上面,我们用于测试的配置文件:futurecloud.ymlfuturecloud-dev.ymlfuturecloud-test.ymlfuturecloud-pre.ymlfuturecloud-stable.ymlfuturecloud-apigetway-zuul.yml“-”前面的部分可以随便定义,一般我们用应用名来定义,后面的deb、test…也可以随......
  • spring-mvc系列:详解@RequestMapping注解(value、method、params、header等)
    目录一、@RequestMapping注解的功能二、@RequestMapping注解的位置三、@RequestMapping注解的value属性四、@RequestMapping注解的method属性五、@RequestMapping注解的params属性六、@RequestMapping注解的header属性七、SpringMVC支持ant分格的路径八、SpringMVC支持路径中的占......
  • 【jmeter系列】仅一次控制器+正则表达式用法详解
    一、仅一次控制器做性能测试经常遇到这样的问题:要做接口压力测试,但是需要登录接口返回的token,但是不需要对登录接口进行压测,这个时候jmeter仅一次控制器就起到了关键性的作用。具体操作如下:1、添加仅一次控制器,并在控制器下添加登录接口 2、登录接口返回响应数据-Responesb......