ARM寻址方式
寻址方式指的是确定操作数位置的方式。
寻址方式:
立即数寻址
直接寻址(绝对寻址),ARM不支持这种寻址方式,但所有CISC处理器都支持
寄存器间接寻址
3种寻址方式总结如下:
助记符 RTL格式 描述
ADD r0,r1,#Q [r0]<-[r1] + Q 立即数寻址:把整数Q与寄存器r1的内容相加
LDR r0,Mem [r0]<-[Mem] 绝对寻址:存储单元内容加到寄存器r0中
LDR r0,[r1] [r0]<-[[r1]] 间接寻址:把r1所指存储单元的值加载到r0中
立即数寻址
在介绍ARM如何实现立即数寻址之前,先来看一个使用立即数寻址的实例。高级语言
常用立即数寻址来指定一个常量而不是变量,如:
IF I > 25
THEN J = K + 12
常量12和25由立即数寻址指定。
假设I在r0中,J在r1中,K在r2中,可表示为:CMP r0,#25 ; 把I与25比较BLE Exit ; 如果 I <= 25,则退出ADD r1,r2,#12 ; 否则K+12Exit ;
使用条件执行表示:CMP r0,#25 ; I与25比较ADDGT r1,r2,#12 ; 如果I > 25,则J = K + 12
ARM的立即数实现方法
ARM在指令中提供了12位的立即数字段,但不支持值在0~4095之间的12位无符号立即数或
值在-2048~2047之间的12位有符号立即数。实际上ARM提供的是可按2的幂缩放的8位立即
数。
下图,给出了ARM立即数的编码结构,当操作码的第25位为0时,ARM将进行一次移位操
作,当第25位为1时,操作数2字段将编码12位立即数,它被分成两部分:8位立即数和4位
对齐码。
立即数字段中最高4位知道了立即数在32位字中的对齐方式。
如果8位立即数为N,4位对齐码为 n(范围在0~15之间),则立即数的值为N x 22n,因
此ARM提供了一个可按2的幂缩放的8位立即数,这与浮点数的表示和存储方式相似。
下图描述了一个ARM立即数缩放,该图说明了对齐码是如何在32位框架移动立即数的。为
了得到立即数循环右移的位数,必须将对齐码的数字翻倍。
尽管不能直接指定32位立即数,但ARM缩放结构可以表示常量FF00000016,即用8位FF16
左移24位(即右移8位)来表示。
取反传送指令MVN r0,r1,#literal,能够指定一个不必移位且范围在0xFFFFFF00到
0xFFFFFFFF的常量。程序员不用担心如何产生移位常量,这是汇编器的工作。立即数的缩
放不是伪操作。
如,指令MOV r1,#0x0000FF00的二进制代码为E3A01CFF16:
MOV r0,#0xFF
MOV r1,#0x0000FF00
MOV r2,#0xFF000000
以指令MOV r1,#0xFF00为例,立即数编码为CFF16(1100111111112),对齐码为C16或1210。
实际移位位数是这个数的2倍,即2410,通过循环右移来实现。24次循环右移操作等于8次
循环左移操作,即将FF16移位为FF0016,最后来看0xFF000000,将十六进制数左移6次,
相当于将二进制左移24次,不过这里使用循环右移,等价于循环右移8次。要保存的缩放
常数(即对齐码)是移位次数的一半,即4。上图中第6行指令的常量编码为4FF。
寄存器的间接寻址
操作数的地址保存在寄存器中,这种寻址方式叫作寄存器间接寻址,也叫索引寻址或基址
寻址。ARM的立即数偏移量为12位。它确实是12位立即数,不是8位可缩放的值。
什么时候是真正的12位
ARM处理器指定12位常量作为立即操作数(如ADD r0,r1,#123)。常量为8位数并通过4位
对齐码进行放大。
然而,当ARM处理器指定某个立即数偏移量作为索引地址的一部分时,它就是真正的12位
数,如LDR r0,[r1,#123]。
寄存器间接寻址
寄存器间接寻址通过3个读操作来访问一个操作数:
(1)读指令得到指针寄存器
(2)读指针寄存器得到操作数地址
(3)读操作数地址所指的存储器单元得到操作数
寄存器间接寻址可以在运行时修改寄存器的内容,而寄存器中含有指向实际操作数的指针
(地址),因此地址是变量,允许访问如数组、列表、矩阵、向量、表格等数据结构。
下图,通过ARM的加载指令LDR r1,[r0]说明了寄存器间接寻址,指针寄存器r0的值为n,则
指向或引用存储单元n:
LDR r1,[r0]和ADD r0,r0,#4这两个操作的RTL定义:
[r1] <- [[r0]] ; 读取r0所指存储单元的值,[[r0]]就是r0所指存储单元的值
[r0] <- [r0] + 4 ; 指针递增指向下一个存储单元
考虑下面的例子,表格中的7个项分别代表一个星期的每一天。D1代表星期一,D2代表星
期二,等等。如果Di是第i天,则Di+1就表示下一天,从一天移到下一天,只需将索引i加1,
这就是需要变址的原因:
ADR r0,Week ; r0指向数组WeekADD r0,r0,r1, LSL #2 ; r0现在指向r1所含那一天LDR r2,[r0] ; 读取这一天的数据到r2Week DCD ; 第1天的数据DCD ; 第2天的数据.DCD ; 第7天的数据
假定天数的索引为0~6,天数的索引必须乘以4,因为这里的数组是字的数组(每个字4个
字节),连续两个元素的地址之差为4。
字符串是一个很好的使用基于指针寻址的例子。假设要找到某个特定字符在字符串中的位
置,可以写出下面代码。这不是ARM代码,因为还没有介绍字节操作。使用下标表明操作
数的大小:
LDR32 r0,#String ; r0指向StringLoop LDR8 r1,[r0] ; REPEAT 读取字符ADD32 r0,r0,#1 ; 更新字符指针CMP8 r1,#Terminator ; UNTIL 发现终止符(终止符为行结束字符)BNE Loop ;
有些计算机将寄存器间接寻址与指针更新结合在一起,这样指针在被使用之后就可以自动
地指向下一个存储单元。
考虑下面的C代码段:
程序中数0、21、10是汇编期间有立即数寻址方式指定的常量。转为下面的ARM汇编语言
带偏移量的寄存器间接寻址
操作数有效地址是寄存器的内容加上编码在load/store指令的立即数偏移量,这种寻址方式
也叫基址加位移寻址。
下图,用指令LDR r0,[r1,#4]说明,图中有效地址是指针寄存器r1的内容加上偏移量4的和,
即操作数距离指针所指的地址4个字节:
下述代码段说明了如何使用偏移量实现数组访问,偏移量是常量,在运行时不能改变:
Sun EQU 0 ; 一星期中每一天的偏移量Mon EQU 4 Tue EQU 8 .Sat EQU 24 ADR r0,Week ; r0指向数组WeekLDR r2,[r0,#Tue] ; 读取星期二的数据到r2LDR r3,[r0,#Wed] ; 读取星期三的数据到r3ADD r4,r2,r3 ; 星期二的数据与星期三的数据相加STR r4,[r0,#Mon] ; 把结果存放到星期一Week DCD ; 第1天的数据(星期天)DCD ; 第2天的数据(星期一)DCD ; 第3天的数据(星期二)DCD ; 第4天的数据(星期三)DCD ; 第5天的数据(星期四)DCD ; 第6天的数据(星期五)DCD ; 第7天的数据(星期六)
ARM允许指定第二个寄存器作为偏移量,这样就可以使用运行时可以修改的动态偏移量,
如下图:
LDR r2,[r0,r1] ; [r2]<-[[r0] + [r1]]把r0加r1所指存储单元的值加载到r2中
LDR r2,[r0,r1,LSL #2] ; [r2]<-[[r0] + 4 x [r1]]将r1乘4
寄存器r1扩大了4倍。当处理数组时,允许使用一个被缩放的偏移量。如果r0指向数组X且
r1包括索引i,则元素i的地址为X+4i。这样就可以使用指令LDR r2,[r0,r1,LSL #2]访问该元素。
可以通过向基址寄存器增加立即数偏移量或寄存器偏移量来扩展寄存器间接寻址方式。在
ARM术语中,基址寄存器加偏移量的寻址方式叫作前索引,因为是在访问操作数之前把偏
移量加到指针上。指令LDR r0,[r1,#8]指定了前索引寻址方式,操作数的有效地址由[r1]+8给
出。这里,前索引表示偏移量#8在load操作的读阶段访问寄存器之前就被加到基址寄存器
r1上。
前索引寻址方式可以来访问数组X的元素i,如:
ADR r0,X ; 寄存器r0指向数组X
LDR r1,[r0,i] ; 读出元素i
ARM的自动前索引寻址方式
通过将偏移量加到基址寄存器(指针寄存器)上ARM实现了两种自动索引方式。这两种方
式的差别在于基址寄存器递增的时机——要么在访问寄存器之前,要么在之后。
ARM的自动前索引寻址方式是在有效地址后面添加后缀!来表示。如:
LDR r0,[r1,#8]! ; 将寄存器r1+8所指存储单元中的字加载到r0中
; 然后将r1加8以更新指针
RTL定义如下:
[r0] <- [[r1] + 8] 访问地址为基址寄存器r1+8的存储单元
[r1] <- [r1] + 8 加上偏移量更新指针(基址寄存器)
考虑下面两个数组相加的例子:
Len BQU 8 ; 数组长度为8个字ADR r0,A-4 ; 寄存器r0指向数组AADR r1,B-4 ; 寄存器r1指向数组BADR r2,C-4 ; 寄存器r2指向数组CMOV r5,#Len ; 寄存器r5用作循环计数器
Loop LDR r3,[r0,#4]! ; 取出A的元素LDR r4,[r1,#4]! ; 取出B的元素ADD r3,r3,r4 ; 两元素相加STR r3,[r2,#4]! ; 和保存到C中SUBS r5,r5,#1 ; 测试循环是否结束BNE Loop ; 重复直到全部完成
ARM自动后索引寻址方式
自动后索引寻址方式首先访问基址寄存器所指的存储单元中的操作数,然后将基址寄存器
递增。如:
LDR r0,[r1],#8 ; 将r1所指的字加载到r0中,然后完成后索引,即r1加8
后索引把偏移量放在方括号的外面(如[r1],#8),RTL定义为:
[r0] <- [[r1]] 访问基址寄存器r1所指存储单元
[r1] <- [r1] + 8 加上偏移量更新指针(基址寄存器)
程序计数器相对寻址
寄存器r15是一个程序计数器,把r15用作访问操作数的指针寄存器,这种寻址方式叫作程
序计数器(PC)相对寻址。操作数地址由其与当前代码的相对位置确定。这意味着可以将
代码及与之相关的数据移动到存储器中的不同地方,而无需重新计算操作数地址。
假设执行指令LDR r0,[r15,#100],操作数地址距离寄存器r15的内容的相对偏移为100字
节(25个字),因此,操作数位于当前位置偏移100字节处。
ARM的load与store指令编码
访存操作有一条条件执行字段,即操作码的第28~31位,它们可以像其他ARM指令一样条
件执行:
; if(a==b) then x = p else x = qCMP r1,r2 ; if(a==b)LDREQ r3,[r4] ;then x = pLDRNE r3,[r5] ;else x = q
操作码第20位选择数据传送方向,即指令是load还是store
第25位(#位)决定了偏移量是带可选移位的寄存器内容还是12位常量
第22位选择操作数大小,并确定ARM是传送32位字还是8位字节,当字节被加载到32位寄存器中时,
寄存器的第8~31位被置0。
基址寄存器r基址是存储器指针
U位定义了有效地址的计算机是加上还是减去偏移量
W位决定了当前指令结束时基址寄存器是否会被更新,W=1,则会更新基址寄存器
P位控制偏移量是计算机有效地址之前还是在之后被加到基址寄存器上
因为U位决定了对偏移量进行加法还是减法,ARM能使用以下寻址方式:
LDR r0,[r1,+r2] ; 有效地址是[r1] + [r2]
LDR r0,[r1,-r2] ; 有效地址是[r1] – [r2]
下表总结了ARM基于寄存器的寻址方式:
考虑二进制字符串01010111001000100100000100000110表示一条ARM指令,把这条指令分
解,得到下表中的编码,得到的指令就是STRPL r4,[r2,-r6,LSL #2]!