ARM 指令集 和 Thumb 指令集

From:https://gitee.com/lsliushuang/ASM/blob/master/arm汇编.txt

ARM 汇编指令集汇总:https://blog.csdn.net/qq_40531974/article/details/83897559

ARM 指令集详解(超详细!带实例!):https://blog.csdn.net/mickey35/article/details/82011449

ARM 基础编程实验:https://blog.csdn.net/qq_40531974/article/details/83897252

arm 汇编文件分析:https://blog.csdn.net/shenlong1356/article/details/81805408

ARM 汇编指令:https://blog.csdn.net/zhangmiaoping23/article/details/8875193

大脸猫讲逆向之ARM汇编中PC寄存器详解:https://www.cnblogs.com/ichunqiu/p/9056630.html

1、ARM 指令 和 Thumb 指令

ARM 和 x86 的指令集有出入,X86是复杂指令集(CISC),ARM是精简指令集(RISC)。比如 x86 中 MOV 可以把数据从内存中加载到寄存器中去,但在ARM 的RISC结构中,MOV 只能将数据从寄存器移动到寄存器,x86 的看小甲鱼的视频( :https://www.bilibili.com/video/BV1zW411n79C/ )。ARM汇编初学者视频:https://space.bilibili.com/37877654/channel/series

ARM 指令是32位的,而 Thumb指令是16位的。Thumb 不是完整的指令集,Thumb 是ARM 指令集的一个子集。

如果在1K的存储空间中,可以放32条ARM指令,就可以放64条Thumb指令,因此在存放Thunb指令时,代码密度高。

arm 处理器的 thumb 和 arm 指令集

:https://www.cnblogs.com/revercc/p/16350188.html

arm 处理器拥有多种指令集,不同的架构支持不同的指令集。

armeabi-v7a 是一个32位的架构,其支持 thumb-1,thumb-2,arm三种指令集。

thumb-1 和 arm

thumb-1指令集是arm指令集的子集,thumb-1指令长度为16位(两个字节),arm指令为32位(4个字节)。thumb-1指令集并不是一个完整的指令集,有一些操作无法使用thumb-1指令集完成,但是对于简单的操作使用thumb-1指令集的程序比使用arm指令集的程序体积更小。

thumb-1 和 thumb-2

thumb-2是对thumb-1的扩展,thumb-2的一些指令使用了32位(4个字节)。但是一般情况下thumb-1指令集是和thumb-2指令集配合使用,在编译程序时如果使用thumb指令,生成的程序既会包含thumb-1的指令又会包含thumb-2的指令。当一个操作可以使用一条 32bits指令完成时就使用 32bits 的指令,加快运行速度,而当一次操作只需要一条16bits 指令完成时就使用16bits 的指令,节约存储空间。

在ndk编译时使用thumb和arm指令集生成指令

默认情况下是使用thumb指令集生成二进制程序,这样可以减小二进制程序的体积。使用编译参数-mthumb 可以显式选择使用thumb指令集生成二进制文件。使用-marm 编译参数显式使用arm指令集生成二进制程序。

IDA 判断 Thumb 指令集和 Arm 指令集

  • IDA – Options – General – number of opcode bytes – 设置为 4
  • 此时查看 IDA VIew 中 opcode 的长度, 如果出现 2 个字节和 4 个字节的, 说明为 thumb 指令集
  • 如果都是 4 个字节的, 说明是 arm 指令集;
  • 在 Thumb 指令集下, inline hook 的偏移地址需要进行 +1 操作;

上图中的程序就是既包含16位的thumb-1指令又包含32位的thumb-2指令。一般含有.w后缀的指令都是thumb-2指令集特有的指令。

ARM 指令集 助记符 

助记符 说明 助记符 说明
EQ 相等 LS 无符号数小于等于
NE 不相等 GE 带符号数大于等于
CS/HS 无符号数大于/等于 LT 带符号数小于
CC/LO 无符号数小于 GT 带符号数大于
MI 负数 LE 带符号数小于等于
PL 非负数 AL 无条件执行
VS 上溢出 NV 该指令从不执行
VC 没有上溢出
HI 无符号数大于

助记符    操作
MOV    移动
ADD    加
SUB    减
RSB    反减
CMP    比较
TST    测试
AND    逻辑与
EOR    逻辑或
LSL    逻辑左移
ASR    算术右移
MUL    有符号长乘
SMUL    乘
SMLAL    有符号长累乘
MSR    移入状态寄存器
B    分支
BX    分支与交换
LDR    载入字
LDRH    载入半字
LDRB    载入字节
LDRSH    载入有符号半字
LDRSB    载入有符号字节
LDM    载入乘
LDRBT    转化载入寄存器字节
LDRT    转化载入寄存器
MCR    移入协处理器
LDC    协处理器数据处理
MVN    移非
ADC    带进位加
SBC    带进位减
RSC    带进位反减
CMN    比较取反
TEQ    测试等价
BIC    位清零
ORR    逻辑 ( 包括 ) 或
MLA    累乘
UMULL    无符号长乘
UMLAL    无符号长累乘
MRS    由状态寄存器移出
BL    分支与链接
SWI    软件中断
STR    恢复字
STRH    恢复半字
STRB    恢复字节
STRBT    转化保存寄存器字节
STRT    转化保存寄存器
STM    多路保存
SWPB    交换字节
MRC    由协处理器移出
STC    由协处理器保存

Thumb 指令集 助记符 

Thumb 指令可以看做是 ARM 指令压缩形式的子集,是针对代码密度【1】的问题而提出的,它具有16为的代码密度。Thumb不是一个完整的体系结构,不能指望处理程序只执行Thumb指令而不支持ARM指令集。因此,Thumb指令只需要支持通用功能,必要时,可借助完善的ARM指令集,例如:所有异常自动进入ARM状态。

在编写Thumb指令时,先要使用伪指令CODE16声明,而且在ARM指令中要使用BX指令跳转到Thumb指令,以切换处理器状态。编写ARM指令时,可使用伪指令CODE32声明。

Thumb 指令集分为:

  • 分支指令
  • 数据处理指令
  • 载入与保存指令
  • 批量载入与保存指令
  • 异常产生指令

Thumb 模式下,R0~R7八个通用功能寄存器有效。与执行ARM指令时的R0~R7相同。 某些 Thumb 指令还访问程序计数器 (ARM 寄存器 15)、链接寄存器 (ARM 寄存器 14) 及
堆栈指针 (ARM 寄存器 13)。其他指令对 ARM 寄存器 8 ~ 15 的访问有所限制。

助记符    操作
MOV    移动
ADD    加
SUB    减
CMP    比较
TST    测试
AND    逻辑与
EOR    逻辑或
LSL    逻辑左移
ASR    算术右移
MUL    乘
B    分支
BX    分支与交换
LDR    载入字
LDRH    载入半字
LDRB    载入字节
LDRSH    载入有符号半字
LDMIA    载入乘
PUSH    将寄存器推入堆栈
MVN    移非
ADC    带进位加
SBC    带进位减
CMN    比较取反
NEG    去反
BIC    位清零
ORR    逻辑 ( 包括 ) 或
LSR    逻辑右移
ROR    右转
BL    分支与链接
SWI    软件中断
STR    保存字
STRH    保存半字
STRB    保存字节
LDRSB    载入有符号字节
STMIA    多路保存
PUSH 将寄存器推入堆栈 POP    将寄存器推出堆栈

ARM 和 Thumb 状态 切换

ARM 和 Thumb 状态的区别,及如何进行状态切换 ?( https://www.cnblogs.com/yygsj/p/5428500.html )

区别:ARM状态是32位指令,Thumb状态是16位指令。
状态切换:

  • 进入 Thumb 状态:执行 BX 指令,当操作数寄存器最低位为 1 时,可以使微处理器从 ARM状态 切换到 Thumb状态(处理器工作在Thumb状态,如果发生异常并进入异常处理子程序,异常处理完毕返回时自动从ARM状态切换到Thumb状态)。
  • 进入 ARM 状态:执行 BX 指令,当操作数寄存器最低位为 0 时,可以使微处理器从Thumb状态切换到ARM状态(处理器工作在Thumb状态,如果发生异常并进入异常处理子程序,则进入时处理器自动从Thumb状态切换到ARM状态)。

2、ARM 指令 寻址方式

  • 立即数寻址:  MOV R2, #0xf3 ;    值0xf3 赋值给 寄存器R2
  • 寄存器寻址:  ADD R0,R1,R2 ;      R1 + R2  的值 赋值 给  R0。 即 R0 = R1 + R2
  • 寄存器移位寻址方式:
ASR      算术右移
LSL      逻辑左移
LSR      逻辑右移
ROR      循环右移
RRX    扩展的循环右移eg:MOV R0,R1,LSL #3 ;        R0 = R1 * (2^3)
ADD R0,R1,R1,LSL #3 ;     R0 = R1 + R1 * (2^3)
SUB R0,R1,R2,LSR #4 ;     R0 = R1 - R2 /(2^4)
MOV R0,R1,ROR R2 ;        R0 = R1循环右移R2位
  • Load/Store 指令寻址方式:
Load   用于从内存中读取数据放入寄存器中。
Store  用于从寄存器中读取数据保存到内存中。
LDR    用于从内存中将一个字的数据传送到寄存器中。

3、ARM 常用指令集合

跳转指令:ARM 跳转指令可以从当前指令向前或向后的 32M 地址空间跳转。

  • B    跳转指令
  • BL     带返回的跳转指令
  • BX     带状态切换的跳转指令
  • BLX   带返回和状态切换的跳转指令s

BL 和 BLX 指令可将下一个指令的地址复制到 lr(r14,链接寄存器)中

BX 和 BLX 指令可将处理器的状态从 ARM 更改为 Thumb,或从 Thumb 更改为 ARM

B BL BLX BX详解:https://blog.csdn.net/chungle2011/article/details/7950285

数据处理指令:数据处理指令大致分为3类,

  • 数据传送指令,
  • 算术逻辑运算指令,
  • 比较指令

指令:

  • MOV    数据传送指令
  • MVN    数据求反传送指令
  • CMP    比较指令
  • CMN    基于相反数的比较指令
  • TST    位测试指令
  • TEQ    相等测试指令
  • ADD    加法指令
  • SUB    减法指令
  • RSB    逆向减法指令
  • ADC    带位加法指令
  • SBC    带位减法指令
  • RSC    带位逆向减法指令
  • AND    逻辑与操作指令
  • BIC     位清除指令
  • EOR    逻辑异或操作指令
  • ORR    逻辑或操作指令
  • MUL    32位乘法指令      MUL R0,R1,R2 ;       R0 = R1 * R2
  • MLA    32位带加数的乘法指令   MLA R0,R1,R2,R3 ;    R0 = R1 * R2 + R3
  • SMULL    64位有符号数乘法指令 SMULL R0,R1,R2,R3 ;   //低32位:R0 = R2*R3    //高32位:R1 = R2*R3
  • SMLAL    64位带加数的有符号数乘法指令
  • UMULL    64位无符号数乘法指令
  • UMLAL    64位带加数的无符号数乘法指令
  • CLZ    前导 0 个数计算指令
  • 状态寄存器 访问指令:ARM 中有两条指令用于在状态寄存器和通用寄存器之间传送数据
            MRS 状态寄存器到通用寄存器的传送指令。MRS R0,CPSR ;读取CPSR到R0寄存器
            MSR 通用寄存器到状态寄存器的传送指令。
  • BIC R0,R0,#0x1F ;    修改,去除当前处理器模式
  • ORR R0,R0,#0x13 ;    修改,设置特权模式
  • MSR CPSR_c,R0 ;    写回,仅仅修改CPSR中的控制位域
  • Load / Store 内存 访问指令

    LDR 字数据读取指令。
            LDR R0,[R1,#4]; 将内存单元R1+4中的字读取到R0寄存器中

    LDRB 字节数据读取指令 。
            LDRB R0,[R1]; 将内存单元(R1)中的字节数据读取到R0中,R0中高24位设置成了0
    LDRBT 用户模式的字节数据读取指令
    LDRH  半字数据读取指令 。
            LDRH R0,[R1] ;将内存单元(R1)中的字节数据读取到R0中,R0中高16位设置成了0
    LDRSB 有符号的字节数据读取指令 。
            LDRSB R0,[R1,#3] ;将内存单元(R1+3)中的有符号字节数据读取到R0中,R0中高24位设置成该字节数据的符号位
    LDRSH 有符号的半字数据读取指令 
            LDRSH R0,[R1,#3] ;将内存单元(R1+3)中的有符号字节数据读取到R0中,R0中高16位设置成该字节数据的符号位
    LDRT 用户模式的字数据读取指令

    STR  字数据写入指令 STR R0,[R1,#0x100] ;将R0中的自数据保存到内存单元(R1 + 0x100)中
    STRB 字节数据写入指令 STRB R3,[R5,#0x200] ;将R3中的低8位数据保存到内存地址(R5+0x200)中
    STRBT 用户模式下字节数据写入指令
    STRH 半字数据写入指令
    STRT 用户模式下字数据写入指令

  • 批量 Load/Store 内存访问指令:批量Load/Store内存访问指令可以一次从连续的内存单元中读取数据,传送到指令中的内存列表中的各个寄存器中,同时也可以将指令中寄存器列表中的各个寄存器值写入到内存中。
            LDM(1)    批量内存字数据读取指令
            LDM(2)    用户模式下的批量内存字数据读取指令
            LDM(3)    带状态寄存器的批量内存字数据读取指令
            STM(1)    批量内存字数据写入指令
            STM(2)    用户模式下的批量内存字数据写入指令

  • 信号量操作指令:
    SWP 交换指令 SWP R1,R2,[R3] ;
            将内存单元(R3)中字数据读取到R1寄存器中,同时将R2寄存器的数据写入到内存单元(R3)中
    SWPB 字节交换指令

  • 异常中断产生指令:
            SWI(软中断指令):用于产生软中断。
            BKPT:断点中断指令,用于产生软件断点中断。

一.汇编数据处理指令

1. 数据传送指令

【MOV指令】:它的传送指令只能是把一个寄存器的值(要能用立即数表示)赋给另一个寄存器,或者将一个常量赋给寄存器,将后边的量赋给前边的量。

  • MOV指令的格式为:MOV{条件}{S} 目的寄存器,源操作数
  • MOV指令中,条件缺省时指令无条件执行;S选项决定指令的操作是否影响CPSR中条件标志位的值,当没有S时指令不更新CPSR中条件标志位的值。

指令示例:

MOV R1,R0   ;将寄存器R0的值传送到寄存器R1
MOV PC,R14   ;将寄存器R14的值传送到PC,常用于子程序返回
MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位后传送到R1(即乘8)
MOVS PC, R14	  ;将寄存器R14的值传送到PC中,返回到调用代码并恢复标志位

除了 MOV 指令外,还有数据取反传送指令 MVN。

【MVN指令】

  • MVN 指令的格式为:MVN{条件}{S} 目的寄存器,源操作数
  • MVN 指令可完成从另一个寄存器、被移位的寄存器、或将一个立即数加载到目的寄存器。与MOV指令不同之处是在传送之前按位被取反了,即把一个被取反的值传送到目的寄存器中。其中S决定指令的操作是否影响CPSR中条件标志位的值,当没有S时指令不更新CPSR中条件标志位的值。

指令示例:

MVN R0,#0   ;将立即数0取反传送到寄存器R0中,完成后R0=-1(有符号位取反)

2. 算术运算指令

(1)【加法指令】:ADD

  • ADD指令的格式为:ADD{条件}{S} 目的寄存器,操作数1,操作数2
  • ADD指令用于把两个操作数相加,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。

指令示例:

ADD  R0,R1,R2           ; R0 = R1 + R2
ADD  R0,R1,#256            ; R0 = R1 + 256
ADD  R0,R2,R3,LSL#1      ; R0 = R2 + (R3 << 1)

(2)【带进位的加法指令】:ADC

  • ADC 指令的格式为:ADC{条件}{S} 目的寄存器,操作数1,操作数2
  • ADC 指令用于把两个操作数相加,再加上CPSR中的C条件标志位的值,并将结果存放到目的寄存器中。它使用一个进位标志位,这样就可以做比32位大的数的加法,注意不要忘记设置S后缀来更改进位标志。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。

以下指令序列完成两个128位数(此处应为两个四字数相加)的加法,第一个数由高到低存放在寄存器R7~R4,第二个数由高到低存放在寄存器R11~R8,运算结果由高到低存放在寄存器R3~R0:

ADDS  R0,R4,R8          ; 加低端的字,R0=R4+R8
ADCS  R1,R5,R9            ; 加第二个字,带进位,R1=R5+R9
ADCS  R2,R6,R10       ; 加第三个字,带进位,R2=R6+R10
ADC  R3,R7,R11       ; 加第四个字,带进位,R3=R7+R11

(3)【减法指令】:SUB

  • SUB指令的格式为:SUB{条件}{S} 目的寄存器,操作数1,操作数2
  • SUB指令用于把操作数1减去操作数2,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令可用于有符号数或无符号数的减法运算。

指令示例:

SUB  R0,R1,R2           ; R0 = R1 - R2
SUB  R0,R1,#256            ; R0 = R1 - 256
SUB  R0,R2,R3,LSL#1      ; R0 = R2 - (R3 << 1)

(4)【带借位减法指令】:SBC

  • SBC指令的格式为:SBC{条件}{S} 目的寄存器,操作数1,操作数2
  • BC 指令用于把操作数1减去操作数2,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令使用进位标志来表示借位,这样就可以做大于32位的减法,注意不要忘记设置S后缀来更改进位标志。该指令可用于有符号数或无符号数的减法运算。

指令示例:

SUBS  R0,R1,R2           ;R0 = R1 - R2 - !C,并根据结果设置CPSR的进位标志位

3. 比较指令

(1)【直接比较指令】:CMP

  • CMP 指令的格式为:CMP{条件} 操作数1,操作数2
  • CMP 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新CPSR中条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。标志位表示的是操作数1与操作数2的关系(大、小、相等),例如,当操作数1大于操作操作数2,则此后的有GT 后缀的指令将可以执行。

指令示例:

CMP R1,R0   ;将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR的标志位
CMP R1,#100  ;将寄存器R1的值与立即数100相减,并根据结果设置CPSR的标志位

(2)【负数比较指令】:CMN

  • CMN 指令的格式为:CMN{条件} 操作数1,操作数2
  • CMN 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数取反后进行比较,同时更新CPSR中条件标志位的值。该指令实际完成操作数1和操作数2相加,并根据结果更改条件标志位。

指令示例:

CMN R1,R0   ;将寄存器R1的值与寄存器R0的值相加,并根据结果设置CPSR的标志位
CMN R1,#100  ;将寄存器R1的值与立即数100相加,并根据结果设置CPSR的标志位

4. 逻辑运算指令

(1)【逻辑与指令】:AND

  • AND指令的格式为:AND{条件}{S} 目的寄存器,操作数1,操作数2
  • AND 指令用于在两个操作数上进行逻辑与运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于屏蔽操作数1的某些位。

指令示例:

AND  R0,R0,#3           ; 该指令保持R0的0、1位,其余位清零。

(3)【逻辑异或指令】:EOR

  • EOR指令的格式为:EOR{条件}{S} 目的寄存器,操作数1,操作数2
  • EOR指令用于在两个操作数上进行逻辑异或运算,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于反转操作数1的某些位。

指令示例:

EOR  R0,R0,#3           ; 该指令反转R0的0、1位,其余位保持不变。

(4)【位清零指令】:BIC

  • BIC指令的格式为:BIC{条件}{S} 目的寄存器,操作数1,操作数2
  • BIC指令用于清除操作数1的某些位,并把结果放置到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。操作数2为32位的掩码,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不变。

指令示例:

BIC  R0,R0,#%1011         ; 该指令清除 R0 中的位 0、1、和 3,其余的位保持不变。

5. 测试指令

(1)【位测试指令】:TST

  • TST 指令的格式为:TST{条件} 操作数1,操作数2
  • TST 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值。操作数1是要测试的数据,而操作数2是一个位掩码,该指令一般用来检测是否设置了特定的位。

指令示例:

TST R1,#%1  ;用于测试在寄存器R1中是否设置了最低位(%表示二进制数)
TST R1,#0xffe  ;将寄存器R1的值与立即数0xffe按位与,并根据结果设置CPSR的标志位

(2)【位测试指令】:TEQ

  • TEQ 指令的格式为:TEQ{条件} 操作数1,操作数2
  • TEQ 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行按位的异或运算,并根据运算结果更新CPSR中条件标志位的值。该指令通常用于比较操作数1和操作数2是否相等。

指令示例:

TEQ R1,R2  ;将寄存器R1的值与寄存器R2的值按位异或,并根据结果设置CPSR的标志位

6. 乘法指令

二.汇编转移指令

【跳转指令】

跳转指令用于实现程序流程的跳转,在ARM程序中有两种方法可以实现程序流程的跳转:

  • 1.  使用专门的跳转指令。
  • 2.  直接向程序计数器PC写入跳转地址值。

通过向程序计数器PC写入跳转地址值,可以实现在4GB的地址空间中的任意跳转,在跳转之前结合使用MOV LR,PC等类似指令,可以保存将来的返回地址值,从而实现在4GB连续的线性地址空间的子程序调用。

ARM指令集中的跳转指令可以完成从当前指令向前或向后的32MB的地址空间的跳转,包括以下4条指令:

B     跳转指令
BL    带返回的跳转指令
BLX   带返回和状态切换的跳转指令
BX    带状态切换的跳转指令

1、【B指令】

  • B指令的格式为:B{条件} 目标地址
  • B指令是最简单的跳转指令。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。注意存储在跳转指令中的实际值是相对当前PC值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。

它是 24 位有符号数,左移两位后有符号扩展为 32 位,表示的有效偏移为 26 位(前后32MB的地址空间)。以下指令:

B  Label  ;   程序无条件跳转到标号Label处执行
CMP R1,#0  ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行
BEQ Label  

2、【BL指令】

  • BL 指令的格式为:BL{条件} 目标地址
  • BL 是另一个跳转指令,但跳转之前,会在寄存器R14中保存PC的当前内容,因此,可以通过将R14 的内容重新加载到PC中,来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本但常用的手段。

以下指令:

BL Label  ;当程序无条件跳转到标号Label处执行时,同时将当前的PC值保存到R14中

3、【BLX指令】

  • BLX 指令的格式为:BLX 目标地址
  • BLX 指令从 ARM 指令集跳转到指令中所指定的目标地址,并将处理器的工作状态有ARM状态切换到Thumb状态,该指令同时将 PC 的当前内容保存到寄存器R14中。因此,当子程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令实现子程序的调用和处理器工作状态的切换。同时,子程序的返回可以通过将寄存器R14值复制到PC中来完成。

4、【BX指令】

  • BX 指令的格式为:BX{条件} 目标地址
  • BX 指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。

三.汇编程序状态寄存器访问指令

1、【MRS指令】

  • MRS指令的格式为:MRS{条件} 通用寄存器,程序状态寄存器(CPSR或SPSR)
  • MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下几种情况:
    当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。
    当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。

指令示例:

MRS R0,CPSR   ;传送CPSR的内容到R0
MRS R0,SPSR   ;传送SPSR的内容到R0

2、【MSR指令】

  • MSR 指令的格式为:MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数
  • MSR 指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要操作的位,32位的程序状态寄存器可分为4个域:
位[31:24]为条件标志位域,用f表示;
位[23:16]为状态位域,用s表示;
位[15:8]为扩展位域,用x表示;
位[7:0]为控制位域,用c表示;

该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在 MSR 指令中指明将要操作的域。

指令示例:

MSR CPSR,R0   ;传送R0的内容到CPSR
MSR SPSR,R0   ;传送R0的内容到SPSR
MSR CPSR_c,R0  ;传送R0的内容到SPSR,但仅仅修改CPSR中的控制位域

四.汇编加载/存储指令

ARM微处理器支持加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则完成相反的操作。常用的加载存储指令如下:

1、【LDR指令】

  • LDR 指令的格式为:LDR{条件} 目的寄存器,<存储器地址>
  • LDR 指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。

指令示例:

LDR  R0,[R1]             ;将存储器地址为R1的字数据读入寄存器R0。
LDR  R0,[R1,R2]        ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR  R0,[R1,#8]        ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR  R0,[R1,R2] !       ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR  R0,[R1,#8] !       ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR  R0,[R1],R2        ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR  R0,[R1,R2,LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR  R0,[R1],R2,LSL#2  ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。

2、【LDRB指令】

  • LDRB 指令的格式为:LDR{条件}B 目的寄存器,<存储器地址>
  • LDRB 指令用于从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。该指令通常用于从存储器中读取8位的字节数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。

指令示例:

LDRB  R0,[R1]      ;将存储器地址为R1的字节数据读入寄存器R0,并将R0的高24位清零。
LDRB  R0,[R1,#8]     ;将存储器地址为R1+8的字节数据读入寄存器R0,并将R0的高24位清零。

3、【LDRH指令】

  • LDRH指令的格式为:LDR{条件}H 目的寄存器,<存储器地址>
  • LDRH指令用于从存储器中将一个16位的半字数据传送到目的寄存器中,同时将寄存器的高16位清零。该指令通常用于从存储器中读取16位的半字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。

指令示例:

LDRH  R0,[R1]      ;将存储器地址为R1的半字数据读入寄存器R0,并将R0的高16位清零。
LDRH  R0,[R1,#8]     ;将存储器地址为R1+8的半字数据读入寄存器R0,并将R0的高16位清零。
LDRH  R0,[R1,R2]     ;将存储器地址为R1+R2的半字数据读入寄存器R0,并将R0的高16位清零。

4、【STR指令】

  • STR指令的格式为:STR{条件} 源寄存器,<存储器地址>
  • STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。

指令示例:

STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。

5、【STRB指令】

  • STRB指令的格式为:STR{条件}B 源寄存器,<存储器地址>
  • STRB指令用于从源寄存器中将一个8位的字节数据传送到存储器中。该字节数据为源寄存器中的低8位。

指令示例:

STRB  R0,[R1]      ;将寄存器R0中的字节数据写入以R1为地址的存储器中。
STRB  R0,[R1,#8]     ;将寄存器R0中的字节数据写入以R1+8为地址的存储器中。

6、【STRH指令】

  • STRH指令的格式为:STR{条件}H 源寄存器,<存储器地址>
  • STRH指令用于从源寄存器中将一个16位的半字数据传送到存储器中。该半字数据为源寄存器中的低16位。

指令示例:

STRH  R0,[R1]      ;将寄存器R0中的半字数据写入以R1为地址的存储器中。
STRH  R0,[R1,#8]     ;将寄存器R0中的半字数据写入以R1+8为地址的存储器中。

7、【批量数据加载/存储指令指令】
ARM 微处理器所支持批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数据,批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令则完成相反的操作。常用的加载存储指令如下:

LDM    批量数据加载指令
STM    批量数据存储指令

【LDM(或STM)指令】

  • LDM(或STM)指令的格式为:LDM(或STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
  • LDM(或STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈或出栈。其中,{类型}为以下几种情况:
IA 每次传送后地址加1;
IB 每次传送前地址加1;
DA 每次传送后地址减1;
DB 每次传送前地址减1;
FD 满递减堆栈;
ED 空递减堆栈;
FA 满递增堆栈;
EA 空递增堆栈;

{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
{∧}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。
同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。

指令示例:

STMFD  R13!,{R0,R4-R12,LR}  ;将寄存器列表中的寄存器(R0,R4到R12,LR)存入堆栈。
LDMFD  R13!,{R0,R4-R12,PC}  ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)。

五.汇编异常产生指令

1、【SWI指令】

  • SWI 指令的格式为:SWI{条件} 24位的立即数
  • SWI 指令用于产生软件中断,以便用户程序能调用操作系统的系统例程。操作系统在SWI的异常处理程序中提供相应的系统服务,指令中24位的立即数指定用户程序调用系统例程的类型,相关参数通过通用寄存器传递,当指令中24位的立即数被忽略时,用户程序调用系统例程的类型由通用寄存器R0的内容决定,同时,参数通过其他通用寄存器传递。

指令示例:SWI  0x02      ;该指令调用操作系统编号位02的系统例程。

2、【BKPT 指令】

  • BKPT 指令的格式为:BKPT   16位的立即数
  • BKPT 指令产生软件断点中断,可用于程序的调试。

六.汇编伪代码

1.【AREA】一个汇编程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段,因此在汇编程序的开头,我们一般的语句会用到 AREA 。

语法格式:AREA 段名 属性 1 ,属性 2 ,....  

AREA 伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用 “ | ” 括起来,如 |1_test| 。
属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:

— CODE 属性:     用于定义代码段,默认为 READONLY 。            
— DATA 属性:     用于定义数据段,默认为 READWRITE 。      
— READONLY 属性: 指定本段为只读,代码段默认为 READONLY 。          
— READWRITE 属性:指定本段为可读可写,数据段的默认属性为 READWRITE 。  
— ALIGN 属性:    使用方式为ALIGN 表达式。在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为 0 ~31,相应的对齐方式为2表达式次方。
— COMMON 属性:   该属性定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的 COMMON 段共享同一段存储单元。

使用示例:AREA Init , CODE , READONLY ;该伪指令定义了一个代码段,段名为 Init ,属性为只读。 

2、【ALIGN】语法格式:ALIGN { 表达式 { ,偏移量 }}    

ALIGN 伪指令可通过添加填充字节的方式,使当前位置满足一定的对其方式。其中,表达式的值用于指定对齐方式,可能的取值为2的幂,如 1 、2 、4 、8 、16 等。
若未指定表达式,则将当前位置对齐到下一个字的位置。偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2的表达式次幂+偏移量。            

使用示例:

AREA Init,CODE ,READONLY,ALIEN=3;指定后面的指令为 8 字节对齐。      
....
;指令序列  
.... 
END      

3、【CODE16、CODE32】            

  • 语法格式:CODE16 (或 CODE32 )
  • CODE16 伪指令通知编译器,其后的指令序列为 16 位的 Thumb 指令。      
  • CODE32 伪指令通知编译器,其后的指令序列为 32 位的 ARM 指令。            

若在汇编源程序中同时包含 ARM 指令和 Thumb 指令时,可用 CODE16 伪指令通知编译器其后的指令序列为 16 位的 Thumb 指令, CODE32 伪指令通知编译器其后的指令序列为 32 位的 ARM 指令。

因此,在使用 ARM 指令和 Thumb 指令混合编程的代码里,可用这两条伪指令进行切换,但注意他们只通知编译器其后指令的类型,并不能对处理器进行状态的切换。            

使用示例:

AREA Init ,CODE ,READONLY            
....      
CODE32 ;通知编译器其后的指令为 32 位的 ARM 指令            
LDR R0,=NEXT+1 ;将跳转地址放入寄存器 R0      
BX R0 ;程序跳转到新的位置执行,并将处理器切换到 Thumb 工作状态      
....     
CODE16 ;通知编译器其后的指令为 16 位的 Thumb 指令            
NEXT LDR R3,=0x3FF            
....     
END ;程序结束         

4、【ENTRY】            

语法格式:ENTRY      
ENTRY 伪指令用于指定汇编程序的入口点。在一个完整的汇编程序中至少要有一个 ENTRY (也可以有多个,当有多个 ENTRY 时,程序的真正入口点由链接器指定),但在一个源文件里最多只能有一个 ENTRY (可以没有)。            

使用示例:

AREA Init , CODE , READONLY            
ENTRY ;指定应用程序的入口点
.....   

5、【END】            

语法格式:END      

END 伪指令用于通知编译器已经到了源程序的结尾。           

使用示例:

AREA Init , CODE , READONLY            
......     
END ;指定应用程序的结尾

4、ARM 协处理器 指令

  • CDP         协处理器数据操作指令
  • LDC         协处理器数据读取指令
  • STC         协处理器数据写入指令
  • MCR         ARM寄存器到协处理器寄存器的数据传送指令
  • MRC         协处理器寄存器到ARM寄存器的数据传送指令

5、符号定义伪操作:

用于定义 ARM 汇编程序中的变量,对变量进行赋值以及定义寄存器名称

  • GBLA,GBLL及GBLS  声明全局变量
  • LCLA,LCLL及LCLS  声明局部变量
  • SETA,SETL及SETS  给变量赋值
  • RLIST        为通用寄存器列表定义名称
  • CN             为协处理器的寄存器定义名称
  • CP         为协处理器定义名称
  • DN,SN    为VFP的寄存器定义名称
  • FN           为FPA的浮点寄存器定义名称

eg1:

        GBLA objectsize ;        声明一个全局的算术变量        
        objectsize SETA 0xff ;   向该变量赋值为0xff ==> objectsize = 0xff        
        SPACE objectsize ;       引用该变量

eg2:

        局部变量的作用范围为包含该局部变量的宏代码的一个实例。

        MACRO ;               声明一个宏
        $label message $a ;   宏的原型
        LCLS err ;            声明一个局部字符串变量
        err SETS "error no:" ;向该局部变量赋值
        $label ;                 代码
        INFO 0,"err":CC::STR:$a ;使用该串变量
        MEND ;                   宏定义结束 

6、数据定义 伪操作

  • LTORG 声明一个数据缓冲池的开始
  • MAP 定义一个结构化的内存表的首地址
  • FIELD 定义结构化的内存表中的一个数据域
  • SPACE 分配一块内存单元,并用0初始化
  • DCB 分配一段字节的内存单元,并用指定的数据初始化
  • DCD,DCDU 分配一段字的内存单元,并用指定的数据初始化
  • DCDO 分配一段字的内存单元,并将个单元的内容初始化成该单元相对于静态基值寄存器的偏移量
  • DCFD,DCFDU 分配一段双字的内存单元,并用双精度的浮点数据初始化
  • DCFS,DCFSU 分配一段字的内存单元,并用单精度的浮点数据初始化
  • DCI 分配一段字节的内存单元,用指定的数据初始化,指定内存中存放的代码而不是数据
  • DCQ,DCQU  分配一段双字的内存单元,并用指定的数据初始化
  • DCW及DCWU  分配一段半字的内存单元,并用指定的数据初始化
  • DATA 在代码段中使用数据,现在已经不在使用,用于向前兼容

7、汇编控制 伪操作

  • IF, ELSE 及 ENDIF
  • WHILE, WEND
  • MACRO, MEND
  • MEXIT    用于从宏代码中跳转出去

8、各种杂类伪操作

  • CODE16, CODE32 告诉编译器后面的指令序列为 16位 Thumb指令 还是 32位 ARM指令
  • EQU 作为数字常量、基于寄存器的值和程序中的标号(基于PC的值)定义一个字符名称,
            *是EQU的同义词   abcd EQU 2 ;定义abcd符号的值为2,相当于#define
  • AREA 用于定义一个代码段或者数据段
  • ENTRY 伪操作指定程序的入口点
  • END 告诉编译器已经到了源程序结尾
  • ALIGN 通过添加补丁字节使当前位置满足一定的对其方式
  • EXPORT 声明一个符号可以被其他文件引用,相当于声明全局变量,类似GLOBAL
  • IMPORT 告诉编译器当前的符号不是再本源文件中定义的,而是再其他源文件中定义的
  • GET 将一个源文件包含到当前源文件中,并将被包含的文件再其当前位置进行汇编处理,类似 INCLUDE
  • INCBIN 将一个文件包含到当前源文件中,被包含的文件不进行汇编处理
  • KEEP 告诉编译器将局部符号包含在目标文件的符号表中
  • NOFP 禁止源程序中包含浮点运算指令
  • REQUIRE 指定段之间的相互依赖关系
  • RN 用于给一个寄存器定义名称,方便记忆功能
  • ROUT 作用于定义局部变量的有效范围

9、ARM 汇编 伪指令

  • ADR 将基于PC的地址值或基于寄存器的地址值读取到寄存器中。(小范围的地址读取伪指令)
  • ADRL 中等范围的地址读取伪指令
  • LDR 大范围的地址读取伪指令
  • NOP 空操作伪指令

10、ARM 汇编语言 语句格式

格式:{symbol} {instruction | directive | pseudo-instruction} {;comment}

  • instruction 为指令,在 ARM 汇编中,指令不能从一行的行头开始,在一行语句中,指令的前面必须有空格或者符号。
  • directive 为伪操作。
  • pesudo-instruction 为伪指令。
  • symbol 为符号,符号必须从一行的行头开始,并且不能包含空格,在指令和伪指令中符号用作地址标号(label),有些伪操作中符号用作变量或者常量。
  • comment 为注释。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平