Android软件安全与逆向分析笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Barnettdove/article/details/78729344

Dalvik虚拟机概述
Google于2007年年底正式发布了Android SDK,Dalvik虚拟机也第一次进入了人们的视野。它的作者是丹·伯恩斯坦(Dan Bornstein),名字来源于他的祖先曾经居住过的名叫Dalvik的小渔村。Dalvik虚拟机作为Android平台的核心组件,拥有如下几个特点:
·体积小,占用内存空间小;
·专有的DEX可执行文件格式,体积更小,执行速度更快;
·常量池采用32位索引值,寻址类方法名、字段名、常量更快;
·基于寄存器架构,并拥有一套完整的指令系统;
·提供了对象生命周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等重要功能;
·所有的android程序都运行在android系统进程里,每个进程对应一个Dalvik虚拟机实例。


Dalvik虚拟机和java虚拟机的区别
1.Java虚拟机运行的是java字节码,Dalvik虚拟机运行的是Dalvik字节码。
传统的Java程序经过编译,生成Java字节码保存在class文件中,Java虚拟机通过解码class文件中的内容来运行程序。而Dalvik虚拟机运行的是Dalvik字节码,所有的Dalvik字节码由Java字节码转换而来,并被打包到一个DEX(Dalvik Executable)可执行文件中。Dalvik虚拟机通过解释DEX来执行这些字节码。
2.Dalvik可执行文件体积更小。
Android SDK中一个叫dx的工具负责将Java字节码转换为Dalvik字节码。dx工具对Java类文件重新排列,消除在类文件中出现的所有冗余信息,避免虚拟机在初始化时出现反复的文件加载与解析过程。一般情况情况下,Java类文件中包含多个不同的方法签名,如果其他的类文件引用该类文件中的方法,方法签名也会被复制到其类文件中,也就是说,多个不同的类会同时包含相同的方法签名,同样地,大量的字符串常量在多个类文件中也被重复使用。这些冗余信息会直接增加文件的体积,同时也会严重影响虚拟机解析文件的效率。dx工具针对这个问题占们专门做了处理它将所有的Java类文件中的敞亮吃进行分解,消除其中的冗余信息,重新组合形成了一个常量池,所有的类文件共享一个常量池。由于dx工具对常量池的压缩,使得相同的字符串、常量在DEX文件中只出现一次,从而减少了文件的体积。
3.Java虚拟机与Dalvik虚拟机架构不同。
Java虚拟机基于栈结构。程序在运行时虚拟机需要频繁的从栈上读取或写入数据,这个过程需要更多的指令与内存访问次数,会耗费不少CPU时间。
Dalvik虚拟机基于寄存器架构。数据的访问通过寄存器间直接传递,比基于栈方式快得多。


Java虚拟机的指令集被称为零地址形式的指令集,所谓零地址形式,是指指令的源参数与目标参数都是隐含的,它通过Java虚拟机中提供的一种数据结构“求值栈”来传递的。
对于Java程序来说,每个线程在执行时都有一个PC计数器与一个Java栈。PC计数器以字节为单位记录当前运行位置距离方法开头的偏移量,它的作用类似于ARM架构CPU的PC寄存器与x86架构CPU的IP寄存器,不同的是PC计数器只对当前方法有效,Java虚拟机通过它的值来取值令执行。Java栈用于记录Java方法调用的“活动记录”(activation record),Java栈以帧(frame)为单位保存线程的运行状态,每调用一个方法就会分配一个新的栈帧压入Java栈上,每从一个方法返回则弹出并撤销相应的栈帧。每个栈帧包括局部变量区、求值栈(JVM规范中将其称为“操作数栈”)和其他一些信息。局部变量区用于存储方法的参数和局部变量,其中参数按源码中从左到右顺序保存在局部变量区开头的几个slot中。求值栈用于保存求值的中间结果和调用别的方法的参数等。
Dalvik虚拟机运行时间同样为每个线程维护一个PC计数器和调用栈,与Java虚拟机不同的是,这个调用栈维护一份寄存器列表,寄存器的数量在方法结构体的registers字段中给出,Dalvik虚拟机会根据这个值来创建一份虚拟的寄存器列表。


Dalvik虚拟机是如何执行程序的
Android系统的架构采用分层思想,减少了各层之间的依赖性、便于独立分发、容易收敛问和错误。
Linux内核(Linux Kernel)、函数库(Libraries)、Android运行时(Android Runtime)、应用程序框架(Application Framework)以及应用程序(Applications)
Android系统启动加载完内核后,第一个执行的是init进程,init进程首先要做的是设备的初始化工作,然后读取ini.rc文件并启动系统中的重要外部程序Zygote。

Zygote进程是Android所有进程的孵化器进程,它启动后会首先初始化Dalvik虚拟机,然后启动system_server并进入Zygote模式,通过socket等候命令。当执行一个Android应用程序时,system_server进程通过Binder IPC方式发送命令给Zygote,Zygote受到命令后通过fork自身创建一个Dalvik虚拟机的实例来执行应用程序的入口函数,这样一个程序就启动完成了。
Zygote提供了三种创建进程的方法:

  • fork(),创建了一个Zygote进程(这种方式实际不会被调用);
  • forkAndSpecialize(),创建一个非Zygote线程;
  • forkSystemServer(),创建一个系统服务进程;

Zygote进程可以再fork()出其他进程,非Zygote进程则不能fork其他进程,而系统服务进程在终止后它的子进程也必须终止。
当进程fork成功后,执行的工作就交给了Dalvik虚拟机。Dalvik虚拟机首先通过loadClassFormDex()函数完成类的装载工作,每个类被成功解析后都会拥有一个ClassObject类型的数据结构存储在运行时环境中,虚拟机使用gDvm.loadedClasses全局哈希表来存储与查询所有装载进来的类,随后,字节码验证器使用dvmVerifyCodeFlow()函数对装入的代码进行校验,接着虚拟机调用FindClass()函数查找并装载main方法类,随后调用dvmInterpret()函数初始化解释器并执行字节码流。
虚拟机线程→装载程序类→验证字节码→查找主类→执行字节码流→结束


关于Dalvik虚拟机JIT(即时编译)
JIT(Just-in-time Compilation,即时编译),又称为动态编译,是一种通过在运行时将字节码翻译为机器码的技术,使得程序的执行速度更快。
主流的JIT包含两种字节码编译方式:

  • method方式:以函数或方法为单位进行编译。
  • trace方式:以trace为单位进行编译。(trace方式编译则能够快速的获取“热路径代码”,使用更少的时间与更少的内存来编译代码。)

