一.指令格式
<opcode>{<cond>}{S} {Rd}, {Rn}, {shifer_operand}
<opcode>: 指令操作符编码,如 LDR、STR 等。
{<cond>}: 指令执行的条件编码,如 EQ、NE 等。
{S}: 决定指令的执行是否影响 CPRS 的值。
{Rd}: 目标寄存器编码。
{Rn}: 包含第一个源操作数的寄存器编码。
{{shifer_operand}: 第二个操作数
以三星2440为例,一共支持以下汇编指令
二.条件字段
在ARM状态,所有的指令都可以按照CPSR状态码和指令条件字段的状态来有条件地执行。此字段(位[31:28])确定了在什么情况下哪一个指令被执行。如果C,N,Z和V标志位的状态符合字段的条件码,将执行指令,否则忽略不执行。
有16种可能的条件,每种表示为在指令助记符后附加两个字符后缀。例如,一个分支(汇编语言中的B)跳转指令变成BEQ为“如果相等则分支跳转”,这意味着只有Z标志位被置位了才会执行分支跳转。
在实际应用当中,将会使用到15种不同的条件:如表所列,保留第16种(1111),并且一定不要使用
三.寻址方式
汇编的取地址方式大概分为几种:
立即寻址,寄存器直接寻址,寄存器间接寻址,基地址寻址,相对寻址,堆栈寻址,多寄存器存址
立即寻址
立即寻址是一种特殊的寻址方式,操作数本身包含在指令中,只要取出指令也就取到了操作数。这个操作数叫做立即数,对应的寻址方式叫做立即寻址。例如:
MOVR0,#64 ;R0 ← 64
ADD R0, R0, #1 ; R0 ← R0 + 1
SUB R0, R0, #0X3D ; R0 ← R0 – 0X3D
在立即数寻址中,要求立即数以“#”为前缀
寄存器直接寻址
寄存器寻址就是利用寄存器中的数值作为操作数,也称为寄存器直接寻址。
例如:ADD R0,R1, R2 ;R0 ← R1 + R2
该指令的执行效果是将寄存器R1和R2的内容相加,其结果存放在寄存器R0中。
这种寻址方式是各类微处理器经常采用的一种方式,也是执行效率较高的寻址方式。
寄存器间接寻址
寄存器间接寻址就是把寄存器中的值作为地址,再通过这个地址去取得操作数,操作数本身存放在存储器中。例如:
LDR R0,[R1]
;R0 ←[R1],以寄存器R1的值作为操作数的地址,把取得操作数传送到R0中
假设R1的值为0x54000000,那么此语句的作用是把0x54000000地址的值取出来付给R0
ADD R0,R1,[R2]
;R0 ←R1 + [R2],以寄存器R2的值作为操作数的地址,取得操作数后与R1相加,结果存入寄存器R0中。
基地址寻址
寄存器基址变址寻址又称为基址变址寻址,它是在寄存器间接寻址的基础上扩展来的。它将寄存器(该寄存器一般称作基址寄存器)中的值与指令中给出的地址偏移量相加,从而得到一个地址,通过这个地址取得操作数。
例如:
LDR R0,[R1,#4]
;R0 ←[R1 + 4],将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中。
LDR R0,[R1,#4]!
;R0 ←[R1 + 4]、R1 ←R1 + 4,将R1的内容加上4形成操作数的地址,取得的操作数存入寄存器R0中,然后,R1的内容自增4个字节。其中!表示指令执行完毕把最后的数据地址写到R1。
LDR R0,[R1,R2]
;R0 ←[R1 + R2],将寄存器R1的内容加上寄存器R2的内容形成操作数的地址,取得的操作数存入寄存器R0中。
STR R0, [R1,#-4]
;R0→[R1 -4],将R1中的数值减4作为地址,把R0中的数据存放到这个地址中。
LDR R0,[R1],#4 ;R0 ←[R1]、R1 ←R1+4
相对寻址
相对寻址是一种特殊的基址寻址,特殊性是它把程序计数器PC中的当前值作为基地址,语句中的地址标号作为偏移量,将两者相加之后得到操作数的地址。
BL NEXT ;相对寻址,跳转到NEXT处执行。
……
……
NEXT
……
堆栈寻址
堆栈是一种数据结构,按先进后出(First InLast Out,FILO)的方式工作,使用堆栈指针(StackPointer, SP)指示当前的操作位置,堆栈指针总是指向栈顶。
根据堆栈的生成方式不同,可以把堆栈分为递增堆栈和递减堆栈两种类型。
l 递增堆栈:向堆栈写入数据时,堆栈由低地址向高地址生长。
l 递减堆栈:向堆栈写入数据时,堆栈由高地址向低地址生长。
同时,根据堆栈指针(SP)指向的位置,又可以把堆栈分为满堆栈(Full Stack)和空堆栈(Empty Stack)两种类型。
l 满堆栈(Full Stack):堆栈指针指向最后压入堆栈的数据。满堆栈在向堆栈存放数据时的操作是先移动SP指针,然后存放数据。在从堆栈取数据时,先取出数据,随后移动SP指针。这样保证了SP一直指向有效的数据。
l 空堆栈(Empty Stack):堆栈指针SP指向下一个将要放入数据的空位置。空堆栈在向堆栈存放数据时的操作是先放数据,然后移动SP指针。在从堆栈取数据时,是先移动指针,再取数据。这种操作方式保证了堆栈指针一直指向一个空地址(没有有效数据的地址)。
上述两种堆栈类型的组合,可以得到四种基本的堆栈类型,即:
· 满递增堆栈(FA):堆栈指针指向最后压入的数据,且由低地址向高地址生长。
· 满递减堆栈(FD):堆栈指针指向最后压入的数据,且由高地址向低地址生长。
· 空递增堆栈(EA):堆栈指针指向下一个将要压入数据的地址,且由低地址向高地址生长。
· 空递减堆栈(ED):堆栈指针指向下一个将要压入数据的地址,且由高地址向低地址生长。
堆栈寻址举例如下:
STMFD SP!,{R1-R7, LR}
STM: Store Data From register toStack (Memory).
;将R1-R7, LR压入堆栈。满递减堆栈。
LDMED SP!,{R1-R7, LR}
;将堆栈中的数据取回到R1-R7, LR寄存器。空递减堆栈。
多寄存器存址
多寄存器寻址用于寄存器数据的批量复制,它实现从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器传送数据。块拷贝寻址与堆栈寻址有所类似。两者的区别在于:堆栈寻址中数据的存取是面向堆栈的,块拷贝寻址中数据的存取是面向寄存器指向的存储单元的。
在块拷贝寻址方式中,基址寄存器传送一个数据后有4种增长方式,即:
1. IA:每次传送后地址增加4;(IncrementAfter Operating)
2. IB:每次传送前的地址增加4;(IncrementBefore Operating)
3. DA:每次传送后地址减少4;(DecrementAfter Operating)
4. DB:每次传送前地址减少4。(DecrementBefore Operating)
对于32位的ARM指令,每次地址的增加和减少的单位都是4 个字节单位。
例如:
STMIA R0!,{R1—R7}
;将R1-R7的数据保存到R0指向的存储器中,存储器指针在保存第一个值之后增加4,向上增长。R0作为基址寄存器。
STMIB R0!,{R1—R7}
;将R1-R7的数据保存到存储器中,存储器指针在保存第一个值之前增加4,向上增长。R0作为基址寄存器。
STMDA R0!,{R1—R7}
;将R1-R7的数据保存到R0指向的存储器中,存储器指针在保存第一个值之后减少4,向下减少。R0作为基址寄存器。
STMDB R0!,{R1—R7}
;将R1-R7的数据保存到存储器中,存储器指针在保存第一个值之前减少4,向下减少。R0作为基址寄存器。
ARM指令中{!},为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
{^}为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的数据传送之外,还将SPSR复制到CPSR。同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
LDMIA R0,{R1,R2,R3,R4}
与LDM IA R0!,{R1,R2,R3,R4}的区别?
前一条指令,执行完毕之后,R0的值保持不变;后一条指令执行完毕之后,R0的值发生了变化。
四.ARM指令大全
数据操作指令
格式:mov{<cond>}{S} {Rd}, {Rn}, {shifer_operand}
作用:
1.将数据从一个寄存器传送到另一个寄存器MOV R0,R1
2.将一个常数值传送到寄存器MOV R0,#3
3.实现无算数和逻辑运算的单纯移位操作,MOV R0,R0,LSL#3
4.当PC用作目的寄存器的时,实现程序的跳转,MOV PC,LR
5.PC作为目标寄存器指令中S被设置时候,MOVS PC,LR,可以实现某些从某些异常中断返回。
和mov指令相似,只不过传送的是操作数的反码
作用:
1. 向寄存器传送一个负数
2. 生成位掩码
3. 求一个数的反码
举例:MVN R0,#4;
用二进制表示可以00100,取反得11011,第一个1是符号位,结果是-5.
格式AND{<cond>}{S} {Rd}, {Rn}, {shifer_operand}
ORR{<cond>}{S} {Rd}, {Rn}, {shifer_operand}
AND表示shifter_operand 表示的数值和Rn值按位与,把结果保存在Rd.
ORR表示shifter_operand 表示的数值和Rn值按位或,把结果保存在Rd.
举例:AND R0,R1,R2 的效果就是类似于C语言的R0=R1&R2
ORR R0,R1,R2 的效果就是类似于C语言的R0=R1|R2
同上,按位异或
同上,相减。
从shifter_operand中减去Rn表示的数值,并将结果保存在目标寄存器Rd里面。
举例:求一个64位数的负数,64位数放在寄存器R0和R1,其负数放在R2,R3,其中R0和R2放低32位的值。
RSBS R2,R0,#0;
RSC R3,R1,#0;
假设一个64位数表示的是1,R0:31位是0,最低位是1,R1全都是0,第一个指令操作后,用0减去1,后缀有S,最终导致 R2所有的位都是1,并且CPSR里面C位变成了0,第二个指令是RSC这里表示是带借位的翻转减,所以,R3=0-R1+ C-1导致R3最终里面的书全部都是1,把R2和R3结合起来当成一个有符号数计算结果是-1.
加指令,例如:ADD R0,R1,R2. ADD R0,R1,R3,LSL#1
Rd=Rn+op2+C
带进位的加,shifter_operand的值+Rn表示数值再加上CPSR为的C的值,将结果保存到寄存器Rd。
举例相加两个128位的数:
第一个128位的数,R4,R5,R6,R7,第二个128位的数:R8,R9,R10,R11;
ADDS R0,R4,R8;
ADCS R1,R5,R9,
ADCS R2,R6,R10
ADCS R3,R7,R11
Rd=Rn-op1+c-1
用于执行32位的指令减法操作,代表位的减。
例子:
实现64位的减法
SUBS R0,R0,R2
SBCS R1,R1,R3
带进位的翻转减,Rd=op2-Rn+C-1.
格式TST {<cond>}<Rn>,<shifter_operand>,用于一个寄存器的值和一个操作数进行比较忙,条件标志位根据两个操作数做“逻辑与”运算后设置。TST指令类似与CMP指令,不产生放置目标寄存器的结果,而是给出的两个操作数上进行操作并把结果反应在状态标志上,使用TST指令来检查是否设置了特定的位。
操作数2当成一个位掩码,测试后如果结果匹配设置zero标志位,否则清除它。这个指令不用制定S后缀。
例子:
TST R0,#1,检测R0里面是否设置了位0.
用于将一个寄存器的值和一个算数值做比较,条件码根据两个数做逻辑异或后的结果进行设置,一变后面的指令根据相应的条件标志位判断是否执行。
CMP {<cond>}<Rn>,<shifter_operand>.
用法用Rn-shifter_operand根据结果更新CPSR相应的条件标志位来运行进行条件执行,它虽然进行一次减法运算但是不储存运算结果,而是正确的更改标志位,所以和SUBS
指令是不一样的,标志位表示的是运算结果。大于小于等于都是可能的,如果大于,后面的后GT后缀的指令会执行。
CMN {<cond>} <Rn>,<shifter_operand>
根据Rn+shifter_operand的结果设置标志位,和CMP操作对CPSR条件标志位有细微影响。
当第二个操作数是0或者0x80000000时候,结果不同。
例如CMP Rn,#0,CMN Rn,#0 第一个指令使得标志位C值为1,第二条指令使得标志位C取0.
BIC {<cond>}{S}<Rd>,<Rn>,<shifter_operand>
将寄存器Rn的值和第二个源操作数shifter _operand的值的反码按位取“逻辑与操作”运算结果保存到Rd里面。
BIC R0,R0,0xb,清除0,1,3位,其余不变。就是指定哪个位为1,把那个位清零。
乘法指令
arm乘法指令完成两个数据的运算,两个32位的二进制数相乘的结果是64位的积,有些arm版本将乘积的结果保存在两个独立的寄存器,有些只把最低有效32位存放在一个寄存器对于只保存32位的乘法指令,不需要区分有符号还是无符号。
MUL{<cond>}{S} <Rd>,<Rm>,<Rs>
32位乘法指令将Rm和Rs相乘,结果的最低32位保存在Rd。
MLA{<cond>}{S} <Rd>,<Rm>,<Rs>,<Rn>
32位乘-累加指令将Rm和Rs中的值相加,在将乘积加上第三个操作数,结果的低32位保存在Rd里面。
UMULL{<cond>}{S}<RdLo>,<RdHi>,<Rm>,<Rs>
64位无符号乘法指令,将Rm和Rs的值做无符号数相乘,结果的低32位保存在RsLo里面,高32位保存在RdHi里面。
4.UMLAL指令
UMLAL{<cond>}{S}<RdLo>,<RdHi>,<Rm>,<Rs>
64位无符号长乘累加指令,先相乘然后把这个64位数和RdHi,RdLo相加,其他和上面一样。
64位有符号长乘指令,参考UMULL。
64位有符号数长乘累加指令,参考UMLAL
单寄存器Load/Store指令
从内存中讲一个32位的字读取到目标寄存器。
例如:LDR R1,[R2]
将一个32位的字数据写入内存。
用途:变量访问,GPIO设置,程序散转
B表示操作的是最后一个字节,H表示的是低16位,没有后缀表示32位
多寄存器load/store访问指令
将数据从连续的内存单元中读取到指令中指定的寄存器列表中的各寄存器,当PC包含在寄存器列表时候,指令从内存读取的字数据被作为目标地址值,指令执行后程序从目标地址处开始执行,实现了指令的跳转。寄存器R0~R15分别对应指令编码的bit ['0]~bit[15]位,如果Ri存在寄存器列表,相应的位为1,否则为0,LDM指令将数据从连续的内存单元读取到指令中指定的寄存器列表中各寄存器。
LDM{<cond>}<addressing_mode><Rn>{!},<registers>
LDM{<cond>}<addressing_mode><Rn><registers_without_pc>
STM指令将指令中寄存器列表的各寄存器数值写入到连续的内存单元,主要用于块数据的写入,数据栈操作及子程序进入时保存相关寄存器等操作。
STM{<cond>} <addressing_mode><Rn>{!},<registers>
STM{<cond>} <addressing_mode><Rn>,<registers>^
3.数据传送指令应用
LDM/STM批量加载/存储指令可以实现在一组寄存器和一块连续的内存单元之间传输数据,LDM为加载多个寄存器,STM为存储多个寄存器,允许一条指令传送16个寄存器的任何子集或所有的寄存器。
LDM{cond}<模式> Rn{!},regist{^}
STM{cond}<模式> Rn{!},regist{^}
模式在堆栈操作寻址方式和批量load/store存储方式可以看到。
注意:
- Rn不允许是R15,Rn是基址寄存器,装有传送数据的初始地址。
- “!”表示最后的地址要写会Rn
- 寄存器列表regist可以包含一个或者寄存器范围,用“,”分开,范围用“~”表示,寄存器按照大小排列
- “^"后缀不允许在用户模式下使用,只能在系统模式下使用"^"进行数据传送且寄存器列表不包含PC时候,加载存储的是用户模式寄存器而不是当前模式寄存器。
- LDM指令的寄存器列表包含有pc时使用,除了正常的多寄存器传送外,将SPSR赋值到CPSR,用于异常处理返回
这类指令经常见到,如下:
LDMIA R0!,{R3~R9} ;加载R0指向的地址上的多字数据,保存到R3~R9里面。R0值更新。
STMIA R1!,{R3,R9} ;将R3~R9的数据存储到R1指向的地址上,R1值更新。
STMFD SP!,{R0~R7,LR}现场保存,将R0~R7,LR入栈。
LDMFD SP!,{R0~R7,PC}^;恢复现场,异常处理返回;
单数据交换指令
SWP{<cond>}<Rd>,<Rm>,[<Rn>]
假设内存单元地址存放在寄存器Rn里面,指令将<Rn>里面的数据读取到目的寄存器Rd,同时另一个寄存器<Rm>的内容写入到内存单元。当Rd和Rm是同一个寄存器时候指令交换该寄存器和内存单元的内容。
跳转指令
B{L}{<cond>}<target_address>
此种命令是为止无关码
跳转B和跳转连接BL指令是改变指令执行顺序的标准方式调用函数的时候会用到。
跳转指令可以使得程序转到制定的地址执行程序,带连接的跳转指令BL将下一条指令的地址复制到R14,这两条指令都可以根据CPSR中的条件标志位的值决定指令是否执行。
BL指令用于实现子程序的调用,子程序的返回可以通过将LR寄存器的值复制到PC来实现实现程序返回的指令
- BX R14
- MOV PC,R14
- 在子程序入口处使用压栈指令:STMFD R13,{<registers>,R14}
LDMFD R13!,{<registers>,PC},把子程序放入PC中
程序实例:
B LABLE; 跳转到LABEL
ADD R1,R2,#4;
LABLE:
SUB R1,R2,#8
B 0x1234 跳转到绝对地址0x1234
BCC LABLE 当CPSR寄存器的C条件标志位为1时候,程序跳转到标号LABLE出
带状态切换的跳转指令,使程序跳转到指令中指定的参数Rm指定的地址执行程序,Rm的第0位赋值到CPSR中T位,bit[31:1]移入PC,若Rm的bit[0]为1,则自动将CPSR中标志位T置位,把目标代码解释为thumb代码,若Rm的bit[0]为0,则跳转时自动将CPSR中的标志位T复位,把目标代码解释为ARM代码。(在arm和thumb状态间切换)
指令语法格式:BX{<cond>}<Rm>
当Rm[1:0]=0b10时,之类的执行结果不可预知,因为ARM状态下,指令是4字节对齐的
PC可以作为Rm寄存器使用,当PC作为<Rm>使用时,BX PC将程序跳转到下面的第二条指令处执行。
BX R0;转移到R0中地址,如果R0[0]=1,进入Thumb状态
带连接和状态切换的跳转指令,用于程序跳转到Thumb状态或者从Thumb状态。该指令为无条件执行指令,并用分支寄存器的最低位来更新CPSR中的T位,将返回地址写入到
连接寄存器LR。
状态操作指令
格式 MRS {cond}Rd,其中为目标寄存器,Rd不允许为程序计数器,PSR为CPSR或SPSR。在ARM处理器中,只有MRS指令可以将状态寄存器CPSR活SPSR读出到通用寄存其中。
MRS R1,CPSR;将CPSR状态寄存器读取,保存到R1
MRS,R2,SPSR ;将SPSR状态寄存器读取,保存到R2
这个指令可以判断里面的标志为,根据标志位的情况判断相应的状态和操作是否允许,进程切换或允许异常中断嵌套时,也需要MRS指令读取SPSR状态值并保存起来。
这是唯一一个可以直接设置状态寄存器CPSR或者SPSR的指令
MSR {cond}PSR_field,#immed_8r
MSR {cond}PSR_filed,Rm
其中PSR是指CPSR或者SPSR ,<fields>设置状态寄存器要操作的位,状态寄存器的32位分成4个8位的域,见到很多次
- bits[31:24]为条件标志域,用f表示
- bits[23:16]为状态标志域,用s表示
- bits[15:8]为扩展域,用x表示
- bits[7:0]为控制域,用c表示
只有在特权模式下才可以修改状态寄存器
举例:MSR_C,#0xD3,切换到管理模式
程序里不能通过MSR指令修改CPSR里T位控制实现ARM状态/Thumb状态的切换,必须使用BX指令完成处理器的状态切换,BX指令属于转移指令,会打断流水线的状态,实现处理器的状态切换,MRS与MSR配合使用,实现PSR读-修改-写操作,用来进行处理器模式的切换及允许/禁止IRQ/FIQ中断设置等。
程序状态寄存器指令应用:
ENABLE_IRQ: 使能IRQ中断
MRS R0,CPSR
BIC R0,R0,0x80
MSR CPSR_c,R0
MOV PC,LR
SWI软件中断指令
作用产生软中断,实现用户模式到管理模式(感觉重要)。
SWI {<cond>} <immed_24>
SWI 0;产生软中断,中断立即数是0
使用SWI指令时,传递参数的方法
- 指令的操作数是个24位的立即数,指定了用户请求类型,中断服务程序的参数通过寄存器传递。
MOV R0,#34 设置功能号34
SWI 12 产生软中断,中断号12
- 指令中24位数被忽略,用户请求的服务类型由寄存器R0值决定,
MOV R0,#12;设置12号软中断
MOV R1,#34;设置功能号34
SWI R0;
ARM支持的伪指令
1.ADR伪指令
ADR {cond}{.w} register,label
ADR小范围地址读取伪指令。
cond可选执行条件,W指定指令宽度,Thumb-2指令集支持,register,寄存器,label基于PC或者具体寄存器的表达式,这个指令可以使用add指令或者sub指令来实现功能。
2.ADRL伪指令
和ADR相似,只是范围更大,真正被编译成两条指令,所以范围大。
3.LDR伪指令
装载一个32位常数到寄存器
LDR{cond}{.W} register,=[expr|label-expr]
expr:32位常量表达式
label-exper是一个程序相关或声明为外部的表达式,汇编器将label-expr放入数据缓冲池,使一条程序相关的LDR指令将此值取出放入寄存器。
label-expr被声明为外部表达式,汇编器在目标文件插入链接重定位伪操作,由链接器在链接时生成地址。
当数据超过MOV和MVN范围时可以用这个指令,传输的是绝对地址。
GUN ARM支持的伪操作
1.数据定义伪操作
用于为特定的数据分配单元,也可以完成已经分配的单元的初始化。常见的有
.byte .short .long .float .string .asciz .sacii .rept .quad
.quad 定义八个字节的数据
.string和.asciz和.sacii一样都是定义多个字符串,.ascii要加‘\0'
.rept 重复定义伪操作
.equ/.set赋值语句,
2.汇编控制伪操作
.if .else .endif 根据条件成立选择执行某个序列,和C语言一样
.macro 伪操作可以将一段代码定义为一个整体,成为宏指令,然后在程序通过宏指令多次调用这段代码。常用语代码较短但是参数较多情况,在.macro和.endm之间的指令序列称为宏定义体。