ARM汇编(2)(指令)
一,ARM汇编语言立即数的表示方法
十六进制:前缀:0x
十进制:无前缀
二制:前缀:0b
二,常用的ARM指令(标准的ARM语法,GNU的ARM语法)
1、@M开头系列
MOV R0, #12 @R0 = 0XFF。(1)、寄存器或寄存器之间的数据传送 MVF = MOV
MOV R0, R0
MOV R0, R0, LSL#2 @OR =ASL#2, LSR/ASR/ROR/RRX,(2)、移位操作
MVN R0, #4 @数据取反传送 R0 = -5
MVN R0, #0
@MOV R0, #0X88888888 这样是不合法的,mov操作立即数范围为0~512,可以通过使用LDR/STR的伪指令实现将大的立即数或内存写入寄存器,如下
LDR R0, =0X88888888
@MSR CPSR, R0
MSR SPSR, R0
MSR CPSR_flg, R0
MSR CPSR_flg, #1<<28
MUL R0, R1, R2
@MOV PC, R14 @(3)、程序跳转(默认模式为从程序第一句到当前这一句循环执行)
@MOVS PC, R14 @(4)、S->C 拷贝,实现从异常中断返回
@MOVS PC, LR @这三句在这里是等价的
2、ADD、ADDS、ADC、ADCS
ADD指令将<shifter_operand>表示的数据加上寄存器<Rn>的值,将结果保存到目标寄存器<Rd>中,并根据指令的执行结果更新CPSR中相应的条件标志位。
@两个64位数相加
LDR R1, =0X10101010
LDR R2, =0X10010010
LDR R3, =0X20202020
LDR R4, =0X20020020
ADDS R5, R1, R3 @加低位的字
ADCS R6, R2, R4 @加高位的字,带进位
ADC指令常用于实现两个数的相加,无进位时为C=0
3、SUB、SBC、RSB、RSC
SUB指令的作用是将寄存器<Rn>中的数值减去<shifter_operand>所表示的数值,将运算结果保存到目标寄存器<Rd>中,并根据指令的执行结果更新CPSR中的条件标志位。
SUB指令常用于实现两个数的相减,若结果>=0,则C位为1,若结果为0,则Z位为1
SBC指令的作用是将寄存器<Rn>中的值减去<shifter_operand>表示的数值,再减去寄存器CPSR中C条件标志位的反码[NOT (Carry Flag)],将结果保存到目标寄存器<Rd>中,并根据指令的执行结果更新CPSR中相应的条件标志位。
SBC指令常与SUB指令联合使用,可以实现两个64位数的减法。
@减法***************************
SUB R0, R1, R2 @ R0 = R1 - R2 不带借位的减法
SUB R0, R1, #255 @ R0 = R1 - 256
SUB R0, R2, R3, LSL#1 @ R0 = R2 - (R3 << 1)
SBC R0, R1, R2 @ R0 = R1 - R2 -!CARRY 带借位的解减法
RSB R0, R1, R2 @ R0 = R2 - R2 反向减法
RSC R3, R4, R5 @ R3 = R5 - R4 - !CARRY 带借位的反向减法
4、 AND、EOR、ORR、BIC、
*AND指令将<shifter_operand>表示的数值与寄存器<Rd>的值按位做“与”运算,将将结果保存到目标寄存器<Rd>中,同时根据操作的结果更新CPSR寄存器中的条件标志位。
(1) 指令的语法格式
AND{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
(2) 指令举例
AND R0, R0, #0x03 ;R0寄存器中的0、1位不变,其它位清零
AND R2, R1, R3 ;R2 = R1 & R3
ANDS R0, R0, #0x1 ;R0 = R0 & 0x1
*EOR(Exclusive OR)指令将寄存器<Rn>中的值和<shifter_operand>表示的值按位进行“异或”操作,并将执行结果保存中目标寄存器<Rd>中,同时根据指令的执行结果更新CPSR中相应的条件标志位。
(1) 指令的语法格式
EOP{<cond>}{S} <Rd>, <Rn>, <shifter_operand>
(2) EOR指令举例
EOR R0, R0, #3 ;反转R0的第0位和第1位
EOR R1, R1, #0x0F ;将R1的低4位取反
EOR R2, R1, R0 ;R2 = R1 ^ R0
EORS R0, R5, #0x1 ;将R5和0x1进行逻辑异或,结果保存在R0中,并根据执行结果更新标志位。
*ORR(logical OR)为按位或指令,它将第2个源操作数<shifter_operand>表示的值与寄存器<Rn>表示的值按位做“或”操作,结果保存到<Rd>寄存器中。
(1) 指令的语法格式
ORR{条件}{S} <dest>, <op 1>, <op 2>
(2)指令的例子
ORR R0, R0, #3 ;将R0的第1、3位设置成1
ORR R0, R0, #0xFF ;将R0的低4位置1
*BIC(Bit Clear)位清零指令,将寄存器<Rn>的值与第2个源操作数<shifter_operand>的值的反码按位做“逻辑与”运算,结果保存到寄存器<Rd>中
(1) 指令的语法格式
BIC{条件}{S} <Rd>, <op 1>, <op 2>
(2)指令例子
BIC R0, R0, #0x1011 ;清零R0中的第0、1、3位,其余位保持不变
BIC R1, R2, R3 ;将R3的反码与R2进行逻辑与,结果保存到R1中
5、 CMP、CMN、TST、TEQ
*CMP(Compare)指令的实质是使用寄存器<Rn>的值减去<shifter_operand>表示的值,根据操作结果更新CPSR中相应的条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。
(1) 指令的语法格式
CMP{<cond>} <Rn>, <shifter_operand>
(2) CMP使用举例
CMP指令允许把一个寄存器中的数值和另一个寄存器中的数值或立即数进行比较,比较结果将更新状态寄存器CPSR中对应的标志,从而后续的指令可以根据状态寄存器中的标志位有条件的执行。它的实质是进行了一次减法运算,但不保留结果,而是只更改CPSR中的条件标志位,标志位表示的是操作数1与操作数2比较的结果(其值可能大于,小于或等于),比如操作数1大于操作数2,则此后的带有GT后续指令会被执行。
显然,CMP不需要额外再添加S后缀来指示其更改状态标志位。
CMP R1, #10 ;比较R1和立即数10并更新相关的状态标志位
CMP R1, R2 ;比较寄存器R1和R2中的值并设置相关的标志位。
通过上面的例子可以 看出,CMP指令与SUBS指令的区别在于CMP指令并不保留运算结果,在进行两个数大小的比较时,应常用CMP指令及相的条件标志位来确定后续的指令是否执行。
*CMN:(Compare Negative)比较取负的值
CMN{条件}{P} <op 1>, <op 2>
status = op_1 - (- op_2)
CMN 同于 CMP,但它允许你与小负值(操作数 2 的取负的值)进行比较,比如难于用其他方法实现的用于结束列表的 -1。这样与 -1 比较将使用:
CMN R0, #1 ; 把 R0 与 -1 进行比较
*TST(Test)测试指令用于将一个寄存器的值和一个算术值进行逻辑“与”运算,条件标志位根据两个操作数做“逻辑与”的结果设置
(1)TST指令的语法格式
TST {<cond>} <Rn>, <shifter_operand>
(2) TST 指令举例 TST指令类似于CMP指令,也不会把结果保存到目标寄存器,而是在给出的两个操作数上进行与运算,把结果反映在状态寄存器的标志位上。使用TST指令来检查是否设置了特定的位,操作数1是要测试的数据,而操作数2是一个位掩码,经过测试后,如果匹配则设置Zero标志,否则清除它,与CMP指令一样,该指令不需要S后缀。下面的指令用于测试R0中的最低位是否为0
TST R0, #1
*TEQ(Test Equivalence)指令用于将一个寄存器的值和一个算术值做比较,条件标志位根据两个操作数做“逻辑异或”后的结果设置,以便后续的指令根据相应的条件标志位来判断是否执行。比较两个数是否相等不影响V位和C位。
(1) 指令的语法格式
TEQ {<cond>} <Rn>, <shifter_operand>
(2) 指令举例
比较R0和R1是否相等,该指令不影响CPSR中的V位和C位。
TEQ R0, R1
使用TEQ进行相等测试,常与EQ和NE条件码配合使用,当两个数相等时,条件码EQ有效,否则条件码NE有效。
6、跳转指令
跳转指令B与BL都可以使程序跳转到指定的地址执行程序。指令BL的作用是跳转的同时将下一条指令的地址复制到R14(即返回地址连接寄存器LR)寄存器中。需要注意的是,这两条指令和目标地址处的指令都要属于ARM指令集。两条指令都可以根据CPSR中的条件标志位的值决定指令是否执行。
MOVEQ PC, LR
B LAB1
(1)指令格式
B {L} {<cond>} <target_address>
(2)指令的例子
循环10次的例子
MOV R1, #0
BL
MOV R2, #1
......
LAB1:
ADD R1, R1, #1
CMP R1, #10
@带连接的分支
load_new_format:
BL switch_screen_mode
BL get_screen_info
BL load_palette
new_loop:
MOV R1, R5
BL read_byte
CMP R0, #255
BLEQ read_loop
STRB R0, [R2, #1]!
7、Load/Store指令
*LDR指令
(1)指令语法格式
LDR指令用于从内存中将一个32位的字读取到目标寄存器。
指令的编码格式如图所示。
LDR指令编码格式
LDR{<cond>} <Rd>,<addr_mode>
(2)指令举例
LDR r1,[r0,#0x12] ;将r0+12地址处的数据读出,保存到r1中(r0的值不变)
LDR r1,[r0] ;将r0地址处的数据读出,保存到r1中(零偏移)
LDR r1,[r0,r2] ;将r0+r2地址的数据读出,保存到r1中(r0的值不变)
LDR r1,[r0,r2,LSL #2] ;将r0+r2×4地址处的数据读出,保存到r1中(r0,r2的值不变)
LDR Rd,label ;label为程序标号,label必须是当前指令的±4KB范围内
LDR Rd,[Rn],#0x04 ;Rn的值用作传输数据的存储地址。在数据传送后将偏移量0x04与
Rn相加,结果写回到Rn中。Rn不允许是r15
注意:(1)地址对齐问题:大多数情况下,必须保证用于32位传送的地址是32位对齐的。
(2)LDR有两种形式,一种是指令,一种是伪指令,使用LDR的伪指令时,在第二个操作数前加"="
*STR指令用于将一个32位的字写入到指令中指定的内存单元
(1) 指令的语法格式
STR {<cond>} <Rd>, <addr_mode>
(2) 指令举例
LDR/STR指令用于对内存变量的访问、内存缓冲区数据的访问、查表、外围部件的控制操作等。
① 变量访问
NumCount EQU 0x40003000 ;定义变量NumCount
LDR R0,=NumCount ;使用LDR伪指令装载NumCount的地址到R0
LDR R1,[R0] ;取出变量值
ADD R1,R1,#1 ;NumCount=NumCount+1
STR R1,[R0] ;保存变量
8、单数据交换指令
单数据交换指令是Load/Store指令的一种特例,它把一个内存单元中的内容与寄存器中的内容进行交换,交换指令是一个原子操作,也就是说,在连续的总线操作中读/写一个存储单元,在操作期间阻止其他任何指令对该存储单元的读/写。
SWP指令一般有两种形式:
(1), SWP 字交换 tmp=mem32[Rn]; mem32[Rn] = Rm; Rd = tmp
指令的格式:
SWP {<cond>} <Rd>, <Rm>, [<Rn>]
SWP R1, R1, [R0] ;将R1的内容与R0指向的存储单元内容进行交换。
(2), SWPB 字节交换
9、状态寄存器传输指令
ARM指令集提供了两条指令,用于读写程序状态寄存器,MRS指令用于把CPSR或SPSR的值传送到一个寄存器中;MSR相反,把一个寄存器的内容传送到CPSR或SPSR中,这两条指令结合起来,可用于对CPSR和SPSR进行读/写操作。
MRS 把程序状态寄存器的值传送给一个通用寄存器, Rd=SPSR
MSR 把通用寄存器的值传送给程序状态寄存器或把一个立即数传送给程序状态寄存器
(1)MRS指令
在ARM指令集中,只有MRS指令可以 将状态寄存器中的值读取到通用寄存器中。
格式:
MRS {<cond>} Rd, CPSR/SPSR
其中,Rd为目标寄存器,Rd不允许为程序计数器(R15)。
(2) MSR指令
在ARM指令集中,只有MSR指令可以直接设置 状态寄存器的值
格式:
MSR {<cond>} SPSR/CPSR , #immed
Msr {<cond>} CPSR/SPSR , Rm
3,LDM和STM的配对规则
LDMFD--STMFD
LDMED--STMED
LDMFA--STMFA
LDMEA--STMEA
LDMIA--STMDB
LDMIB--STMDA
LDMDA--STMIB
LDMDB--STMIA
指令代码如下:
.global _start
_start:
.if 0
@M开头系列
MOV R0, #12 @R0 = 0XFF。(1)、寄存器或寄存器之间的数据传送 MVF = MOV
MOV R0, R0
MOV R0, R0, LSL#2 @OR =ASL#2, LSR/ASR/ROR/RRX,(2)、移位操作
MVN R0, #4 @数据取反传送 R0 = -5
MVN R0, #0
@MOV R0, #0X88888888 这样是不合法的,mov操作立即数范围为0~512,可以通过使用LDR/STR的伪指令实现将大的立即数或内存写入寄存器,如下
LDR R0, =0X88888888
@MSR CPSR, R0
MSR SPSR, R0
MSR CPSR_flg, R0
MSR CPSR_flg, #1<<28
MUL R0, R1, R2
@MOV PC, R14 @(3)、程序跳转(默认模式为从程序第一句到当前这一句循环执行)
@MOVS PC, R14 @(4)、S->C 拷贝,实现从异常中断返回
@MOVS PC, LR @这三句在这里是等价的
.endif
@加减乘
@加法***************************
MOV R1, #24
ADD R0, R1, R2 @不带进位的加法
ADD R0, R1, #256
ADD R0, R2, R1, LSL#1
ADC R0, R1, R3 @带进位的加法
@两个64位数相加
LDR R1, =0X10101010
LDR R2, =0X10010010
LDR R3, =0X20202020
LDR R4, =0X20020020
ADDS R5, R1, R3 @加低位的字
ADCS R6, R2, R4 @加高位的字,带进位
@减法***************************
SUB R0, R1, R2 @ R0 = R1 - R2 不带借位的减法
SUB R0, R1, #255 @ R0 = R1 - 256
SUB R0, R2, R3, LSL#1 @ R0 = R2 - (R3 << 1)
SBC R0, R1, R2 @ R0 = R1 - R2 -!CARRY 带借位的解减法
RSB R0, R1, R2 @ R0 = R2 - R2 反向减法
RSC R3, R4, R5 @ R3 = R5 - R4 - !CARRY 带借位的反向减法
@按位与、按位或、按位异或
@按位与*************************
AND R0, R0, #0X03 @R0 = 保持R0的位0和1不变,其余位归零
AND R0, R1, R2 @R0 = R1 & R2
ANDS R0, R0, #0X03 @R0 = R0 & 0X03
@按位或*************************
LDR R0, =0X00000000
ORR R0, R0, #3 @将R0的第1、3位设置成1
LDR R0, =0X00000000
ORR R0, R0, #0xFF @将R0的低16位置1
@按位异或***********************
EOR R0, R0, #3 @反转R0的第0位和第1位
EOR R1, R1, #0x0F @将R1的低4位取反
EOR R2, R1, R0 @R2 = R1 ^ R0
EORS R0, R5, #0x1 @将R5和0x1进行逻辑异或,结果保存在R0中,并根据执行结果更新标志位。
@位清除*************************
@BIC R0, R0, #%1011 @清除 R0 中的位 0、1、和 3。保持其余的不变。
BIC R0, R0, #11 @清零R0中的第0、1、3位,其余位保持不变
BIC R1, R2, R3 @将R3的反码与R2进行逻辑与,结果保存到R1中
@比较、测试***************************
CMP R1, #10 @比较R1和立即数10并更新相关的状态标志位,CMP不需要额外再添加S后缀来指示其更改状态标志位。
CMP R1, R2 @比较寄存器R1和R2中的值并设置相关的标志位。常用CMP指令及相的条件标志位来确定后续的指令是否执行。
CMN R0, #1 @把 R0 与 -1 进行比较
TST R0, #1 @测试R0中的最低位是否为0
TEQ R0, R1 @比较R0和R1是否相等,该指令不影响CPSR中的V位和C位。
@分支与带连接的分支B/BL***************
@循环10次
MOV R1, #0
MOV R2, #1
LAB1:
ADD R1, R1, #1
CMP R1, #10
MOVEQ PC, LR
B LAB1
@带连接的分支
load_new_format:
BL switch_screen_mode
BL get_screen_info
BL load_palette
new_loop:
MOV R1, R5
BL read_byte
CMP R0, #255
BLEQ read_loop
STRB R0, [R2, #1]!
@LOAD/STORE
LDR r1, [r0, #0x12] @将r0+12地址处的数据读出,保存到r1中(r0的值不变)
LDR r1, [r0] @将r0地址处的数据读出,保存到r1中(零偏移)
LDR r1, [r0, r2] @将r0+r2地址的数据读出,保存到r1中(r0的值不变)
LDR r1, [r0, r2, LSL #2] @将r0+r2×4地址处的数据读出,保存到r1中(r0,r2的值不变)
LDR R1, label @label为程序标号,label必须是当前指令的±4KB范围内
LDR R1, [R2], #0x04 @R2的值用作传输数据的存储地址。在数据传送后将偏移量0x04与R2相加,结果写回到R2中。R2不允许是r15
abcd EQU 2 @定义abcd符号的值为2
abcd EQU label+16 @定义abcd符号的值为(label+16)
abcd EQU 0x1c,CODE32 @定义abcd符号的值为绝对地址值0x1c,而且此处为ARM指令
NumCount EQUD 0x40003000, code32 @定义变量NumCount
LDR R0, =NumCount @使用LDR伪指令装载NumCount的地址到R0
LDR R1, [R0] @取出变量值
ADD R1, R1, #1 @NumCount=NumCount+1
STR R1, [R0] @保存变量
SWP R1, R1, [R0] @将R1的内容与R0指向的存储单元内容进行交换
MRS R1, CPSR @其中,Rd为目标寄存器,Rd不允许为程序计数器(R15)。
MSR SPSR, #0X12
Msr CPSR, R2
.end
备注:
一,ARM中的注释
1,"@"符号作为注释可以放在语句的开始处
2,";"作为 流程只能放在语句的末尾
二,指令与伪指令
1,指令有对应的机器码,CPU可以直接识别并执行。
2,伪指令,没有对应的机器码,它需要经过编译器翻译成指令才能被CPU识别和执行。
ldr r1, =0xfff //伪指令
.text
.global _start
_start:
@这是一条伪指令,没有对应的机器码,被编译器翻译为LDR R0,[PC,#0x0008]
LDR R0, =var ; int * R0 = var
@这是一条指令
LDR R1, [R0] ; int R1 = *R0
@指令有对应的机器码,编译器原样执行
MOV R1, R0
NOP
NOP
.data
var: int * var; *var = 0x8;
.word 0x8
.end
GNU ARM汇编器的伪操作
一,符号定义伪操作
1,.global
用于声明一个ARM程序中的全局变量,使得被声明的符号在整个程序中可见,变成整个工程中都可以使用的全局变量。
用法举例://声明_start为一个全局的符号
.global _start
2,.local
用于声明一个ARM程序中的局部变量,这样它对外部是不可见的,作用域是在本文件范围内
用法举例://声明_label为一个只可在本文件范围内使用的符号
.local _label
2,.set
用于给一个全局变量或局部变量赋值
用法举例:
.set _label, 0x10 //给变量_label赋值为0x10
3, .equ
用于给一个变量赋值
用法:
.equ _label, 0x10
二,数据定义伪操作
数据定义伪操作一般用于为特定的数据分配内存单元,同时对该内存单元中的数据进行初始化,觉的数据定义伪操作有:
1, .byte
在存储器中分配 一个字节的内存单元,用指定的数据对该存储单元进行初始化
_label:
.byte 0x1
在当前地址分配一个字节的存储单元,将将其初始化为1,类似于C语言中的char _label = 1.
2, .short
在存储器中分配2个字节的内存单元,并用指定的数据对该存储单元进行初始化,用于与.byte类似
3,.word
在存储器中分配4个字节的内存单元,并用指定的数据对该存储单元进行初始化,用于与.byte类似
4, .long
与.word的功能相同
5, .quad
.quad的功能是在内存中分配8个字节的存储单元,并用指定的数据对该存储单元进行初始化。
6,.float
在存储器中分配4个字节的存储空间,并用指定的浮点数据对该空间进行初始化。
7, .space
.space伪操作用于分配一片连续的内存区域,并将其初始化为指定的值,如果后面的填充值省略不写,则默认在后面填充0
8, .skip
等同与.space
9, .string, ascii, .asciz
这3条伪操作的功能都是定义一个字符串:
用法:
_label:
.string "Hello, World!"
10, .rept
.rept伪操作功能是 重复执行后面的指令,以.rept开始,并以.endr结束
用法:
.rept 3
add r1, r1, #1
.endr
三,汇编控制伪操作
1,
.if, .else, .endif
四,杂项操作伪指令
GNU汇编中还有一些其他的伪操作,在汇编程序中经常会使用到它们,包括而在这些:
1,.align
.align伪操作可通过添加填充字节的试,使当前位置满足指定的对齐方式
举例:
.align 2
.string "abcde"
声明后面的字符串的对齐方式是4(2的2次方)字节对齐,这个字符串会占用8个字节的存储空间。
2, .section
.section伪操作用于定义一个段,一个GNU的源程序至少需要一个段,大的程序可以包含多个代码段和数据段。
可能用来定义自定义段
3, .data
.data伪操作用来定义一个数据段
4, .text
.text伪操作用来定义一个数据段
5, .include
.include 伪操作用来包含一个头文件。
6, .extern
.extern用于声明一个外部符号,即告诉编译器当前符号不是在本源文件中定义的,而是在其它源文件中定义的,当前文件需要引用这个符号。
7, .weak
.weak用来声明一个符号是弱符号,即如果这个符号没有定义,编译器就会忽略,不会报错。
8, .end
.end代表程序的结束位置