Part 4 内存指令:加载和存储
ARM使用加载(Load)/存储(Stroe)指令来读写内存,这意味着你只能使用LDR和STR指令访问内存。在ARM上数据必须从内存中加载到寄存器之后才能进行其他操作,而在x86上大部分指令都可以直接访问内存中的数据。如前所述,在ARM上增加内存里的一个32-bit数据值,需要三个指令(load,increment,store)。为了解释 ARM 上的 Load 和 Store 操作的基本原理,我们从一个基本示例开始,然后再使用三个基本偏移形式,每个偏移形式具有三种不同的寻址模式。为了简单化,每个示例,我们将在同一段汇编代码中使用不同 LDR/STR 偏移形式的。遵循这本段教程的最佳方法是在你的测试环境中用调试器(GDB)运行代码示例。
- 偏移形式:立即数作为偏移量
- 寻址模式:立即寻址
- 寻址模式:前变址寻址
- 寻址模式:后变址寻址
- 偏移形式:寄存器作为偏移量
- 寻址模式:立即寻址
- 寻址模式:前变址寻址
- 寻址模式:后变址寻址
- 偏移形式:缩放寄存器作为偏移量
- 寻址模式:立即寻址
- 寻址模式:前变址寻址
- 寻址模式:后变址寻址
第一个例子:
LDR 用于将内存中的值加载到寄存器中,STR 用于将寄存器内的值存储到内存地址。
LDR R2, [R0] @ [R0] - R0中保存的值是源地址。
STR R2, [R1] @ [R1] - R1中保存的值是目标地址。
LDR : 把R0内保存的值作为地址值,将该地址处的值加载到寄存器R2中。
STR : 把R1内保存的值作为地址值,将寄存器R2中的值存储到该地址处。
下面是汇编程序的样子:
.data /*.data段是动态创建的,无法预测 */
var1: .word 3 /* 内存中的变量var1=3*/
var2: .word 4 /* 内存中的变量var2=4*/
.text /* 代码段开始位置 */
.global _start
_start:
ldr r0, adr_var1 @ 通过标签adr_var1获得变量var1的地址,并加载到R0。
ldr r1, adr_var2 @ 通过标签adr_var2获得变量var2的地址,并加载到R1。
ldr r2, [r0] @ 通过R0内的地址获取到该地址处的值(0x03),加载到R2。
str r2, [r1] @ 将R2内的值(0x03)存储到R1中的地址处。
bkpt
adr_var1: .word var1 /* 变量var1的地址位置 */
adr_var2: .word var2 /* 变量var2的地址位置 */
在程序底部有我们的文本池(在代码段用来存储常量、字符串或其他可以引用的位置无关的偏移量),使用adr_var1
和adr_va2
两个标签来存储var1
和var2
的内存地址。第一个LDR
将var1
的地址加载到R0
,然后第二个LDR
将var2
的地址加载到·。之后将R0
中的地址指向的值(0x03
)加载到R2
,最后将R2
中的值(0x03
)存储到R1
中的地址处。
当加载数据到寄存器中时,使用[]
符号意思时:取寄存器中的值作为地址值,然后再从该地址处加载数据到目标寄存器中,如果不加[]
那就是将寄存器中保存的值直接加载到目标寄存器。
同样STR命令中也是一个意思。
这听起来比实际要复杂的多,没关系,下面是一个更直观的演示图:
下面我们看一下调试器中的这段代码:
gef> disassemble _start
Dump of assembler code for function _start:
0x00008074 <+0>: ldr r0, [pc, #12] ; 0x8088 <adr_var1>
0x00008078 <+4>: ldr r1, [pc, #12] ; 0x808c <adr_var2>
0x0000807c <+8>: ldr r2, [r0]
0x00008080 <+12>: str r2, [r1]
0x00008084 <+16>: bx lr
End of ass