ARM常用指令(1)
加载与存储指令
基于基地址的寻址模式
- 基地址寻址
1 | ldr x0, [x1] |
- 基地址偏移量寻址
1 | ldr x0, [x1, #4] |
- 基地址扩张寻址
1 | ldr x0, [x1, x2] |
UXTW表示从寄存器中提取32位数据,高位补0,无符号数。相同的extend还包括UXTB,UXTH。
SXTW表示从寄存器中提取32位数据,高位补0,有符号数。相同的extend还包括SXTB,SXTH。
变基寻址模式
- 前变基模式
1 | ldr x0, [x1, #4]! |
- 后变基模式
1 | ldr x0, [x1], #4 |
LDR伪指令
ldr既可以是普通的加载指令,也可以在第二个参数前加上“=”后,表示大范围加载地址的伪指令。注:无str伪指令
1 | ldr x0, =label // 此时x0中即为label的值 |
‘[]’方括号表示从括号中参数的内存地址中读取或者存储数据,‘!’感叹号表示更新存放地址的寄存器,也即写回和更新寄存器
ldr和str的变种
指令 | |
---|---|
ldr | 普通数据加载指令,4字节与8字节数据,以W或者X寄存器区分 |
ldrsw | 有符号数据加载指令,word |
ldrb | 数据加载指令,byte |
ldrsb | 有符号数据加载指令,byte |
ldrh | 数据加载指令,half-word |
ldrsh | 有符号数据加载指令,half-word |
strb | 数据存储指令,byte |
strh | 数据存储指令,halfword |
多字节加载与存储指令
在A32中通常使用LDM,STM指令实现多字节内存加载和存储,但是在A64架构中取消了LDM和STM指令,取而代之的是LDP(Load Pair)和STP(Store Pair)指令。LDP和STP支持基地址偏移量寻址、前变基模式和后变基模式三种寻址方式。
基地址偏移量寻址
1
2ldp x0, x1, [x2]
stp x0, x1, [x2]前变基寻址
1
2ldp x0, x1, [x2, #4]!
stp x0, x1, [x2]后编辑寻址
1
2ldp x0, x1, [x2], #4
stp x0, x1, [x2], #4
1 | // 以上常用指令简易测试汇编代码如下 |
入栈与出栈
在程序中栈通常有以下用途:
- 临时存储数据,如临时变量等
- 传递参数。ArmV8平台,如果函数参数不超过8个,那么将使用x0~x7通用寄存器传递参数,当传递参数超过8个时,就需要用到栈了。
一般情况,栈是从高地址向低地址生长的数据结构,起始地址称为栈底,最后一个入栈元素地址称之为栈顶,指向栈顶的指针称之为栈指针,SP(Stack Point)。
A32提供了push和pop指令进行入栈出栈操作,在A64指令集中移除了push和pop,使用ldp和stp取代。
1 | .arch armv8-a |
MOV指令
mov指令通常用于寄存器之间的搬移和立即数搬移。
而能搬运的立即数只有以下两种:
- 16位立即数
- 16位立即数左移16位、32位或者48位的立即数
此时mov指令等同于movz指令
1 | movz x0, 0x12bc, LSL #16 |
mov指令还可以用来搬运bitmask,此时等同orr指令
1 | orr x0, XZR, #0xffff0000ffff |
可以通过反汇编验证以上结论:
1 | main.s: |
通常一个cpp文件要变成熟悉的可执行文件a.out,需要经过以下四个步骤
调用aarch64-linux-android-g++对main.s进行汇编过程:
1 | aarch64-linux-android-g++ -c main.s -o main.o |
调用aarch64-linux-android-objdump -s -d -M no-aliases test.o对main.o进行反汇编
1 | aarch64-linux-android-objdump -s -d -M no-aliases main.o |
可以看到mov指令变成了movz和orr指令