3.5 ARM汇编程序设计
熟悉了ARM体系结构和常用的汇编指令,我们就可以尝试编写简单的ARM汇编程序了。在一段完整的汇编程序中,不仅包含了各种汇编指令和伪指令,还包含了各种伪操作。伪操作可以让程序员更加方便地编写汇编程序,实现更加复杂的逻辑功能。
3.5.1 ARM汇编程序格式
ARM汇编程序是以段(section)为单位进行组织的。在一个汇编文件中,可以有不同的section,分为代码段、数据段等,各个段之间相互独立,一个ARM汇编程序至少要有一个代码段。我们可以使用AREA伪操作来标识一个段的起始、段名和段的读写属性。
上面的汇编程序实现了数据块的复制功能。该汇编程序由两个程序段组成:一个代码段,一个数据段,两个段相互独立,由AREA伪操作来标识一个段的起始、段名、段的属性(CODE、DATA)和读写权限(READONLY、READWRITE)。
C程序一般都是从main()函数开始执行的,那汇编程序从哪里开始执行呢?ARM汇编程序通过ENTRY这个伪操作来标识汇编程序的运行入口,使用伪操作END来标识汇编程序的结束。
在ARM汇编程序中可以使用标号。像C语言一样,在汇编语言中,标号代表的指令地址,如上述代码中的LOOP标号,和BNE指令结合使用可以构建一个循环程序结构。
在C程序中,我们可以使用//或/**/来注释代码;在汇编程序中,我们同样也可以添加注释,我们使用分号;来注释代码。在一个空行的行首或者一条指令语句的末尾添加一个分号,然后就可以在分号后面添加注释,以增加程序的可读性。
3.5.2 符号与标号
在ARM汇编程序中,我们可以使用符号来标识一个地址、变量或数字常量。当用符号来标识一个地址时,这个符号通常又被称为标号。
符号的命名规则和C语言的标识符命名规则一样:由字母、数字和下画线组成,符号的开头不能使用数字,但标号除外。标号比较任性,标号的开头不仅可以是数字,甚至整个标号可以是一个纯数字。
符号的命名在其作用域内必须唯一,不能与系统内部或系统预定义的符号同名,不能与指令助记符、伪指令同名。一般情况下,一个符号的作用域是整个汇编源文件。有时候我们会直接通过数字[0,99]而不是使用字符来进行地址引用,我们称这种数字为局部标号。局部标号的作用域为当前段,在汇编程序中,我们可以使用下面的格式来引用局部标号。
在局部标号的引用格式中,由大括号{}括起来的部分是可选项,N表示局部标号,其余的参数说明如下。
● %:引用符号,对一个局部标号产生引用。
● F:指示编译器只向前搜索。
● B:指示编译器只向后搜索。
● A:指示编译器搜索宏的所有宏命令层。
● T:指示编译器搜索宏的当前层。
● N:局部标号的名字。
● routename:局部标号作用范围名称,使用ROUT定义。
若B、F没有指定,编译器将默认先向后搜索,然后向前搜索。若A、T都没指定,则汇编程序默认搜索从当前层到最顶层的所有宏命令,但不搜索较低层的宏命令。如果在标签中或者对一个标签的引用中指定了routename,则汇编程序将其与最近的一个前ROUT指令的名称进行比较,如果不匹配,则汇编程序会生成一条错误消息,汇编失败。
在汇编代码中,使用局部标号的示例程序如下:
在上面的汇编程序中,我们定义了一个局部标号:0,然后通过BNE%B0指令引用了这个标号,B表示向后跳转,程序直接跳到了局部标号0处的代码执行,构成了一个循环程序结构。
3.5.3 伪操作
在C语言中,为了编程方便,编译器会定义一系列预处理命令,并用#来标识,如#include、#define、#if、#else、#end等。这些预处理命令并不是真正的C语言关键字,而是为了编程方便,编译器提供给我们使用的预定义标识符。一个C程序经过预处理之后,这些预处理命令一般会全部消失,预处理后的代码也就变成了一个完全由C语言关键字和标准语法构成的原汁原味的C程序,然后编译器才能去对这些源程序进行语法、语义分析,最后编译成二进制可执行文件。在整个编译过程中,编译器是不认识这些预处理命令的。如果在编译之前不做预处理操作,则编译器就会报错,这一点大家要搞明白。
同样的道理,在汇编语言中,为了编程方便,汇编器也定义了一些特殊的指令助记符,以方便对汇编程序做各种处理。如使用AREA来定义一个段(section),使用GBLA来定义一个数据,使用ENTRY来指定汇编程序的执行入口等,这些指令助记符统称为伪指令或伪操作。伪操作是为编写汇编程序服务的,即使在同一个CPU架构下,不同的编译环境或汇编器虽然会遵循和兼容同一套指令集,但是可能会定义各自不同的伪操作,它们的使用方法和格式也各不相同。
伪操作一般用在符号定义、数据定义、汇编程序结构控制等场合。在一个汇编程序中经常使用的伪操作如下。
关于数据定义,常用的伪操作有DCD、DCB、SPACE、DATA,这些伪操作的使用方法如下所示。
除此之外,还有一些其他常用的伪操作,如用来标识程序的入口地址、程序的结束地址、用来定义段的属性等,具体如表3-4所示。
表3-4 伪操作
有了这些伪操作辅助,我们就可以设计出更加灵活、功能更加复杂的程序结构,也可以定义一个个汇编子程序,然后在主程序中分别去调用它们,实现汇编语言的模块化编程。
在上面的汇编程序中,我们实现了一个汇编子程序SUM_ASM,使用EXPORT伪操作将其声明为一个全局符号,然后其他汇编程序或C程序就可以直接调用它了。
SUM_ASM汇编子程序自身又调用了其他子程序sum,这个sum子程序可以是一个汇编子程序,也可以是一个使用C语言定义的函数。在调用之前我们要先使用IMPORT伪操作把sum子程序导入进来,然后就可以直接使用BL指令跳转过去运行了。只要遵循一些约定的规则,C程序和汇编程序其实是可以相互调用的,从汇编指令的层面上看,它们之间并无本质的区别。