冷路径:有些代码路径在实际运行过程中是很少被执行的,这部分路径被称为“冷路径”。
热路径:执行比较频繁的路径被称为“热路径”。


Dalvik指令格式
一段Dalvik汇编代码由一系列Dalvik指令组成,指令语法由指令的位描述与指令格式标识来决定。位描述约定如下:
● 每16位的字采用空格分隔开来。
● 每个字母表示4位,每个字母按顺序从高字节开始,排列到低字节。每4位之间可能使用竖线 “|” 来表示不同的内容。
● 顺序采用 A ~ Z 的单个大写字幕作为一个4位的操作码,op表示一个8位的操作码。
● “Φ” 来表示这字段所有位为0值。
以指令格式 “A|G|op BBBB F|E|D|C” 为例
指令中间有两个空格,每个分开的部分大小为16位,所以这条指令由三个16位的字组成。
第一个16位是 “A|G|op”,高8位由A与G组成,低字节由操作码op组成。
第二个16位由 BBBB 组成,它表示一个16位的偏移值。
第三个16位分别由F、E、D、C 共4个4字节组成,在这里他们表示寄存器参数。

单独使用位表示还无法确定一条指令,必须通过指令格式标识来指定格式的格式编码。它的约定如下:
● 指令格式标识大多由三个字符组成,前两个是数字,最后一个是字母。
● 第一个数字是表示指令有多少个16位的字组成。
● 第二个数字是表示指令最多使用寄存器的个数。特殊标记 “r” 标识使用一定范围内的寄存器。
● 第三个字母为类型码,表示指令用到的额外数据的类型。取值见如下表。
这里写图片描述
以指令格式标识22x 为例:
第一个数字2表示有2个16位字组成,第二个数字2表示指令使用到2个寄存器,第三个字母x表示没有使用到额外的数据。

Dalvik指令对语法做了一些说明,它约定如下:
● 每条指令从操作码开始,后面紧跟参数,参数个数不定,每个参数之间采用逗号分开。
● 每条指令的参数从指令第一部分开始,op位于低8位,高8位可以是一个8位的参数,也可以是两个4位的参数,还可以为空,如果指令超过16位,则后面部分一次作为参数
● 如果参数采用 “vX” 的方式表示,表示它是一个寄存器,如v0、v1等。这里采用v而不用r是为了避免与基于该虚拟机架构本身的寄存器名字产生冲突,如ARM架构寄存器命名采用r开头。
● 如果参数采用 “#+X” 的方式表示,表明它是一个常量数字。
● 如果参数采用 “+X” 的方式表示,表明它是一个相对指令的地址偏移。
● 如果参数采用 “kind@X” 的方式表示,表明它是一个常量池的索引值。其中kind表示常量池类型,它可以是 “string” 字符串常量池索引)、“type”(类型常量池索引)、“field”(字段常量池索引)或者 “meth”(方法常量池索引)。

在Andorid4.0源码Dalvik/docs 目录下提供了一份文档 instruction-formats.html,里面详细列举了Dalvik指令的所有格式。


Dalvik 字节码的类型、方法与字段表示方法
1.类型
Dalvik只有两种类型,基本类型与引用类型。
这里写图片描述

每个Dalvik寄存器都是32位大小,对于小于或等于32长度的类型来说,一个寄存器就可以存放该类型的值,而像J、D等64位的类型,它们的值是用相邻两个寄存器带来存储的。

