1. MOV/MVN
可以直接装载一些特定范围的32位值到寄存器中,这些值包括:
(1) 8位常量,即0--255
(2) 8位常量右移偶数位
(3) MVN可以处理(1)(2)中值的按位取反值
如果MOV/MVN指令中给出的立即数常量不在上述范围内,则汇编器会报错。
2. LDR Rd,=字面数值常量
可以装载任何32位值到寄存器中,汇编器先试图用MOV/MVN指令来处理给出的字面数值常量;如果不能处理,再用文字池来处理,即把常量放在文字池中,然后从文字池中取得所需的常量
3. ADR 指令计算给定的PC相关的表达式相对于PC的偏移量,然后试图生成一条指令来进行地址装载,可以处理的地址范围是255字节(非字对齐的地址)或者1020字节(字对齐的地址,255字)以内。如果给定的表达式表示的地址相对于PC的偏移量不在这个范围内,则汇编器会报错。
4. ADRL 指令计算给定的PC相关的表达式相对于PC的偏移量,然后试图生成两条指令来进行地址装载,可以处理的地址范围是64K字节(非字对齐的地址,255*255)或者256K字节(字对齐的地址,64K*4)以内。如果给定的表达式表示的地址相对于PC的偏移量不在这个范围内,则汇编器会报错。
5. LDR Rd,=相对于PC的表达式
指令会计算表达式表示的地址,然后在文字池中放入这个地址值,从文字池中加载地址。注意:汇编后的代码总是从文字池加载地址,不会用相对于PC的偏移量来表示地址。这样执行时间会较长(需要访问文字池),所以应该尽量使用ADR或者ADRL,只有在不能使用ADR和ADRL时,才使用LDR。
6. 关于调试
发现文档中说的用armsd进行调试的方法,对本章的只有汇编代码的程序不管用,大概是armsd的版本问题。但这一章涉及的程序都可以用ADS 1.2版本中的AXD调试器进行调试,还是图形界面的,比较好用。如果要在调试器中看到源代码,需要的armcc或者armasm的命令行中加入-g参数,表示需要调试信息。
5 装载常量到寄存器
5.1 为什么装载常量是个问题?
因为所有的ARM指令都是32位长的,而且不把指令流作为数据使用,所以单个指令是没有办法在不从内存加载数据的情况下,把任何32位立即数常量装载到寄存器中的。
虽然数据装载可以把任何32位值装入寄存器中,但还有更直接,也更有效的方法来装载很多常用的常量。
5.2 使用MOV/MVN直接装载
MOV指令可以直接把8位常量(0--255)装载到寄存器中;MVN可以把这些值的按位取反值(0xFFFFFF00到0xFFFFFFFF)装载到寄存器中。
把MOV和MVN与桶形移位器结合使用可以构造更多的常量。可以构造的常量都是8位值循环右移偶数位的结果。例如:
0--255 0--0xFF 不移位
256,260,264,...,1016,1020 0x100--0x3FC之间步进4,循环右移30位
1024,1040,1056,...4080 0x400--0xFF0之间步进16,循环右移28位
等等,以及这些值的按位取反值。可以直接使用指令把这些常量装载到寄存器中:
MOV r0,#0xFF ;r0=255
MOV r0,#0x1,30 ;r0=1020
MOV r0,#0xFF,28 ;r0=4080
然而,把常量转化成这种形式是不太容易的。汇编器会试图进行这种转化,如果转化不能进行,汇编器会报错。
下面的例子展示了汇编器是怎样进行这种转化的。左边的列是用户输入的ARM指令,右边是汇编器试图进行的转换。
MOV r0,#0 ========> MOV r0,#0
MOV r1,#0xFF000000 ========> MOV r1,#0xFF000000
MOV r2,#0xFFFFFFFF ========> MVN r2,#0
MVN r0,#1 ========> MVN r0,#1
MOV r1,#0xFC000003 ========> MOV r1,#0xFF,6
MOV r2,#0x03FFFFFC ========> MVN r2,#0xFF,6
5.3 使用LDR Rd,=字面数常量进行直接装载
汇编器提供不同于MOV和MVN的,不需要数据处理操作的,可构造任何32位数值常量的方法,这就是LDR Rd,=指令。
如果LDR Rd,=指令中给出的常量可以用MOV或者MVN构造,汇编器将使用MOV或者MVN,否则将生成一个带相对PC地址的LDR指令来从文字池中读取所需的常量。文字池是一块为常量分配的内存。通常,在每个END伪指令后面会有一个文字池。然而在较大的程序中,它却可能是不能访问到的(因为LDR指令中的偏移量是12位值,只能表示4KB的范围)。这时可以使用LTORG伪指令。
当LDR Rd,=指令要访问文字池中的常量时,汇编器先检查在当前文字池是否可以访问到,池中是否有所需要的常量。如果是,则对已存在的常量进行编址,否则试图把常量放到下一个文字池中。如果下一个文字池不可访问(因为不存在或者到它的距离超过4KB),则汇编器会报错,这时应该在LDR Rd,=指令后较近的地方放置一个LTORG伪指令。
请看下面的例子(作为注释列出的指令是汇编器产生的):
AREA Loadcon2,CODE
ENTRY
BL func1
BL func2
SWI 0x11
func1
LDR r0,=42 ======> MOV r0,#42
LDR r1,=0x55555555 ======> LDR r1,[pc,#offset 到文字池1]
LDR r2,=0xFFFFFFFF ======> MVN r2,#0
MOV pc,lr
LTORG ======> 文字池1包含字面常量0x55555555
func2
LDR r3,=0x55555555 ======> LDR r3,[pc,#offset 到文字池1]
;LDR r4,=0x66666666 ;注释掉这一句会发生错误,因为文字池2不可访问(距离超过4KB)
MOV pc,lr
LargeTable % 4200 ;从当前位置清除4200字节的内存,从而让END后面的文字池距离超过4KB
END
注意文字池必须放置在代码段外面,否则处理器会把它当作指令来执行。
6 装载地址到寄存器
装载某个地址到寄存器的操作是很常见的,例如装载代码段中的字符串常量或者跳转表的起始地址到寄存器中。然而由于ARM代码是可重定位的,以及可以直接装载到寄存器中的值是有限的,这时不能使用绝对地址。这时应该用相对于当前PC的偏移量来表示地址,可以直接用当前PC和合适的偏移量来表示,或者可以从文字池中装载。
6.1 ADR和ADRL伪指令
从效率方面考虑,不需要内存访问的地址装载是很重要的,为此汇编器提供了ADR和ADRL伪指令。ADR和ADRL接受一个PC相关的表达式(同一代码段中的标签)并计算到达指定地方的偏移量。
ADR试图用与LDR Rd,=指令相同的机制来生成单个指令进行地址装载操作。如果指定地址不能在单个指令中构造,汇编器会报错。通常对于非字对齐的地址,偏移量范围是255字节以内;对于字对齐的地址,偏移量范围是1020字节(255字)。
ADRL试图用两条数据处理指令进行地址装载操作。即使可以用单个指令完成操作,第二条冗余的指令也还是会生成。如果不能用两条指令完成操作,汇编器会报错,这时LDR Rd,=可能是最好的选择。通常对于非字对齐的地址,ADRL可以处理的偏移量范围是64K字节以内;对于字对齐的地址可以处理的范围是256K字节。
请看下面的例子:
AREA Loadcon3,CODE
ENTRY
Start
ADR r0,Start ;=====> SUB r0,pc,#pc到Start的偏移量(8)
ADR r1,DataArea ;=====> ADD r1,pc,#pc到DataArea的偏移量(8)
;ADR r2,DataArea + 4300 ;=====> 偏移量不能用ADD的第二个操作数表示,会报错
ADRL r3,DataArea + 4300 ;=====> ADD r2,pc,#pc offset1(4096)
; ADD r2,pc,#pc offset2(208)
SWI 0x11
DataArea % 8000
END
分析:
(1) ADR r0,Start =====> SUB r0,pc,#8
ADR r1,DataArea =====> ADD r1,pc,#8
ARM采用流水线机制,当前指令地址等于PC-8
(2) ADRL r3,DataArea + 4300
偏移地址为4304,因为ADRL是要被替换成两天指令的,在执行第一条指令的时候,PC指向SWI 0x11指令,则DataArea相对于PC的偏移为+4,再加上4300,最终偏移地址为4304,可以表示成1024*4+208,所以ADRL分解成两条指令后的偏移地址分别为4096和208.
6.2 LDR Rd,=相对于PC的表达式
与数常量一样,LDR Rd,=也可以处理相对于PC的表达式,如标签。即使可以用ADD或者SUB来构造所需的地址,也还是会生成LDR指令来装载相对于PC的表达式。
AREA Loadcon3,CODE
ENTRY
Start
LDR r0,=Start ;=====> LDR r0,[文字池中表示Start地址的常量的地址]
LDR r1,=DataArea ;=====> LDR r1,[文字池中表示DataArea地址的常量的地址]
LDR r3,=DataArea + 4300 ;=====> LDR r1,[文字池中表示DataArea地址的常量的地址]
SWI 0x11
LTORG
DataArea % 8000
END
分析:这段代码与6.1节中的不同之处在于用LDR替换ADR或者ADRL,这样可以看出二者的差别来:
- ADR和ADRL用相对于PC的偏移量表达式来装载地址,处理得地址范围有限,但可以节省运行时间;
- LDR总是在文字池中存储所需的地址值,然后从文字池中装载地址,需要进行内存访问,运行时间较长
6.3 装载地址到寄存器中的示例
下面的程序含有函数strcpy,它把字符串从一个内存地址复制到另一个地址。传入函数的两个参数是:源字符串的地址和目标地址。表示字符串结束的空字符也会被复制。
AREA StrCopy,CODE
ENTRY
main
ADR r1,srcstr
ADR r0,dststr
BL strcopy
SWI 0x11
srcstr DCB "This is my first(source) string",0
dststr DCB "This is my second(destination) string",0
ALIGN
strcopy
LDRB r2,[r1],#1
STRB r2,[r0],#1
CMP r2,#0
BNE strcopy
MOV pc,lr
END