深入理解bootloader_4----- ARM 指令集

来源:互联网 发布:淘宝客 余杭区法院 编辑:程序博客网 时间:2024/05/22 21:55

ARM 指令集分为以下几个大类:数据处理指令、分支指令、软中断指令、程序状态寄存器指令、协处理器指令、加载常量的伪指令。

1、数据处理指令

数据处理指令操作寄存器的数据。他又可以细分为移动指令、算数指令、逻辑指令、比较指令、乘法指令。大多数数据指令可以处理一个使用移位器的参数。
如果数据处理指令使用S后缀,那么该指令就会更新cpsr中的标志位。移动和逻辑操作会更新C、N、Z。当移位器最后一位移出,C位置位。N位设为结果的bit[31].结果为0,设置为Z位。

1.1、移动指令

移动指令是最简单的ARM指令,它复制N到目的寄存器RD中,其中N 可以是个寄存器或立即数值。该指令用于设置初始值和寄存器之间的 数据传输。
语法格式:
{} {s} Rd , N
MOV 将32位的值移到一个寄存器之中 Rd=N
MVN 将32位的值取反后移到一个寄存器中 Rd=~N
举例:在 arch/arm/lib/crt0.S中有
mov r9,sp
mov r0,#0

第一行的含义为将sp寄存器的值复制到r9中,
第二行的含义是将寄存r0的值设为0.

在arch/arm/cpu/armv7/nonsec_virt.S中有
mvn r1,#0
该行将寄存器r1的值设为0xffffffff。

1.2、移位器

N不仅是上面介绍的MOV指令中代表的寄存器或者立即数,它还可以是寄存器Rm经过移位器预处理后的值。
数据处理指令由算数逻辑单元处理,ARM处理器的一个独有且强大的特性是在数据进入ALU处理之前可以将一个源寄存器中的32位二进制向左或向右移动一个特定的位置。这个移位器极大的增加了许多数据处理指令的功能和灵活性。
也有一些数据处理器不适应移位器,比如MUL、CLZ、QADD指令。
预处理操作在指令周期内进行。这个在将数据乘除2的指数方后再存入寄存器中特别高效。
共有5种移位操作:逻辑移位左移LSL、逻辑右移LSR、算术右移ASR、循环右移ROR和扩展的循环右移RRX。
举例:
在arch/arm/cpu/arm92ejs/orion5x/lowlevel_init.S中有
mov r1,r1,LSL #9
该行表示 r1 =(r1<<9)。

1.3、算数指令

算数指令执行32位有符号和无符号的加减法。
语法格式:
{} {s} Rd, Rn,N
常用算数指令:
ADC 带进位的加法 Rd = Rn + N + carry
ADD 加法 Rd = Rn + N
RSB 反向减法 Rd = N - Rn
RSC 带借位的反向减法 Rd = N - Rn -!(carry flag)
SBC 带借位的减法 Rd = Rn - N - ! (carry flag)
SUB 减法 Rd = Rn - N
举例:
在arch/arm/cpu/armv7/atsrt.S中有
sub sp, sp , #S_FRAME_SIZE
add r0, sp, #S_FRAME_SIZE
其中S_FRAME_SIIE 值 由宏 #define S_FRAME_SIZE 72 定义,所以,第一行指令的结果是sp = sp - 72; 第二行指令的的结果是 r0 = sp + 72。

1.4、使用移位器的算术指令

在算数指令中使用移位器可产生非常qiangda灵活的功能。
例如:
ADD r0, r1, r1, LSL #2
该指令是r0=r1 + (r1 <<2)

1.5、逻辑运算指令

逻辑运算指令执行两个源寄存器的逻辑位运算语法格式:
{} {s} Rd,Rn,N
常用逻辑运算指令如下:
AND 两个32位数值的逻辑与操作 Rd = Rn & N
ORR 两个32 位数值的逻辑运算或操作 Rd = Rn | N
EOR 两个 32 位 数值的逻辑异或运算或操作 Rd = Rn^N
BIC 逻辑清位操作
举例:
在arch/arm/cpu/armv7/start.S 中有
and r1, r0, #0x1f
orr r0, r0, #0xc0

第一行指令的结果是r1=r0&0x1f;
第二行的指令结果是 r0 = r0|0xc0。

1.6、比较指令

比较指令用于将寄存器和一个32位数比较或者测试。指令根据结果更新cpsr的标志位,但不影响其他的寄存器。当设定标志位后,可以使用条件来执行改变程序的执行流程,无需在比较指令中使用S后缀就可以更新标志位。
语法格式:
{} Rn ,N
常见比较指令如下:
CMN 否定比较 依照Rn + N的结果来设定标志位
CMP 比较 依照Rn - N 的结果来设定标志位
TEQ 测试两个32位数是否相等 依照 Rn^N 的结果来设定标志位
TST 测试一个32位数的位 依照Rn&N的结果来设定标志位
举例:
在 arch/arm/cpu/armv7/start.S 中有:
teqr1 , #0x1a
该行的含义为比较r1是否的等于0x1a,并设定标志位,这样后面的指令可以根据标志位来条件执行。

1.7、乘法指令

