目录
ARM处理器的八种寻址方式
1.立即数寻址
立即寻址,操作数本身就在指令中给出,并且指令中的立即数是由一个8bit的常数移动4bit偶数位得到的(循环右移),我们可以通过这个来判断立即数的合法性。
MOV R0,#9
MOV R1,#0x09
MOV R2,$9
十六进制立即数#后面要加0x
二进制数要加0b
十进制默认不加
八进制要加0
如何判断立即数合法性:
将该立即数写成二进制,将包含所有1的最短路径选出来,如果该路径长度大于8(循环右移得到的目标数间隔<=8也可以,比如0xf000000f)那么一定不合法,然后我们再左右两边同时去除偶数个0得到目标数即为合法
举例:0x104
将其写为二进制 000100000100,可以看到我们包含所有1的最短路径为1000001,小于8位,我们两边同时去除偶数个0可以得到我们的目标八位数01000001,循环右移得到我们的立即数所以是合法的。
2.寄存器寻址
寄存器寻址就是利用寄存器中的数值作为操作数,这种寻址方式是各类微处理器经常采用的一种方式。
例子:
MOV R0,R1 //R0=R1
ADD R0,R1,R2 //R0=R1+R2
3.寄存器间接寻址
寄存器间接寻址就是以寄存器中的值作为操作数的地址,而操作数本身放在存储器中
LDR R0,[R1]
4.寄存器移位寻址
寄存器移位寻址的操作由寄存器中的数值相应移位而得到的,移位的方式在指令中以助记符给出
移位数可用立即数或寄存器给出
MOV R1,#7
MOV R2,#1
ADD R0,R1,R2,LSL #2 //R0=R1+R2<<2
5.基址变址寻址
基址变址寻址就是将基址寄存器的内容与指令中给出的偏移量相加,得到一个有效的操作数的地址。通常用于访问连续的地址空间
前索引 LDR R0 ,【R1,#4】等价于r0=*(r1+4)
自动索引 LDR R0,【R1,#4】! 等价于 r0=*(r1+4), r1=r1+4
后索引 LDR R0,【R1】,#4 等价于 r0=*r1,r1=r1+4
下面我们来看一个代码示例:
.text
ldr r0,=buf
@ldr默认从地址上取4字节
@【r0,#12】表示r0+12,12表示12个字节
@ldr r1,[r0,#12] @r1=*(r0+12)
mov r10,#1
loop:
cmp r10,#5
ldrle r1,[r0],#4 @后索引 r1=*r0,r0=r0+4
addle r10,#1 @le为条件码,小于等于
ble loop @跳转循环
.data
buf:
.word 5,3,1,4,2 @word类型为4个字节
.end
6.多寄存器寻址
多寄存器寻址可实现一条指令完成多个寄存器值的传送,最多可以一次传送16个通用寄存器的值,连续的寄存器“-”连接,否则用,分割
xx可以是:ia,ib,da,db
ldmxx r0! ,{r1-r5}
stmxx r0!,{r1-r5}
看一段代码:
.text
ldr r0,=buf
@多寄存器寻址,基址不变,连续取出4字节
ldmia r0,{r1-r5}
@r0基址自动更新
@ldmia r0!,{r6-r11}
@基地址不变,将r1-r5五个寄存器的内容写入r0对应的地址上
stmia r0,{r1-r5}
.data
buf:
.word 5,3,1,4,2
.end
7.相对寻址
以PC的当前值为基地址,指令中的地址标号为偏移量,两者之和得到操作数的地址
8.堆栈寻址
堆栈寻址是一种数据结构,按先进后出的方式操作,R13(SP)寄存器指示当前的栈顶位置,ARM处理器支持4种堆栈操作方式(FD,FA,ED,EA),ATPCS标准规定使用FD栈。
入栈:stmfd sp!,{r0-r12} (!表示会自动偏移)
出栈:ldmfd sp!,{r0-r12}(^表示会恢复spsr到cpsr状态寄存器)
一般堆栈寻址用于处理中断异常,将下一条指令地址LR入栈,在恢复的时候出栈给PC
我们来看一段代码:
.text
ldr r0,=buf
mov r1,#8
mov r2,#9
mov r3,#10
@sp栈顶指针,每入栈一个数据,sp自动向小地址方向偏移
@所以必须将sp指向缓冲区末尾
ldr sp,=buf_end
@入栈:寄存器从右向左依次入栈
stmfd sp!,{r1-r3}
NOP
NOP
@出栈:寄存器从左往右依次出栈,先进后出
ldmfd sp!,{r5,r6,r7}
NOP
NOP
.data
buf:
.space 5*4
buf_end:
.end
9.GNU汇编伪指令
异常中断处理
1.7种异常源
七种类型的异常按优先级从高到低为:复位异常,数据异常,快速中断异常,外部中断异常,预取异常,软中断异常,未定义指令异常。这决定了多个中断源递交中断申请时的中断控制器对中断源的响应优先级别!
2.异常向量表
当异常发生时,处理器会把PC设置为一个特定的存储器地址。这一地址放在被称为向量表的特定地址范围内,向量表的入口是一些跳转指令,跳转到准们处理某个异常或中断的子程序。
为什么使用异常向量表呢:因为异常的产生是随机的,而每一种异常都需要处理,所以接口无法确定,所以需要硬件指定一个固定位置,但是零散不好,所以把异常的入口集中到一个区域分别存放跳转指令,跳转到对应的处理程序(地址为0-1c,可以通过协处理器更改)
我们来看一段代码:
.text
_start:@异常向量表的开始
@上电时,cpu默认在异常向量表的0地址处取址
b reset @ 0x00 上电复位异常 SVC
NOP @ 0x04 未定义指令异常 UNDEF
ldr pc,_swi_handle @ 0x08 软中断异常 SVC
NOP @ 0x0c 预取指令异常 abort
NOP @ 0x10 数据异常 abort
NOP @ 0x14 保留位
NOP @ 0x18 IRQ低优先级中断异常 IRQ
NOP @ 0x1c FIQ高优先级中断异常 FIQ
@标准写法
_swi_handle:
@异常处理函数往往不在当前文件,为了突破当前文件32M的空间限制
@我们将文件名放到 .long的空间中去
.long swi_handle
@异常处理
swi_handle:
@1.进栈,保护现场
stmfd sp!,{r0-r12,lr}
@2.出栈恢复现场,^自动恢复spsr到cpsr
ldmfd sp!,{r0-r12,pc}^
reset:
ldr sp,=stack_top @栈顶
@将svc切换成user模式,模拟用户程序
mrs r0,cpsr
bic r0,#0x03
msr cpsr,r0
NOP
@产生软中断指令
@1.cpu会将cpsr自动保存到spsr寄存器
@2.处理器由user模式进入到SVC模式
@3.lr保存下条指令的地址
swi 3
NOP
.data
buf:
.space 125*4
stack_top:
.end
上述代码我们通过软中断来模拟出现中断时的处理步骤,cpu会将cpsr自动保存到cpsr寄存器种,lr会保存下一条指令地址方便中断处理完成后恢复到之前状态,处理器会由user模式进入到svc模式,完成中断处理后,会将lr出栈给pc,并自动将spsr的内容给到cpsr,还原到中断之前的状态。
我们用两张图来展示这个过程:
在建立异常向量表时一般我们有下面这样的标准:
下面我们展示一段代码通过软中断来模拟系统调用:
.text
_start:
@ 上电时,cpu默认在异常向量表的 0地址处取址
b reset @ 0x00 上电复位异常 SVC
NOP @ 0x04 未定义指令异常 UNDEF
ldr pc,_swi_handler @ 0x08 软中断异常 SVC
NOP @ 0x0c 预取指令异常 abort
NOP @ 0x10 数据异常 abort
NOP @ 0x14 保留位
NOP @ 0x18 IRQ低优先级中断异常 IRQ
NOP @ 0x1c FIQ高优先级中断异常 FIQ
_swi_handler:
@ 异常处理函数往往不在当前文件,
@ 为了突破当前文件32M的空间限制
.long swi_handler
@ 进行异常的相关处理
swi_handler:
@ 进栈,保护现场
stmfd sp!, {r0-r12, lr}
@ swi机器码,swi指令所在地址为 lr-4
ldr r0, [lr, #-4]
@ 取出机器码中的中断号 0~23
and r0, r0, #0xffffff
cmp r0, #1
@ bleq ....
cmp r0, #2
@ bleq ....
cmp r0, #3
@ bleq ....
@ 出栈,恢复现场, ^自动恢复spsr到cpsr
ldmfd sp!, {r0-r12, pc}^
reset:
ldr sp, =stack_top
@ 将svc切换成 usr模式,模拟用户程序
mrs r0, cpsr
bic r0, #0x3
msr cpsr, r0
NOP
@ swi软中断:
@ 1、cpu会将cpsr自动保存在spsr寄存器中,
@ 2、处理器由 usr 模式 进入svc模式,
@ 3、lr 保存下条指令的地址
swi 1
NOP
swi 2
NOP
swi 3
NOP
.data
buf:
.space 125*4
stack_top:
.end
根据取出指令码中对应的中断号来进行判断进行对应的处理程序。