ARM汇编之内存处理一网打尽

RISC架构可以认为是加载/存储的架构,因为所有存储在外部数据都需要通过指令加载到处理器进行处理。

加载/存储的指令很多,常用的如下:

加载 存储 长度和类型
LDR STR 32位的字(默认)
LDRB STRB 8位的无符号字节
LDRH STRH 16位无符号的半字
LDRSB 8位的有符号字节
LDRSH 16位的有符号半字
LDM STM 多个字的处理

  • 加载/存储的单指令处理的格式如下:
    LDR|STR{}{} ,
    LDR表示从内存中加载数据并写到通用寄存器,STR表示从通用寄存器读取数据并把它存储到内存中。其中size是可选的如上表中的B/SB/H/SH等,addressing_mode表示寻址模式就是前面的文章《ARM汇编之内存寻址模式》介绍的,总共3种。
    下面这种格式不加!是偏移寻址模式,加!是前变址寻址模式
    LDR|STR{}{} , [, ]{!}
    下面这种格式是后变址寻址模式
    LDR|STR{}{} , [],
    例子:
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{^}
其中告诉汇编指令寻址的模式有下面四种:
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{}
POP{}
其中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的合适指令。
下面是一张等价图

ARM汇编之内存处理一网打尽_第1张图片
image.png

如果你是基于FD的栈来进行开发,如果没用FD助记符,那么入栈是你需要使用STMDB,而出栈是需要使用LDMIA,如果使用FD的话入栈STMFD,出栈LDMFD就行了剩下的交个汇编器去解释,这种就很容易记忆了。下面是四种栈入栈/出栈的指令及等效的addr_mode模式下的原始指令


ARM汇编之内存处理一网打尽_第2张图片
image.png

例子:

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

你可能感兴趣的:(ARM汇编之内存处理一网打尽)