ARM汇编语言.docx
《ARM汇编语言.docx》由会员分享,可在线阅读,更多相关《ARM汇编语言.docx(21页珍藏版)》请在冰豆网上搜索。
![ARM汇编语言.docx](https://file1.bdocx.com/fileroot1/2023-1/14/58a6b942-79d5-4170-a65c-9db041e8b5fd/58a6b942-79d5-4170-a65c-9db041e8b5fd1.gif)
ARM汇编语言
ARM汇编语言
伪操作:
伪操作又称为伪指令,它不像机器指令那样是在程序运行期间由计算机来执行的,它是在汇编程序对源程序汇编期间由汇编程序处理的操作,它们可以完成如数据定义、分配存储区、指示程序结束等功能。
宏指令是一段独立的程序代码,它通过伪操作来定义。
宏通过宏名来调用,并可以设置相应的参数。
宏定义本身不会产生代码,只是在调用它时把宏体插入到源程序中。
a、符号定义伪操作
伪操作
作用
GBLA
声明全局算术变量
GBLL
声明全局逻辑变量
GBLS
声明全局字符串变量
LCLA
声明局部算术变量
LCLL
声明局部逻辑变量
LCLS
声明局部字符串变量
SETA
给全局或局部算术变量赋值
SETL
给全局或局部逻辑变量赋值
SETS
给全局或局部字符串变量赋值
RLIST
为通用寄存器列表定义名称
CN
为协处理器的寄存器定义名称
CP
为协处理器定义名称
DN/SN
为双精度/单精度VFP的寄存器定义名称
FN
为FPA浮点寄存器定义名称
①GBLA、GBLL、GBLS用于声明一个ARM程序中的全局变量,并将其初始化。
GBLA声明一个全局的算术变量,并将其初始化为0;
GBLL声明一个全局的逻辑变量,并将其初始化为{FALSE};
GBLS声明一个全局的字符串变量,并将其初始化为空字符串””。
句法:
variable
GBLX是三种伪操作之一,variable是所说明的全局变量的名称,在其作用范围唯一。
用法:
如果用这些伪操作重新声明已经声明过的变量,则变量的值将被初始化成后一次声明语句中的值。
全局变量的作用范围为包含该变量的源程序。
例子:
GLBAarithmetic;声明全局算术变量arithmetic
arithmeticSETA0xff;向该变量赋值
SPACEarithmetic;引用该变量
GBLLlogical;声明全局逻辑变量logical
logicalSETL{TRUE};向该变量赋值
②LCLA、LCLL、LCLS用于声明一个ARM程序中的局部变量,并将其初始化。
LCLA声明一个局部的算术变量,并将其初始化为0;
LCLL声明一个局部的逻辑变量,并将其初始化为{FALSE};
LCLS声明一个局部的字符串变量,并将其初始化为空字符串””。
句法:
variable
…
用法:
如果用这些伪操作重新声明已经声明过的变量,则变量的值将被初始化成后一次声明语句中的值。
局部变量的作用范围为包含该局部变量的宏代码的一个实例。
例子:
MACRO;声明一个宏
$labelmessage$a;宏的原型
LCLSerr;声明一个局部字符串变量err
errSETS“errorno:
”;向该变量赋值
$label
INFO0,“err”:
CC:
:
STR:
$a;使用该字符串变量
MEND;宏定义结束
③SETA、SETL、SETS用于给一个ARM程序中的变量赋值
SETA给一个算术变量赋值;
SETL给一个逻辑变量赋值;
SETS给一个字符串变量赋值。
句法:
variableexpr
variable是已定义变量的名称,在其作用范围内唯一。
是三种伪操作之一,expr是要赋的值。
在向变量赋值前必须先声明该变量。
④RLIST为一个通用寄存器列表定义名称。
句法:
nameRLIST{list-of-register}
name为寄存器列表名称,list-of-register为通用寄存器列表。
用法:
定义的名称可以在LDM/STM指令中使用。
在LDM/STM指令中,寄存器列表中的寄存器访问次序总是先访问编号较低的寄存器,再访问编号较高的寄存器,不管寄存器列表中各寄存器的排列顺序。
例子:
ContextRLIST{R0-R6,R8,R10,R15};
⑤CN为一个协处理器的寄存器定义名称
句法:
nameCNexpr
name为该寄存器的名称,expr为协处理器编号,数值为0~15。
用法:
CN用于给一个协处理器的寄存器定义名称,方便程序员记忆该寄存器的功能。
例子:
PowerCN6;将协处理器的寄存器6的名称定义为Power
⑥CP为一个协处理器定义名称
句法:
nameCPexpr
name为该协处理器的名称,expr为协处理器的编号,数值为0~15。
用法:
CP用于给一个协处理器定义名称,方便程序员记忆该协处理器的功能。
例子:
DmuCN6;将协处理器6的名称定义为Dmu
⑦DN/SN
DN为一个双精度的VFP寄存器定义名称
SN为一个单精度的VFP寄存器定义名称
句法:
nameDNexpr/nameSNexpr
name为该VFP寄存器的名称。
expr为VFP双精度寄存器编号(0~15)或者单精度寄存器编号(0~15)。
用法:
便于程序员记忆该寄存器的功能。
例子:
heightDN6
widthSN15
⑧FN为一个FPA浮点寄存器定义名称。
句法:
nameFNexpr
name为该浮点寄存器的名称。
expr为该浮点寄存器的编号,数值为0~7。
用法:
FN用于给一个浮点寄存器定义名称,方便程序员记忆该寄存器的功能。
例子:
heightFN6;将浮点寄存器6的名称定义为height
程序中的变量代换
①程序中的变量可通过代换操作取得一个常量。
代换操作符为“$”。
②如果在数字变量前面有一个代换操作符“$”,编译器会将该数字变量的值转换为十六进制的字符串,并将该十六进制的字符串代换“$”后的数字变量。
③如果在逻辑变量前面有一个代换操作符“$”,编译器会将该逻辑变量代换为它的取值(真或假)。
④如果在字符串变量前面有一个代换操作符“$”,编译器会将该字符串变量的值代换“$”后的字符串变量。
b、数据定义(内存分配)伪操作
伪操作
作用
LTORG
声明一个数据缓冲池的开始
MAP
定义一个结构化的内存表的首地址
FIELD
定义一个结构化内存表中的一个数据域
SPACE
分配一块内存单元,并用0初始化
DCB
分配一段字节的内存单元,并用指定数据初始化
DCD/DCDU
分配一段字的内存单元
DCDO
分配一段字的内存单元,并将单元的内容初始化成该单元相对于静态基值寄存器的偏移量
DCFD/DCFDU
分配一段双字的内存单元,并用双精度的浮点数据初始化
DCFS/DCFSU
分配一段字的内存单元,并用单精度的浮点数据初始化
DCI
分配一段字节的内存单元,用指定的数据初始化,指定内存单元中存放的是代码,而不是数据
DCQ/DCQU
分配一段双字的内存单元,并用64位的整数数据初始化
DCW/DCWU
分配一段半字的内存单元,并用指定的数据初始化
①LTORG用于声明一个数据缓冲池的开始
句法:
LTORG
用法:
通常ARM汇编编译器把数据缓冲池放在代码段的最下面,即下一个代码段开始之前,或在END之前。
该指令通常放在无条件分支指令之后,或者子程序返回指令之后,这样处理器就不会错误地将数据缓冲池中的数据当作指令来执行了。
当程序中使用LDFD之类的指令时,数据缓冲池的使用可能越界。
这时可以使用LTORG定义数据缓冲池。
通常大的代码段可以使用多个数据缓冲池。
②MAP用于定义一个结构化的内存表的首地址。
此时,内存表的位置计数器设置成该地址。
句法:
MAPexpr{,base-register}
其中:
expr为数字表达式或者程序中的标号。
当指令中没用base-register时,expr即为结构化内存表的首地址。
base-register为一寄存器。
当指令包括这一项时,结构化内存表的首地址为expr和base-register寄存器值的和。
用法:
MAP和FIELD伪操作配合使用来定义结构化的内存表。
例子:
MAP0x80,R9;内存表的首地址为0x80+R9
③FIELD用于定义一个结构化的内存表的数据域。
句法:
{label}FIELDexpr
其中{label}是可选的,当包括这一项时,label的值为当前内存表的位置计数器{VAR}的值。
处理了这条FIELD伪操作后,VAR的值将加上expr。
expr表示本数据域在内存表中所占的字节数。
用法:
MAP和FIELD伪操作配合使用来定义结构化的内存表结构,MAP定义了首地址,FIELD定义了各数据域的字节长度,并为每个数据域定义了一个标号。
MAP和FIELD仅仅是定义数据结构,它们并不实际分配内存单元。
例子:
定义一个内存表,其首地址为固定地址4096(0x1000),该内存表中包含5个数据域:
consta和constb长度分别为4个字节;x和y长度分别为8个字节;string长度为256字节。
这种内存表称为基于绝对地址的内存表。
MAP4096
constaFIELD4
constbFIELD4
xFIELD8
yFIELD8
stringFIELD256
在指令中,可以这样引用内存表的数据域:
LDRR6,consta
④SPACE用于分配一块内存单元,并用0初始化。
句法:
{label}SPACEexpr
其中{label}是可选的。
expr表示本伪操作分配的内存字节数。
例子:
DatastructSPACE280;分配280字节的内存,并将单元内容初始化为0
⑤DCB用于分配一段字节内存单元,并用伪操作中的expr初始化它。
句法:
{label}DCBexpr{,expr}…
其中{label}是可选的。
expr可以为-128~255的数值或字符串。
例子:
NullstringDCB“Nullstring”,0;构造一个以NULL结尾的字符串。
⑥DCD和DCDU。
DCD用于分配一段字内存单元(分配的内存都是字对齐),并用伪操作中的expr初始化它。
DCDU的不同之处在于分配的内存并不严格对齐。
句法:
{label}DCD{U}expr{,expr}…
其中{label}是可选的。
expr可以为数字表达式或程序中的编号。
用法:
DCD可能在分配的第一个内存单元前插入填补字节来保证分配的内存是字对齐的。
例子:
data1DCD1,5,20;其值分别为1、5、20
data2DCDmemaddr+4;分配一个字单元,其值为程序中的标号memaddr加上4
⑦DCDO用于分配一段字内存单元(分配的内存都是字对齐),并将这个字单元的内容初始化成expr标号基于基址寄存器R9的偏移量。
句法:
{label}DCDOexpr{,expr}…
其中{label}是可选的。
expr可以为数字表达式或程序中的编号。
用法:
为基于静态基址寄存器R9的偏移量分配内存单元。
例子:
IMPORTexternsym
DCDOexternsym;分配一个字单元,其值为标号;externsym基于R9的偏移量。
⑧DCFD和DCFDU。
DCFD用于双精度的浮点数分配字对齐的内存单元,并用伪操作中的fpliteral初始化它。
每个双精度数占据两个字单元。
DCFDU的分配的内存并不严格对齐。
句法:
{label}DCFD{U}fpliteral{,fpliteral}…
其中{label}是可选的。
fpliteral为双精度浮点数。
用法:
DCFD可能在分配的第一个内存单元前插入填补字节来保证分配的内存是字对齐的。
例子:
DCFD1E208,-4E-100
DCFDU10000,-.1,3.1E26
⑨DCFS和DCFSU。
DCFD用于单精度的浮点数分配字对齐的内存单元,并用伪操作中的fpliteral初始化它。
每个单精度数占据1个字单元。
DCFSU的分配的内存并不严格对齐。
句法:
{label}DCFS{U}fpliteral{,fpliteral}…
其中{label}是可选的。
fpliteral为双精度浮点数。
用法:
DCFS可能在分配的第一个内存单元前插入填补字节来保证分配的内存是字对齐的。
例子:
DCFS1E3,-4E-9
DCFSU1.0,-.1,3.1E6
⑩DCI。
在ARM代码中,用于分配一段字对齐的内存单元,并用伪操作中的expr初始化它。
在Thumb代码中,用于分配一段半字对齐的内存单元。
句法:
{label}DCIexpr{,expr}…
用法:
DCI和DCD非常类似,不同之处在于DCI分配的内存中数据被标识为指令,可用于通过宏指令来定义处理指令系统不支持的指令。
例子:
MACRO
Newinst$Rd,$Rm
DCI0xe16f0f10:
OR:
($Rd:
SHL:
12):
OR:
$Rm;这个宏指令将指令newinst定义为相应的机器指令。
DCQ和DCQU。
DCQ用于分配一段8个字节为单位的内存(分配的内存都是字对齐),并用伪操作中的literal初始化它。
DCQU的不同之处在于分配的内存并不严格对齐
句法:
{label}DCQ{U}literal{,literal}…
其中{label}是可选的。
literal为64位数字表达式,取值范围为-263~264-1。
用法:
DCD可能在分配的第一个内存单元前插入多达3个字节的填补字节来保证分配的内存是字对齐的。
例子:
dataDCQ-225,2_101;2_101表示为二进制的101
DCQUnumber+4
DCW和DCWU。
DCW用于分配一段半字内存单元(分配的内存都是半字对齐),并用伪操作中的expr初始化它。
DCWU的不同之处在于分配的内存并不严格半字对齐
句法:
{label}DCW{U}expr{,expr}…
其中{label}是可选的。
expr为数字表达式,取值范围为-32768~65535。
用法:
DCW可能在分配的第一个内存单元前插入1个字节的填补字节来保证分配的内存是半字对齐的。
例子:
Data1DCW-235,num1+8
c、汇编控制定义伪操作
伪操作
作业
IF
ELSE
ENDIF
能够根据条件把一段源代码包括在汇编语言程序内或者将其排除在程序之外。
WHILE
WEND
能够根据条件重复汇编相同的一段源代码。
MACRO
MEND
MEXIT
MACRO标识宏定义的开始,MEND标识宏定义的结束。
MEXIT用于从宏中跳转出去。
用MACRO和MEND定义的一段代码,称为宏定义体。
通过宏名称来调用宏。
①IF、ELSE和ENDIF。
能够根据条件把一段源代码包括在汇编语言内或者将其排除在外。
其中ELSE伪操作是可选的。
例子:
IFVersion=“1.0”
;指令
;伪指令
ELSE
;指令
;伪指令
ENDIF
②WHILE和WEND。
能够根据条件重复汇编相同的或者几乎相同的一段源代码。
例子:
countSETA1
WHILEcount<=4
countSETAcount+1
;代码
WEND
③MACRO及MEND。
MACRO标识宏定义的开始,MEND标识宏定义的结束。
用它们定义一段代码,称为宏定义体。
语法格式:
MACRO
{$label}macromane{$parameter{,parameter}…}
;code
…
;code
MEND
其中,$label在宏指令被展开时,label可被替换成相应的符号,通常是一个标号。
$parameter为宏指令的参数。
macroname为所定义的宏名字。
例子:
MACRO
$labelxmac$p1,$p2
;code
$label.loop1;code;label.loop1为宏定义体的内部标号。
;code
BGE$label.loop1
$label.loop2;code
BL$p1;参数p1为一子程序的名称
BGT$lqabel.loop2
;code
ADR$p2
;code
MEND
;在程序中调用时
abcxmacsubr1,de;通过宏的名称调用宏,宏的标号为abc
④MEXIT用于从宏中跳转出去。
例子:
MACRO
$abcmacroabc$param1,$param2
;code
WHILEcondition1
;code
IFcondition2
;code
MEXIT;从宏中跳转出去
ELSE
;code
ENDIF
WEND
;code
MEND
d、信息报告伪操作
伪操作
作用
ASSERT
报告错误信息
INFO
报告诊断信息
OPT
在源程序中设置列表选项
TTL和SUBT
在列表文件中的每一页开头插入标题和子标题
e、其它伪操作
伪操作
作用
ALIGN
通过添加补丁字节使当前位置满足一定的对齐方式
AREA
定义一个代码段或者数据段
CODE16
CODE32
CODE16告诉编译器后面的指令序列为16位的Thumb指令;CODE32告诉编译器后面的指令序列为32位的ARM指令
END
告诉编译器已经到了源程序结尾
ENTRY
指定程序的入口点
EQU
为数字常量、基于寄存器的值和程序中的标号(基于PC的值)定义一个字符名称
EXPORT/GLOBAL
声明一个符号可以被其他文件引用,相当于声明了一个全局变量
EXTERN
告诉编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可能引用该符号
GET/INCLUDE
将一个源文件包含到当前源文件中,并将被包含的文件在其当前位置进行汇编处理
IMPORT
告诉编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可能引用该符号
INCBIN
将一个文件包含到当前源文件中,被包含的文件不进行汇编处理
KEEP
告诉编译器将局部符号包含在目标文件的符号表中
NOFP
禁止源程序中包含浮点运算指令
REQUIRE
指定段之间的相互依赖关系
RN
为一个特定的寄存器定义名称
ROUT
定义局部变量的有效范围
①ALIGN通过添加补丁字节使当前位置满足一定的对齐方式。
句法:
ALIGN{expr{,offset}}
其中,expr为数据表达式,用于指定对齐方式。
可能的取值是2的次幂,如1、2、4、8等。
如果没有指定expr,则当前位置对齐到下一个字边界处。
Offset为数字表达式。
当前位置对齐到下面形式的地址处:
offset+n*expr。
用法:
在下列情况中,需要特殊的地址对齐方式:
Thumb的宏指令ADR要求地址是字对齐的,而Thumb代码中的地址标号可能不是字对齐的。
这时就要使用伪操作ALIGN4使Thumb代码中的地址标号字对齐
由于有些ARM处理器的CACHE采用了其他对齐方式,如16字节的对齐方式,这时使用ALIGN指定合适的对齐方式可以充分发挥CACHE的性能优势。
LDRD和STRD指令要求内存单元是8字节对齐的。
这样在为LDRD/STRD指令分配的内存单元前要使用ALIGN8实现8字节对齐方式。
地址标号通常自身没有对齐要求。
而在ARM代码中要求地址标号是字对齐的,在Thumb代码中要求半字对齐。
需要使用合适的ALIGN伪操作来调整对齐方式。
例子1:
在AREA伪操作中的ALIGN和ALIGN伪操作中的expr含义是不同的
AREAcacheable,CODE,ALIGN=3;下面的指令时8字节对齐
rout1;code
;code
MOVPC,LR;程序跳转后变成4字节对齐的
ALIGN8;指定下面的指令只8字节对齐
rout2;code
例子2:
将两个字节数据放在同一个字的第一个和第四个字节
AREAoffsetExample,CODE
DCB1
ALIGN4,3
DCB1
例子3:
通过ALIGN伪操作使程序中地址标号字对齐
AREAExample,CODE,READONLY
StartLDRR6,=label11
;code
MOVPC,LR
Label11DCB1
ALIGN
Subroutine1
MOVR5,#0x5
②AREA用于定义一个代码段或者数据段。
句法:
AREAsectionname{,attr}{,attr}…
其中,sectionname为所定义的代码段或者数据段的名称。
如果是数据打头的,则该名称必须用“|”括起来,如|1_datasec|。
还有一些代码段具有约定的名称,如|.text|表示C语言编译器产生的代码或者与C语言库相关的代码段。
attr是该代码段的属性。
在AREA中,各属性间用逗号隔开。
可能的属性有:
ALIGN=expression。
默认的情况下,ELF代码段和数据段是4字节对齐。
Expression可以取0-31的数值,相应的对齐方式是(2expression)字节对齐,如expression=3时为8字节对齐。
ASSOC=section。
指定与本段相关的ELF段。
CODE定义代码段,默认属性为READONLY。
COMDEF定义一个通用段。
该段可以包含代码或者数据。
在各源文件中,同名的COMMON段必须相同。
COMMON定义一个通用段。
该段不包含任何代码和数据,连接器将其初始化为0。
各源文件中同名的COMMON段公用同样的内存单元。
DATA定义数据段。
默认属性为READWRITE。
NOINIT指定本数据段仅仅保留内存单元,而没有将各初值写入内存单元,或者将内存单元值初始化为0。
READONLY指定本段为只读,代码段的默认属性为READONLY。
READWRITE指定本段为可读可写,数据段的默认属性为READWRITE。
用法:
一个大的程序可以包含多个代码段和数据段,一个汇编程序至少包含一个段。
例子:
AREAExample,CODE,READONLY
;code
③CODE16和CODE32。
CODE16告诉编译器后面的指令序列是16位的Thumb指令;CODE32指示32位的ARM指令。
句法:
CODE16