ADS 编译环境下,在ARM 的汇编程序中,有符号定义伪指令、数据定义伪指令、汇编控制伪指令和宏指令以及其他伪指令。
(1)符号定义伪指令
符号定义伪指令用于定义ARM 汇编程序中的变量,对变量进行赋值以及定义寄存器名称。
1)GBLA、GBLL 及GBLS
用途:GBLA、GBLL 及GBLS 伪指令用于声明一个ARM 程序中的全局变量并在默认情况下将其初始化。 其中,GBLA 伪指令声明一个全局的算术变量,并将其初始化成“0”;GBLL 伪指令声明一个全局的逻辑变量,并将其初始化成{FALSE};GBLS 伪指令声明一个全局的字符串变量,并将其初始化成空串“”。
格式:<GBLX> Variable
其中:<GBLX>是GBLA、GBLL 或GBLS 三种伪指令之一;Variable 是全局变量的名称。在其作用范围内必须唯一,即同一个变量名只能在作用范围内出现一次。
示例:
2)LCLA、LCLL 及LCLS
用途:LCLA、LCLL 及LCLS 伪指令用于声明一个ARM 程序中的局部变量,并在默认情况下将其初始化。 其中,LCLA 伪指令声明一个局部的算术变量,并将其初始化成“0”;LCLL 伪指令声明一个局部的逻辑变量,并将其初始化成{FALSE};LCLS 伪操作声明一个局部的串变量,并将其初始化成空串“”。
格式:<LCLX> Variable
其中:<LCLX >是LCLA、LCLL 或LCLS 三种伪指令之一;Variable 是局部变量的名称。在其作用范围内必须唯一,即同一个变量名只能在作用范围内出现一次。
示例:
3)SETA、SETL 及SETS
用途:SETA、SETL 及SETS 伪指令用于给一个ARM 程序中的全局或局部变量赋值。 其中,SETA 伪指令给一个全局或局部算术变量赋值;SETL 伪指令给一个全局或局部逻辑变量赋值;SETS 伪指令给一个全局或局部字符串变量赋值。
格式:<SETX> Variable expr
其中:<SETX>是SETA、SETL 或SETS 三种伪指令之一;Variable 是使用GBLA、GBLL、GBLS、LCLA、LCLL 或LCLS 定义的变量的名称,在其作用范围内必须唯一;expr 为表达式,即赋予变量的值。
示例:
4)RLIST
用途:RLIST 伪操作用于给一个通用寄存器列表定义名称。 定义的名称可以在LDM/STM指令中使用,即这个名称代表了一个通用寄存器列表。 在LDM/STM 指令中,寄存器列表中的寄存器的访问次序总是先访问编号较低的寄存器,再访问编号较高的寄存器;也就是说,编号低的寄存器对应于存储器的低地址,而不管寄存器列表中各寄存器的排列顺序。 但为了编程的统一性,寄存器列表中各寄存器一般按编号由低到高排列。
格式:name RLIST{list of registers}
其中:name 是将要定义的寄存器列表的名称;{list of registers}为通用寄存器列表。
示例:
Reg List RLIST {R0—R5,R8,R10};将寄存器列表{R0—R5,R8,R10}的名称定义为Reg List
(2)数据定义伪指令
数据定义伪指令用于数据缓冲池定义、数据表定义、数据空间分配等,包括以下的伪指令。
1)LTORG
用途:LTORG 用于声明一个数据缓冲池(也称为“文字池”)的开始。 在使用伪指令LDR时,常常需要在适当的地方加入LTORG 声明数据缓冲池,LDR 加载的数据暂时放于数据缓冲池。 当程序中使用LDR 之类的指令时,数据缓冲池的使用可能越界。 为防止越界发生,可以使用LTORG 伪指令定义数据缓冲池。 通常大的代码段可以使用多个数据缓冲池。 ARM 汇编编译器一般将数据缓冲池放在代码段的最后面,即下一个代码段开始之前或END 伪操作之前。 LTORG 伪指令通常放在无条件跳转指令之后或子程序返回指令之后,这样处理器就不会错误地将数据缓冲池中的数据当作指令来执行。
格式:LTORG
示例:
2)MAP
用途:MAP 用于定义一个结构化的内存表的首地址。 此时,内存表的位置计数器{VAR}(汇编器的内置变量)设置成该地址值。 MAP 可以用“^”代替。 MAP 伪指令和FIELD 伪指令配合使用来定义结构化的内存表结构。 其具体使用方法将在FIELD 伪指令中详细介绍。
格式:MAP expr{,base-register}
其中:expr 为数字表达式或是程序中已经定义过的标号。 base-register 为一个寄存器,当指令中没有base-register 时,expr 即为结构化内存表的首地址。 此时,内存表的位置计数器{VAR}设置成该地址值。 当指令中包含这一项时,结构化内存表的首地址为expr 和base-register 寄存器内容的和。
示例:
3)FIELD
用途:FIELD 用于定义一个结构化内存表中的数据域。 FIELD 可以用“#”代替。 MAP 伪指令和FIELD 伪指令配合使用来定义结构化的内存表结构。 MAP 伪指令定义内存表的首地址;FIELD 伪指令定义内存表中各数据域的字节长度,并可以为每一个数据域指定一个标号,其他指令可以引用该标号。
MAP 伪指令中的base-register 寄存器值对于其后所有的FIELD 伪指令定义的数据域是默认使用的,直至遇到新的包含base-register 项的MAP 伪指令。
需要特别注意的是,MAP 伪指令和FIELD 伪指令仅仅是定义数据结构,它们并不实际分配内存单元。
由MAP 伪指令和FIELD 伪指令配合定义的内存表有三种:基于绝对地址的内存表、基于相对地址的内存表和基于PC 的内存表。
格式:{label} FIELD expr
其中:{label}为可选的。 当指令中包含这一项时,label 的值为当前内存表的位置计数器{VAR}的值。 汇编编译器处理了这条FIELD 伪操作后,内存表计数器的值将加上expr。 expr表示本数据域在内存表中所占的字节数。
示例:
4)SPACE
用途:SPACE 用于分配一块连续内存单元,并用“0”初始化。 SPACE 可以用“%”代替。
格式:{label} SPACE expr
其中:{label}是一个标号,是可选的,expr 表示本伪操作分配的内存字节数。
示例:
5)DCB
用途:DCB 用于分配一段字节内存单元,并用伪操作中的expr 初始化。 DCB 可以用“ =”代替。
格式:{label} DCB expr{,expr}
其中:{label}为可选的;expr 可以为-128 ~255 的数值或者为字符串。
示例:
6)DCD(或DCDU)
用途:DCD 用于分配一段字内存单元(分配的内存都是字对齐的),并用伪操作中的expr初始化。 DCDU 与DCD 的不同之处在于,DCDU 分配的内存单元并不严格字对齐。 DCD 和DCDU 一般用来定义数据表格或其他常数。 DCD 可以用“&”代替。
格式:
其中:{label}为可选的标号。 expr 可以为数字表达式或程序中的标号。 内存分配的字节数由expr 的个数决定。
示例:
Datal DCD 4,5,6 ; 分配一个字单元,且是字对齐的,其值分别为4、5 和6
7)DCDO
用途:DCDO 用于分配一段字对齐的字内存单元,并将每个字单元的内容初始化为该单元相对于静态基址寄存器R9 内容的偏移量。
格式:{label} DCDO expr{,expr}…
其中:{label}为可选的标号。 expr 可以为数字表达式或为程序中的标号。 内存分配的字节数由expr 的个数决定。
示例:
8)DCFD 及DCFDU
用途:DCFD 用于为双精度的浮点数分配字对齐的内存单元,并将字单元的内容初始化为双精度浮点数。 每个双精度的浮点数占据两个字单元。 DCFD 与DCFDU 的不同之处在于,DCFDU 分配的内存单元并不严格字对齐。
格式:{label} DCFD {U}fpliteral{,fpliteral}…
其中:{label}为可选的;fpliteral 为双精度的浮点数。
示例:
9)DCFS 及DCFSU
用途:DCFS 用于为单精度的浮点数分配字对齐的内存单元,并将各字单元的内容初始化成fpliteral 表示的单精度浮点数。 每个单精度的浮点数占据一个字单元。 DCFS 与DCFSU 的不同之处在于DCFSU 分配的内存单元并不严格字对齐。
格式:{label} DCFS {U} fpliteral {,fpliteral}…
其中:{label}为可选的标号;fpliteral 为单精度的浮点数。
示例:
10)DCI
用途:在ARM 代码中,DCI 用于分配一段字对齐的内存单元,并用伪指令中的expr 将其初始化;在Thumb 代码中,DCI 用于分配一段半字对齐的半字内存单元,并用伪指令中的expr将其初始化。
格式:{label} DCI expr{,expr}…
其中:{label}为可选的标号;expr 可以为数字表达式。
示例:
11)DCQ 及DCQU
用途:DCQ 用于分配一段以双字(8 字节)为单位的内存,分配的内存要求必须字对齐,并用伪操作中的64 位的整数数据初始化。 DCQU 与DCQ 的不同之处在于,DCQU 分配的内存单元并不严格字对齐。
格式:{label} DCQ{U}{ -}literal{,{ -}literal}…
其中:{label}是一个标号,是可选的。 literal 为64 位的数字表达式,可以选正负号。 其取值范围为0 ~264 -1。 当在literal 前加上“ -”时,literal 的取值范围为-263 ~-1。 在内存中,264 -n 与-n 具有相同的表达形式。 这是因为数据在内存中都是以补码形式表示的。
示例:
12)DCW 及DCWU
用途:DCW 用于分配一段半字对齐的半字内存单元,并用伪指令中的expr 初始化。DCWU与DCW 的不同之处在于DCWU 分配的内存单元并不严格半字对齐。
格式:{label} DCW{U}expr{,expr}…
其中:{label}为可选的标号;expr 为数字表达式,其取值范围为-32 768 ~65 535。
示例:
Data DCW -235,748,2446
DCW num+8
(3)汇编控制伪指令和宏指令
汇编控制伪指令用于条件汇编、宏定义、重复汇编控制等。
1)IF、ELSE 及ENDIF
用途:IF、ELSE 及ENDIF 伪指令能够根据条件将一段源代码包括在汇编语言程序内或将其排除在程序之外,它与C 语言中的if 语句的功能很相似。
格式:
其中,logical expression 是用于控制选择的逻辑表达式。 ELSE 伪操作为可选的。
示例:
2)WHILE 及WEND
用途:WHILE 及WEND 伪操作能够根据条件重复汇编相同的一段源代码,它与C 语言中的while 语句很相似,只要满足条件,将重复汇编语法格式中的指令或伪指令。
格式:
WEND
示例:
3)MACRO、MEND 及MEXIT(www.xing528.com)
用途:MACRO 伪操作标识宏定义的开始,MEND 标识宏定义的结束。 MERIT 用于从宏中跳转出去。 用MACRO 和MEND 定义的一段代码,称为宏定义体,这样在程序中就可以通过宏名多次调用该代码段来完成相应的功能。
格式:
其中:macroname 为所定义的宏的名称;$label 在宏指令被展开时,label 可被替换成相应的符号,通常是一个标号(在一个符号前使用“$”,表示程序被汇编时将使用相应的值来替代“$”后的符号);$parameter 为宏指令的参数,当宏指令被展开时将被替换成相应的值,类似于函数中的形式参数,可以在宏定义时为参数指定相应的默认值。
MEXIT 用于从宏定义中跳转出去。
格式:
MEXIT
(4)其他伪指令
1)CODE16 及CODE32
用途:当汇编源程序中同时包含ARM 指令和Thumb 指令时,使用CODE16 伪指令通知汇编编译器,其后的指令序列为16 位的Thumb 指令;使用CODE32 伪指令通知汇编编译器,其后的指令序列为32 位的ARM 指令。 CODE16 伪操作告知汇编编译器后面的指令序列为16位的Thumb 指令。 但是,CODE16 伪指令和CODE32 伪指令只是告知编译器后面指令的类型,该伪指令本身并不进行程序状态的切换。
格式:
CODE16
CODE32
示例:
在下面的示例中,程序先在ARM 状态下执行,然后通过BX 指令切换到Thumb 状态,并跳转到相应的Thumb 指令处执行。 在Thumb 程序入口处用CODE16 伪操作标识下面的指令为Thumb 指令。
2)EQU
用途:EQU 伪指令为数字常量、基于寄存器的值和程序中的标号(基于PC 的值)定义一个字符名称。
格式:name EQU expr{,type}
其中:expr 为基于寄存器的地址值、程序中的标号、32 位的地址常量或32 位的常量;name为EQU 伪指令expr 定义的字符名称;type 当expr 为32 位常量时,可以使用type 指示expr 表示的数据的类型。 EQU 伪指令的作用类似于C 语言中的#define,用于为一个常量定义字符名称。 EQU 可以用“*”代替。 type 有下面三种取值。
①CODE16 表明该地址处为Thumb 指令
②CODE32 表明该地址处为ARM 指令
③DATA 表明该地址处为数据区
示例:
这里的寄存器是除ARM 中的寄存器以外的寄存器。 例如,外设中的寄存器因为I/O 与存储器是统一编址的。
3)AREA
用途:AREA 伪指令用于定义一个代码段或数据段。 ARM 汇编程序中一般采用分段式设计,一个ARM 源程序至少有一个代码段。 通常可以用AREA 伪指令将程序分为多个ELF 格式的段。 一个大的程序可以包括多个代码段和数据段,一个汇编程序至少包含一个代码段。
格式:AREA sectionname{,attr}{,attr}…
其中:sectionname 为所定义的代码段或数据段的名称。 如果该名称是以数字开头的,则该名称必须用“|”括起来,例如,|1_datasec|。 还有一些代码段具有约定的名称,例如,|.text|表示C 语言编译器产生的代码段或与C 语言库相关的代码段。
attr 是该段的属性。 在AREA 伪操作中,各属性间用逗号隔开。 下面列举所有可能的属性:
①ALIGN=expression。 在默认的情况下,ELF(可执行链接文件,由链接器生成)的代码段和数据段是4 字节对齐的。 expression 可以取0 ~31 的数值,相应的对齐方式为(2expression)字节对齐,如expression=4 时为16 字节对齐。
②ASSOC = section,指定与本段相连的ELF 段。 任何时候链接section 段,也必须包括sectionname 段。
③CODE 定义代码段,默认属性为READONLY。
④COMDEF 定义一个通用的段。 该段可以包含代码或数据。 在其他源文件中,同名的COMDEF 段必须相同。
⑤COMMON 定义一个公用的段。 该段不包含任何用户代码和数据,链接器将其初始化为“0”。 各源文件中同名的COMMON 段公用同样的内存单元,链接器为其分配合适的尺寸。
⑥DATA 定义数据段,默认属性为READWRITE。
⑦NOINIT 指定本数据段仅仅保留了内存单元,而没有将各初始值写入内存单元,或将各内存单元值初始化为“0”。
⑧READONLY 指定本段为只读,代码段的默认属性为READONLY。
⑨READWRITE 指定本段为可读可写,数据段的默认属性为READWRITE。
示例:
下面的伪指令定义了一个代码段,代码段的名称为Example,属性为READONLY。
AREA Example,CODE,READONLY
4)ENTRY
用途:ENTRY 伪指令用于声明程序的入口。 一个程序可以包含多个源文件,而一个源文件中最多只能有一个ENTRY(也可以没有ENTRY),所以,一个程序可以有多个ENTRY,但至少要有一个ENTRY。
用途:ENTRY
5)END
用途:END 伪指令告知编译器已经到了源程序结尾。 每一个汇编源程序都包含END 伪操作,来表示本源程序的结束。
格式:END
示例:
6)ALIGN
用途:ALIGN 伪指令通过添加补丁字节使当前位置满足一定的对齐方式。
格式:ALIGN {expr{,offset}}
其中:expr 为指定对齐方式,可能的取值为2 的次幂,如1,2,4,8 等。 如果伪操作中没有指定expr,则默认当前位置对齐到下一个字边界处。 不指定offset 表示将当前位置对齐到以expr 为单位的起始位置,比如,“ALIGN 8”表示将当前位置以两个字的方式对齐。 如果指定offset,如“ALIGN 4,3”,如果原始位置在0x0001(字节),使用“ALIGN 4,3”以后,当前位置会转到0x0007(0x0004 +3),如图5.1 所示。
图5.1 对齐方式图例
在下面的情况中,需要特定的地址对齐方式:
①Thumb 的伪指令ADR 要求地址是字对齐的,而Thumb 代码中地址标号可能不是字对齐的,这时就要用伪指令“ALIGN 4”,使Thumb 代码中的地址标号字对齐。
②由于有些ARM 处理器的Cache 采用了其他对齐方式,如16 字节的对齐方式,这时使用ALIGN 伪指令指定合适的对齐方式,可以充分发挥该Cache 的性能优势。
③LDRD 及STRD 指令要求内存单元是8 字节对齐的,这样在为LDRD/STRD 指令分配的内存单元前,要使用ALIGN 8 实现8 字节对齐方式。
④地址标号通常自身没有对齐要求,而在ARM 代码中要求地址标号是字对齐的,在Thumb 代码中要求半节对齐。 这样需要使用合适的ALIGN 伪操作来调整对齐方式。
ALIGN 伪指令示例1:
在AREA 伪指令中使用ALIGN 与单独使用ALIGN 时,伪指令中expr 含义是不同的,如下面例子中所示。
ALIGN 伪指令示例2:
将两个字节数据放在同一个字的第一个字节和第四个字节中。
ALIGN 伪指令示例3:
在下面的例子中通过ALIGN 伪指令使程序中地址标号字对齐。
7)EXPORT 及GLOBAL
用途:EXPRORT 伪指令用于声明一个源文件中的符号,使得该符号可以被其他源文件引用,相当于声明了一个全局变量。 GLOBAL 是EXPORT 的同义词。
格式:
其中,symbol 为声明的符号的名称,它是区分大小写的。 [WEAK]选项声明其他的同名符号优先于本符号被引用。
示例:
8)IMPORT
用途:IMPORT 伪指令通知编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的。 在本源文件中,可能引用该符号,而且无论本源文件是否实际引用该符号,该符号都将被加入本源文件的符号表中。
使用IMPORT 伪指令声明一个符号是在其他源文件中定义的。 如果链接器在链接处理时不能解析该符号,而且IMPORT 伪指令中没有指定[WEAK]选项,则链接器将会报告错误;如果链接器在链接处理时不能解析该符号,而IMPORT 伪指令中指定了[WEAK]选项,则链接器将不会报告错误,而是进行下面的操作:
①如果该符号被B 或BL 指令引用,则该符号被设置成下一条指令的地址,该B 或BL 指令相当于一条NOP 指令。 例如B sign,sign 不能被解析,则该指令被忽略为NOP 指令,继续执行下面地址的指令,也就是将sign 理解为下一条指令的地址。
②其他情况下该符号被设置为“0”。
格式:IMPORT symbol {[WEAK]}
其中:symbol 为声明的符号的名称,它是区分大小写的。 [WEAK]指定这个选项后,如果symbol 在所有的源文件中都没有被定义,编译器也不会产生任何错误信息,同时编译器也不会到当前没有被INCLUDE 进来的库中去查找该符号。
9)EXTERN
用途:EXTERN 伪指令通知编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可能引用该符号。 这与IMPORT 伪操作的作用相同,不同之处在于,如果本源文件没有实际引用该符号,该符号都将不会被加入到本源文件的符号表中。
使用EXTERN 伪指令声明一个符号是在其他源文件中定义的。 如果链接器在链接处理时不能解析该符号,而EXTERN 伪指令中没有指定[WEAK]选项,则链接器将会报告错误。如果链接器在链接处理时不能解析该符号,而EXTERN 伪指令中指定了[WEAK]选项,则链接器将不会报告错误,而是进行下面的操作:
a.如果该符号被B 或BL 指令引用,则该符号被设置成下一条指令的地址,该B 或BL 指令相当于一条NOP 指令。
b.其他情况下该符号被设置为“0”。
格式:EXTERN symbol {〔WEAK〕}
其中,symbol 为声明的符号的名称,它是区分大小写的。 [WEAK]指定该选项后,如果symbol 在所有的源文件中都没有被定义,编译器也不会产生任何错误信息,同时编译器也不会到当前没有被INCLUDE 进来的库中去查找该符号。
10)GET 及INCLUDE
用途:GET 伪指令将一个源文件包含到当前源文件中,并将被包含的文件在其当前位置进行汇编处理。 INCLUDE 是GET 的同义词。
通常可以在一个源文件中定义宏,用EQU 定义常量的符号名称,用MAP 和FIELD 定义结构化的数据类型,这样的源文件类似于C 语言中的“.h”文件。 然后用GET 伪指令将这个源文件包含到它们的源文件中,类似于在C 源程序的“include *.h”。
编译器通常在当前目录中查找被包含的源文件,可以使用编译选项I 添加其他的查找目录。 同时,被包含的源文件中也可以使用GET 伪指令,即GET 伪指令可以嵌套使用。 如在源文件A 中包含了源文件B,而在源文件B 中包含了源文件C,编译器在查找C 源文件时将把源文件B 所在的目录作为当前目录。
GET 伪指令不能用来包含目标文件。 包含目标文件需要使用INCBIN 伪操作。
格式:
其中:filename 为被包含的源文件的名称,这里可以使用路径信息。 注意:路径信息中可以包含空格。
示例:
11)INCBIN
用途:INCBIN 伪指令将一个文件包含到当前源文件中,被包含的文件不进行汇编处理。通常可以使用INCBIN 将一个可执行文件或任意的数据包含到当前文件中。 被包含的执行文件或数据将被原封不动地放到当前文件中。 编译器从INCBIN 伪指令后面开始继续处理。
格式:INCBIN filename
其中,filename 为被包含的文件的名称,这里可以使用路径信息。 注意:这里所包含的文件名称及其路径信息中都不能有空格。
示例:
12)RN
用途:RN 伪指令用于给一个特定的寄存器定义名称,方便记忆该寄存器的功能。
格式:name RN expr
其中:expr 为某个寄存器的编码;name 为本伪指令给寄存器expr 定义的名称。
示例:
13)ROUT
用途:ROUT 伪指令用于定义局部变量的有效范围。 当没有使用ROUT 伪指令定义局部变量的作用范围时,局部变量的作用范围为其所在的段。 ROUT 伪指令作用的范围为本ROUT 伪指令和下一个ROUT(指同一个段中的ROUT 伪指令)伪指令之间。 若只有一个ROUT,则局部标号的作用范围在ROUT 与段结束伪指令(END)之间。
格式:{name} ROUT
其中:name 为所定义的作用范围的名称。
示例:
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。