3.1 编制第一个ARM汇编程序
请仔细阅读以下代码段:
;文件名:TEST1.S
;功能:实现两个寄存器相加
;说明:使用ARMulate软件仿真调试
① AREA Example1,CODE,READONLY ;声明代码段Example1
② ENTRY ;标识程序入口
③ CODE32 ;声明32位ARM指令
④ START MOV R0,#0 ;设置参数
⑤ MOV R1,#10
⑥ LOOP BL ADD_SUB ;调用子程序ADD_SUB
⑦ B LOOP ;跳转到LOOP
⑧ ADD_SUB
⑨ ADDS R0,R0,R1 ;R0 = R0 + R1
⑩ MOV PC,LR ;子程序返回 END ;文件结束
从以上代码段中,分析你看到的一个完整的ARM汇编语言程序所包含的几个元素:
______________________________
______________________________
______________________________
______________________________
______________________________
______________________________
ARM(Thumb)汇编语言的语句格式为:
{标号} {指令或伪指令} {;注释}
● 标号:从一行的行头开始,不能包含空格
● 指令或伪指令:指令的前面必须有空格或符号
● 注释:以“;”开头
每一条指令的助记符可以全部用大写,或全部用小写,但不允许在一条指令中大、小写混用。
语句之间可以插入空行。
如果一条语句太长,可将该长语句分为若干行来书写,在行的末尾用“\”表示下一行与本行为同一条语句。
在ARM(Thumb)汇编语言程序中,以程序段为单位组织代码。段是相对独立的指令或数据序列,具有特定的名称。段可以分为代码段和数据段,代码段的内容为执行代码,数据段存放代码运行时需要用到的数据。一个汇编程序至少应该有一个代码段,当程序较长时,可以分割为多个代码段和数据段,多个段在程序编译链接时最终形成一个可执行的映象文件。链接器根据系统默认或用户设定的规则,将各个段安排在存储器中的相应位置。因此源程序中段之间的相对位置与可执行的映象文件中段的相对位置一般不会相同。
在汇编语言程序中,用AREA伪指令定义一个段,并说明所定义段的相关属性,本例定义一个名为Example1的代码段,属性为只读。ENTRY伪指令标识程序的入口点,接下来为指令序列,程序的末尾为END伪指令,该伪指令告诉编译器源文件的结束,每一个汇编程序段都必须有一条END伪指令,指示代码段的结束。
3.1.1 指令系统分类
1.ARM 存储器访问指令
(1)LDR/ STR ——加载 / 存储指令
(2)LDM/ STM ——多寄存器加载 / 存储指令
(3)SWP ——寄存器和存储器交换指令
2.ARM数据处理指令
(1)数据传送指令
a)MOV——数据传送指令
b)MVN ——数据非传送指令
(2)算术逻辑运算指令
a)ADD——加法运算指令
b)SUB——减法运算指令
c)RSB——逆向减法指令
d)ADC——带进位加法指令
e)SBC——带进位减法指令
f)RSC——带进位逆向减法指令
g)AND——逻辑“与”
h)ORR——逻辑“或”
i)EOR——逻辑“异或”
j)BIC——位清除指令
(3)比较指令
a)CMP ——比较指令
b)CMN ——负数比较指令
c)TST ——位测试指令
d)TEQ ——相等测试指令
(4)乘法指令
a)MUL ——32位乘法指令
b)MLA ——32位乘加指令
c)UMULL ——64位无符号乘法指令
d)UMLAL ——64位无符号乘加指令
e)SMULL ——64位有符号乘法指令
f)SMLAL ——64位有符号乘加指令
3.ARM分支指令
(1)B ——分支指令
(2)BL ——带连接的分支指令
(3)BX ——带状态切换的分支指令
4.ARM 协处理器指令
(1)CDP ——协处理器数据操作指令
(2)LDC ——协处理器数据读取指令
(3)STC ——协处理器数据写入指令
(4)MCR ——ARM处理器到协处理器的数据传送指令
(5)MRC ——协处理器到 ARM处理器的数据传送指令
5.ARM 杂项指令
(1)SWI ——软中断指令
(2)MRS ——读状态寄存器指令
(3)MSR ——写状态寄存器指令
6.ARM 伪指令
(1)ADR ——小范围的地址读取伪指令
(2)ADRL ——中等范围的地址读取伪指令
(3)LDR ——大范围的地址读取伪指令
(4)NOP ——空操作伪指令
3.1.2 ARM寄存器
ARM处理器有如下37个32位长的寄存器:
(1)30个通用寄存器;
(2)6个状态寄存器:1个CPSR(Current Program Status Register,当前程序状态寄存器),5个SPSR(Saved Program Status Register,备份程序状态寄存器);
(3)1个PC(Program Counter,程序计数器)。
ARM处理器共有7种不同的处理器模式,在每一种处理器模式中有一组相应的寄存器组,如表3-1所示。表3-2列出了ARM处理器的寄存器组织概要。
通用寄存器根据其分组与否可分为以下两类:
(1)未分组寄存器(the Unbanked Register),包括R0~R7。
(2)分组寄存器(the Banked Register),包括R8~R14。
未分组寄存器包括R0~R7。未分组寄存器没有被系统用于特殊的用途,任何可采用通用寄存器的应用场合都可以使用未分组寄存器。
表3-1 ARM处理器模式
表3-2 ARM处理器的寄存器组织概要
对于分组寄存器R13和R14来说,每个寄存器对应6个不同的物理寄存器。其中的一个是用户模式和系统模式公用的,而另外5个分别用于5种异常模式。访问时需要指定它们的模式。名字形式如下:
(1)R13_<mode>
(2)R14_<mode>
其中,<mode>可以是以下几种模式之一:usr、svc、abt、und、irp及fiq。
寄存器R14又被称为连接寄存器(Link Register,LR),在ARM体系结构中具有下面两种特殊的作用:
(1)每一种处理器模式用自己的R14存放当前子程序的返回地址。
(2)当异常中断发生时,该异常模式特定的物理寄存器R14被设置成该异常模式的返回地址,对于有些模式R14的值可能与返回地址有一个常数的偏移量(如数据异常使用SUB PC,LR,#8返回)。
R14也可以被用做通用寄存器使用。
当前程序状态寄存器(Current Program Status Register,CPSR)可以在任何处理器模式下被访问,它包含下列内容:
(1)ALU(Arithmetic Logic Unit,算术逻辑单元)状态标志的备份;
(2)当前的处理器模式;
(3)中断使能标志;
(4)设置处理器的状态(只在4T架构)。
8086 中的寄存器
寄存器可以被装入数据,可以在不同的寄存器之间移动这些数据,或者做类似的事情。基本上,像四则运算、位运算等这些计算操作,都主要是针对寄存器进行的。
3.1.3 如何寻找操作数
(1)寄存器寻址
操作数的值在寄存器中,指令中的地址码字段指出的是寄存器编号,指令执行时直接取出寄存器值来操作。
请根据上图描述立即寻址方式:
_________________________________
_________________________________
_________________________________
_________________________________
_________________________________
_________________________________
寄存器寻址指令举例如下:
MOV R1,R2 ;将R2的值存入R1
SUB R0,R1,R2 ;将R1的值减去R2的值,结果保存到R0
(2)立即寻址
立即寻址指令中的操作码字段后面的地址码部分即是操作数本身,也就是说,数据就包含在指令当中,取出指令也就取出了可以立即使用的操作数(这样的数称为立即数)。
请根据上图描述立即寻址方式:
_________________________________
_________________________________
_________________________________
_________________________________
_________________________________
_________________________________
立即寻址指令举例如下:
SUBS R0,R0,#1 ;R0减1,结果放入R0,并且影响标志位
MOV R0,#0xFF000 ;将立即数0xFF000装入R0寄存器
(3)寄存器移位寻址
寄存器移位寻址是ARM指令集特有的寻址方式。当第2个操作数是寄存器移位方式时,第2个寄存器操作数在与第1个操作数结合之前,选择进行移位操作。
请根据上图描述立即寻址方式:
____________________________
____________________________
____________________________
____________________________
____________________________
____________________________
寄存器移位寻址指令举例如下:
MOV R0,R2,LSL #3 ;R2的值左移3位,结果放入R0,即是R0=R2×8
ANDS R1,R1,R2,LSL R3 ;R2的值左移R3位,然后和R1相“与”操作,结果放入R1
(4)寄存器间接寻址
寄存器间接寻址指令中的地址码给出的是一个通用寄存器的编号,所需的操作数保存在寄存器指定地址的存储单元中,即寄存器为操作数的地址指针。
请根据上图描述立即寻址方式:
________________________________
________________________________
________________________________
________________________________
________________________________
________________________________
寄存器间接寻址指令举例如下:
LDRR1,[R2] ;将R2指向的存储单元的数据读出保存在R1中
SWP R1,R1,[R2] ;将寄存器R1的值和R2指定的存储单元的内容交换
(5)基址寻址
基址寻址就是将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址。
请根据上图描述立即寻址方式:
________________________________
________________________________
________________________________
________________________________
________________________________
________________________________
基址寻址指令举例如下:
LDRR2,[R3,#0x0C] ;读取R3+0x0C地址上的存储单元的内容,放入R2
STRR1,[R0,#-4]! ;先R0=R0−4,然后把R1的值寄存到保存到R0指定的存储单元
(6)多寄存器寻址
多寄存器寻址一次可传送几个寄存器值,允许一条指令传送16个寄存器的任何子集或所有寄存器。
请根据上图描述立即寻址方式:
________________________________
________________________________
________________________________
________________________________
________________________________
多寄存器寻址指令举例如下:
LDMIA R1!,{R2-R7,R12} ;将R1指向的单元中的数据读出到R2~R7、R12中(R1自动加1)
STMIA R0!,{R2-R7,R12} ;将寄存器R2~R7、R12的值保存到R0指向的存储;单元中(R0自动加1)
(7)堆栈寻址
堆栈是一个按特定顺序进行存取的存储区,操作顺序为“后进先出”。堆栈寻址是隐含的,它使用一个专门的寄存器(堆栈指针)指向一块存储区域(堆栈),指针所指向的存储单元即是堆栈的栈顶。存储器堆栈可分为两种:
①向上生长:向高地址方向生长,称为递增堆栈。
②向下生长:向低地址方向生长,称为递减堆栈。
堆栈指针指向最后压入的堆栈的有效数据项,称为满堆栈;堆栈指针指向下一个待压入数据的空位置,称为空堆栈。
(8)块拷贝寻址
多寄存器传送指令用于将一块数据从存储器的某一位置拷贝到另一位置。如:
STMIA R0!,{R1-R7} ;将R1~R7的数据保存到存储器中。存储指针在保存第一个值之后增加,增长方向为向上增长
STMIB R0!,{R1-R7} ;将R1~R7的数据保存到存储器中。存储指针在保存第一个值之前增加,增长方向为向上增长
(9)相对寻址
相对寻址是基址寻址的一种变通。由程序计数器PC提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址。
相对寻址指令举例如下:
BL SUBRl ;调用到SUBRl子程序
…
SUBR1…
MOV PC,R14;返回
3.1.4 伪操作
在ARM汇编语言程序中,有一些特殊指令助记符,这些助记符与指令系统的助记符不同,没有相对应的操作码,通常称这些特殊指令助记符为伪操作标识符(directive),例如我们在第一个ARM程序中看到的第① ② ③ 行等,它们所完成的操作称为伪操作。伪操作在源程序中的作用是为了完成汇编程序做各种准备工作的,这些伪操作仅在汇编过程中起作用,一旦汇编结束,伪操作的使命就完成。
在ARM的汇编程序中,伪操作主要有符号定义伪操作、数据定义伪操作、汇编控制伪操作及其杂项伪操作等。
常用的伪操作:
1.符号定义伪操作
用于定义ARM汇编程序中的变量、对变量赋值及定义寄存器的别名等操作。常见的符号定义伪操作有如下几种:
(1)用于定义全局变量的GBLA、GBLL和GBLS。
(2)用于定义局部变量的LCLA、LCLL和LCLS。
(3)用于对变量赋值的SETA、SETL和SETS。
(4)为通用寄存器列表定义名称的RLIST。
2.数据定义伪操作
一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。常见的数据定义伪操作有如下几种:
(1)DCB用于分配一片连续的字节存储单元并用指定的数据初始化。
(2)DCW(DCWU)用于分配一片连续的半字存储单元并用指定的数据初始化。
(3)DCD(DCDU)用于分配一片连续的字存储单元并用指定的数据初始化。
(4)DCFD(DCFDU)用于为双精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。
(5)DCFS(DCFSU)用于为单精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。
(6)DCQ(DCQU)用于分配一片以8字节为单位的连续的存储单元并用指定的数据初始化。
(7)SPACE用于分配一片连续的存储单元。
(8)MAP用于定义一个结构化的内存表首地址。
(9)FIELD用于定义一个结构化的内存表的数据域。
3.杂项伪操作
ARM汇编中还有一些其他的伪操作,在汇编程序中经常会被使用,包括以下几条:
(1)AREA用于定义一个代码段或数据段。
(2)ALIGN用于使程序当前位置满足一定的对齐方式。
(3)ENTRY用于指定程序入口点。
(4)END用于指示源程序结束。
(5)EQU用于定义字符名称。
(6)EXPORT(或GLOBAL)用于声明符号可以被其他文件引用。
(7)EXPORTAS用于向目标文件引入符号。
(8)IMPORT用于通知编译器当前符号不在本文件中。
(9)EXTERN用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用。
(10)GET(或INCLUDE)用于将一个文件包含到当前源文件。
(11)INCBIN用于将一个文件包含到当前源文件。
编制程序
程序分析:
第1条是让灯灭,第2条应当是延时,第3条是让灯亮,第4条和第2条一模一样,也是延时,第5条应当是转去执行第一条指令。LJMP是一条指令,意思是转移,往什么地方转移呢?后面跟的是LOOP,看一下,什么地方还有LOOP,对了,在第1条指令的前面有一个LOOP,所以很直观地,我们可以认识到,它要转到第1条指令处。这个第1条指令前面的LOOP被称之为标号,它的用途就是给这一行起一个名字,便于使用。是否一定要给它起名叫LOOP呢?当然不是,起什么名字,完全由编程序的人决定,可以称它为A、X等,当然,这时,第5条指令LJMP后面的名字也得跟着改了。
第2条和第4条指令的用途是延时,它是怎样实现的呢?指令的形式是LCALL,这条指令称为调用子程序指令,看一下指令后面跟的是什么?DELAY,找一下DELAY,在第六条指令的前面,显然,这也是一个标号。这条指令的作用是这样的:当执行LCALL指令时,程序就转到LCALL后面的标号所标定的程序处执行,如果在执行指令的过程中遇到RET指令,则程序就返回到LCALL指令的下面的一条指令继续执行,从第6行开始的指令中,可以看到确实有RET指令。在执行第2条指令后,将转去执行第6条指令,而在执行完6、7、8、9条指令后将遇到第10条令:RET,执行该条指令后,程序将回来执行第3条指令,即将P10清零,使灯亮,然后又是第4条指令,执行第4条指令就是转去执行第6、7、8、9、10条指令,然后回来执行第5条指令,第5条指令就是让程序回到第1条开始执行,如此周而复始,灯就在不断地亮、灭了。
调试程序
请调试你的程序。并将产生的问题写在下面,你遇到的警告错误和严重错误是什么?
________________________
________________________
________________________
________________________
________________________
________________________
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。