一、读内存:从内存读取数据到寄存器
- src:从哪里读?
- len:读多长?(ldr四字节,ldrb一字节,ldrh两字节)
- dst:读到哪里去?
(1)指令格式和作用:
LDR{条件} 目的寄存器,<内存地址>:从内存中将一个32位的字数据传送到目的寄存器中。该指令通常用于从内存中读取32位的字数据到通用寄存器,然后对数据进行处理。
LDR{条件}B 目的寄存器,<内存地址>:从内存中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。
LDR{条件}H 目的寄存器,<内存地址>:从内存中将一个16位的半字数据传送到目的寄存器中,同时将寄存器的高16位清零。
特别说明:当程序计数器PC作为目的寄存器时,从内存中读取的字数据将被当作目的地址,从而实现程序流程的跳转。
(2)代码示例:
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。
(3)LDR伪指令
LDR Rn,=expr
COUNT EQU 0x40003100 ;COUNT是我们定义的一个变量,地址为0x40003100。
LDR R1,=COUNT ;将COUNT这个变量的地址,也就是0x40003100放到R1中。
MOV R0,#0 ;将立即数0放到R0中。
STR R0,[R1] ;将R0中的值放到以R1中的值为地址的存储单元去。
;实际就是将0放到地址为0x40003100的存储单元中去。
//这三条指令是为了完成对变量COUNT赋值。
//用三条指令来完成对一个变量的赋值,跟ARM的采用RISC有关。
//注意,变量、变量的地址、变量的值
// COUNT 0x40003100 0
二、写内存:
(1)指令格式和作用:
STR{条件} 源寄存器,<内存地址>:从源寄存器中将一个32位的字数据传送到内存中。
STR {条件}B源寄存器,<内存地址>:从源寄存器中将一个8位的字节数据传送到内存中。该字节数据为源寄存器中的低8位。
STR {条件}H源寄存器,<内存地址>:从源寄存器中将一个16位的字节数据传送到内存中。该字节数据为源寄存器中的低16位。
(2)代码示例:
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器中。
STR R1,[R0] ;将R1寄存器的值,传送到以r0为地址的存储器中。
三、算数运算指令
(1)指令格式和作用:
ADD{条件}{S} 目的寄存器,操作数1,操作数2:把两个操作数相加,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器、被移位的寄存器,或者一个立即数。
SUB{条件}{S} 目的寄存器,操作数1,操作数2:把操作数1减去操作数2,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器、被移位的寄存器,或者一个立即数。该指令可用于有符号数或无符号数的减法运算。
(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)
SUB R0,R1,R2 ;R0 = R1 - R2
SUB R0,R1,#256 ;R0 = R1 - 256
SUB R0,R2,R3,LSL#1 ;R0 = R2 - (R3 << 1)
四、比较指令
(1)指令格式和作用:
CMP{条件} 操作数1,操作数2:将一个寄存器的内容和另一个寄存器的内容或立即数进行比较,比较结果放在CPSR中条件标志位的值。
(2)代码示例:
CMP R1,R0 ;将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR的标志位
CMP R1,#100 ;将寄存器R1的值与立即数100相减,并根据结果设置CPSR的标志位
五、跳转指令
在ARM程序中有两种方法可以实现程序流程的跳转:
(1)使用跳转指令实现程序流程的跳转
跳转指令可以完成从当前指令向前或向后的32MB的地址空间的跳转,包括以下4条指令:
指令 | 功能描述 | 应用场景 | 对寄存器的影响 | 示例 |
B | 实现无条件跳转,跳转值为相对当前 PC 的偏移量(24 位有符号数扩展为 32 位) | 短距离跳转、循环或条件跳转 | 仅改变程序计数器(PC) | B Label; CMP R1, #0; BEQ Label; |
BL | 跳转前将下一条指令地址存入链接寄存器 LR(R14),便于子程序返回 | 子程序调用 | 改变 PC,将返回地址存入 LR | BL my_subroutine |
BX | 跳转到指定地址,根据目标地址寄存器最低位 / 两位切换处理器状态(1 为 Thumb,0 为 ARM) | ARM 与 Thumb 指令集间切换跳转 | 改变 PC,切换处理器状态 | BX R0 |
BLX | 结合 BL 和 BX 功能,跳转前存返回地址到 LR,并切换处理器状态 | 调用 Thumb 指令集子程序(调用者为 ARM 指令集) | 改变 PC,将返回地址存入 LR,切换处理器状态 | BLX R0 |
(2)直接向程序计数器PC写入跳转地址值
通过向程序计数器PC写入跳转地址值,可以实现在4GB的地址空间中任意跳转,在跳转之前结合使用MOV LR,PC等类似指令,可以保存将来的返回地址值,从而实现在4GB连续的线性地址空间的子程序调用。
(2)代码示例:
B Label ;程序无条件跳转到标号Label处执行
CMP R1,#0 ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行
BEQ Label
BL Label ;程序无条件跳转到标号Label处,同时将当前的PC值保存到R14中
六、数据传送指令
(1)指令格式和作用:
压栈的指令为 PUSH,出栈的指令为 POP,PUSH 和 POP 是一种多存储和多加载指令,即可以一次操作多个寄存器数据,他们利用当前的栈指针 SP 来生成地址,PUSH 和 POP 的用法如下表所示:
指令 | 描述 |
PUSH <reg list> | 将寄存器列表存入栈中 |
POP <reg list> | 从栈中恢复寄存器列表 |
(2)代码示例:
假如当前的 SP 指针指向 0X80000000,处理器的堆栈是向下增长的,压栈汇编代码如下:
PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈
PUSH {LR} @将 LR 进行压栈
对 LR 进行压栈完成以后的堆栈模型如下图所示:
如果我们要出栈的话就是使用如下代码:
POP {LR} @先恢复 LR
POP {R0~R3,R12} @在恢复 R0~R3,R12
出栈的就是从栈顶,也就是 SP 当前执行的位置开始,地址依次减小来提取堆栈中的数据到要恢复的寄存器列表中。