L类型可以表示Java类型中的任何类。这些类在Java代码以package.name.ObjectName方式引用,到了Dalvik汇编代码中,它们以Lpackage/name/ObjectName;形式表示,注意最后有个分号,L表示后面跟着一个Java类,Lpackage/name/表示对象所在的包,ObjectName表示对象的名称,最后得分号表示对象名结束。
[类型可以表示所有基本类型的数组。[后面紧跟基本类型描述符。如 [I 表示 int [] , [[I 表示 int [][], 以此类推。注意多维数组的维数最大为255个。

2.方法
方法的表现形式要比类名复杂一些,Dalvik使用方法名、类型参数和返回值来详细描述一个方法。这样做一方面有助于Dalvik虚拟机在运行时从方法表中快速地找到正确的方法,另一方面,Dalvik也可以用它们来做一些静态分析,比如Dalvik字节码的验证与优化。
方法格式举例如下:
Lpackage/name/ObjectName;->MethodName(III)Z
Lpackage/name/ObjectName;-应该理解为一个类型,MethodName为具体的方法名,(III)Z 是方法的签名部分,其中III 为方法的参数(在此表示有三个int类型的参数),

Z表示方法的返回类型(boolean类型)
例:
method(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
按照上面的知识,将其转换成Java形式的代码应该为:
String method(int,int[][],int,String,Object[])
BakSmali生成的方法代码以.method指令开始,以.end method指令结束,根据方法类型的不同,在方法指令开始前可能会用“#”加以注释。如“# virtual methods”表示这是一个虚方法,“# direct methods”表示这是一个直接方法。

3.字段
字段与方法很相似,只是字段没有方法签名域中的参数与返回值,取而代之的是字段的类型。同样,Dalvik虚拟机定位字段与字节码静态分析时会用到它。字段的格式如下:
Lpackage/name/ObjectName;->FieldNmae:Lpackage/name/ObjectName;
字段由类型(Lpackage/name/ObjectName;)、字段名(FieldName)与字段类型
(Lpackage/name/ObjectName;)组成。其中字段名与字段类型中间用冒号 “:” 隔开。
BakSmali 生成的字段代码以。field指令开头,根据字段类型的不同,在字段指令的开头可能会用井号“#” 加以注释, 如 “# instance fields” 表示这是一个实例字段,“# static fields”表示这是个静态字段 。


1.指令特点

Dalvik指令在调用格式上模仿了C语言的调用约定。Dalvik指令的语法与助词符有如下特点:

  • 参数采用从目标(destination)到源(source)的方式。
  • 根据字节码的大小与类型不同,一些字节码添加了名称后缀以消除岐义。
    • 32位常规类型的字节码末添加任何后缀。
    • 64位常规类型的字节码添加 -wide后缀。
    • 特殊类型的字节码根据具体类型添加后缀。它们可以是 -boolean,-byte,-char,-short,-int,-long,-float,-double,-object,-string,-class,-void
      之一。
  • 根据字节码的布局与选项不同,一些字节码添加了字节码后缀以消除岐义。这些后缀通过在字节码主名称后添加斜杠“/”来分隔开。
  • 在指令集的描述中,宽度值中每个字母表示宽度为4位。
    例如这条指令: “move-wide/from16 vAA, vBBBB”
    move 为基础字节码。标识这是基本操作。
    wide 为名称后缀。标识指令操作的数据宽度(64位)。
    from16 为字节码后缀。标识源为一个16位的寄存器引用变量。
    vAA 为目的寄存器。它始终在源的前面,取值范围为v0~v255。
    vBBBB 为源寄存器。取值范围为v0~v65535

Dalvik 指令集中大多数指令用到了寄存器作为目的操作数或源操作数,其中 A/B/C/D/E/F/G/H 代表一个4位的数值, 可用来表示0~65535的数值或v0~v65535的寄存器。注意:Android官方指令文档描述寄存器时,对不同取值范围的寄存器以括号说明其大小,如A:destination register(4 bits),A:destination register(16 bits)。请注意,Dalvik虚拟机中的每个寄存器都是32位的。描述指令时所说的位数表示的是寄存器数值的取值范围。

2.空操作指令
空操作指令的助记符为nop。它的值为00,通知被用来作对齐代码之用,无实际操作。

3.数据操作指令
数据操作指令为move。move指令的原型为 move 目标,源。 move 指令根据字节码大小与类型不同,后面会跟上不同的后缀。

  • “move vA, vB”:将vB寄存器的值赋给vA寄存器,源寄存器与目的寄存器都为4位。
  • “move/from16 vAA, vBBBB”:将vBBBB寄存器的值赋给vAA寄存器,源寄存器为16位,目的寄存器为8位。
  • “move/16 vAAAA, vBBBB”:将vBBBB寄存器的值赋给vAAAA寄存器,源寄存器与目的寄存器都为16位。
  • “move-wide vA, vB”:为4位的寄存器对赋值。源寄存器与目的寄存器都为4位。
  • “move-wide/from16 vAA, vBBBB”与“move-wide/16 vAAAA, vBBBB”实现与“move-wide”相同。
  • “move-object vA, vB”:为对象赋值。源寄存器与目的寄存器都为4位。
  • “move-object/from16 vAA, vBBBB”:为对象赋值。源寄存器为16位,目的寄存器为8位。
  • “move-object/16 vAA, vBBBB”:为对象赋值。源寄存器与目的寄存器都为16位。
  • “move-result vAA”:将上一个invoke类型指令操作的单字非对象结果赋给vAA寄存器。
  • “move-result-wide vAA”:将上一个invoke类型指令操作的双字非对象结果赋给vAA寄存器。
  • “move-result-object vAA”:将上一个invoke类型指令操作的对象结果赋给vAA寄存器。
  • “move-exception vAA”:保存一个运行时发生的异常到vAA寄存器,这条指令必须是异常发生时的异常处理器的一条指令。否则的话,指令无效。

4.返回指令
返回指令指的是函数结尾时运行的最后一条指令。它的基础字节码为teturn,共有以下四条返回指令:
“return-void”:表示函数从一个void方法返回。
“return vAA”:表示函数返回一个32位非对象类型的值,返回值寄存器为8位的寄存器vAA。
“return-wide vAA”:表示函数返回一个64位非对象类型的值,返回值为8位的寄存器对vAA。
“return-object vAA”:表示函数返回一个对象类型的值。返回值为8位的寄存器vAA。

5.数据定义指令
数据定义指令用来定义程序中用到的常量,字符串,类等数据。它的基础字节码为const。
“const/4 vA, #+B”:将数值符号扩展为32位后赋给寄存器vA。
“const/16 vAA, #+BBBB”:将数据符号扩展为32位后赋给寄存器vAA。
“const vAA, #+BBBBBBBB”:将数值赋给寄存器vAA。
“const/high16 vAA, #+BBBB0000“:将数值右边零扩展为32位后赋给寄存器vAA。
“const-wide/16 vAA, #+BBBB”:将数值符号扩展为64位后赋给寄存器对vAA。
“const-wide/32 vAA, #+BBBBBBBB”:将数值符号扩展为64位后赋给寄存器对vAA。
“const-wide vAA, #+BBBBBBBBBBBBBBBB”:将数值赋给寄存器对vAA。
“const-wide/high16 vAA, #+BBBB000000000000”:将数值右边零扩展为64位后赋给寄存器对vAA。
“const-string vAA, string@BBBB”:通过字符串索引构造一个字符串并赋给寄存器vAA。
“const-string/jumbo vAA, string@BBBBBBBB”:通过字符串索引(较大)构造一个字符串并赋给寄存器vAA。
“const-class vAA, type@BBBB”:通过类型索引获取一个类引用并赋给寄存器vAA。
“const-class/jumbo vAAAA, type@BBBBBBBB”:通过给定的类型索引获取一个类引用并赋给寄存器vAAAA。这条指令占用两个字节,值为0xooff(Android4.0中新增的指令)。

6.锁指令
锁指令多用在多线程程序中对同一对象的操作。Dalvik指令集中有两条锁指令:
“monitor-enter vAA”:为指定的对象获取锁。
“monitor-exit vAA”:释放指定的对象的锁。

7.实例操作指令
与实例相关的操作包括实例的类型转换,检查及新建等:
“check-cast vAA, type@BBBB”:将vAA寄存器中的对象引用转换成指定的类型,如果失败会抛出ClassCastException异常。如果类型B指定的是基本类型,对于非基本类型的A来说,运行时始终会失败。
“instance-of vA, vB, type@CCCC”:判断vB寄存器中的对象引用是否可以转换成指定的类型,如果可以vA寄存器赋值为1,否则vA寄存器赋值为0。
“new-instance vAA, type@BBBB”:构造一个指定类型对象的新实例,并将对象引用赋值给vAA寄存器,类型符type指定的类型不能是数组类。
“check-cast/jumbo vAAAA, type@BBBBBBBB”:指令功能与“check-cast vAA, type@BBBB”相同,只是寄存器值与指令的索引取值范围更大(Android4.0中新增的指令)。
“instance-of/jumbo vAAAA, vBBBB, type@CCCCCCCC”:指令功能与“instance-of vA, vB, type@CCCC”相同,只是寄存器值与指令的索引取值范围更大
(Android4.0中新增的指令)。
“new-instance/jumbo vAAAA, type@BBBBBBBB”:指令功能与“new-instance vAA, type@BBBB”相同,只是寄存器值与指令的索引取值范围更大(Android4.0中新增的指令)。

8.数组操作指令
数组操作包括获取数组长度,新建数组,数组赋值,数组元素取值与赋值等操作。
“array-length vA, vB”:获取给定vB寄存器中数组的长度并将值赋给vA寄存器,数组长度指的是数组的条目个数。
“new-array vA, vB, type@CCCC”:构造指定类型(type@CCCC)与大小(vB)的数组,并将值赋给vA寄存器。
“filled-new-array {vC, vD, vE, vF, vG},type@BBBB”构造指定类型(type@BBBB)与大小(vA)的数组并填充数组内容。vA寄存器是隐含使用的,除了指定数组的大小外还指定了参数的个数,vC~vG是使用到的参数寄存序列。
“filled-new-array/range {vCCCC ..vNNNN}, type@BBBB”指令功能与“filled-new-array {vC, vD, vE, vF, vG},type@BBBB”相同,只是参数寄存器使用range

字节码后缀指定了取值范围 ,vC是第一个参数寄存器,N = A +C -1。
“fill-array-data vAA, +BBBBBBBB”用指定的数据来填充数组,vAA寄存器为数组引用,引用必须为基础类型的数组,在指令后面会紧跟一个数据表。
“new-array/jumbo vAAAA, vBBBB,type@CCCCCCCC”指令功能与“new-array vA,vB,type@CCCC”相同,只是寄存器值与指令的索引取值范围更大(Android4.0中新增的指令)。
“filled-new-array/jumbo {vCCCC ..vNNNN},type@BBBBBBBB”指令功能与“filled-new-array/range {vCCCC ..vNNNN},type@BBBB”相同,只是索引取值范围更大(Android4.0中新增的指令)。
“arrayop vAA, vBB, vCC”对vBB寄存器指定的数组元素进入取值与赋值。vCC寄存器指定数组元素索引,vAA寄存器用来存放读取的或需要设置的数组元素的值。读取元素使用aget类指令,元素赋值使用aput类指定,根据数组中存储的类型指令后面会紧跟不同的指令后缀,指令列表有 aget, aget-wide, aget-object, aget-boolean, aget-byte,aget-char, aget-short, aput, aput-wide, aput-object, aput-boolean, aput-byte, aput-char, aput-short。

9.异常指令
Dalvik指令集中有一条指令用来抛出异常。
“throw vAA”抛出vAA寄存器中指定类型的异常。

10.跳转指令
跳转指令用于从当前地址跳转到指定的偏移处。Dalvik指令集中有三种跳转指令:无条件跳转(goto),分支跳转(switch)与条件跳转(if)。
“goto +AA”:无条件跳转到指定偏移处,偏移量AA不能为0。
“goto/16 +AAAA”:无条件跳转到指定偏移处,偏量AAAA不能为0。
“goto/32 +AAAAAAAA”:无条件跳转到指定偏移处。
“packed-switch vAA, +BBBBBBBB”:分支跳转指令。vAA寄存器为switch分支中需要判断的值,BBBBBBBB指向一个packed-switch-payload格式的偏移表,表中的值是有规律递增的。
“sparse-switch vAA, +BBBBBBBB”:分支跳转指令。vAA寄存器为switch分支中需要判断的值,BBBBBBBB指向一个sparse-switch-payload格式的偏移表,表中的值是无规律的偏移量。
“if-test vA, vB, +CCCC”:条件跳转指令。比较vA寄存器与vB寄存器的值,如果比较结果满足就跳转到CCCC指定的偏移处。偏移量CCCC不能为0。if-test类型的指

令有以下几条:

“if-eq”:如果vA等于vB则跳转。Java语法表示为“if(vA == vB)”
"if-ne":如果vA不等于vB则跳转。Java语法表示为“if(vA != vB)”
“if-lt”:如果vA小于vB则跳转。Java语法表示为“if(vA < vB)”
“if-ge”:如果vA大于等于vB则跳转。Java语法表示为“if(vA >= vB)”
“if-gt”:如果vA大于vB则跳转。Java语法表示为“if(vA > vB)”
“if-le”:如果vA小于等于vB则跳转。Java语法表示为“if(vA <= vB)”

“if-testz vAA, +BBBB”:条件跳转指令。拿vAA寄存器与0比较,如果比较结果满足或值为0时就跳转到BBBB指定的偏移处。偏移量BBBB不能为0。if-testz类型的指令有以下

几条:

“if-eqz”:如果vAA为0则跳转。Java语法表示为“if(vAA == 0)”
"if-nez":如果vAA不为0则跳转。Java语法表示为“if(vAA != 0)”
"if-ltz":如果vAA小于0则跳转。Java语法表示为“if(vAA < 0)”
“if-gez”:如果vAA大于等于0则跳转。Java语法表示为“if(vAA >= 0)”
“if-gtz”:如果vAA大于0则跳转。Java语法表示为“if(vAA > 0)”
“if-lez”:如果vAA小于等于0则跳转。Java语法表示为“if(vAA <= 0)”

11.比较指令
比较指令用于对两个寄存器的值(浮点型或长整型)进行比较。它的格式为“cmpkind vAA, vBB, vCC”,其中vBB寄存器与vCC寄存器是需要比较的两个寄存器或寄存器对,比较的结果放到vAA寄存器。Dalvik指令集中共有5条比较指令:
“cmpl-float”:比较两个单精度浮点数。如果vBB寄存器大于vCC寄存器,结果为-1,相等则结果为0,小于的话结果为1
“cmpg-float”:比较两个单精度浮点数。如果vBB寄存器大于vCC寄存器,则结果为1,相等则结果为0,小于的话结果为-1
“cmpl-double”:比较两个双精度浮点数。如果vBB寄存器对大于vCC寄存器对,则结果为-1,相等则结果为0,小于则结果为1
“cmpg-double”:比较两个双精度浮点数。如果vBB寄存器对大于vCC寄存器对,则结果为1,相等则结果为0,小于的话,则结果为-1
“cmp-long”:比较两个长整型数。如果vBB寄存器大于vCC寄存器,则结果为1,相等则结果为0,小则结果为-1

12.字段操作指令
字段操作指令用来对对象实例的字段进入读写操作。字段的类型可以是Java中有效的数据类型。对普通字段与静态字段操作有两种指令集,分别是“iinstanceop vA, vB, fidld@CCCC” 与 “sstaticop vAA, field@BBBB”。
普通字段指令的指令前缀为i,如对普通字段读操作使用 iget 指令,写操作使用 iput 指令;静态字段的指令前缀为s,如对静态字段读操作使用 sget 指令,写操作使用

sput 指令。
根据访问的字段类型不同,字段操作指令后面会紧跟字段类型的后缀,如 iget-byte指令表示读取实例字段 的值类型为字节类型,iput-short指令表示设置实例字段的值类型

为短整型。两类指令操作结果都是一样,只是指令前缀与操作的字段类型不同。
普通字段操作指令有:iget,iget-wide,iget-object,iget-boolean,iget-byte,iget-char,iget-short,iput,iput-wide,iput-object,iput-boolean,iput-byte,iput-char,iput-short。
静态字段操作指令有:sget,sget-wide,sget-object,sget-boolean,sget-byte,sget-char,sget-short,sput,sput-wide,sput-object,sput-boolean,sput-byte,sput-char,sput-short。
在Android4.0系统中,Dalvik指令集中增加了“iinstanceop/jumbo vAAAA, vBBBB, field@CCCCCCCC”与”sstaticop/jumbo vAAAA, field@BBBBBBBB”两类指令,它们与上面介绍的两类指令作用相同,只是在指令中增加了jumbo字节码后缀,且寄存器值与指令的索引取值范围更大。
13.方法调用指令
方法调用指令负责调用类实例的方法。它的基础指令为 invoke,方法调用指令有“invoke-kind {vC, vD, vE, vF, vG},meth@BBBB”与“invoke-kind/range {vCCCC .. vNNNN},meth@BBBB”两类,两类指令在作用上并无不同,只是后者在设置参数寄存器时使用了range来指定寄存器的范围。根据方法类型的不同,共有如下五条方法调用指令:
“invoke-virtual” 或 “invoke-virtual/range”调用实例的虚方法。
“invoke-super”或”invoke-super/range”调用实例的父类方法。
“invoke-direct”或“invoke-direct/range”调用实例的直接方法。
“invoke-static”或“invoke-static/range”调用实例的静态方法。
“invoke-interface”或“invoke-interface/range”调用实例的接口方法。

在Android4.0系统中,Dalvik指令集中增加了“invoke-kind/jumbo {vCCCC .. vNNNN},meth@BBBBBBBB”这类指令,它与上面介绍的两类指令作用相同,只是在指令中增加了jumbo字节码后缀,且寄存器值与指令的索引取值范围更大。
方法调用指令的返回值必须使用move-result*指令来获取。如下面两条指令:
invoke-static {}, Landroid/os/Parcel;->obtain() Landroid/os/Parcel;
move-result-object v0

14.数据转换指令
数据转换指令用于将一种类型的数值转换成另一种类型。它的格式为“unop vA, vB”,vB寄存器或vB寄存器对存放需要转换的数据,转换后的结果保存在vA寄存器或vA寄存器对中。
“neg-int”:对整型数求补。
“not-int”:对整型数求反。
“neg-long”:对长整型数求补。
“not-long”:对长整型数求反。
“neg-float”:对单精度浮点型数求补。
“neg-double”:对双精度浮点型数求补。
“int-to-long”:将整型数转换为长整型。
“int-to-float”:将整型数转换为单精度浮点型数。
“int-to-dobule”:将整型数转换为双精度浮点数。
“long-to-int”:将长整型数转换为整型。
“long-to-float”:将长整型数转换为单精度浮点型。
“long-to-double”:将长整型数转换为双精度浮点型。
“float-to-int”:将单精度浮点数转换为整型。
“float-to-long”:将单精度浮点数转换为长整型数。
“float-to-double”:将单精度浮点数转换为双精度浮点型数。
“double-to-int”:将双精度浮点数转换为整型。
“double-to-long”:将双精度浮点数转换为长整型。
“double-to-float”:将双精度浮点数转换为单精度浮点型。
“int-to-byte”:将整型转换为字节型。
“int-to-char”:将整型转换为字符型。
“int-to-short”:将整型转换为短整型。

15.数据运行指令
数据运算指令包括算术运算指令与逻辑运算指令。算术运算指令主要进行数值间如加,减,乘,除,模,移位等运算。逻辑运算指令主要进行数值间与,或,非,抑或等运算。数据运算指令有如下四类(数据运算时可能是在寄存器或寄存器对间进行,下面的指令作用讲解时使用寄存器来描述):
“binop vAA, vBB, vCC”:将vBB寄存器与vCC寄存器进行运算,结果保存到vAA寄存器。
“binop/2addr vA, vB”:将vA寄存器与vB寄存器进行运算,结果保存到vA寄存器。
“binop/lit16 vA, vB, #+CCCC”:将vB寄存器与常量 CCCC进行运算,结果保存到vA寄存器。
“binop/lit8 vAA, vBB, #+CC”:将vBB寄存器与常量CC进行运算,结果保存到vAA寄存器。

后面3类指令比第1类指令分别多出了2addr,lit16,lit8等指令后缀。四类指令中基础字节码相同的指令执行的运算操作是类似的,第1类指令中,根据数据的类型不同会在基础字节码后面加上数据类型后缀,如 -int 或 -long 分别表示操作的数据类型为整型与长整型。第1类指令可归类如下:
“add-type”:vBB寄存器与vCC寄存器值进行加法运算(vBB + vCC)
“sub-type”:vBB寄存器与vCC寄存器值进行减法运算(vBB - vCC)
“mul-type”:vBB寄存器与vCC寄存器值进行乘法运算(vBB * vCC)
“div-type”:vBB寄存器与vCC寄存器值进行除法运算(vBB / vCC)
“rem-type”:vBB寄存器与vCC寄存器值进行模运算(vBB % vCC)
“and-type”:vBB寄存器与vCC寄存器值进行与运算(vBB & vCC)
“or-type”:vBB寄存器与vCC寄存器值进行或运算(vBB | vCC)
“xor-type”:vBB寄存器与vCC寄存器值进行异或运算(vBB ^ vCC)
“shl-type”:vBB寄存器值(有符号数)左移vCC位(vBB << vCC )
“shr-type”:vBB寄存器值(有符号)右移vCC位(vBB >> vCC)
“ushr-type”:vBB寄存器值(无符号数)右移vCC位(vBB >>> vCC)

其中基础字节码后面的-type可以是-int,-long, -float,-double。后面3类指令与之类似。
至此,Dalvik虚拟机支持的所有指令就介绍完了。在android4.0系统以前,每个指令的字节码只占用一个字节,范围是0x0~0x0ff。在android4.0系统中,又扩充了一部分指令,这些指令被称为扩展指令,主要是在指令助记符后添加了jumbo后缀,增加了寄存器与常量的取值范围。

本篇讲的可能更容易理解一些:http://www.jianshu.com/p/01d6f769925d

关于smali的好文:
https://www.tuicool.com/articles/EjYnuim
https://www.brogrammer.cn/android/smali/
http://blog.csdn.net/wdaming1986/article/details/8299996

非常详尽、透彻的一篇关于Dalvik虚拟机的文章:
http://blog.csdn.net/innost/article/details/50377905
跟上面这个有点类似的http://www.jianshu.com/p/92c2f732d2d6?from=timeline
老罗(罗升阳)讲的Dalvik:
http://blog.csdn.net/luoshengyang/article/details/8852432
http://blog.csdn.net/luoshengyang/article/details/18006645


Android可执行文件
可执行文件时操作系统的基础,它反映着系统的运行机制。
Android程序总结一下有以下四种安装方式:
1.系统程序安装:开机时安装,这类安装没有安装界面。
2.通过Android市场安装:直接通过Android网络市场进行安装,这类安装没有安装界面。
3.ADB工具安装:使用ADB工具进行安装,这类安装没有安装界面。
4.手机自带安装:使用SD卡里的APK文件安装,这类安装没有安装界面。

dex文件格式
dex文件中的数据结构
dex文件整体结构
odex文件格式
odex文件整体结构
odex文件结构分析


静态分析Android程序
静态分析(Static Analysis)是指在不运行代码的情况下,采用词法分析、语法分析等各种技术手段对程序文件进行扫描从而声称程序的反汇编代码,然后阅读反汇编代码来掌握程序功能的一种技术。
静态分析Android程序有两种办法:
一种方法是阅读反汇编生成的Dalvik字节码,可以使用IDA Pro分析dex文件,或者使用文本编辑器阅读baksmali反编译生成的smali文件
另一种方法是阅读反汇编生成的Java源码,可以使用dex2jar生成jar文件,然后再使用jd-gui阅读jar文件的代码。

快速定位Android程序的关键代码六种方法

  • 信息反馈法
    所谓信息反馈法,是指先运行目标程序,然后根据程序运行时给出的反馈信息作为突破口寻找关键代码。通常情况下,程序中用到的字符串会存储在String.xml文件或者硬编码到程序代码中,如果是前者的话,字符串在程序中会以id的形式访问,只需在反汇编代码中搜索字符串的id值即可找到调用代码处;如果是后者的话,在反汇编代码中直接搜索字符串即可。
  • 特征函数法
    这种定位代码的方法与信息反馈法类似。在信息反馈中,无论程序给出什么样的反馈信息,终究是需要调用Android SDK中提供的相关API函数来完成的。比如弹出注册码错误的提示信息就需要调用Toast.MakeTest().Show()方法,在反汇编代码中直接搜索Toast应该很快就能定位到调用代码,如果Toast在程序中有多处的话,可能需要分析
    人员逐个甄别。
  • 顺序查看法
    顺序查看法是指从软件的启动代码开始,逐行的向下分析,掌握软件的执行流程,这种分析方法在病毒分析时经常用到。
  • 代码注入法
    代码注入法属于动态调试方法,它的原理是手动修改apk文件的反汇编代码,假如Log输出,配合LogCat查看程序执行到特定点时的状态数据。这种方法在解密程序数据时经常使用。
  • 栈跟踪法
    栈跟踪法属于动态调试方法,它的原理是栈输出运行时的栈跟踪信息,然后查看栈上的函数调用序列来理解方法的执行流程。
  • Method Profiling
    Method Profiling(方法剖析)属于动态调试方法,他主要用于热点分析和性能优化。该功能除了可以记录每个函数占用的CPU时间外,还能跟踪所有的函数调用关系,并提供比栈跟踪法更详细的函数调用序列报告,这种方法可以帮助分析人员节省很多时间。

注解类
注解是Java的语言特性,安卓系统中涉及到的包公有两个:一个是dalvik.annotation,该程序包下的注解不对外开放,仅供核心代码库与代码测试使用,所有的注解声明位于Android系统源码的libcire\dalvik\src\main\java\dalvik\annotation目录下;另一个是android.annotation,相应注解声明位于Android系统源码的frameworks\base\core\java\android\annotion目录下。


6.2.2原生程序的生成过程
交叉编译工具链,指的是能在一种平台上编译出另一种平台上运行的程序的工具集合,也称为跨平台编译。
本地编译即当前平台编译,编译所得的程序只能在当前平台上运行。
1.预处理
2.编译
3.汇编
4.链接

ARM汇编语言与Java语言的区别
ARM汇编语言是一门“低级”语言 ,它能够与系统的底层打交道,直接访问底层硬件资源,而Java语言为高级语言,它只存在于框架层面,能访问的资源都是由框架提供;其次,ARM汇编语言编写的程序运行速度快,占用内存少,缺点是编写的代码难懂,也难以维护,而Java语言开发的程序运行速度相对较慢,占用内存多,好处是编写的代码容易理解,开发效率高;最后,ARM汇编语言编写的程序几乎不需要其他代码的转换就能直接运行,源码与反编译出来的代码基本相似,而Java语言编写的程序需要将其转换成特定的字节码才能在Android虚拟机中运行。
ARM汇编语言与C语言共用同一套原生程序开发的API接口,而且两者都是基于模块化的面向过程的编程思想。因为C语言编写的代码在编译时有一个过程是将其转换成ARM汇编代码,所以可以这么理解:C语言能实现的功能ARM汇编语言都能实现。
最后是ARM汇编语言中特有的寄存器。寄存器是处理器特有的高速存储部件,它们可用来暂存指令、数据和地址。高级语言中用到的变量、常量、结构体、类等数据到了ARM汇编语言中,就是使用寄存器保存的值或内存地址。寄存器的数量有限,ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。ARM处理器支持七种运行模式,它们分别为:

  • 用户模式(user):ARM处理器正常的程序执行状态。
  • 快速中断模式(fip):用于高速数据传输或通道处理。
  • 外部中断模式(irp):用于通用的中断处理。
  • 管理模式(svc):操作系统使用的保护模式。
  • 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护。
  • 系统模式(sys):运行具有特权的操作系统任务。
  • 未定义指令终止模式(und):当未定义的指令执行时进入该模式。

ARM处理器的运行模式可以通过软件改变,也可以通过外部中断或异常处理处理。在不同模式下,处理器使用的寄存器也不尽相同,而且可供访问的资源也不一样。在这七个模式中,除了用户模式外,其它六种模式均为“特权”模式,在“特权”模式下,处理器可以任意访问受保护的系统资源。
在用户模式下,处理器可以访问的寄存器为不分组寄存器R0~R7、分组寄存器R8~R14、程序计数器R15(PC)以及当前程序状态寄存器CPSR。
ARM处理器有两种工作状态:ARM状态与Thumb状态。处理器可以在两种状态之间随意切换。当处理器处于ARM状态时,会执行32位字对齐的ARM指令,当处于Thumb状态时,执行

的是16位对齐的Thumb指令。Thumb状态下对寄存器的命名与ARM有部分差异,它们的关系如下:
- Thumb状态下的R0~R7与ARM状态下的R0~R7相同。
- Thumb状态下的CPSR与ARM状态下的CPSR相同。
- Thumb状态下的FP对应于ARM状态下的R11。
- Thumb状态下的IP对应于ARM状态下的R12。
- Thumb状态下的SP对应于ARM状态下的R13。
- Thumb状态下的LR对应于ARM状态下的R14。
- Thumb状态下的PC对应于ARM状态下的R15。

寄存器可以通俗的的理解为可以存放东西的“储物柜”,并不具备其它的功能,代码能实现什么功能完全是由处理器的指令来决定的。
我们将ARM处理器所有支持的指令统称为ARM指令集,指令集中的每一条指令都有着自己的格式,在编写ARN汇编程序时需要严格遵守指令规范。

ARM汇编语言程序结构
Android平台的ARM汇编是GNU ARM汇编格式,使用的汇编器(汇编器的功能是将汇编代码转换为二进制目标文件)为GAS(GNU Assembler,GNU汇编器 ),它有着一套

自己的语法结构。
一个完整的ARM汇编程序包括处理器架构定义、数据段、代码段、与main函数。

处理器架构定义
程序的开头代码语句为:
·arch armv5te @处理器架构
·fpu softvfp @协处理器类型
·eabi_attribute 20,1 @接口属性
·eabi_attribute 21,1
·eabi_attribute 23,3
·eabi_attribute 24,1
·eabi_attribute 25,1
·eabi_attribute 26,2
·eabi_attribute 30,6
·eabi_attribute 18,4
这些指令指定了处理器使用的处理器架构、协处理器类型与接口的一些属性。
·arch指定了ARM处理器架构。armv5te表示本程序的代码可以在armv5te架构的处理器上运行,除此之外,它还可以是armv6、armv7-a等,不同的处理器架构支持的指令集不同,如果代码中使用了指定处理器架构不支持的指令,代码在编译时会报错。
·fpu指定了协处理器的类型。softvfp表示使用浮点数运算库来模拟协处理器运算,之所以会出现这个选项,是因为很多时候为了节省处理器的生存产成本,出厂的ARM处理器中不带协处理器单元,所有的浮点运算只能通过软模拟的形式来完成,在对硬件条件没有要求的情况下,可以使用这个保守选项。另外,还可以给.fpu赋值为vfpv2、vfpv3来指定使用处理器的协处理器。
·eabi_attribute制定了一些借口属性。EABI(Embedded Application Binary Interface,嵌入式应用二进制接口)是ARM制定的一套接口规范,Android系统实现了它,此处的属性值在编译多数程序时都是固定的。

6.3.3段定义
程序中段的概念大概要追溯到远古的DOS时代了,现在的高级语言很少直接使用到段,这些都是由编译器来自动生成的。像C语言中使用到的全局变量、常量等信息编译器都会将其编译到一个名为“.data”的数据段中,如果细分的话常量数据会被编译到名为“.rodata”的只读数据段中,这些数据段都是不可执行的,而代码则会编译到名为“.text”的代码段中。ARM汇编使用”.section”指令来定义段(section原义为区段,有些书籍中解释为节区,读者在此不必计较,理解其含义即可),它的格式为:
·section name[,”flags”[,%type[,flag_specific_arguments]]]
name为段名,flags为段的属性如读、写、可执行等,type指定了段的类型,如progbits表示段中包含有数据、note表示段中包含的数据非程序本身使用,flag_specific_arguments指定了一些平台相关的参数。
本实例定义了三个段:
“.section.rodata”定义只读取数段,属性采用默认。
“.text”定义了代码段,没有使用.section关键字。
“.section.note.GNU-stack,”“%progbits”定义.note.GNU-stack段,它的作用是禁止生成可执行堆栈,用来保护代码安全,可执行堆栈常常被用来引发堆栈溢出之类的漏洞,关于这方面的探讨读者可以阅读软件漏洞研究方面的的书籍。
6.3.4注释与标号
6.3.5汇编器指令
程序中所有以点“.”开头的指令都是汇编器指令,汇编器指令是与汇编器相关的,它们不属于ARM指令集。GAS汇编器支持的汇编器指令比较多,在GAS在线文档的第7章“Assembler Firectives”中列出了所有的汇编器指令。
本实例使用到的汇编器指令有:
.file:制定了源文件名。实例hello.s是从hello.c编译得来的,手写汇编代码可以忽略它。
.align:指定代码对齐方式,后面跟的数值为2的次数方。如:“.align4”表示2^4=16字节对齐。
.ascii:声明字符串。
.global:声明全局符号。全局符号是指在本程序外可以访问的符号。
.type:指定符号的类型。“.type main,%function”表示main符号为函数。
.word:用来存放地址值。“.word.LC0-(LPIC0+8)”存放的是一个与地址无关的偏移值。
.size:设置指定符号的大小。“.size main,-main”中的点“.”表示当前地址,减去main符号的地址即为整个main函数的大小。
.ident:编译器标识,无实际用途,生成可执行程序后它的值被放置到“.comment”段中。
6.3.6子程序与参数传递
子程序在代码中用来完成一个独立的功能,很多时候子程序与函数是相同的概念。ARM汇编中声明函数的方法如下:
.global 函数名
.type 函数名,%function
函数名:
<···函数体···>
例如声明一个实现两个数相加的函数的代码为:
.global MyAdd
.type MyAdd,%function
MyAdd:
ADD r0, r0, r1 @ 两个参数相加
MOV pc, lr @ 函数返回
既然是函数调用,就肯定存在函数参数传参的问题。 ARM汇编中规定:R0-R3这四个寄存器用来传递函数调用的第1到第4个参数,超出的参数通过堆栈来传递。R0寄存器同时用来存放函数调用的返回值。被调用的函数在返回前无须恢复这些寄存器的内容。
6.4ARM处理器寻址方式
处理器寻址方式是指通过指令中给出的地址码字段来寻找真实操作数地址的方式。尽管ARM处理器采用的是精简指令集,但指令间的组合灵活度却比x86处理器要高,x86处理器支持七种寻址方式,而ARM处理器支持九种寻址方式。
6.4.1立即寻址
立即寻址是最贱的一种寻址方式,大多数的处理器都支持这种寻址方式。立即寻址指令中后面的地址码部分为立即数(即常量或常数),立即寻址多数用于给寄存器赋初值。并且立即数只能用于源操作数字段,不能用于目的操作数字段。例如:
MOV R0,#1234
指令执行后R0=1234.立即数以“#”作为前缀,表示十六进制数值时以“0x”开头,如#0x20。
6.4.2寄存器寻址
寄存器寻址中,操作数的值在寄存器中,指令执行时直接从寄存器中取值进行操作。例如:
MOV R0,R1
指令执行后R0=R1。
6.4.3寄存器移位寻址
寄存器移位寻址是ARM指令集特有的寻址方式,寄存器移位寻址与寄存器寻址类似,只是在操作前需要对源寄存器操作数进行移位操作。
寄存器移位寻址支持以下五种移位操纵:
LSL:逻辑左移,移位后寄存器空出的低位补0。
LSR:逻辑右移,移位后寄存器空出的高位补0。
ASR:算数右移,移位过程中符号为保持不变,如果源操作数为正数,则移位后空出的高位补0,否则补1。
ROR:循环右移,移位后移出的低位填入移位空出的高位。
RRX:待扩展的循环右移,操作数右移一位,移位空出的高位用C标志的值填充。例如:
MOV R0,R1,LSL #2
指令的功能是将R1寄存器左移2位,即“R1<<2”后赋值给R0寄存器,指令执行后R0=R1*4。
6.4.4寄存器间接寻址
寄存器间接寻址中地址码给出的寄存器式操作数的地址指针,所需的操作数保存在寄存器指定地址的存储单元中。例如:
LDR R0,[R1]
指令的功能是将R1寄存器的数值作为地址,取出此地址中的值赋给R0寄存器。
6.4.5基址寻址
基址寻址是将地址码给出的基址寄存器与偏移量相加,形成操作数的有效地址,所需的操作是保存在有效地址所指向的存储单元中。基址寻址多用于查表、数组访问等操作。例如:
LDR R0,[R1,#-4]
6.4.6多寄存器寻址
多寄存器寻址一条指令最多可以完成16个通用寄存器值的传送。例如:
LDMIA R0,{R1,R2,R3,R4}
LDM是数据加载令,指令的后缀IA表示每次执行完加载操作后R0寄存器的值自增1个字,ARM指令集中,字表示的是一个32位的数值。这条指令执行后,R1=[R0],R2=[R0+#4],R3=[R0+#8],R4=[R0+#12]
6.4.7堆栈寻址
堆栈寻址是ARM处理器特有的一种寻址方式,堆栈寻址需要使用特定的指令来完成。堆栈寻址的指令有LDMFA/STMFA,LDMEA/STMEA,LDMFD/STMFD,LDMED/STMED。
LDM和STM为指令前缀,表示多寄存器寻址,即一次可以多传送多个寄存器值。
堆栈寻址举例:
STMFD SP!,{R1-R7,LR}@将R1~R7,LR入栈。多用于保存子线程“现场”
LDMFD SP!,{R1~R3}@存储R1-R3寄存器的内容到R0寄存器指向的存储单元。
6.4.9相对寻址
相对寻址以程序计数器PC的当前职位为基地址,指令中的地址标号作为偏移量,将两者相加之后得到的操作数的有效地址。例如:
BL NEXT
……
NEXT:
……
BL NEXT是跳到NEXT标号处执行。这里的BL采用的就是相对寻址,标号NEXT就是偏移量。
6.5ARM与Thumb指令集
指令及时处理器的核心,随着ARM处理器版本的升级,支持的指令集也在不断的增加,其中被广泛使用的应属ARM指令集与Thumb指令集了。Thumb指令集可以理解为ARM指令集的一个子集。
6.5.2 条砖指令
跳转指令又称为分支指令,它可以改变指令序列的执行流程。ARM中有两种方式可以实现程序跳转:一种是使用跳转指令直接跳转;另一种是给PC寄存器直接赋值实现跳转。
跳转指令有以下4条。
1.B跳转指令
B{cond}label
B指令属于ARM指令集,是最简单的分支指令。当执行B指令时,如果条件cond满足,ARM处理器将立即跳转到label指定的地址处继续执行。例如:“BNE LABEL”表示条件码Z=0时跳转到LABEL处执行。
2.BL带链接的跳转指令
BL{cond}label
当执行BL指令时,如果条件cond满足,会首先将当前指令的下一条指令的地址拷贝到R14(即LR)寄存器中,然后跳转到label指定的地址处继续执行。这条指令通常用于调用子程序,在子程序的尾部,可以通过“MOV PC,LR”返回到主程序中。
3.BX带状态切换的跳转指令
BX{cond}Rm
当执行BX指令时,如果条件cond满足,则处理器会判断Rm的位[0]是否为1,如果为1则跳转时自动将CPSR寄存器的标志T置位,并将目标地址处的代码解释为Thumb代码来执行,即处理器会切换至Thumb状态;反之,若Rm的位[0]为0,则跳转时自动将CPSR寄存器的标志T复位,并将目标地址处的代码解释为ARM代码来执行,即处理器会切换到ARM状态。例如下面的代码:
.code 32
……
ADR R0,thumbcode+1
BX R0 @跳转到thumbcode处执行,并将处理器切换到Thumb模式
thumbcode:
.code 16
……
4.BLX带链接和状态切换的跳转指令
BLX{cond} Rm
BLX指令集合了BL和BX的功能,当条件满足时,除了设置链接寄存器,还根据Rm位[0]的值来切换处理器状态。
6.5.3储存器访问指令
储存器访问操作包括从储存器中加载数据、存储数据到存储器、寄存器与存储器间数据的交换等。


阅读更多
换一批

没有更多推荐了,返回首页