一、ARM指令的分类

ARM 指令集可以分为跳转指令、数据处理指令、程序状态寄存器(PSR)传输指令、Load/Store指令、协处理器指令和异常中断产生指令6类。

二、ARM指令的一般编码格式

ARM指令字长为固定的32位。

一条典型的ARM指令编码格式如下:

第二章 ARM指令分类及其寻址方式 - 图1

经典ARM指令格式如下:

  1. <opcode> {<cond>} {S} <Rd>,<Rn>,<operand2>
  • <> 为必选项,{}为可选项
  • <opcode>为操作码(指令助记符),如ADD表示算术加操作指令
  • {<cond>} 表示执行的条件
  • {S} 决定指令执行是否影响CPSR寄存器的值
  • <Rd>表示目标寄存器
  • <Rn>表示包含的第一个操作数的寄存器
  • <operand2>为第二个操作数

三、ARM指令的条件码域

大多数ARM 指令都可以条件执行,也就是根据CPSR中的条件标志位决定是否执行该指令。当条件满足时执行该指令,条件不满足时该指令被当作一条NOP指令,这时处理器进行判断中断请求等操作,然后转向下条指令。

在ARM v5之前的版本中,所有的指令都是条件执行的,从ARM v5版本开始引入了一些指令必须无条件执行。

每一条ARM指令包含4位的条件码,如下所示:

第二章 ARM指令分类及其寻址方式 - 图2

条件码共有16个,各条件码的含义和助记符如下表所示。可条件执行的指令可以在助记符的扩展域加上条件码助记符,从而在特定的条件下执行。

第二章 ARM指令分类及其寻址方式 - 图3

四、ARM指令寻址方式

ARM指令寻址方式有以下几种:

  • 数据处理指令的操作数的寻址方式。
  • 字及无符号字节的Load/Store指令的寻址方式。
  • 杂类Load/Store指令的寻址方式。
  • 批量Load/Store指令的寻址方式。
  • 协处理器Load/Store指令的寻址方式。

1、数据处理指令的操作数的寻址方式

通常数据处理指令的格式如下所示:
<opcode>{<cond> } {S} <Rd>,<Rn>,<shifter operand>

第二章 ARM指令分类及其寻址方式 - 图4

其中:
<opcode>是指令助记符,如ADD表示算术加操作指令。

<cond>表示指令执行的条件。

{S}决定指令的操作是否影响CPSR的值。

<Rd>表示目标寄存器。

<Rn>表示包含第主个操作数的寄存器。

<shifter_operand>表示第2个操作数。