乘法指令将一个寄存器的值相乘,然后根据指令将结果累加存入另一个寄存器中,长乘法累加指令将一对寄存器组成一个64位的值。最后的结果放在一对寄存器中。
语法格式:
MLA{} {S} Rd, Rm,Rs,Rn
MUL{} {S} Rd,Rm,Rs
常见的乘法指令如下:
MLA 乘法和累加 Rd = (Rm*Rs)+Rn
MUL 乘法 Rd = Rm*Rs
语法格式:
{} {S} RdLo , RdHi,Rm,Rs
常见乘法指令如下:
SMLAL 有符号的长乘法累加 [RdHi,RdLo] = [RdHi,RdLo] + (Rm*Rs)
SMULL 有符号的长乘法 [RdHi,RdLo] = Rm*Rs
UMLAL 无符号的长乘法累加 [RdHi,RdLo] = [RdHi,RdLo] + (Rm*Rs)
UMULL 无符号的长乘法 [RdHi,RdLo] = Rm*Rs
举例:
在 board/mpl/vcma9/lowlevel_init.S 中:
mla r3 ,r4 , r1, r3
执行的结果是 r3 = (r4*r1) + r3

2、分支指令

分支指令改变程序执行流程或者用于调用子程序。该类指令可以使得程序有一个子程序、if - then - else结构和循环。
执行流程的改变强制程序计数器PC指向一个新的地址。ARMv5E指令集包含四种不同的分支指令。
语法格式:
B{} label
BL{} label
BX{} Rm
BLX{} label | Rm
常用的跳转指令如下:
B 跳转 PC= label
BL 带返回的跳转 PC = label,lr = 执行BL后下一条指令的地址。
BX 跳转并切换状态 PC = Rm&0xfffffffe ,T = Rm&1
BLX 带返回的分支并切换状态 PC = label,T =1
PC =Rm & 0xfffffffe, T = Rm & 1
lr 为执行BLX后下一条指令的地址
地址label以一个有符号的相对与pc的偏移量保存在指令中,必须被限制在分支指令的约32M范围内。
举例:
跳转的指令在程序中使用最多,函数的调用应该都是跳转指令实现的。在arch/arm/cpu/armv7/start.S中最初的reset异常处理函数有下面两个函数的跳转:
b1 cpu_init_cp15
b1 cpu_init_crit

3、软中断指令

软中断指令会引起软件中断异常,这为操作系统中应用程序调用系统历程提供了一种机制。
语法格式:
SWI{} SWI_number
常用的中断指令:
SWI 软中断 Ir_svc = SWI 指令之后下一条指令的地址
Spsr_svc = cpsr
Pc = vectors + 0x8
Cpsr mode = SVC
Cpsr I = I (屏蔽IRQ中断)
当处理器执行一条SWI指令,设置程序计数器PC为向量表中偏移量为0x8的位置。该条指令强制处理器模式改为SVC,这样就允许在特权模式下操作系统历程可以被调用。每条SWI指令有一个相关的SWI数,用于代表一个特别的函数调用或属性。
SWI指令最常见的应用就是操作系统的系统调用的实现。
举例:
在Linux下汇编写一个“hello word”:

.data
msg : .asciz “hello word\n”
.text
.align 2
    .global _start
_start :
    ldr r1 , =msg
    mov r0 , #1
    mov r2 , #13
    swi #0x900004
    mov r0 ,#0
    swi #0x900001
    .align 2
使用交叉编译器编译逅放在运行的ARM平台下Linux系统中运行,就可以 在终端看到“hello word”的输出。这里0x900004 是system write的swi数;0x900001是system exit的SWI数。

4、 程序状态寄存器指令

ARM指令集提供两条指令来直接控制程序状态寄存器(psr)。MRS指令将cpsr或spsr的内容传到寄存器中;相反的,MSR指令将寄存器的内容传到cpsr 或spsr中。综合起来,这些指令用于读写cpsr和spsr。
语法格式:
MRS{} Rd,

5、协处理器指令

协处理器是指令集的扩展。协处理器既可以提供额外的计算能力,也用于控制包括Cache和存储管理在内的存储系统。协处理器指令包括数据处理,寄存器传输和内存传输指令。这里只对协处理器做简单说明,因为这些指令与具体的协处理器相关,注意协处理器指令只能用于带有写处理器的ARM核。
语法格式:

CDP{} cp,opcode1,Cd , Cn {, opcode2}

6、加载常量的伪指令

前面介绍了那么多指令,但仍然没有一条ARM可以把一个32
位的常量装入寄存器中。因为ARM指令本身就是32位长度,所以很显然指令中不可能在定义一个32位常量。
为了方便编程,有两条伪指令可以将32位值装入寄存器中。
语法格式:
LDR Rd, =constant
ADR Rd , label
加载常量的伪指令含义如下:
LDR 加载常量的伪指令 Rd= 32位的常量
ADR 加载地址的伪指令 Rd = 32 位的相对地址
伪指令用于把一个32位常量送入寄存器,他的具体执行可以是任何可实现功能指令的功能。如果常熟无法用其他的指令编码,该指令就读取内部存储。
指令向寄存器写入一个相对地址,他会使用一个PC相对地址的表达式进行编码

举例:
在arch/arm/lib/crt0.S 有
adr lr, here
其中here 是一个标号,本身就是32位长度,该条指令实际上是用add lr ,pc, #12 来实现的。

文献参考:

抄录自 《深入理解BoorLoader》 胡尔佳 编著 (学习笔记仅作学习,交流,详细阅读请购买正版)