多寄存器L/S指令

  • LDR和STR指令仅能load/store一个32位字
  • ARM可以在一条指令中load/store 16个寄存器中的一个任意数目的子集,如LDMIA指令。

LDM/STM

1
2
3
4
LDMIA	STMIA	Increment After     这里的增加/减少是相对于基地址使用前/后的
LDMIB STMIB Increment Before
LDMDA STMDA Decrement After 如:当使用LDMIA和STMIA指令时
LDMDB STMDB Decrement Befor 基地址被使用后(After)增加(Increment)

同样可以使用!使多寄存器L/S指令的基地址自动变址。

如图,r9表示起始指针,r9'表示结束指针。

控制流指令

条件转移指令

条件转移指令一览

B{L}{<cond>} <target address>

  • 地址计算方式:先对指令中定义的24位偏移量进行符号扩展,左移两位形成字的偏移,然后将它加到程序计数器
  • 转移指令的范围大致为+/-32MB(正负23位数左移2位)
  • 转移指令有位L(第24位)置位的链接形式,它将转移后下一条指令的地址传送到当前处理器模式下的链接寄存器(r14,LR)。这一般用于实现子程序的调用,在子程序返回时将链接寄存器的内容拷贝回PC。

  • 栈(Stack)在计算机科学中是限定仅在表尾进行插入或删除操作的线性表。
  • 栈结构是一种在内存空间中临时存储数据的数据结构,按照 “先进后出、后进先出” 的原则存储数据。
  • 进栈(PUSH)操作指将多个寄存器的数据存入栈结构。

在 PUSH 之前,r13 指向栈顶,在 PUSH 之后仍指向栈顶,然而这时 r13 变小了,因为栈顶往低地址跑。r13 永远指向一个空地址,而不是最后的地址。

栈操作指令

ARM没有PUSH指令,可以使用STM指令完成操作:

1
STMDA	r13!, {r1,r3-r5,r14}

LDM/STM和LDR/STR不同!

LDMxx是前面给后面,如LDMIA r1, {r0, r2, r4}是r0赋上r1基地址,r2赋上r1第二个元素(r1+4)基地址。

STMxx是后面给前面。

执行进栈操作的STM指令可以有个不同的名字。对应关系如下。

栈指令 等同指令 含义
LDMED LDMIB Pre-Increment Load
LDMFD LDMIA Post-Increment Load
LDMEA LDMDB Pre-Decrement Load
LDMFA LDMDA Post-Decrement Load
STMFA STMIB Pre-Increment Store
STMEA STMIA Post-Increment Store
STMFD STMDB Pre-Decrement Store
STMED STMDA Post-Decrement Store

如何思考?

  • 入栈
    • 数据入栈时:STMEA或STMIA表示数据存入一个地址增大的栈,故Ascending和Increment是等价的;
    • Empty表明SP总是指向下一个数据空位(空栈),因此对应After。
  • 出栈
    • LDMFA对应LDMDA,对入栈地址增大(Ascending)的栈,出栈地址是反方向减少的(Decrease)
    • Full表明SP总是指向最后压入栈的有效数据(满栈),因此对应After。
对应图

下面哪条指令可以实现:把r0到r12寄存器的值通过栈指针SP进行压栈保存,并且这是一个地址递减的满栈?(多选)

  1. STMFD r13, {R0-R12}
  2. STMDA r13!, {R0-R12}
  3. STMDB r13!, {R0-R12}
  4. STMFD r13!, {R0-R12}
答案

34

栈与子程序

  • BL是转移链接指令,用于跳转到子程序。它执行以下两个操作:
    • 保存PC寄存器的值(下一条指令地址)到r14寄存器。这是子程序的返回地址。
    • 将子程序的起始地址装载到PC寄存器中。这是执行子程序跳转操作。
    • BL永远使用r14寄存器来保存返回地址,因此r14也被称作链接寄存器(link register, LR)。
    • 子程序返回很简单:直接将r14的值放回到PC寄存器中。

子程序有什么问题?

  • 子程序执行期间不能修改保存在r14的PC值
  • 子程序最好不要改变任何寄存器的值,除非需要给调用程序传送结果。

解决方法:使用栈实现子程序的数据保护,在返回时恢复。

  • 保存链接寄存器r14;
  • 保存子程序使用的其他寄存器值。
通过栈实现子程序嵌套

过程调用

例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int foo (int x1,int x2) {
return x1+x2 ;
}
int baz (int x1) {
return x1+1;
}
void scum (int r) {
for (i=0;i=2;i++)
foo(r+i, 5);
}
main () {
scum(3);
baz (2);
}
答案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
foo		add	r0, r0, r1
mov pc, r14

baz add r0, r0, #1
mov pc, r14

scum str r14,[r13,#-4]!
mov r3, r0
mov r2, #0
tmp add r0, r3, r2
mov r1, #5
bl foo
add r2, r2, #1
cmp r2, #2
ble tmp
ldr pc,[r13],#4

main str r14, [r13,#-4]!
mov r0, #3
bl scum
mov r0, #2
bl baz
mov r0, #0
ldr pc,[r13],#4

为什么scum和main要使用str/ldr?因其调用子程序会改变原有r14的值,需要存进栈内进行保存。

下一章要开始讲流水线了。重中之重