<shifler_operand>通常有下面3种格式:

  • 立即数:就是一个常数,是指令的一部分,类似于常量,不能作为被赋值的量来使用
    • 立即数的优点:取指令的时候,可以将他读取到cpu,不用单独去内存读取,速度比较快
    • 立即数的缺点:不能是任意的32位的数
  • 有效立即数:
    • 在12位的第二个操作数里,[11:8]是4位的循环右移值,[7:0]是8位的常数
    • 有效立即数的范围0-0xff
  • 确定0x123是不是有效立即数
    • 汇编指令:MOV r0,#0x123
    • 第一步:转化成二级制
      第二章 ARM指令分类及其寻址方式 - 图5
      无法通过循环右移得到8位位图
    • 结论:0x123不是立即数
  • 确定0x234是不是有效立即数
    • 汇编指令:MOV r0,0x234
    • 0000 0000 0000 0000 0000 0010 0011 0100
    • 0000 0000 0000 0000 0000 0000 1000 1101—循环右移30位,可以得到rotate_imm的值是15
  • 确定0x3F0是不是有效立即数
    • 汇编指令:MOV R0,#0x3f0
    • 0000 0000 0000 0000 0000 0011 1111 0000
    • 0000 0000 0000 0000 0000 0000 0011 1111
  • 0x20000018
    • 0010 0000 0000 0000 0000 0000 0001 1000
    • 左移4位得到:0000 0000 0000 0000 0000 0001 1000 0010
  • 总结
    1. 判断一个数是否符合8位位图原则,首先看这个数字的二进制,他们中表示1的个数是否不超过8个
    2. 如果超过了8个,再看看这n个(n<=8)是否能够放到8个二进制位中;
    3. 如果可以放进去的话,再看下这8个二进制位是否可以循环右移得到原来的二进制书;
    4. 如果可以的话,就是符合8位位图,是合法的立即数
    5. 如果不可以,不是合法的立即数;
    6. 当把数据转换成二级制的时候,如果1不超过8个,但是间隔很多个偶数倍的0,我们可以循环左移
    7. 然后找到最高位的1,去掉前面偶数个0
    8. 找到最低位的1,去掉后面偶数个0,得到这个数是否能够放进8位位图,能的话就是一个有效立即数,反之则不是。
  • 立即数方式 每个立即数由一个8位的常数循环右移偶数位得到。其中循环右移的位数由一个4位二进制的两倍表示。如果立即数记作<immediate>,8位常数记作immed_8,4位的循环右移值记作rotate_imm,则有:
    <immediate>= immed_8 循环右移(2*rotate_imm)
    立即数 = 8位常数 ROR(2 * 移位数) ROR:循环右移指令
    这样并不是每一个32位的常数都是合法的立即数,只有能够通过上面构造方法得到的才是合法的立即数。下面的常数是合法的立即数:
    0xff,0x104,0xff0,0xff00
    而下面的数不能通过上述构造方法得到,则不是合法的立即数:
    0x101,0x102,0xFF1
    同时按照上面的构造方法,一个合法的立即数可能有多种编码方法。如0x3F0是一个合法的立即数,它可以采用下面两种编码方法:
    immed_8=0x3F,rotate_imm=0xE
    或者
    immed_8=0xFC, rotate_imm=0Xf
    但是,由于这种立即数的构造方法中包含了循环移位操作,而循环移位操作会影响CPSR的条件标志位C。因此,同一个合法的立即数由于采用了不同的编码方式,将使某些指令的执行产生不同的结果,这是不能允许的。ARM 汇编编译器按照下面的规则来生成立即数的编码。

    • 当立即数数值在0和0xFF范围时,令immed_8=<immediate>,rotate_imm=0
    • 其他情况下,汇编编译器选择使rotate_imm 数值最小的编码方式。
    • 寄存器方式 在寄存器寻址方式下,操作数即为寄存器的数值。如下例所示:

      1. MOV R3,R2 ;将R2的数值放到R3
      2. ADD R0,R1,R2 ;R0数值等于R1的数值加上R2的数值
    • 寄存器移位方式 寄存器移位方式的操作数为寄存器的数值做相应的移位(或者循环移位)而得到。只体的移位(或者循环移位)的方式有下面几种:
      移位(或者循环移位)的位数可以用立即数方式或者寄存器方式表示。
      下面是一些寄存器移位方式的操作数示例。
      数据处理指令操作数的具体寻址方式有下面11种。

      1. ASR 算术右移
      2. LSL 逻辑左移
      3. LSR 逻辑右移
      4. ROR 循环右移
      5. RRX 扩展的循环右移
      1. MOV R0,R1,LSL #3 ; R0=R1*(2**3)
      2. ADD R0,R1,R1,LSL #3 ; R0=R1+R1*(2**3)
      3. SUB R0,R1,R2,LSR #4 ; R0=R1-R2/(2**4)
      4. MOV R0,R1,ROR R2 ; R0-R1循环右移R2

      ``` 立即数

寄存器

,LSL # 逻辑左移 shifter_imm 代表移位的位数,下同

,LSL Rs为寄存器

, LSR # 逻辑右移

, LSR 逻辑右移寄存器Rs的值的位

, ASR # 算术右移shift_imm位

, ASR 算术右移寄存器Rs 的值的位

, ROR # 循环右移shifter_imm位

, ROR 循环右移寄存器Rs的值的位

, RRX 扩展的循环右移

  1. <a name="b6dc7d63"></a>
  2. #### 1.立即数寻址方式 `#<immediate>`
  3. 指令编码格式如下图所示:
  4. ![](https://gitee.com/waqwb/my-pic-go/raw/master/202203011051805.png#crop=0&crop=0&crop=1&crop=1&id=uj5Nj&originHeight=67&originWidth=755&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  5. rotate_imm:4位循环右移值。即之前所提到的公式中的移位数。
  6. immed_8:8位常数。参照下面的立即数公式。
  7. **立即数 = 8位常数 ROR(2 * 移位数) ROR:循环右移指令**
  8. 指令操作数<shiter_operand>即为立即数#,立即数#的生成方式之前已经介绍,不在赘述。当rotate_imm = 0时,循环器的进位值(即Carry-out位)为CPSR中的C条件标志;当rotate_imm != 0时,循环器的进位值(即Carry-out位)为操作数<shiter_operand>的最高为bit[31]。
  9. 指令中操作数寻址操作的伪代码如下:
  10. shifter_operand = imme_8 Rotate_Right (rotate_imm * 2)
  11. if rotate_imm == 0 then

shifter_carry_out = C flag

  1. else

shifter_carry_out =shifter_operand[31]

  1. 立即数的合法性和编码规则,请查看之前文章中介绍。
  2. 示例:

MOV R0 , #0xFC0 ;R0=0xFC0

  1. <a name="a081a215"></a>
  2. #### 2.寄存器寻址`<Rm>`
  3. 指令编码格式如下:
  4. ![](https://gitee.com/waqwb/my-pic-go/raw/master/202203011051509.png#crop=0&crop=0&crop=1&crop=1&id=sFEL9&originHeight=89&originWidth=895&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  5. 指令的操作数<shifter_operand>即为寄存器Rm的数值。另外循环器进位值(即 Carry_Out 位)为CPSR的C条件标志。
  6. 指令中操作数语法格式
  7. 其中,指定操作数所在的寄存器。
  8. 伪代码如下:

shifter_operand = Rm

shifter_carry_out = C Flag

  1. 需要注意的是,当R15(PC指针)用作第一个源操作数Rm或者第二个操作数Rm时,操作数即为当前指令地址加常数8。造成上述的原因,是因为ARM指令集采用的是流水线的方式造成的。
  2. 示例

MOV R3,R2 ;将R2的数值放到R3中 ADD R0,R1,R2 ;R0数值等于R1的数值加上R2的数值

  1. <a name="0c98f2ea"></a>
  2. #### 3.寄存器逻辑左移:`<Rm>, LSL #<shift_imm>`
  3. 指令编码格式:
  4. ![](https://gitee.com/waqwb/my-pic-go/raw/master/202203011051516.png#crop=0&crop=0&crop=1&crop=1&id=qnTy1&originHeight=77&originWidth=887&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  5. 指令操作数<shifter_operand>为寄存器Rm的值逻辑左移shift_imm位。shift_imm为5位,范围是0~31位。进行逻辑左移的时候,空出的位补0。
  6. 循环器的进位值(Carry_Out位):
  7. - 当shift_imm = 0时,Carry_Out = C Flag(CPSR的C位)。
  8. - 当shift_imm !=0时,Carry_Out = Rm[32 - shift_imm](即shifter_operand 的最高位bit[31])
  9. 指令中操作数的语法格式:
  10. , LSL #<shift_imm>
  11. - 为进行逻辑左移操作的寄存器。
  12. - LSL 逻辑左移操作。
  13. - <shift_imm>逻辑左移位数,范围0~31。
  14. 指令中操作数寻址操作的伪代码

if shiftimm == 0 then /寄存器操作数/ shifter_operand = Rm shifter_carry_out = Flag else / shift_imn > 0 / shifter_operand = Rm Logical_Shift Left shiftimm shifter_carry_out = Rm [32 - shift imm]

  1. 使用说明
  2. R15用作第1个源操作数Rn或者第⒉个操作数Rm 时,操作数即为当前指令地址加常数8
  3. 示例:

MOV R0,R0,LSL #n ; R0 = R0 (2*n);

  1. <a name="6f3c849b"></a>
  2. #### 4. 寄存器逻辑左移:`<Rm>, LSL <Rs>`
  3. 指令编码格式:
  4. ![](https://gitee.com/waqwb/my-pic-go/raw/master/202203011051673.png#crop=0&crop=0&crop=1&crop=1&id=YTDmm&originHeight=49&originWidth=721&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  5. 指令操作数的生成方式和之前提到的逻辑左移的方式相同,但移位的位数是由寄存器中的低八位[7:0]决定。
  6. 当Rs[7:0] = 0时,指令的操作数<shiter_operand>为寄存器Rm的值,循环器的进位值<Carry_out>是CPSR的C位条件标志。
  7. 当Rs[7:0] > 0 && Rs[7:0] < 32时,指令操作数<shiter_operand> 是寄存器 Rm的值逻辑左移Rs[7:0]位,循环器的进位值<Carry_out>是Rm最后被移出的位Rm[32 - Rs[7:0]]。
  8. 当Rs[7:0] = 32 时,指令操作数<shifter_operand> 为0,循环器的进位值是Rm[0]。
  9. Rs[7:0] > 0 时,指令的操作数<shiter_operand> 为0,循环器进位值为0。
  10. 指令操作数的语法格式和标题相同:`<Rm>, LSL <Rs>`
  11. 其中:
  12. - `<Rm>`为进行逻辑左移操作的寄存器。
  13. - LSL逻辑左移操作。
  14. - `<Rs>`包含逻辑左移位数的寄存器。
  15. 指令中操作数寻址操作的伪代码

if Rs [7:0] == then shifter operand = Rm shifter carry_out = C Fiag else if Rs[7:0] < 32 then shifter_operand = Rm Logical_shift_Left Rs[7:0] shifter_carry_out = Rm[32 - Rs[7:0]] else if Rs[7:0] == 32 then shifter_operand = 0 shifter_carry_out = Rm[0] else / Rs[7:0]>32 / shifter_operand = 0 shifter_carry_out = 0 ```

需要注意的是:

当R15(PC指针)用作Rn、Rm、Rd和Rs时,会产生不可预知的后果。