本系列文章,所需代码请从以下地址下载:
http://download.csdn.NET/download/scyangzhu/4602585
1.3.1 汇编伪操作在汇编程序中的使用范例
掌握了基本的ARM汇编指令后,要写出简单的ARM汇编程序,还必须要掌握基本的ARM汇编伪操作(directive)。现在我们来看一个简单的汇编程序,该程序调用子程序完成了加法操作。
1 ;文件名:TEST.S
2 ;功能:实现两个寄存器相加
3 AREA Example,CODE,READONLY ;声明代码段Example
4 ENTRY ;标识程序入口
5 CODE32 ;声明32位ARM指令
6 START MOV R0,#0 ;设置参数
7 MOV R1,#10
8 BL ADD_SUB;调用子程序ADD_SUB
9 LOOP B LOOP ;跳转到LOOP
10 ADD_SUB
11 ADD R0,R0,R1 ;R0 = R0 + R1
12 MOV PC,LR ;子程序返回
13 END ;文件结束
第 6、7行将传递给子程序的参数存放在r0和r1中,第8行调用子程序。第11、12行是子程序的代码,完成了2个参数相加,并将结果放在r0后返回主程序。第6、9、10行的START、LOOP、ADD_SUB是标号,最经常用于跳转指令B和BL,由于汇编语法要求的缘故,标号必须顶格写(即:不能在行首有空格),否则编译器会报错。与之对应的是,汇编指令一定不能顶格写。
很明显分号(;)在汇编程序中是注释符号,相当于C语言的// 号。除此之外,当然大家注意到了第3、4、5、13行是我们没学习过的符号,其实它们就是本文的重点——ARM汇编伪操作。首先我先来解释这几个伪操作, 第3行定义了一个代码段。汇编伪操作AREA表示定义一个段,其段名为Example,CODE表明是代码段(而不是数据段),属性为只读(READONLY),从而表示第6——12行是程序代码(而不是程序数据)。第4行的ENTRY表示整个程序的入口点(即:程序运行的第一条指令。注1)是第6行的MOV指令。第5行的CODE32表示第6——12行的程序代码是ARM指令,而不是thumb指令。第13行的END表示源代码文件结束,其背后的含义就是:如果程序员在第13行后还写有汇编指令,编译器也根本不会理会这些代码,更不会去编译它们,当然这些代码也就不可能出现在最后的可执行文件中。哈哈,所以请务必记住,在END伪操作的后面再写代码,那是无用功,写了也白写。不要不以为然哟,根据经验,初学者总是会犯这样的错误。
特别说明:第9行的含义是要让程序在运行结束后,在第9行进行死循环,从而让整个程序定格在第9行。这一点也许你很困惑:在写应用程序时,程序结束就结束了,源代码根本不需要再去写个死循环。但你现在要弄清楚:你写应用程序时,有OS为你处理程序结束后的若干事情。可是,你现在已经得不到OS服务。如果你不自己写第9行的代码,那么当你认为程序已经运行结束(第8行执行完成)的时候,CPU不会聪明地停下来,它会继续任劳任怨地去取指第11行,继续运行,这不是你所希望的。其实这还不是最糟糕的,最糟糕的是,如果你的程序没有11-13行,那么CPU任劳任怨取出的指令其实是内存中的随机数,但CPU却会把它当作指令来执行,那么,你认为此时会出现什么情况呢?哈哈,只有天知道!
注1:ENTRY的本意并非如此,此处的含义仅是ENTRY的副作用而以。关于其本意,后续章节将予以解释。
1.3.2 最常见汇编伪操作精解
当然,伪操作远不止这几条,下面我们再来介绍经常使用的若干伪操作。
(1) GBLA:定义全局算术变量(准确说,应该是全局符号),例如:GBLAtestval
(2)SETA:对全局算术符号进行赋值,例如:testval SETA 9;testval SETAtestval + 1
(3) DCD:在编译时为整数分配字存储空间,例如:DCD 0x123456ab,这条伪操作将导致编译器在最终的二进制可执行文件中分配一个字的空间,并在该空间中存放整数0x123456ab
(4)DCB:在编译时为数分配字节存储空间,例如:DCB ‘a’,这条伪操作将导致编译器在最终的二进制可执行文件中分配一个字节的空间,并在该空间中存放字符a的ASCII码
(5) IF,ELSE及ENDIF:相当于C语言的条件编译,例如:
GBLAtestval
testval SETA 9
IFtestval < 5
mov r0, #testval
ELSE
movr1, #testval
ENDIF
IF :DEF:testval
movr2, #testval
ELSE
INFO 4, "you should definetestval"
ENDIF
编译器编译该段代码的结果是:
mov r1, #9
mov r2, #9
(6) WHILE及WEND :例如
GBLAtestval
testval SETA 1
WHILE testval <= 3
testval SETA testval + 1
mov r0,#testval
WEND
编译器编译该段代码的结果是:
mov r0, #2
mov r0, #3
mov r0, #4
(7) MACRO 、MEND及MEXIT:相当于C语言的宏替换,例如:
MACRO
$label xmac$p1,$p2
; code1
$label.loop1
;code2
BGE $label.loop1
$label.loop2
;code3
BL $p1
BGT $label.loop2
; code4
ADR r0,$p2
;code5
MEND
;主程序
abc xmac subr1,de
编译器编译该段代码的结果是:
;code1
abc.loop1
;code2
BGE abc.loop1
abc.loop2
;code3
BL subr1
BGT abc.loop2
;code4
ADR r0,de
;code5
8. EQU:相当于C语言的宏定义,例如:testval EQU 4
9. EXPORT: 参见“ATPCS与混合编程”
10. IMPORT:参见“ATPCS与混合编程”
非常重要的一点是:必须深刻理解汇编伪操作是给编译器提供某些必要的信息,以帮助编译器正确完成程序的编译。当编译完成后,汇编伪操作就完成了它的历史使命,它不可能在最终的可执行程序的二进制代码中留下哪怕是一点点痕迹,当然也就不可能在程序运行时受到CPU的“青睐”。总之记住一句话,汇编伪操作是给编译器看的,而不是给CPU看的。这是汇编伪操作与汇编指令最大的区别。
1.3.3 汇编伪操作列表
为了保持内容的完整,下面给出较为完整的汇编伪操作列表。如需完整的列表,请自行查阅ads自带的“OnlineBooks”相关章节。
l 符号定义(Symbol Definition)伪操作:
表1 - 3符号定义伪操作
伪操作 | 语法格式 | 作用 |
GBLA | GBLA Variable | 声明一个全局的算术变量,并将其初始化成0 |
GBLL | GBLL Variable | 声明一个全局的逻辑变量,并将其初始化成{FALSE} |
GBLS | GBLS Variable | 声明一个全局的字符串变量,并将其初始化成空串“” |
LCLA | LCLA Variable | 声明一个局部的算术变量,并将其初始化成0 |
LCLL | LCLL Variable | 声明一个局部的逻辑变量,并将其初始化成{FALSE} |
LCLS | LCLS Variable | 声明一个局部的串变量,并将其初始化成空串“” |
SETA | Variable SETA expr | 给一个全局或局部算术变量赋值 |
SETL | Variable SETL expr | 给一个全局或局部逻辑变量赋值 |
SETS | Variable SETS expr | 给一个全局或局部字符串变量赋值 |
RLIST | name LIST (list of registers) | 为一个通用寄存器列表定义名称 |
CN | name CN expr | 为一个协处理器的寄存器定义名称 |
CP | name CP expr | 为一个协处理器定义名称 |
DN/SN | name DN/SN expr | DN/SN为一个双精度/单精度的VFP寄存器定义名称 |
FN | name FN expr | 为一个FPA浮点寄存器定义名称 |
l 数据定义(Data Definition)伪操作:
表1 - 4 数据定义伪操作
伪操作 | 语法格式 | 作用 |
LTORG | LTORG | 声明一个数据缓冲池(也称为文字池)的开始 |
MAP | MAP expr {,base_register} | 定义一个结构化的内存表(Storage Map)的首地址 |
FIELD | {label} FIELD expr | 定义一个结构化内存表中的数据域 |
SPACE | {label} SPACE expr | 分配一块连续内存单元,并用0初始化 |
DCB | {label} DCB expr {, expr} | 分配一段字节内存单元,并用expr初始化 |
DCD DCDU | {label} DCD {U} expr {, expr}… | 分配一段字(对齐)的内存单元,DCD可能在分配的第1个内存单元前插入填补字节(padding),以保证分配的内存是字对齐的,DCDU不需要对齐 |
DCFD/ DCFDU | {label} DCFD{U} fpliteral{,fpliteral}... | 为双精度的浮点数分配字对齐的内存单元 |
DCFS/ DCFSU | {label} DCFS{U} fpliteral{,fpliteral}... | 为单精度的浮点数分配字对齐的内存单元 |
DCI | {label} DCI expr{,expr}... | 在ARM代码中分配一段字对齐的内存单元;在Thumb代码中,分配一段半字对齐的半字内存单元 |
DCQ/DCQU | {label} DCQ{U} {-}literal{,{-}literal}... | 分配一段以双字(8个字节)为单位的内存 |
DCW/ DCWU | {label} DCW expr{,expr}... | DCW用于分配一段半字对齐的半字内存单元 |
l 汇编控制(Assembly Control)伪操作:
表1 - 5 汇编控制伪操作
伪操作 | 语法格式 | 作用 |
IF, ELSE及ENDIF | IF logical expr … {ELSE …} ENDIF | 能够根据条件把一段源代码包括在汇编语言程序内或者将其排除在程序之外 |
WHILE及WEND | WHILE logical expr … WEND | 能够根据条件重复汇编相同的一段源代码 |
MACRO, MEND及MEXIT | MACRO {$label} macroname{$param {,$param}…} …;宏代码 MEND | MACRO标识宏定义的开始,MEND标识宏定义的结束,MEXIT用于从宏中跳转出去,用MACRO和MEND定义的一段代码,称为宏定义体,通过宏名称来调用宏 |
l 信息报告(Reporting)伪操作:
表1 - 6 信息报告伪操作
伪操作 | 语法格式 | 作用 |
ASSERT | ASSERT logical expr | 对汇编程序的第二遍扫描中,如果其中ASSERT中条件不成立,ASSERT伪操作将报告该错误信息。 |
INFO | INFO numberic-expr, string-expr | 对汇编程序的第一遍扫描或者第二遍扫描时INFO伪操作报告诊断信息 |
OPT | OPT n | 通过OPT伪操作可以在源程序中设置列表选项 |
TTL | TTL title | 在列表文件的每一页的开头插入一个标题 |
l 其他(Miscellaneous)伪操作:
表1 - 7 其他伪操作
伪操作 | 语法格式 | 作用 |
CODE16 | CODE16 | 告诉汇编编译器后面的指令序列为16位的Thumb指令 |
CODE32 | CODE32 | 告诉汇编编译器后面的指令序列为32位的ARM指令 |
EQU | name EQU expr{, type} | 为数字常量,基于寄存器的值和程序中的标号(基于PC的值)定义一个字符名称,类似于C语言中的#define宏定义 |
AREA | AREA sectionment {, attr}{, attr}… | 定义一个代码段或者数据段 |
ENTRY | ENTRY | 指定程序的入口点 |
END | END | 告诉编译器已经到了源程序结尾 |
ALIGN | ALIGN {expr{, offset)} | 通过添加补丁字节使当前位置满足一定的对齐方式
|
EXPORT/ GLOBAL | EXPORT symbol{[WEAK]}
| 声明一个符号可以被其他文件引用 |
IMPORT/ EXTERN | IMPORT Symbol{WEAK} EXTERN symbol{(WEAK)} | 告诉编译器当前的符号不是在本源文件中定义的,而是在其他源文件中定义的,在本源文件中可能引用该符号 |
GET/INCLUDE | GET filename | 将一个源文件包含到当前源文件中,并将被包含的文件在其当前位置进行汇编处理 |
INCBIN | INCBIN filename | 将一个文件包含到当前源文件中,被包含的文件不进行汇编处理 |
KEEP | KEEP{symbol} | 告诉编译器将局部符号包含在目标文件的符号表中 |
NOFP | NOFP | 禁止源程序中包含浮点运算指令 |
REQUIRE | REQUIRE lable | 指定段之间的相互依赖关系 |