ARM指令集特点
计算机的指令集一般可分为4种:
复杂指令集(CISC)
精简指令集(RISC)
显式并行指令集(EPIC)
超长指令字指令集(VLIW)
ARM指令集属于RISC,RISC相对于CISC指令集,主要有以下特点:
●Load/Store架构,CPU不能直接处理内存中的数据,要先将内存中的数据Load(加载)到寄存器中才能操作,然后将处理结果Store(存储)到内存中。
● 固定的指令长度、单周期指令。
● 倾向于使用更多的寄存器来存储数据,而不是使用内存中的堆栈,效率更高。
和原汁原味的RISC相比,ARM也存在一些差异:
● ARM有桶型移位寄存器,单周期内可以完成数据的各种移位操作。
● 并不是所有的ARM指令都是单周期的。
● ARM有16位的Thumb指令集,是32位ARM指令集的压缩形式,提高了代码密度。
● 条件执行:通过指令组合,减少了分支指令数目,提高了代码密度。
● 增加了DSP、SIMD/NEON等指令。
ARM工作模式
ARM处理器有多种工作模式
应用程序正常运行时,ARM处理器工作在用户模式(User mode),用户模式属于普通模式,没有权限对内存和底层硬件进行操作,应用程序如果要读写磁盘上的音频数据,驱动声卡播放音乐,往屏幕写数据显示歌词,则要首先通过系统调用或软中断进入处理器特权模式,运行操作系统内核或硬件驱动代码,才能对底层的硬件设备进行读写操作。
当程序运行出错或有中断发生时,ARM处理器就会切换到对应的特权工作模式。有些特权指令是运行不了的,需要切换到特权模式下才能运行。在ARM处理器中,除了用户模式是普通模式,剩下的几种工作模式都属于特权模式。
ARM寄存器
ARM32
ARM32 处理器共有37个寄存器,所有这些寄存器都是32位的。
31 个通用寄存器,包括一个程序计数器寄存器
6 个状态寄存器
所有的寄存器编排有重叠的分组,有当前的处理器模式决定使用哪一个分组。在任何时候,15个通用寄存器
(R0 ~ R14),一个或两个状态寄存器和程序计数器是可见的。如下表所示,每一列显示在指定处理器模式下的那些通用寄存器和状态寄存器是可见的。
通用寄存器:通用寄存器(R0 ~ R15)可以分为3组
1,未分组的寄存器R0 ~ R7
2,分组的寄存器R8 ~ R14
3,程序计数器寄存器 PC
未分组的寄存器R0 ~ R7
寄存器R0 ~ R7是未分组的寄存器。它们中的每一个在所有的处理器模式下都是相同的32位物理寄存器。它
们是完全的通用寄存器,没有被处理器架构定义的特殊用途。
分组的寄存器R8 ~ R14
分组的寄存器R8 ~ R14,它们中的每一个代表的物理寄存器依赖与当前的处理器模式。当使用一个通用寄存
器时,几乎所有的指令都可以使用这些分组寄存器。
寄存器R8 ~ R12中的每一个都有两个分组物理寄存器:一组用于FIQ模式,另一组用于其他处理器模式。第
一组可以用R8_fiq ~ R14_fiq表示,另一组用R8_usr ~ R14_usr表示。
寄存器R8 ~ R12在体系中没有任何指定的特殊目的。然而,在简单的中断处理中只需要使用寄存器R8 ~
R14,FIQ模式下的寄存器允许更快的中断处理。
寄存器R3和R14都有6个分组物理寄存器,其中一个用在User和SYSTEM模式下,其余的5个用在5种异常模式下,当需要区分时用如下格式命名:
R13_
R14_
注: 是指usr, svc, abt, und, irq和fiq
寄存器R13通常被用作栈寄指针SP,每一种异常模式都有自己的分组寄存器R13
寄存器R14(链接寄存器或LR)在体系结构中有两种特殊用途:
1,在各种模式下,R14用来保存子程序的返回地址。当一条BL或者BLX指令执行子程序调用时,R14设为子
程序的返回地址。通过复制R14中的地址值到PC中来实现子程序的返回。
2,当发生异常时,相关异常模式下的R14就设为异常返回地址。异常的返回地址与子程序的返回类似,使用
指令恢复异常发生前的程序状态。
寄存器R15:程序计数器
R15是程序计数器(PC),其内容是处理器要取的下一条指令的地址。在ARM状态下,所有的ARM指令都是4字
节长,一直都是字对齐的,这意味着PC的最低两位一直是0,因此PC只包含30位可变的位。ARM体系中的
一些版本也支持其他两种处理器状态。T变种支持Thumb状态,J变种支持Jazelle状态。这些状态下PC可以
是半字和字节对齐。
ARM64
通用寄存器:
x0~x7:传递子程序的参数和返回值,使用时不需要保存,多余的参数用堆栈传递,64位的返回结果保存在x0中。
x8:用于保存子程序的返回地址,使用时不需要保存。
x9~x15:临时寄存器,也叫可变寄存器,子程序使用时不需要保存。
x16~x17:子程序内部调用寄存器(IPx),使用时不需要保存,尽量不要使用。
x18:平台寄存器,它的使用与平台相关,尽量不要使用。
x19~x28:临时寄存器,子程序使用时必须保存。
x29:帧指针寄存器(FP),用于连接栈帧,使用时必须保存。
x30:链接寄存器(LR),用于保存子程序的返回地址。
x31:堆栈指针寄存器(SP),用于指向每个函数的栈顶。
特殊寄存器
sp寄存器:Stack Pointer,保存栈顶地址
fp寄存器:Frame Pointer,保存栈底地址
lr寄存器:Link Register,保存跳转指令下一条指令的地址(eg:bl跳转进入执行函数,lr则会保存函数调用完成后需要执行的指令地址)
pc寄存器: 保存当前执行中的指令的下一条指令的地址
cpsr寄存器(状态寄存器):其他寄存器都是存储数据,是个统一的整体;状态寄存器有点特殊,它的每一位都有特殊的含义,记录特定的信息。
最常见的是NZCV标志位,分别代表运算过程中产生的不同状态,可以决定运算结果或者代码执行逻辑。
N:Negative Cndition Flag,代表运算结果是负数
Z:Zero Condition Flag, Z 为 1 代表0,否则Z 为 0 代表 1
C:Carry Condition Flag, 无符号运算有溢出时C 为 1
V:Overflow Condition Flag, 有符号运算有溢出时C 为 1
ARM64汇编指令
一个完整的ARM指令通常由操作码+操作数组成,指令的编码格式如下。
<opcode> {<cond> {s} ,<Rd>,<Rn> {,<operand2>}}
● 使用<>标起来的是必选项,使用{}标起来的是可选项。
● 是二进制机器指令的操作码助记符,如MOV、ADD这些汇编指令都是操作码的指令助记符。
● cond:执行条件,ARM为减少分支跳转指令个数,允许类似BEQ、BNE等形式的组合指令。
● S:是否影响CPSR寄存器中的标志位,如SUBS指令会影响CPSR寄存器中的N、Z、C、V标志位,而SUB指令不会。
● Rd:目标寄存器。
● Rn:第一个操作数的寄存器。
● operand2:第二个可选操作数,灵活使用第二个操作数可以提高代码效率
内存存储访问指令
ARM指令集属于RISC指令集,RISC处理器采用典型的加载/存储体系结构,CPU无法对内存里的数据直接操作,只能通过Load/Store指令来实现:当我们需要对内存中的数据进行操作时,要首先将这个数据从内存加载到寄存器,然后在寄存器中对数据进行处理,最后将结果重新存储到内存中。
ARM64取消32位的LDM, STM, PUSH, POP指令. 与之替代的是 ldr/ ldp, str/ stp
LDR /STR 指令
LDR x8, [sp] // 将存储地址为sp的数据读取到x8寄存器中
LDR x9, [sp, #0x8] // 将存储地址为sp+0x8的数据读取到x9寄存器中
STR x0, [sp] // 将x0寄存器中的值存储到sp的存储地址
STR x0, [sp, #0x10] // 将x0寄存器中的值存储到sp+0x10的存储地址
LDP/STP指令(LDR 的变种指令,能够同时操做两个寄存器)
LDP x29, x30, [sp, #0x10] // 将sp+0x10的值取出来,存⼊寄存器 x29 和寄存器 x30
STPx29, x30, [sp, #0x10] // 将 x29, x30 的值存⼊sp+0x10存储地址
LDRB /SDRB 指令(只操作一个字节)
LDRB w8, [sp, #0x8] // 将存储器地址为sp+0x8的1个字节数据读⼊寄存器w8,并将w8的⾼24位清零。
SDRB x0, [sp, #0x10] // 将x0寄存器中的低8位的字节的数据存储到sp+0x10的存储地址
LDRH /STRH 指令(只操作两个字节)
LDRH w8, [sp, #0x8] // 将存储器地址为sp+0x8的2个字节数据读⼊寄存器w8,并将w8的⾼16位清零。
STRH x0, [sp, #0x10] // 将x0寄存器中的低16位的字节的数据存储到sp+0x10的存储地址
LDUR/STUR指令 (u和负地址运算相关)
LDUR w0, [x29, #-0x8] // 将存储地址为x29-0x8的数据读取到w0寄存器中
STUR w0, [x29, #-0x8] // 将x0寄存器中的数据存储到x29-0x8的存储地址
寄存器数据处理指令
MOV指令(不用于内存地址)
MOV w8, #0x1 //将立即数赋值给w8寄存器
MOV x0, x8 //将给x0寄存器赋值给给x8寄存器
ADD指令 (加)
ADD sp, sp, #0x20 // 将寄存器sp的值和立即数0x20相加后保存在寄存器sp中
ADD x0, x1, x2 // 将寄存器 x1 和 x2 的值相加后保存到寄存器 x0 中
ADD x0, x1, [x2] // 将寄存器x1的值加上寄存器x2 的值做为地址,再取该内存地址的内容放⼊寄存器x0中
SUB指令 (减) CMP指令只影响cpsr
SUB sp, sp, #0x20 // 将寄存器sp的值和立即数0x20相减后保存在寄存器sp中
SUB x0, x1, x2 // 将寄存器 x1 和 x2 的值相减后保存到寄存器 x0 中
MUL指令 (乘)
MUL x0, x1, x2 // 将寄存器 x1 和 x2 的值相乘后结果保存到寄存器 x0 中
SDIV指令 (除,无符号除是udiv)
SDIV x0, x1, x2 // 将寄存器 x1 和 x2 的值相除后结果保存到寄存器 x0 中
AND指令 (位与)
AND x0, x1, x2 // 将寄存器 x1 和 x2 的值按位与后保存到寄存器 x0 中
ORR指令 (位或)
ORR x0, x1, x2 // 将寄存器 x1 和 x2 的值按位或后保存到寄存器 x0 中
EOR指令 (位异或)
EOR x0, x1, x2 // 将寄存器 x1 和 x2 的值按位异或后保存到寄存器 x0 中
跳转指令
B指令 (无条件跳转,遇到ret返回上一层函数)
B指令可以接上后缀,用来和cmp比较后待条件的跳转
B 0x100002fd0
CMP W8, #2
B.NE 0x100002fd8
BL指令 (无条件跳转,遇到ret返回该指令的下一条指令)
字母 L: 把下一条指令地址存入LR寄存器
BL 0x100002f54
BLX指令
字母 X: 切换指令模式 arm转thumb thumb转arm
CBNZ/CBZ指令 (和⾮ 0 ⽐较)
CBNZ x0, 0x100002f70 // 如果非0,跳转到0x100002f70指令执行
CBZ x0, 0x100002f70 // 如果为0,跳转到0x100002f70指令执行
BLR 指令 (跳转到某寄存器 (的值)指向的地址)
BLR x10
RET指令 (函数返回)
RET
ARM寻址方式
寄存器寻址:通过寄存器名就可以直接对寄存器中的数据进行读写
MOV R1, R2
ADD R1, R2, R3
立即数寻址:立即数以#为前缀,0x前缀表示该立即数为十六进制,不加前缀默认是十进制
MOV R1, #0x3F
ADD R1, R2, #16, 20 ;R1 = R1 + (16循环右移20位)
寄存器偏移寻址:通过第二个操作数operand2的灵活配置(左移或右移操作)作为新的操作数使用
MOV R2, R1, LSL, #3 ;R2 = R1 << 3
ADD R1, R2, #16, 20 ;R1 = R1 + (16循环右移20位)
寄存器间接寻址:寄存器中保存的是数据在内存中的存储地址
LDR R1, [R2]
基址寻址:将寄存器中的地址与一个偏移量相加,生成一个新地址
LDR R1, [FP, #2] ;将FP中的值加2作为新地址,取新地址的内存中的数据放入R1
LDR R1, [FP, #2]! ;将FP=FP+2,再取地址FP的内存中的数据放入R1
LDR R1, [FP, R0] ;将FP中的值加R0作为新地址,取新地址的内存中的数据放入R1
LDR R1, [FP], #2 ;取地址FP的内存中的数据放入R1,再FP=FP+2
STR R1, [FP, #-2] ;将FP=FP-2,再将R1写入地址FP的内存中
STR R1, [FP], #-2 ;将FP=FP-2将R1写入地址FP的内存中,再将FP=FP-2
多寄存器寻址:STM/LDM指令就属于多寄存器寻址,一次可以传输多个寄存器的值。
相对寻址:基址寻址的一种特殊情况。以PC指针作为基地址进行寻址的,以指令中的地址差作为偏移,两者相加后得到的就是一个新地址。
B LOOP
…
LOOP MOV R0,#3
等价于
ADD PC, PC, #3
ARM汇编程序格式
ARM汇编程序是以段(section)为单位进行组织的。在一个汇编文件中,可以有不同的section,分为代码段、数据段等,各个段之间相互独立,一个ARM汇编程序至少要有一个代码段。我们可以使用AREA伪操作来标识一个段的起始、段名和段的读写属性。
AREA EXAMPLE,CODE,READONLY ;EXAMPLE为段名,段属性CODE/DATA,读写权限READONLY /READWRITE
ENTRY ;汇编程序的运行入口 ;后面表示注释
START
LDR R0,=SRC
LDR R1,=DST
MOV R2, #10
0
LDR R3, [R0], #4
STR R3, [R1], #4
SUBS R2, R2, #1
BNE %B0 ;跳到前面的局部标号0处,构成循环程序结构
AREA EXAMPLEDATA,DATA,READWRITE
SRC DCD 1,2,3,4,5,6,7,8,9,0
DST DCD 0,0,0,0,0,0,0,0,0,0
END ; 汇编程序的结束
标号
在ARM汇编程序中,我们可以使用符号来标识一个地址、变量或数字常量。当用符号来标识一个地址时,这个符号通常又被称为标号。
符号的命名规则和C语言的标识符命名规则一样:由字母、数字和下画线组成,符号的开头不能使用数字,但标号除外。标号比较任性,标号的开头不仅可以是数字,甚至整个标号可以是一个纯数字。符号的命名在其作用域内必须唯一,不能与系统内部或系统预定义的符号同名,不能与指令助记符、伪指令同名。一般情况下,一个符号的作用域是整个汇编源文件。有时候我们会直接通过数字[0,99]而不是使用字符来进行地址引用,我们称这种数字为局部标号。局部标号的作用域为当前段,在汇编程序中,我们可以使用下面的格式来引用局部标号。
%{F|B|A|T} Name{routename} ;{}括起来的部分是可选项
%:引用符号,对一个局部标号产生引用。
F:指示编译器只向前搜索。
B:指示编译器只向后搜索。 ;默认先向后搜索,然后向前搜索
A:指示编译器搜索宏的所有宏命令层。
T:指示编译器搜索宏的当前层。 ;默认搜索从当前层到最顶层的所有宏命令,但不搜索较低层的宏命令
Name:局部标号的名字。
routename:局部标号作用范围名称,使用ROUT定义。 ;对一个标签的引用中指定了routename,则汇编程序将其与最近的一个前ROUT指令的名称进行比较,如果不匹配,则汇编失败
ARM伪指令/伪操作
在汇编语言中,为了编程方便,汇编器也定义了一些特殊的指令助记符,以方便对汇编程序做各种处理。如使用AREA来定义一个段(section),使用GBLA来定义一个数据,使用ENTRY来指定汇编程序的执行入口等,这些指令助记符统称为伪指令或伪操作。
伪操作一般用在符号定义、数据定义、汇编程序结构控制等场合
有了这些伪操作辅助,我们就可以设计出更加灵活、功能更加复杂的程序结构,也可以定义一个个汇编子程序,然后在主程序中分别去调用它们,实现汇编语言的模块化编程。
ARM 伪指令不是ARM 指令集中的指令,只是为了方便编程各编译器厂商定义的辅助指令,使用时可以像其他ARM 指令一样使用,在编译时这些指令将被等效的ARM指令代替,有点类似C语言中的预处理命令
全局变量的 定义/初始化
定义一个全局变量并进行初始化。
GBLA NUM ;定义一个全局的数值变量,变量名为NUM
GBLL LOGIC ;定义一个全局的逻辑变量,变量名为LOGIC
GBLS STRING ;定义一个全局的字符串变量,变量名为STRING
NUM SETA 4 ;将数值变量NUM赋值为4
LOGIC SETL {TRUE} ;将逻辑变量LOGIC赋值为真(不可改为1)
STRING SETS "nhfgtnhgfdb" ;将字符串变量STRING赋值为相应字符串
与全局变量定义中的三条指令类似,LCLA、LCLL和LCLS分别用于定义局部的数值变量、逻辑变量及字符串变量
ADR、ADRL、LDR 将PC偏移地址值或寄存器偏移地址值传送到寄存器中。
在寄存器之间传递数据可以使用MOV指令,但是当传递的一个内存地址是32位的立即数时,MOV指令就应付不了了
这得从ARM指令的编码格式说起。RISC指令的特点是单周期指令,指令的长度一般都是固定的。在一个32位的系统中,一条指令通常是32位的,指令中包括操作码和操作数,指令中的操作码和操作数共享32位的存储空间:一般前面的操作码要占据几个比特位,剩下来的留给操作数的编码空间就小于32位了。当编译器遇到MOV R0,#0x30008000这条指令时,因为后面的操作数是32位,编译器就无法对这条指令进行编码了。
为了解决这个难题,编译器提供了一个LDR伪指令来完成上面的功能.
LDR 大范围地址读取伪指令(并不是ARM指令中的LDR指令)
MOV R0, #0x30008000 ; 指令执行异常
LDR R0, =0x30008000
为了与ARM指令集中的加载指令LDR区别开来,LDR伪指令中的操作数前一般会有一个等于号=,用来表示该指令是个伪指令。通过LDR伪指令,编译器就解决了向一个寄存器传送32位的立即数时指令无法编码的难题。
当LDR伪指令中的操作数小于8位时,LDR伪指令一般会被MOV指令替代。当LDR伪指令中的操作数大于8位时,LDR伪指令会被编译器转换为LDR标准指令+文字池的形式。
LDR R0, =0x30008000
转为
LDR R0, [PC, #OFFSET]
...
...
DCD 0x30008000
当LDR伪指令中的操作数为一个32位的立即数时,编译器会首先在内存中分配一个4字节大小的存储单元,然后将这个32位的地址0x30008000存放到该存储单元中,该存储单元通常也叫作文字池(literal pool)。接着编译器计算出该存储单元到LDR伪指令之间的偏移OFFSET,然后使用寄存器相对寻址,就可以将这个32位的立即数送到R0寄存器中。偏移量OFFSET的大小一般要小于4KB,所以在分析汇编代码时你会看到,存放这些32位地址常量的文字池一般紧挨着当前指令的代码段,直接放置在当前代码段的后面。
ADR 小范围地址读取伪指令
ADR为小范围的地址读取伪指令,底层使用相对寻址来实现,因此可以做到代码与位置无关
ADR R0, LOOP
...
...
LOOP
b LOOP
……
ADR R0,ADDR_TAB ;将ADDR_TAB表地址(数组地址)传递给R0;
LDRB R1,[R0,R2] ;使用R2作为参数进行查表
……
ADDR_TAB DCB 0xA0 ,0xF8,0x80,0x48,0xE0,0x4F,0xA3,0xD2
ADR伪指令的作用是将标号LOOP表征的内存地址送到寄存器R0中。编译器在编译ADR伪指令时,会首先计算出当前正在执行的ADR伪指令地址与标号LOOP之间的地址偏移OFFSET,然后使用ARM指令集中的一条标准指令代替之,如使用ADD指令将标号表征的地址送到寄存器R0中。
OFFSET = LOOP-(PC-8)
ADD R0, PC, #OFFSET
LDR伪指令通常被翻译为ARM指令集中的LDR或MOV指令,而ADR伪指令则通常会被ADD或SUB指令代替。在用途上,LDR伪指令主要用来操作外部设备的寄存器,而ADR伪指令主要用来通过相对寻址,生成与位置无关的代码。在一个程序中,只要各个标号之间的相对位置不变,使用ADR伪指令就可以做到与位置无关,将指令代码加载到内存中的任何位置都可以正常运行。在寻址方式上,LDR使用绝对地址,而ADR则使用相对地址,LDR和ADR伪指令的地址适用范围也不同,LDR伪指令适用的地址范围为[0,32GB],而ADR伪指令则要求当前指令和标号必须在同一个段中,地址偏移范围也较小,地址对齐时偏移范围为[0,1020],地址未对齐时偏移范围为[0,4096]。
ADRL 中等范围地址读取伪指令
NOP
NOP 空操作伪指令, 可用于延时,NOP伪指令在编译时将会被替代成ARM中的空操作,比如MOV R0,R0指令等文章来源:https://www.toymoban.com/news/detail-517279.html
DC系列
DCB、DCW、DCD、DCQ:用于分配一段内存单元,并对其进行做初始化工作。
DCB:分配一段字节的内存单元,它每个操作数都占有一个字节,操作数范围为-128~255的数值或字符串。
DCW:分配一段半字的内存单元,它的每个操作数都占有两个字节,操作数是16位二进制数,取值范围为-32768~65535。
DCD:分配一段字的内存单元,它的每个操作数都占有4个字节,操作数可以是32位的数字表达式,也可以是程序中的标号。
DCQ:分配一段双字的内存单元,它的每个操作数都占有8个字节。
DCFS( DCFSU):为单精度浮点数分配一片连续的字存储单元并初始化
DCFD( DCFDU) 为双精度浮点数分配一片连续的字存储单元并初始化
SPACE 分配一片连续的存储单元并初始化为0文章来源地址https://www.toymoban.com/news/detail-517279.html
到了这里,关于ARM汇编 : 汇编指令,伪指令,汇编程序格式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!