ARM汇编程序设计之:汇编语言文件格式
扫描二维码
随时随地手机看文章
ARM(Thumb)汇编语法语句格式如下所示。
{symbol}{instruction|directive|pseudo-instruction}{;comment}
①symbol
程序符号。通常为地址标号(label)。在指令和伪指令中通常为标号;在一些伪操作中符号可能是变量或常数。详见ARM伪操作一节。
在书写中,符号必须从一行的行头开始,前面不能包含空格或制表符tab。
②instruction
ARM或Thumb指令。
③directive
伪操作。详见ARM伪操作一节。
④pseudo-instruction
ARM伪指令。详见ARM伪指令一节。
⑤comment
语句注释。注释以分号(;)开头,注释的结尾即为一行的结尾。为了程序清晰易读,注释也可以单独占用一行。汇编器在对程序进行汇编时忽略注释。
在汇编语言程序设计中,每一条指令的助记符可以全部用大写或全部用小写,但不允许在一条指令中大、小写混用。
同时,如果一条语句太长,可将该长语句分为若干行来书写,在行的末尾用“\”表示下一行与本行为同一条语句。
10.3.2ARM汇编语言中的符号在汇编语言程序设计中,经常使用各种符号代替地址(addresses)、变量(variables)和常量(constants)等,以增加程序的灵活性和可读性。尽管符号的命名由编程者决定,但并不是任意的,必须遵循以下的约定。
(1)符号区分大小写,同名的大、小写符号会被编译器认为是两个不同的符号。
(2)符号在其作用范围内必须惟一。
(3)自定义的符号名不能与系统的保留字相同。其中保留字包括系统内部变量
(builtinvariable)和系统预定义(predefinedsymbol)的符号。
(4)符号名不应与指令或伪指令同名。如果要使用和指令或伪指令同名的符号要用双斜杠“||”将其括起来,如“||ASSERT||”。
注意
虽然符号被双斜杠括起来,但双斜杠并非符号名的一部分。
(5)局部标号以数字开头,其他的符号都不能以数字开头。
1.变量(variable)程序中的变量是指其值在程序的运行过程中可以改变的量。ARM(Thumb)汇编程序所支持的变量有三种。
·数字变量(numeric)。
·逻辑变量(logical)。
·字符串变量(string)。
数字变量用于在程序的运行中保存数字值,但注意数字值的大小不应超出数字变量所能表示的范围。
逻辑变量用于在程序的运行中保存逻辑值,逻辑值只有两种取值情况:真({TURE})和假({FALSE})。
字符串变量用于在程序的运行中保存一个字符串,注意字符串的长度不应超出字符串变量所能表示的范围。
在ARM(Thumb)汇编语言程序设计中,可使用GBLA、GBLL、GBLS伪指令声明全局变量,使用LCLA、LCLL、LCLS伪指令声明局部变量,可使用SETA、SETL和SETS对其进行初始化。
2.常量(constants)程序中的常量是指其值在程序的运行过程中不能被改变的量。ARM(Thumb)汇编程序所支持的常量有数字常量、逻辑常量和字符串常量。
数字常量一般为32位的整数,当作为无符号数时,其取值范围为0~232−1,当作为有符号数时,其取值范围为−231~231−1。汇编器认为−n和232−n是相等的。对于关系操作,如比较两个数的大小,汇编器将其操作数看作无符号的数,也就是说“0>−1”,对汇编器来说取值为“假({FLASE})”。
逻辑常量只有两种取值情况,真或假。
字符串常量为一个固定的字符串,一般用于程序运行时的信息提示。
3.程序中的变量代换汇编语言中的变量可以作为作为一整行出现在汇编程序中,也可以作为行的一部分使用。
如果在数字变量前面有一个代换操作符“$”,编译器会将该数字变量的值转换为十六进制的字符串,并将该十六进制的字符串代换“$”后的数字变量。
如果在逻辑变量前面有一个代换操作符“$”,编译器会将该逻辑变量代换为它的取值(真或假)。
如果在字符串变量前面有一个代换操作符“$”,编译器会将该字符串变量的值代换“$”后的字符串变量。
如果程序中需要字符“$”,则可以用“$$”来表示。汇编器将不进行变量替换,而是将“$$”作为“$”。
下面的两个例子说明了变量替换的过程。
;直接的变量替换
GBLSadd4ff
;
add4ffSETS"ADDr4,r4,#0xFF" ;给变量add4ff赋值
$add4ff.00 ;引用变量
;codes
ADDr4,r4,#0xFF00
;有特殊符号的变量替换
GBLSs1
GBLSs2
GBLSfixup
GBLAcount
;
countSETA14
s1SETS"a$$b$count" ;s1=a$b0000000E
s2SETS"abc"
fixupSETS"|xy$s2.z|" ;fixup=|xyabcz|
|C$$code|MOVr4,#16 ;label=C$$code
4.程序标号(label)在ARM汇编中,标号代表一个地址,段内标号的地址在汇编时确定,而段外标号地址值在链接时确定。根据标号的生成方式,程序标号分为以下三种。
·程序相关标号(Program-relativelabels)。
·寄存器相关标号(Register-relativelabels)。
·绝对地址(Absoluteaddress)。
(1)程序相关标号
程序相关标号指位于目标指令前的标号或程序中的数据定义伪操作前的标号。这种标号在汇编时将被处理成PC值加上或减去一个数字常量。它常用于表示跳转指令的目标地址或代码段中所嵌入的少量数据。
(2)寄存器相关地址
这种标号在汇编时将被处理成寄存器的值加上或减去一个数字常量。它常被用于访问数据段中的数据。这种基于寄存器的标号通常用MAP和FIELD伪操作定义,也可以用EQU伪操作定义。
(3)绝对地址
绝对地址是一个32位的数字量,使用它可以直接寻址整个内存空间。
5.局部标号局部标号是一个0~99之间的十进制数字,可重复定义。局部标号后面可以紧接一个通常表示该局部变量作用范围的符号。局部变量的作用范围为当前段,也可以用伪操作ROUT来定义局部标号的作用范围。
局部标号在子程序或程序循环中常被用到,也可以配合宏定义伪操作(MACRO和MEND)来使程序结构更加合理。
在同一个段中,可以使用相同的数字命名不同的局部变量。默认情况下,汇编器会寻址最近的变量。也可以通过汇编器命令选项来改变搜索顺序。
局部变量命名语法如下。
n{routname}
局部变量引用的语法格式如下。
%{F|B}{A|T}n{routname}
其中,routname为变量作用范围名称;%表示引用操作;F指示汇编器只向前搜索;B指示汇编器只向后搜索;A指示汇编器搜索所有宏的嵌套。T指示汇编器只搜索宏的当前层。
如果在引用过程中,没有指定F和B,则汇编器先向后搜索,再向前搜索。
如果A和T没有指定,汇编器搜索所有从当前层次到宏最高层次,比当前层次低的层次不再搜索。
如果指定了routname,汇编器向前搜索最近的ROUT操作,若routname与该ROUT伪操作定义的名称不匹配,汇编器报告错误并结束汇编。
10.3.3汇编语言程序中的表达式和运算符在汇编语言程序设计中经常使用各种表达式,表达式一般由变量、常量、运算符和括号构成。常用的表达式有数字表达式、逻辑表达式和字符串表达式。
下面分别介绍表达式中各元素。
1.字符串表达式字符串表达式一般由字符串常量、字符串变量、运算符和括号构成。字符串由包含在双引号内的一系列字符组成。编译器所支持的字符串最大长度为512字节。
当在字符串中包含“$”或引号时,可以用“$$”表示“$”,用两个双引号表示一个双引号。
例如:
abcSETS"one""doublequote"
defSETS"one$$dollarsymbol"
上面的例子分别将字符串abc和def赋值为“one"doublequote”和“one$dollarsymbol”。
字符串可以通过SETA、SETL、SETS伪操作对其赋值。
常用的与字符串表达式相关的运算符如下。
·LEN:计算字符串长度运算符。
·CHR:ASCII码转换运算符。
·STR:字符串转换运算符。
·LEFT:字符串取左运算符。
·RIGHT:字符串取右运算符。
·CC:字符串连接运算符。
详见后面操作符一节。
下面的例子说明了如何使用字符串操作符给字符串变量赋值。
improbSETS"literal":CC:(strvar2:LEFT:4)
这个例子将字符串赋值为“literalatrv”。
2.整数表达式整数表达式一般由数字常量、数字变量、数字运算符和括号构成。
整数表示式可以包含寄存器相关(register-relative)或程序相关(program-relative)表达式,这些表达式在编译时被汇编器翻译为地址无关数字常量。
整数表达式一般被计算为32位的整数,当此整数被定义为无符号数时,其取值范围为0~232-1,当被定义为有符号数时,其取值范围为-231~231-1。汇编器认为-n和232-n是相等的。对于关系操作,如比较两个数的大小,汇编器将其操作数看作无符号的数,也就是说“0>-1”对汇编器来说取值为“假({FLASE})”。
下面的例子说明了在程序中,如何对整数表达式进行操作。
aSETA256*256;将数字变量赋值为256*256
MOVr1,#(a*22);将数字表达式(a*22)的值放入r1
汇编语言中,整数数字量有以下几种形式。
·十进制数(decimal-digis)
·“0x”+十六进制数(0xhexadecimal-digits)
·“&”+十六进制数(&hexadecimal-digits)
·n进制数(n_base-n-digits)
·字符(character)
其中,十进制数(decimal-digis)可以是“0”到“9”数字的任意组合;十六进制数(hexadecimal-digits)可以是“0”到“9”数字和字母“A”到“F”的任意组合;“n_”可以取2到9,“base-n-digits”是在n进制下合法的任意数值;字符(character)可以是除单引号以外的所有字符。
下面的例子说明了整数表达式的基本用法。
aSETA34906
addrDCD0xA10E
LDRr4,=&1000000F
DCD2_11001010
c3SETA8_74007
DCQ0x0123456789abcdef
LDRr1,='A' ;ARM伪指令将整数65(A的ASCII码)存入寄存器
ADDr3,r2,#'\'' ;将整数39(字符“/”的ASCII码)加到r2,结果存入r3
3.浮点数字量表达式浮点数字量有以下几种形式。
·{-}digitsE{-}digits。
·{-}{digits}.digits{E{-}digits}。
·0xhexdigits。
·&hexdigits。
其中,digits为十进制数,要在其后加上字母E(大写或小写)来表示其指数;hexdigits为十六进制数。
单精度浮点数的表示范围为1.17549435e−38~3.40282347e+38;双精度浮点数的表示范围为2.22507385850720138e-308~1.79769313486231571e+308。
下面的例子说明了浮点数据量的基本用法。
DCFD1E308,-4E-100
DCFS1.0
DCFD3.725e15
LDFS0x7FC00000;
LDFD&FFF0000000000000;
4.逻辑表达式逻辑表达式一般由逻辑量、逻辑运算符和括号构成,其表达式的运算结果为真或假。与逻辑表达式相关的运算符有“=”、“>”、“<”、“>=”、“<=”、“/=”、“<>”运算符和“LAND”、“LOR”、“LNOT”及“LEOR”运算符。
5.程序或寄存器相关表达式寄存器相关表达式的值等于指定寄存器的值加上或减去一个数字表达式。
程序相关表达式的值等于程序计数器PC的值加上或减去一个数字表达式的值。此种表达式通常由程序中的标号与一个数字表达式组成。
下面的例子说明了程序或寄存器相关表达式的基本使用方法。
LDRr4,=data+4*n ;n是汇编时取值变量
;code
MOVpc,lr
dataDCDvalue0
;n-1个DCD伪操作
DCDvaluen ;data+4*n指向此
;更多DCD伪操作
6.汇编中的操作符(1)操作符的优先级
在汇编语言程序设计中,表达式包含一个扩展的操作符集,这些操作符和高级语言中的运算符十分接近。其运算次序遵循如下的优先级。
①优先级相同的双目运算符的运算顺序为从左到右。
②相邻的单目运算符的运算顺序为从右到左,单目运算符的优先级高于其他运算符。
③括号运算符的优先级最高。
汇编语法的操作符优先级和C语言中的不完全相同。例如在汇编中,下面的汇编语言
(1+2∶SHR∶3)相当于(1+(2∶SHR∶3)),而在C语言中,运算则变为((1+2)>>3)=0。类似于这样的操作,在使用时要特别注意。
注意
为了保证表达式运算结果的正确,建议使用“()”来避免异义。
表10.4列出了汇编操作符的优先级以及对应的C语言运算符。
表10.4 汇编操作符优先级
汇编操作符
C语言运算符
单目运算
单目运算
*/:MOD:
*/%
字符串操作
n/a
:SHL::SHR::ROR::ROL:
<<>>
+-:AND::OR::EOR:
+-$|
=>>=<<=/=<>
==>>=<<=!=
:LAND::LOR::LEOR:
&&||
说明
表10.3是按操作符的优先级从上到下排列的。
C语言运算符优先级从高到低排列如下。
·单目运算
·*/%
·+-(asbinaryoperators)
·<<>>
·<<=>>=
·==!=
·&
·^
·|
·&&
·||
(2)单目运算
最高优先级的单目运算在表达式中最先被计算。单目操作符写在操作数的前面。运算顺序为从右到左。
表10.5列出了汇编中单目运算操作符及其返回值。
表10.5 汇编中单目运算操作符及其返回值。
操作符
使用
描述
:CHR:
:CHR:A
返回字母A的ASCII码
:LOWERCASE
:LOWERCASE:string
将给定字符串中的所有大写字母变成小写
REVERSE_CC
:REVERSE_CC:cond_code
对条件码取反
:STR:
:STR:A
将一个数字量或逻辑表达式转换成串
:UPPERCASE:
:UPPERCASE:string
将给定字符串中的所有小写字母变成大写
?
?A
返回定义符号A的代码行所生产代码行的字节数
续表
操作符
使用
描述
+和-
+A和-A
单目加和单目减,操作数为数学或程序相关表达式
:BASE:
:BASE:A
如果A是程序或寄存器相关表达式,:BASE:返回基址寄存器的编号
:CC_ENCODING:
:CC_ENCODING:cond_code
返回条件码中的数字值
:DEF:
:DEF:A
判断A是否被定义,如果被定义返回{TRUE};如果没有定义返回{FALSE}
:INDEX:
:INDEX:A
如果A是寄存器相关表达式,:INDEX:返回A相对于寄存器的偏移量,常用在宏操作中
:LEN:
:LEN:A
字符串A的长
:LNOT:
:LNOT:A
逻辑表达式A的值取反
:NOT:
:NOT:A
~A
A的值按位取反
:RCONT:
:RCONT:Rn
返回寄存器编号,0~15对应寄存器r0~r15
(3)双目运算
ARM汇编中将双目运算符放在两个操作数中间。一般情况下,双目运算的优先级低于单目运算。下面将以操作符的优先级为序分别介绍各操作符。
注意
操作符的优先级与C语言中操作符优先级顺序略有不同,详见单目运算一节。
表10.6列出了乘法相关操作符。
表10.6 乘法相关操作符
操作符
别名
使用
说明
*
A*B
乘法操作
/
A/B
除法操作
:MOD:
%
A:MOD:B
以B为除数对A取模
乘法相关操作符包括乘、除、取模运算,在双目运算中具有最高优先级。这些运算的操作数只能是数字表达式。
表10.7列出了字符串相关操作符。
表10.7 字符串操作符
操作符
使用
说明
:CC:
A:CC:B
连接两个字符串
:LEFT:
A:LEFT:B
返回字符串A最左端B长度的字符,操作数A必须为字符串,B必须为整数表达式
:RIGHT:
A:RIGHT:B
返回字符串A最右端B长度的字符,操作数A必须为字符串,B必须为整数表达式
表10.8列出了移位操作符。移位操作中两个操作数均为数字表达式。
表10.8 移位操作符
操作符
别名
使用
说明
:ROL:
A:ROL:B
A循环左移B位
:ROR:
A:ROR:B
A循环右移B位
:SHL:
<<
A:SHL:B
A左移B位
:SHR:
>>
A:SHR:B
A右移B位
注意
SHR是逻辑右移,不影响符号位。
表10.9列出了所有加、减、逻辑操作符。
表10.9 加减运算操作符
操作符
别名
使用
说明
+
A+B
A加上B
−
A−B
从B中减去A
:AND:
&&
A:AND:B
A和B按位与
:EOR:
^
A:EOR:B
A和B按位异或
:OR:
||
A:OR:B
A和B按位或
加、减运算的操作数均为数字表达式。逻辑运算的表达式为数字表达式,此运算按位操作产生结果。
表10.10列出了ARM汇编中的关系符。关系操作符用于表示两个同类表达式之间的关系。关系符的两个操作数必须为同种类型的操作数。操作数可以是数字变量、程序相关表达式、寄存器相关表达式或字符串。
表10.10 关系操作符
操作符
别名
使用
说明
=
==
A=B
判断A是否等于B
>
A>B
判断A是否大于B
>=
A>=B
判断A是否大于等于B
<
A<B
判断A是否小于B
<=
A<=B
判断A是否小于等于B
/=
<>
!=
A/=B
判断A是否不等于B
表10.11列出了汇编语言中的逻辑操作符。逻辑操作符进行两个逻辑表达式之间的基本逻辑操作。操作的结果为{FALSE}或{TURE}。
表10.11 逻辑操作符
操作符
使用
说明
:LAND:
A:LAND:B
A和B做逻辑与
续表
操作符
使用
说明
:LEOR:
A:LEOR:B
A和B做逻辑异或
:LOR:
A:LOR:B
A和B做逻辑或
10.3.4汇编语言预定义寄存器和协处理器ARM汇编器对ARM的寄存器和协处理器进行了预定义(包括APCS对r0~r15寄存器的定义),所有的寄存器和协处理器名都是大小写敏感的。
1.预定义寄存器名下面列出了被ARM汇编器预定义的寄存器名。
·r0~r15和R0~R15(15个通用寄存器)。
·a1~a4(参数、结果或临时寄存器,同r0~r3)。
·v1~v8(变量寄存器,同r4~r11)。
·sb和SB(静态基址寄存器,同r9)。
·sl和SL(栈顶指针寄存器,同r10)。
·fp和FP(帧指针寄存器,同r11)。
·ip和IP(过程调用中间临时寄存器,同r12)。
·sp和SP(栈指针寄存器,同r13)。
·lr和LR(连接寄存器,同r14)。
·pc和PC(程序计数器,同r15)。
2.预定义程序状态寄存器名下面列出了ARM汇编器预定义的程序状态寄存器的名称。
·cpsr和CPSR(当前程序状态寄存器)。
·spsr和SPSR(保留程序状态寄存器)。
3.预定义的浮点寄存器名下面列出了ARM汇编器预定义的浮点运算寄存器。
·s0~s31和S0~S31(VFP单精度浮点运算寄存器)。
·d0~d15和D0~D15(VFP双精度浮点运算寄存器)。
注意
FPA的寄存器f0~f7和F0~F7已不再使用。
4.预定义的协处理器名下面列出了ARM汇编器预定义的协处理器名和协处理器寄存器名。
·p0~p15(预定义的协处理器0~15的名称)。
·c0~c15(预定义的协处理器寄存器0~15的名称)。
10.3.5汇编语言内置变量ARM汇编器中定义了一些内置变量,这些内置变量不能使用伪指令设置(如,SETA、SETL、SETS等),一般用于程序的条件汇编控制。
下面的例子显示了如何使用内置变量控制程序的执行流程。
If{CONFIG}=16;若为Thumb代码则执行If后的语句
;codes
else
;codes
endif
b;程序结束
下面介绍由ARM汇编器预定义的内置变量。
·{ARCHITECTURE}:选定的ARM体系结构的值,如3,3M,4,4T。
·{AREANAME}:当前段名。
·{ARMASM_VERSION}:ARM编译器ARMASM的变量号。
·|ads$version|:ARM编译器ARMASM的变量号,同{ARMASM_VERSION}。
·{CODESIZE}:如果当前指令为ARM指令,该内置变量取值为32,如果当前指令为Thumb指令,该内置变量取值为16,同{CONFIG}。
·{COMMANDLINE}:当前命令行内容。
·{CONFIG}:如果当前指令为ARM指令,该内置变量取值为32,如果当前指令为Thumb指令,该内置变量取值为16,同{CODESIZE}。
·{CPU}:所使用的CPU名称。默认为ARM7TDMI。如果在编译命令行中使用“-CPU”选项确定CPU类型,则该值为“GenericARM”。
·{ENDIAN}:如果编译器在大端模式下,其值为“big”;如果在小端模式下,其值为“little”。
·{FPIC}:默认为{FALSE},如果设置了“/fpic”选项,其值为{TRUE}。
·{FPU}:所选fpu协处理器的名字。默认为“softVFP”。
·{INPUTFILE}:当前源文件名。
·{INTER}:默认为{FALSE},如果设置了“/inter”选项,其值为{TRUE}。
·{LINENUM}:目前源文件行号。
·{NOSWST}:默认为{FALSE},如果设置了“/noswst”选项,其值为{TRUE}。
·{OPT}:保存当前设置的列表选项。伪操作OPT用来保存当前列表选项,改变选项值,或恢复原始值。
·{PC}或“.”:当前程序地址值。
·{PCSTOREOFFSET}:指令STRpc,[...]和STMRb,{...,pc}与存储的PC值之间的偏移量。
·{ROPI}:默认为{FALSE},如果设置了“/ropi”选项,其值为{TRUE}。
·{RWPI}:默认为{FALSE},如果设置了“/rwpi”选项,其值为{TRUE}。
·{SWST}:默认为{FALSE},如果设置了“/swst”选项,其值为{TRUE}。
·{VAR}或@:存储区位置寄存器的当前值。
10.3.6汇编语言的程序结构在ARM(Thumb)汇编语言程序中以程序段为单位组织代码。段是相对独立的指令或数据序列,具有特定的名称。段可以分为代码段(CodeSection)和数据段(DataSection),代码段的内容为执行代码,数据段存放代码运行时需要用到的数据。一个汇编程序至少应该有一个代码段,当程序较长时,可以分割为多个代码段和数据段,多个段在程序编译链接时最终形成一个可执行的映像文件。
可执行映像文件通常由以下几部分构成。
·一个或多个代码段,代码段的属性为只读。
·零个或多个数据段,数据段的属性为可读写。数据段可是被初始化的数据段或没有被初始化的数据段(ZI,zeroinitialized)。
链接器根据系统默认或用户设定的规则,将各个段安排在存储器中的相应位置。因此源程序中段之间的相对位置与可执行的映像文件中段的相对位置一般不会相同。
以下是一个汇编语言源程序的基本结构。
AREA Init,CODE,READONLY
ENTRY
Start
LDR R0,=0x3FF5000
LDR R1,0xFF
STR R1,[R0]
LDR R0,=0x3FF5008
LDR R1,0x01
STR R1,[R0]
……
END
在汇编语言程序中,用AREA伪操作定义一个段,并说明所定义段的相关属性,本例定义一个名为Init的代码段,属性为只读。ENTRY伪操作标识程序的入口点,接下来为指令序列,程序的末尾为END伪指令,该伪操作告诉编译器源文件的结束,每一个汇编程序段都必须有一条END伪操作,指示代码段的结束。
10.3.7汇编语言子程序调用在ARM汇编语言程序中,子程序的调用一般是通过BL指令来实现的。在程序中,使用指令“BL子程序”名即可完成子程序的调用。
该指令在执行时完成如下操作:将子程序的返回地址存放在连接寄存器LR中,同时将程序计数器PC指向子程序的入口点。当子程序执行完毕需要返回调用处时,只需要将存放在LR中的返回地址重新拷贝给程序计数器PC即可。在调用子程序的同时,也可以完成参数的传递和从子程序返回运算的结果,通常可以使用寄存器R0~R3完成。
注意
不同编译器编译的代码间的相互调用,要遵循AAPCS(ARMArchitecture)。详见ARM编译工具手册。
以下是使用BL指令调用子程序的汇编语言源程序的基本结构:
AREA Init,CODE,READONLY
ENTRY
Start
LDR R0,=0x3FF5000
LDR R1,0xFF
STR R1,[R0]
LDR R0,=0x3FF5008
LDR R1,0x01
STR R1,[R0]
BL PRINT_TEXT
……
PRINT_TEXT
……
MOV PC,BL
……
END