ARM指令集可以分为ARM数据处理指令、寄存器装载及存储指令、ARM跳转指令、ARM杂项指令、ARM协处理器指令和ARM伪指令。
数据处理指令大致可分为数据传送指令、比较指令和测试指令、算术逻辑运算指令、乘法指令。所有ARM数据处理指令均可选择使用S后缀,以影响状态标志。
数据传送指令 :只用于在寄存器与寄存器之间进行数据的双向传输。
比较指令和测试指令 :不需要后缀S,它们会直接影响CPSR的条件标志位,并且比较指令不保存运算结果,只起到更新CPSR中相应条件标志位的作用。
算术逻辑运算指令 :完成常用的算术与逻辑运算,该类指令不但将运算结果保存在目的寄存器中,同时也可更新CPSR中的相应条件标志位。
乘法指令 :其操作数全部是寄存器。
凡是具有第2操作数(operand2)的指令(乘法指令除外),均有3种使用形式:立即数形式、寄存器形式和寄存器移位形式。
ARM微处理器的主要数据处理指令见表3-2,以下将分类详细地介绍它们。
表3-2 ARM微处理器的主要数据处理指令
加法运算指令ADD。将Rn的数值与operand2的数值相加,结果保存到Rd寄存器中。指令格式如下:
应用示例:
ADC指令是将Rn的数值与operand2的数值相加,再加上CPSR中的进位标志C,结果保存到Rd寄存器中。指令格式如下:
应用示例: 使用ADC实现64位的2个二进制数相加,即(R1,R0)=(R5,R4)+(R3,R2)。
减法运算指令SUB,是用Rn的数值减去operand2的数值,结果保存到Rd寄存器中。指令格式如下:
应用示例:
SBC指令是用Rn的数值减去operand2的数值,再减去CPSR中的进位标志C,结果保存到Rd寄存器中。
注意 :虽然在ARM中有借位时C=0,无借位时C=1,但是使用该条指令时,程序员不必考虑这些,由ARM系统自动完成。指令格式如下:
应用示例: 使用SBC实现64位的2个二进制数相减,即(R7,R6)=(R5,R4)-(R3,R2)。
反向减法运算指令RSB,是用operand2的数值减去Rn的数值,结果保存到Rd寄存器中。指令格式如下:
应用示例:
RSC指令是用operand2的数值减去Rn的数值,再减去CPSR中的进位标志C,结果保存到Rd寄存器中。指令格式如下:
应用示例: 使用RSC实现64位的2个二进制数的负数,源数为(R5,R4),结果存在(R3,R2)中。
在ARM中还有6条乘法和乘加指令,运算结果分为32位和64位两类,指令中的所有操作数只能使用通用寄存器,同时寄存器和操作数1必须使用不同的寄存器。
32位乘法指令MUL是将Rm的值与Rs中的值相乘,结果的低32位保存在Rd中。指令的格式如下:
应用示例:
32位乘加指令MLA是将Rm的值与Rs中的值相乘,再加上Rn的值,结果的低32位保存在Rd中。指令的格式如下:
应用示例:
64位有符号乘法指令SMULL是将Rm的值与Rs中的值相乘,结果的低32位保存在RdLo寄存器中,高32位保存在RdHi寄存器中。指令的格式如下:
应用示例:
SMLAL指令是将Rm的值与Rs的值相乘,其乘积低32位与寄存器RdLo的值相加同时影响进位标志,再回送给寄存器RdLo;而高32位与寄存器RdHi的值相加,并加上低32位的进位C,再回送给寄存器RdHi。指令的格式如下:
应用示例:
64位无符号乘法指令UMULL是将Rm的值与Rs中的值相乘,结果的低32位保存在RdLo寄存器中,高32位保存在RdHi寄存器中。指令的格式如下:
应用示例:
UMLAL指令是将Rm的值与Rs的值相乘,其乘积低32位与寄存器RdLo的值相加,同时影响进位标志,再回送给寄存器RdLo;而高32位RdHi寄存器中的值是RdHi寄存器的值加上低32位相加的进位位C。指令的格式如下:
应用示例:
AND逻辑“位与”操作指令,将Rn的值与operand2的值按位进行逻辑“与”操作,并将结果保存到Rd中。指令的格式如下:
应用示例:
ORR逻辑“位或”操作指令,将Rn的值与operand2的值按位进行逻辑“或”操作,并将结果保存到Rd中。指令的格式如下:
应用示例:
EOR逻辑“位异或”操作指令,将Rn的值与operand2的值按位进行逻辑“异或”操作,并将结果保存到Rd中。指令的格式如下:
应用示例:
位清除指令BIC是将Rn的值与operand2的值按位取反后,进行逻辑“与”操作,并将结果保存到Rd中。指令的格式如下:
应用示例:
数据传送指令MOV是将operand2操作数传送到目的寄存器Rd中。operand2操作数可以是立即数、寄存器和寄存器移位。在使用立即数时,并非所有的32位立即数都可以使用,具体请参照3.1节。
注意 :在ARM中为了实现将任意的32位立即数传送到寄存器,设计了ARM伪指令。ARM伪指令不同于汇编器中的伪指令,它是有机器码的,具体见3.3.7节。而汇编器中的伪指令只是在汇编器中使用,不产生机器码。
MOV指令的格式如下:
应用示例:
注意 :在ARM中没有设计专门的子程序返回指令和异常(特别是IRQ和FIQ中断服务程序)返回指令,而是使用MOV指令或其他方式完成从子程序返回和中断服务程序返回。只要向程序计数器PC赋值后,就会跳转到相应的地址处执行程序。
数据取反传送指令MVN是将operand2操作数按位取反后,传送到目的寄存器Rd中。指令格式如下:
应用示例:
比较指令CMP是用Rn的值减去operand2操作数,操作的结果影响CPSR中的相应条件标志位,以便其后的指令根据其条件判断是否执行。这里要说明的是,该指令并不改变其两个操作数的内容。指令格式如下:
应用示例:
负数比较指令CMN是将Rn的值加上operand2操作数的值,根据操作的结果影响CPSR中的相应条件标志位,以便其后的指令根据其条件判断是否执行。该指令并不改变其两个操作数的内容。指令格式如下:
使用方法:Rn中存放的是要比较的负数,并且以补码的形式表示,operand2是另一个要比较的数。
应用示例:
注意 :-1的补码就是0xFFFFFFFF,与1相加自然就等于0,所以Z=1,满足MOV指令的执行条件。
推论 :假如立即数1的位置用正数N替换,那么,如果R0是-N的补码,则标志位Z=1。
TST是位测试指令,是将Rn的值与operand2操作数的值按位做逻辑“与”操作,根据操作的结果影响CPSR中的相应条件标志位,以便其后的指令根据其条件判断是否执行。这里要说明的是,该指令并不改变其两个操作数的内容。指令格式如下:
使用方法:该指令主要用于判断Rn中的某一位或多位的位值是否为0,只要将操作数operand2的值对应的位取“1”,然后组成一个32位立即数。如果指令作用的结果使得条件标志位Z=1,说明对应取值为“1”的位全为“0”,达到了检测的目的。
应用示例:
TEQ是测试相等指令,是将Rn的值与operand2操作数的值按位进行逻辑“异或”操作,根据操作的结果影响CPSR中的相应条件标志位,以便其后的指令根据其条件判断是否执行。该指令并不改变其两个操作数的内容。指令格式如下:
使用方法:该指令主要用于测试Rn中的值是否与operand2操作数的值相等,通过按位进行逻辑“异或”运算,如果两者的所有对应位都相同,“异或”结果为全“0”,则条件标志位Z=1,说明两者相等;否则,说明两者不相等。
应用示例:
ARM微处理器系统对于存储器的操作只能使用寄存器装载和存储指令。基本的装载/存储指令仅有5条,其他的指令都是由它们派生出来的,可将其分为3种,分别是LDR和STR指令,称为单寄存器装载/存储指令;LDM和STM指令,称为批量装载/存储指令;SWP指令,称为寄存器与寄存器数据交换指令。
LDR和STR指令派生的指令最多,可以进行字节操作、半字操作和字操作。LDR指令的功能是将存储器中的内容装载到单个寄存器中去;STR指令的功能是从单个寄存器向内存写数据。
LDM和STM指令只能进行字的操作,它们派生的指令一类是对存储器块的操作,另一类是对堆栈区数据块的操作。LDM指令的功能是将存储器或堆栈区中的块数据装载到N个寄存器中;STM指令的作用是将N个寄存器的内容写入块存储器或块堆栈区中。
SWP指令有两种形式:SWP和SWPB。
表3-3列出了寄存器装载和存储指令,以下对其进行较为详细的介绍。
表3-3 寄存器装载和存储指令表
以下对表3-3中的部分内容进行说明。
<addr>:代表的是存储器中的地址单元,它可以由寄存器、寄存器+偏移量或寄存器+寄存器移位偏移量组成,在3.2节ARM寻址方式中已经介绍。
{mode}:如果对存储器进行块操作,则模式mode应是IA、IB、DA、DB其中之一;如果对堆栈区进行块操作,则模式mode应是FA、FD、EA、ED其中之一。
“以用户模式”:指在特权模式下可以以用户的身份操作用户寄存器组中的寄存器。在用户模式下,后缀带T的指令无效。
寄存器装载和存储指令LDR/STR可分为按字操作指令、按半字操作指令、按字节操作指令,以下简要介绍它们的格式与功能以及寻址方式等。
以字方式操作的指令格式与功能:
以半字方式操作的指令格式与功能:
注意 :半字操作时,地址值必须为偶数,即按半字对齐。非半字对齐的操作地址不可靠。
以字节操作的指令格式与功能:
LDR/STR的指令寻址方式非常灵活,由两部分组成,一部分是基址寄存器,可以使用任意一个通用寄存器;另一部分是基址偏移量。它有3种形式,以下进行简要介绍。
1) 立即数形式 。立即数用一个无符号数表示,它既可以与基址寄存器Rn相加,也可以与基址寄存器相减,从而形成一个有效的地址存储器操作地址。例如:
2) 寄存器形式 。即用寄存器的内容作为偏移量,与基址寄存器Rn的内容相加或相减,从而形成一个有效地址作为存储器操作地址。例如:
3) 寄存器移位偏移量形式 。即将寄存器Rm的内容经过移位操作后,与Rn的内容相加,从而形成一个有效地址作为存储器操作地址。移位的方法在3.1.1节已经介绍,主要有逻辑左移LSL、逻辑右移LSR、算术右移ASR、循环右移ROR和带扩展的循环右移RRX。例如:
小结: 在3.1.1节已经介绍过指向存储器地址指针的修改,即Rn值的变化可分为前变址模式、自动变址模式和后变址模式。结合寄存器装载/存储指令可对存储器中字节数据、半字数据和字数据进行操作。此外,对它们可以进行基址寄存器Rn加上立即数偏移量、寄存器偏移量和寄存器移位偏移量的操作,这样可以组合成许许多多的具体指令操作。以下列举一些指令,帮助读者进一步掌握各种指令。
批量寄存器装载指令LDM完成的操作是,将存储器块中的n个字数据装载到n个寄存器中。n个寄存器在指令中组成一个寄存器列表。批量数据存储指令STM,就是将n个寄存器中的值写入到地址连续的存储器块中。
LDM和STM相配合主要完成两项工作:一是可以完成将存储器中某一个首地址连续的数据块传送到存储器中的另一个数据区域中,实现数据的复制;二是用于堆栈区数据的压栈与弹栈。指令的格式如下:
1)<mode>根据完成的工作,可分为两个类型。
类型1是在进行存储器块的复制工作时,使用以下4种模式之一。
IA:每次传送数据后地址寄存器Rn加4,即先传送数据后修改地址指针(+4)。
IB:每次传送数据前地址寄存器Rn加4,即先修改地址指针(+4)后传送数据。
DA:每次传送数据后地址寄存器Rn减4,即先传送数据后修改地址指针(-4)。
DB:每次传送数据前地址寄存器Rn减4,即先修改地址指针(-4)后传送数据。
类型2是当进行堆栈操作时,使用以下4种模式之一。
FA:满栈增堆栈;FD:满栈减堆栈;EA:空栈增堆栈;ED:空栈减堆栈。它们代表的物理操作前已讲述。
注意 :压栈与弹栈指令选取的<mode>必须相同。
2)Rn:基址寄存器,装有传送数据的开始地址。当Rn使用SP(R13)时,主要用于对堆栈区的操作;当Rn使用其他通用寄存器时,用于存储器区内数据块之间的复制。
3){!}:若选取,则自动修改Rn内的有效地址值;否则指令执行后Rn中的内容不变。对于大于列表中寄存器个数的数据块复制一般都要选取使用,以减小汇编程序的额外开销;对于堆栈区域的操作必须使用,以保证堆栈指针的正确性。
4)reglist:一般是在R0~R12中,且从除Rn之外的通用寄存器中选取,PC寄存器、LR寄存器也是列表中可包含的元素。列表的书写方法在3.2.6节中已经介绍。
5){^}:“^”后缀不允许在用户模式和系统模式下使用,在其他模式下使用时,如果LDM指令中的寄存器列表包含PC,这时除了完成列表中的寄存器数据装载外,还将SPSR复制到CPSR中,主要用于异常处理返回。
使用“^”后缀进行数据传送,且寄存器列表不包含PC时,装载/存储的是用户模式下的寄存器,而不是当前模式下的寄存器。
注意 :LDM和STM指令操作时,要求字对齐,否则会出现意想不到的问题;使用这两条指令时,使用的指令应该配对,即选取的<mode>相同。
堆栈操作时有4对指令:LDMFA/STMFA、LDMFD/STMFD、LDMEA/STMEA、LDMED/STMED,它们必须配对使用。
存储器操作时也有4对指令:LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、LDMDB/STMDB,它们最好配对使用。
应用示例:
以下再列举一例,功能是将以R0为首地址的256字节数据复制到以R1为首地址的存储器单元中。
在ARM指令中要实现汇编程序的跳转有两种方法:一种是直接向PC寄存器赋值,可实现程序在4G范围内的任意跳转;另一种是使用跳转指令,可实现相对于当前程序计数器PC指针的跳转,这是本节介绍的主要内容。
ARM的跳转指令主要有以下4条。
B:跳转分支指令。
BL:带链接的跳转分支指令。
BX:带状态切换的跳转分支指令。
BLX:带链接和状态切换的跳转分支指令。
在通常状态下,处理器都是按顺序执行指令,当它执行到跳转分支指令时,将直接跳转到分支程序开始的地方去执行,程序也不会回到原来跳出的程序处继续执行。当指令执行到带链接的跳转分支指令时,程序将跳转到分支程序开始的地方去执行,在这种情况下该分支程序是一个子程序,执行完后将返回到原来跳出的程序处继续执行。
B指令是一条最为简单的指令。指令在执行过程中一旦遇到B指令,ARM微处理器将立即跳转到指令给定的目标地址,从那里开始执行指令。该指令也可以选择根据其附带的条件码来执行,为程序的分支执行控制提供可能。
BL指令是一条带有链接的跳转分支指令,除具有B指令的分支跳转功能外,还具有异常返回或返回主程序的功能。在执行跳转(即调用子程序)前,处理器自动地将当前程序计数器PC(R15)的值存储在链接寄存器LR(R14)中,作为将来子程序返回的指针。当子程序执行完毕后,将LR的内容复制到PC中,就可以实现子程序的返回。其指令格式如下:
其中,<Label>为程序跳转的相对于当前PC值的偏移量,两者结合构成一个相对转移地址,而不是绝对地址。它是将一个24位有符号数左移2位形成一个26位有符号的地址值,这样它的偏移量就是±32MB,也就是说前偏移32MB、后偏移32MB范围内的程序转移。由于PC寄存器是32位的,所以对于它的最高6位使用符号位进行填充,其数值的大小维持不变,使其构成一个32位的有效地址值。正数时填充值为全“0”,负数时为全“1”。它的值是由汇编器计算出来的,程序员只需要写上标号地址即可,但是它的取值范围程序员必须心中有数。
B 指令应用示例:
BL 指令应用示例:
注意 :当在Sub_Route1子程序中调用了Sub_Route2,此时的LR又被调用Sub_Route2时的程序PC值所代替,使得子程序Sub_Route1的最后一条指令MOV PC,LR执行有误。解决的方法如下:
如果在Sub_Route2子程序中还要调用子程序,处理方法相同。
仿照上面的指令B和BL,一个是实现程序的跳转不返回主程序指令,另一个是实现子程序调用而返回主程序指令,这里的X表示在跳转或子程序调用时ARM微处理器的工作状态也会发生变化,即由ARM指令工作状态转换到Thumb指令工作状态。
这两条指令在使用的过程中有两种形式。
形式1:B{L}X{cond} <Label>
形式2:B{L}X{cond} <Rm>
它们使用时的具体格式有BX Label指令、BLX Label指令、BX Rm指令、BLX Rm指令。
1)BX Label指令是由ARM指令程序跳转到Thumb指令程序的Label标号处执行,且不能返回到跳转时的ARM程序指令处,跳转的范围是±32MB。
2)BLX Label指令是由ARM指令程序跳转到Thumb指令程序处执行,在跳转时当前程序计数器PC的值已经存入LR,当执行完Thumb子程序后,恢复PC的原值将会返回到跳转时的ARM程序指令处继续执行。跳转的范围是±32MB。
3)BX Rm指令既可以跳转到ARM指令程序处执行,也可以跳转到Thumb指令处执行,跳转后不会返回到主程序。当寄存器Rm的b0=1时,指令自动将CPSR的T位置1,程序转到Thumb指令处执行;当Rm的b0=0时,程序转到ARM指令处执行。注意跳转的地址由Rm确定。
4)BLX Rm指令既可以跳转到ARM指令程序处执行,也可以跳转到Thumb指令处执行,在跳转时当前程序计数器PC的值已经存入LR,执行完子程序后将链接寄存器LR的值复制给PC,返回到主程序。当寄存器Rm的b0=1时,指令自动将CPSR的T位置1,程序转到Thumb指令子程序入口处执行;当Rm的b0=0时,程序转到ARM指令子程序入口处执行。注意跳转的地址由Rm确定。
应用示例:
ARM杂项指令主要包括程序状态寄存器操作和异常中断操作两种类型的指令,共有4条。
程序状态寄存器操作指令有两条,分别是程序状态寄存器传送到通用寄存器指令MRS、通用寄存器或立即数传送到程序状态寄存器指令MSR。
异常中断指令有软件中断指令SWI和断点中断指令BKPT。
在ARM指令系统中,程序状态寄存器操作指令主要用于程序状态寄存器与通用寄存器间的数据传送,它们之间相互配合,通过MRS读程序状态寄存器→通用寄存器→修改→通过MSR再写回到程序状态寄存器,完成对程序状态寄存器的内容修改,从而进行异常模式的切换或修改普通中断控制位I和快速中断控制位F。
MRS指令的功能是把状态寄存器psr(包括CPSR和SPSR)传送到通用目标寄存器。指令的格式如下:
注意 :在ARM中只有通过该条指令才能读出状态寄存器(CPSR或SPSR)的内容到通用寄存器中;Rd不允许使用R15(PC)。
该指令一般在以下几种情况下使用。
● 当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。
● 当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后压栈保存。
应用示例:
MSR指令的功能是把通用寄存器内容传送到状态寄存器psr(包括CPSR和SPSR)中。指令的格式如下:
其中,<psr_fields>:psr指的是CPSR或SPSR寄存器,fields域是将这两个32位的寄存器按每8位进行划分,共划分为4个域,分别如下。
● 位[31:24]为条件标志位域,用f表示。
● 位[23:16]为状态位域,用s表示(预留备用位)。
● 位[15:8]为扩展位域,用x表示(预留备用位)。
● 位[7:0]为控制位域,用c表示。
<#immed|Rm>:此地方操作数可以是一个8位的立即数,正好对应着上述的某一个域,为程序员的编程提供了便利。也可以使用寄存器,操作时仅将它对应的域值传送到psr的域中。
应用示例:
软件中断指令SWI(Software Interrupt)用于产生软件中断,从而实现在用户模式下对操作系统中特权模式的程序调用等,以便用户程序能调用操作系统的系统例程。指令的格式如下:
该指令的工作机制是这样的,当条件满足时执行该指令,处理器将进入管理模式中,需要完成以下任务。
● 将指令SWI后面的指令地址保存到R14_svc中。
● 将当前的CPSR保存到特权模式中相应的异常模式SPSR_svc中。
● 进入SVC管理模式,将CPSR[4:0]设置为0b10011并将CPSR[7]置1,禁止IRQ中断。
● 将程序计数器(PC)的指针指向0x00000008的异常向量处,开始执行其中的程序,这里一般是一条跳转指令,跳转到某一标号后执行从SWI指令格式中析出24位立即数的程序中,根据析出的24位立即数,查找执行对应的处理程序。处理完毕后子程序返回。
由于在用户模式下不能执行所有特权模式下的一些操作,如模式之间的切换、各种异常模式堆栈的指针设置,T、IRQ、FIQ位的清0或置1等。SWI指令允许用户从用户模式进入特权模式,进行相应的操作,如关闭IRQ等。
操作系统也可以在SWI的异常处理程序中提供相应的系统服务,指令中24位的立即数指定用户程序调用系统的例程,相关参数通过通用寄存器传递。
应用示例:
需要说明的是,软中断是由用户调用的,具有可知性;IRQ或FIQ是由外部条件触发的,具有随机性。
当指令中24位的立即数被忽略时,用户程序调用的系统例程也可由通用寄存器R0的内容决定,同时,参数通过其他通用寄存器传递。
断点中断指令BKPT主要用于产生软件中断,供调试程序使用。指令格式如下:
该16位立即数被调试软件用来保存额外的断点信息。
下列程序段是利用它们之间的配合,通过“读取→修改→写回”操作完成各种异常栈顶指针的设置。这也是Bootloader中主要完成的任务之一。
注意 :只有在特权模式下才能修改状态寄存器控制域[7:0]的值,以实现处理器模式的转换或禁止/允许中断异常。
控制域中的T(Thumb)位在MSR指令中不能赋值修改,而将ARM工作状态切换到Thumb工作状态。只有使用BX指令才能实现前述工作状态的切换。
在用户模式下,只能修改“条件标志位域”,不能修改其他域,即CPSR的[24:0]位。
在MRS和MSR指令中不能使用后缀“S”。
ARM微处理器支持协处理器操作,协处理器的控制要通过协处理器指令实现。在程序执行的过程中,每个协处理器只执行针对自身的协处理指令,忽略ARM微处理器和其他协处理器的指令。如果协处理器不能成功地执行其操作,将产生未定义指令异常。
ARM协处理器指令的主要作用包括:①ARM处理器初始化;②ARM协处理器的数据处理操作;③ARM微处理器寄存器到协处理器寄存器之间的数据传送;④ARM协处理器寄存器到ARM存储器之间的数据传送。
协处理器共有5条指令,见表3-4,下面分别介绍。
表3-4 ARM协处理器指令表
CDP指令为协处理器操作指令。ARM微处理器通过CDP指令通知ARM协处理器执行特定的操作。该操作由协处理器完成,即对命令参数的解释与协处理器有关,指令的使用也取决于协处理器。指令格式如下:
其中,coproc是协处理器名,书写格式为Pn,n=0~15;opcode1为协处理器操作码;CRd为协处理器目标寄存器;CRn、CRm为协处理器的第1、第2操作数寄存器;opcode2是opcode1的可选子操作码。
注意 :协处理器寄存器使用Cn,n=0,1,2,3…。
应用示例:
协处理器数据装载指令LDC是从连续的存储单元将数据读取到协处理器寄存器中。协处理器数据传送的字数由协处理器控制。协处理器数据存储指令STC与LDC的功能相反。指令的格式如下:
其中,<addr>是ARM微处理器的基址寄存器Rn的间接寻址或移位寻址,其他同上。选取后缀“L”时,表示为长整数传送,用于双精度的数据传输。
应用示例:
传送指令MCR是将ARM寄存器的内容传送到协处理器寄存器中去。传送指令MRC是将协处理器寄存器的内容传送到ARM寄存器中去。指令格式如下:
应用示例:
ARM伪指令不是ARM指令集中的指令,只是为了编程方便定义的指令,可以像其他ARM指令一样使用,但在编译时这些指令将被等效的ARM指令代替。ARM伪指令有ADR、ADRL、LDR、NOP,共4条。
小范围的地址读取伪指令ADR将基于PC相对偏移的地址值加载到寄存器中。指令格式如下:
其中,register为加载的目标寄存器,expre为地址表达式。当地址值是非字对齐地址时,取值范围为-255~255字节;当地址是字对齐地址时,取值范围为-1020~1020字节。
ADR伪指令在ARM汇编时始终被汇编成一条指令。在编程时程序员并不需要关心相对于PC的偏移量,也不需要计算偏移量,由汇编器自动完成。汇编器会试图产生一条ADD指令或SUB指令来加载地址,若地址加载不能汇编成一条指令,则产生错误,汇编失败。若标号是程序相对偏移量,则它的值必须与ADR伪指令在同一代码存储区域。
应用示例:
上述第二条伪指令将被汇编成SUB R1,PC,0x0C指令。注意此时的PC值是当前指令地址+8,因此当前的PC值减12(0x0C),就是Label的值。执行上述第3条伪指令ADR时,当前的PC指针也是指令地址+8,即Data_Tab标号处,所以此指令将汇编成ADD R2,PC,#0x00。
中等范围的地址读取伪指令ADRL将程序相对偏移地址或寄存器相对偏移地址加载到寄存器中。在汇编编译源程序时,ADRL伪指令被编译器替换成两条合适的指令。若不能用两条指令实现ADRL伪指令功能,则产生错误,编译失败。指令格式如下:
其中,register为加载的目标寄存器,expre为地址表达式。当地址值是非字对齐地址时,取值范围为-64K~64K字节;当地址值是字对齐地址时,取值范围为-256K~256K字节。
ADRL伪指令始终被汇编成两条指令。即使地址加载可以用一条指令完成,汇编器也会生成两条冗余指令。在编程时程序员并不需要关心相对于PC的偏移量是多少,也不需要计算偏移量,由汇编器自动完成。若标号是程序相对偏移,则它的值必须与ADRL伪指令在同一代码存储区域。
应用示例:
汇编器将第2条伪指令生成ADD R1,PC,#0xE800和ADD R1,R1,#0x254两条指令。注意当前的指令PC值是PC+8,即等价于第1条指令地址是Label标号地址+12。现在程序要以PC为基址,所以相对于Label+6000的地址值,就是相对于当前的PC值+6000-12=PC值+5988(0xE800+0x254)。
大范围的地址读取伪指令LDR用于加载32位的立即数或一个地址值到指定寄存器。在汇编器编译源程序时,LDR伪指令被汇编器替换成一条合适的指令。若加载的常数未超出MOV或MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将产生文字常量放入文字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。LDR伪指令格式如下:
其中,register为加载的目标寄存器,expre为32位立即数,labe1_expre为程序相对偏移或外部表达式。
以下说明LDR伪指令的解释过程。举例如下:
上述的offset_pool是文字池中由系统自动定义的字单元数据相对于pool的位置偏移量,程序员只需在其后适当的地方(具体参见4.1.3节)写一条文字池声明伪指令LTORG即可。自动定义的内容是:pool DCD 0xfff,data。
这条ARM伪指令与寄存器装载指令有着相同的操作码,但是它的操作数前需要使用“=”符号。LDR指令是使用频率最高的指令,也最为方便,程序员不必关心赋值范围是否越限。
应用示例:
注意 :ARM伪指令是假的伪指令,是ARM编译器定义的伪指令,会生成相应的机器码。而一般意义下的伪指令只供汇编器使用,不产生机器码。
另外,由于ARM都是单字指令,无法将任意一个32位的二进制数赋值给它的寄存器,只能将8位的二进制数乘以整数2*(0~15),通过MOV指令传递给寄存器(原理前已讲述),这样造成了不能将任意的32位立即数送入系统作为操作数或有效的地址单元。为了弥补这一点,便引入了ARM伪指令。
空操作指令NOP在汇编时将会被代替成ARM中的空操作,如MOV,R0,R0指令等。NOP可用于延时操作。NOP伪指令格式如下:
应用示例:
Thumb指令集 如果读者需要,可以参考其他书籍学习,这里不再赘述。