1. ARM汇编程序的结构
1.1 段
ARM的汇编语言程序由段组成,段是相对独立的指令或数据单位,每个段由AREA伪指令定义,并定义段的属性:READONLY(只读)或READWRITE(读写)。AREA用于定义一个代码段或数据段,若段名以数字开头则该段名需要用“|”括起来,如“1_data”。
在一个汇编程序中,至少包含一个或多个代码段;0个或多个初始化数据段;0个或多个未初始化数据段。
如汇编程序Hello.S
:
@ Hello.S
/**
* 1.代码段
* 由CODE属性定义,READONLY表示只读
*/
AREA |.text|,CODE,READONLY
ENTRY
EXPORT sqr
EXPORT num
sqr
MUL r1, r0, r0
MOV r0, r1
MOV lr, pc
/**
* 2.数据段
* 由DATA属性定义,READWRITE表示读写
*/
AREA |.data|,DATA,READWRITE
num
DCD 10
/**
* 3.未初始化数据段
* 由NOINIT属性定义,READWRITE表示读写
*/
AREA |.bass|,NOINIT,READWRITE
data
SPACE 1024
END
1.2 标识符
在汇编程序里标识符用于表示指令或数据的地址,如Hello.S
中的sqr和num。局部标识符是0~99的十进制数,局部标识符的引用格式为:
%{F|B}{A|T} N(0~99)
如:
2
CMP r3, r1
STRCC r2, [r3], #4
BCC %B2
其中,
- F——只向前(forwards)搜索局部标识符
- B——只向后(backwards)搜索局部标识符
- A——在所有的宏里搜索局部标识符
- T——只在当前宏里搜索局部标识符
如果F和B都没指定,那么搜索的顺序先向后再向前;如果A和T都没指定,那么搜索的顺序只从但前宏向高层宏搜索。
局部标识符的作用范围在当前ROUT和下一个ROUT之间,如:
LocalStart ROUT
...
1
...
2
...
LocalEnd ROUT
所有的标识符必须在一行开头顶格写不能有空格,标识符后不能加“:“。ARM汇编器对标识符的大小写敏感,标识符大小写要一致。
1.3 程序入口
在C语言中程序的入口是main,而在ARM汇编语言中,用ENTRY来表示程序的入口。在一个源文件中只能指定一个入口,但在一个完整的项目中至少有一个入口,可以有多个。当有多个入口时,程序的真正入口点由链接器通过entry参数指定。
注意:在ARM汇编语言里,用ENTRY(大小写均可)来表示程序的入口。
在用armlink链接时,可以用参数entry来指定入口。
1.4 程序出口
在汇编源文件里,用END来表示汇编源程序的结束。
1.5 包含其它汇编源文件
GET将一个源文件包含到当前源文件中,并将被包含的源文件在当前位置展开进行汇编处理。INCLUDE和GET有相同的作用。GET/INCLUDE只能用于包含源文件,包含其它类型的文件(如目标文件或数据文件)则需要用到INCBIN伪指令。
1.6 引用外部标识符
IMPORT告诉编译器它后面的这个标识符要在当前源文件中使用,但是在其它源文件中定义的。如果在链接时找不到该标识符,链接就会报错:
Error:L6218E:Undefined sysmbol xxxx (referred from param.o)
如果在IMPORT时使用WEAK参数,在链接时即使找不到也不会报错,如:
IMPORT printff, WEAK
如果该标识符是B或BL的地址,如果链接时找不到,B或BL就无处可跳,那么B或BL指令就是一条空指令:NOP。
除B或BL的其它情况如果链接时找不到,标识符的值会被置成0。
2. ARM汇编程序的常量、变量
常量
- 数字常量:
num SETA 100
、ram_start DCD 0xC000000
、LDR R1, =&1000FFFF
- 字符串常量:
DCB “Hello,World”, CR
- 逻辑常量:{TRUE}、{FALSE}
- 字符常量:
LDR R4, = #'B'
变量的定义和赋值 - 数字变量:
LCLS num_1
、GBLA num_g
- 字符串变量:
LCLS s_l
、GBLS s_g
- 逻辑变量:
LCLL debug_l
、GBLL debug_g
变量替换
变量的替换通过操作符“$”来实现。
3. ARM汇编程序的运算符和表达式
数字表达式
- 算术运算符:加、减、乘、除
- 移位运算符:ROL、ROR、SHL、SHR
- 逻辑运算符:AND、OR、NOT、EOR
逻辑表达式
字符串表达式 -
LEN: 字符串长度
: LEN : S
-
CHA: 0~255的整数转换成一个字符
: CHA : M
-
STR: 转换成一个字符串
: STR : N
-
LEFT: 返回某个字符串左端的一个字符串
S : LEFT : N
-
RIGHT: 返回某个字符串右端的一个字符串
S : RIGHT : N
-
CC: 将两个字符串连成一个字符串
S1 : CC : S2
-
DEF: 用于判断是否定义了某个符号
: DEF : S
4. ARM汇编程序的数据定义
数据定义的目的是为特定的数据分配存储单元,同时对分配的存储单元进行初始化。
LTORG
LTORG用于声明一个文字池(literal pool)。
文字池是什么?
它是镶嵌在代码中的一段存储空间,用来存放常量。
用它来做什么?
文字池不能远离LDR指令,它必须在LDR指令前后4KB地址范围。
编译器会在每一个段的末尾放一个缺省的文字池,如果这个段很长这个缺省文字池和LDR的距离可能会超过4KB,那么LDR就失去了装载的功能。这种情况下就要自己在适当的地方加入新的LTORG来生命一个新的文字池,当然,条件时LDR的周围。
DCB、DCW、DCD和SPACE
DCB:用于分配一片连续的字节存储单元,并用指定的表达式初始化。可用“=”代替。
str DCB "This is a test"
DCW:用于分配一片连续的半字存储单元,并用指定的表达式初始化。
Halfword DCW 1,2,3
DCD:用于分配一片连续的字存储单元,并用指定的表达式初始化。
word DCD 4,5,6
SPACE:用于分配一片连续的存储区域并初始化0。可用“%”代替。
space SPACE 100
MAP和FILED
MAP:用于定义一个结构化内存表的首地址。MAP可用“^”代替。
MAP 0x100, R9 @ 内存表的首地址为:R9 + 0x100
FIELD:用于定于一个机构话内存表的数据域。FIELD可用“#”代替。语法格式如下:
{label} FELD expr
其中lable是可选的,当指令中包含这一项时,label的值为当前内存表的位置计数器{VAR}的值。expr表示本数据域在内存中所占的字节数,当汇编编译器处理了FIELD伪操作后,内存表计数器的值将加上expr。
5. ARM汇编程序的控制结构
选择结构
IF 逻辑表达式
指令1
ELSE
指令2
ENDIF
循环结构
WHILE 逻辑表达式
指令
WEND
6. ARM汇编程序实例
(1)计算一个以0结束的字符串包含的字符个数
PRESERVE8 @ 伪指令指示当前文件保持堆栈为 8 字节对齐
AREA str, CODE, READONLY
ENTRY
start
LDR R0, =val1
MOV R1, #-1
count
ADD R1, R1, #1 @ R1 = R1 + 1
LDRB R2, [R0], #1 @ 读取字节数据,源地址加 1
CMP R2, #0
BNE count
STR R1, rel
SWI &11
AREA data1, DATA @ 数据段1
val1
DCB "This is a example", 0
ALIGN
AREA data2, DATA @ 数据段2
rel
DCD 0
END
(2)把字符串前面的0用空格替换
PRESERVE8
AREA zero, CODE, READONLY @ 段zero
blank EQU '' @ EQU 伪指令为数字常量,基于寄存器的值和程序中的标号定义一个名称。*与EQU同义
zero EQU '0' @ EQU 伪指令的作用类似于 C 语言中的#define
ENTRY
/**
* 读取字符串的地址
* 把空格和0装载到寄存器
*/
start
LDR R0, =data
MOV R1, #zero
MOV R3, #blank
/**
* 读取字符串的字符
* 判断是否为0
*/
replace
LDRB R2, [R0}, #1
CMP R2, R1
BNE done /* 不为0,直接中断 */
/**
* 为0,把空格装载到0的位置,然后跳转循环
*/
SUB R0, R0, #1
STRB R3, [R0]
ADD R0, R0, #1
BAL replace
done
SWI &11
AREA data, DATA @ 段data
val1 DCB "00000001"
ALIGN
END
7. 伪操作
伪操作不像机器指令那样在计算机运行期间由机器执行,它是在源程序汇编期间由汇编程序处理的。
7.1 符号定义伪操作
符号定义(Symbol definition)伪操作主要用于定义变量定义寄存器名称等。包括以下伪操作:
- GBLA、GBLL和GBLS 声明全局变量
- LBLA、LBLL和LBLS 声明局部变量
- SETA、SETL和SETS 给变量赋值
- RLIST 为通用寄存器列表定义名称
- CN 为协处理器的寄存器定义名称
- CP 为协处理器定义名称
- DN和SN 为VFP的寄存器定义名称
- FN 为FPA的浮点寄存器定义名称
7.2 数据定义伪操作
数据定义(Data definition)伪操作包括:
- LTORG 声明一个数据缓冲池的开始
- MAP 定义一个结构化的内存表的首地址
- FIELD 定义结构化的内存表中的一个数据域
- SPACE 分配一块内存单元,并用0初始化
- DCB 分配一段字节的内存单元,并用执行的数据初始化
- DCD及DCDU 分配一段字的内存单元,并用指定的数据初始化
- DCDO 分配一段字的内存单元,并将该单元的内容初始化成该单元相对于静态基值寄存器的偏移量
- DCFD及DCFDU 分配一段双字的内存单元,并用双精度的浮点数据初始化
- DCFS及DCFSU 分配一段字的内存单元,并用单精度的浮点数据初始化
- DCI 分配一段字节的内存单元,用指定的数据初始化,指定内存单元中存放的是代码,而不是数据
- DCQ及DCQU 分配一段双字的内存单元,并用64位的整数数据初始化
- DCW及DCWU 分配一段半字的内存单元,并用指定的数据初始化
- DATA 在代码段中使用数据。现已不再使用,只用于向前兼容。
7.3 汇编控制伪操作
汇编控制(Assembly control)伪操作包括:
- IF、ELSE及ENDIF
- WHILE及WEND
- MACRO及MEND
- MEXIT
7.4 框架描述伪操作
栈中数据描述伪操作主要用于调试。
7.5 信息报告伪操作
信息报告(Reporting)伪操作如下:
- ASSERT
- INFO
- OPT
- TTL及SUBL
7.6 其它伪操作
这些杂类的伪操作包括:
- ALIGN
- AREA
- CODE16及CODE32
- ENTRY及END
- EQU
- EXPORT及GLOBAL
- EXTERN
- GET及INCLUDE
- IMPORT
- INCBIN
- KEEP
- NOFP
- REQUIRE
- REQUIRE8及PRESERVE8
- RN
- ROUT
8. 伪指令
伪指令在汇编编译器对源程序进行汇编处理时,被替换成对应的ARM或者Thumb指令(序列)。ARM伪指令包括:ADR、ADRL、LDR、NOP。
- ADR:小范围的地址读取伪指令。该指令将基于PC的地址值或基于寄存器的地址值读取到寄存器中。
- ADRL:中等范围的地址读取伪指令。比ADR伪指令可以读取更大范围的地址。
- LDR:大范围的地址读取伪指令。将一个32位的常数或者一个地址值读取到寄存器中。
- NOP:空操作伪指令。在汇编时将被替换成一个空操作。