RISC架构可以认为是加载/存储的架构,因为所有存储在外部数据都需要通过指令加载到处理器进行处理。
加载/存储的指令很多,常用的如下:
加载 | 存储 | 长度和类型 |
---|---|---|
LDR | STR | 32位的字(默认) |
LDRB | STRB | 8位的无符号字节 |
LDRH | STRH | 16位无符号的半字 |
LDRSB | 8位的有符号字节 | |
LDRSH | 16位的有符号半字 | |
LDM | STM | 多个字的处理 |
- 加载/存储的单指令处理的格式如下:
LDR|STR{<size>}{<cond>} <Rd>, <addressing_mode>
LDR表示从内存中加载数据并写到通用寄存器,STR表示从通用寄存器读取数据并把它存储到内存中。其中size是可选的如上表中的B/SB/H/SH等,addressing_mode表示寻址模式就是前面的文章《ARM汇编之内存寻址模式》介绍的,总共3种。
下面这种格式不加!是偏移寻址模式,加!是前变址寻址模式
LDR|STR{<size>}{<cond>} <Rd>, [<Rn>, <offset>]{!}
下面这种格式是后变址寻址模式
LDR|STR{<size>}{<cond>} <Rd>, [<Rn>], <offset>
例子:
AREA load_store, CODE, READONLY
ARM
ENTRY
start
MOV R1,#0x40000000
MOV R2,#2
LDR R3,[R1,R2,LSL #2];偏移寻址模式
;LDR R3,[R1,R2,LSL#2]!;前变址寻址模式
;LDR R3,[R1],R2,LSL#2;后变址寻址模式
STR R2,[R1]
STR R2,[R1,#4]!
MOV R2,#0XFF
STR R2,[R1],#8
stop
MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SVC #0x123456 ; A32 semihosting (formerly SWI)
END
- 加载/存储多个寄存器的指令
LDM的语法格式如下:
LDM{addr_mode}{cond} Rn{!}, reglist{^}
其中<addr_mode>告诉汇编指令寻址的模式有下面四种:
IA,在每次传输完之后递增地址(默认模式,可省略) 。
IB,在每次传输完之前递增地址 (仅A32支持)。
DA,在每次传输完之后递减地址 (仅A32支持)。
DB,在每次传输完之前递减地址。
cond是可选的,表示条件码。
Rn是基址寄存器。
感叹号!是可选的,如果有表示最后的地址要写回Rn寄存器。
reglist是用来加载数据的寄存器列表(包含一个或多个寄存器,多个的话可用逗号进行分隔,如果是多个连续的寄存器也可用一个范围表示,比如R0-R4),reglist需要用花括号括起来,对A32来说寄存器从R0到R15都可用,但T32只有部分寄存器可用。
reglist之后的^是可选的,仅A32支持,他不能用在用户模式或者系统模式( User mode/System mode),他是为如下场景设计的:
- 当reglist中包含PC寄存器时,不仅将数据加载到寄存器列表中指定的寄存器,同时也顺便把SPSR复制到CPSR,这发生在异常模式下中断处理程序还回时。
- 否则数据将被传输进/出用户模式的寄存器,而不是当前模式的寄存器
备注:因为有多个寄存器,那么怎么考虑寄存器列表里面的寄存器和内存之间的映射,拿存储来说,是最低序号的寄存器存储到最低内存地址中,最高序号的寄存器存储到最高的内存地址中,加载也是按这种方式进行映射的,这个是跟寄存器列表中寄存器的存放顺序没有关系的。
STM的语法格式如下:
STM{addr_mode}{cond} Rn{!}, reglist{^}
STM和LDM所带选项基本一样,释义也基本一样,对LDM来说Rn存储的是内存中的基地址,只是STM要从该地址往内存中写数据,而LDM是从该地址从内存中读数据。reglist之后的^对STM来说,同样也不能在用户模式或者系统模式,只是出现该符号时数据将被传输进/出用户模式的寄存器,而不是当前模式的寄存器。
例子1:
AREA load_store, CODE, READONLY
ARM
ENTRY
start
MOV R9,#0x40000000
ADD R10,R9,#32
LDMIA R9, {R5, R0, R3};低地址的数据先加载到r0,在加载到r3,最后加载到r5,
;加载顺序跟放置在列表中的位置无关,只跟寄存器的编号有关
LDMDB R10!,{R0-R1};最终地址会被写回R10
stop
MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SVC #0x123456 ; A32 semihosting (formerly SWI)
END
例子2:
AREA load_store, CODE, READONLY
ARM
ENTRY
start
MOV R9,#0x40000000
ADD R10,R9,#32
MOV R2,#2
MOV R1,#1
STMIA R9,{R2,R1};注意最低地址存储R1,在接着存储R2,存储顺序跟放置在列表中的位置无关
STMIB R9,{R1,R2}
STMDA R10,{R2,R1}
STMDB R10!,{R1,R2}
stop
MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SVC #0x123456 ; A32 semihosting (formerly SWI)
END
- PUSH和POP
主要是针对栈的操作,PUSH其实就是 STMDB sp!,POP其实就是 LDMIA sp!
语法格式如下:
PUSH{<cond>} <reglist>
POP{<cond>} <reglist>
其中reglist可以参照前面的LDM/STMD的说明
例子:
AREA push_pop, CODE, READONLY
ARM
ENTRY
start
mov sp,#0x40000010
push {r1,r0}
pop {r3,r4}
stop
MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SVC #0x123456 ; A32 semihosting (formerly SWI)
END
- 使用 LDM和STM来实现栈
使用栈时需要注意两点:
- 栈的增长方向,向上增长(ascending)或者向下增长(Descending)
- 栈指针的位置,指向空的位置(empty,栈中下一个空的位置),或者满的位置(Full,最后一个栈成员的位置)
因此就可以组合出四种栈:
FD(Full Descending stack)
FA(Full Ascending stack)
ED(Empty Descending stack)
EA(Empty Ascending stack)
当然这四种操作和LDM/STM中的addr_mode是等价的可以互相替换的,编程时如果你觉得基于这种栈方式的操作更容易理解,可以用这种助记符来进行编写程序,汇编器会自动帮你把这些转换为基于addr_mode的合适指令。
下面是一张等价图
如果你是基于FD的栈来进行开发,如果没用FD助记符,那么入栈是你需要使用STMDB,而出栈是需要使用LDMIA,如果使用FD的话入栈STMFD,出栈LDMFD就行了剩下的交个汇编器去解释,这种就很容易记忆了。下面是四种栈入栈/出栈的指令及等效的addr_mode模式下的原始指令
例子:
AREA push_pop, CODE, READONLY
ARM
ENTRY
start
mov sp,#0x40000010
stmfd sp!,{r0,r1};入栈
ldmfd sp!,{r2,r3};出栈
stop
MOV r0, #0x18 ; angel_SWIreason_ReportException
LDR r1, =0x20026 ; ADP_Stopped_ApplicationExit
SVC #0x123456 ; A32 semihosting (formerly SWI)
END
参考文献
【1】DUI0801I_armasm_user_guide