[ARM][汇编] 02.ARM 汇编常用简单指令

发布于:2025-05-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

目录

1.数据传输指令

MRS - Move from Status Register

指令用途

指令语法

代码示例

读取 CPSR 到通用寄存器

在异常处理程序中读取 SPSR

使用场景

MSR - Move to Status Register

指令语法

使用场景

示例代码

改变处理器模式为管理模式

设置条件标志位

异常处理结束后恢复状态

禁用IRQ中断

注意事项

LDR - Load Register

基本概念

语法格式(寻址方式)

立即数偏移寻址

寄存器偏移寻址

文字池寻址

使用场景

注意事项

STR - Store Register

基本概念

语法格式(寻址方式)

立即数偏移寻址

寄存器偏移寻址

使用场景

注意事项

PUSH

指令功能

语法结构

使用场景

代码示例

代码解释

注意事项

POP

指令功能

语法结构

使用场景

代码示例

代码解释

注意事项

2.数据处理指令

BIC - Bit Clear

指令语法

操作原理

使用场景

示例代码

清除寄存器中特定的位

清除标志位并更新条件标志

ORR - Logical Inclusive OR

指令语法

操作原理

使用场景

示例代码

设置寄存器中特定的位

设置标志位并更新条件标志

ADD - Add

指令功能

语法结构

使用场景

代码示例

注意事项

ADDS - Add

指令功能

语法结构

使用场景

代码示例

注意事项

SUB - Subtract

指令功能

语法结构

使用场景

代码示例

注意事项

SUBS - Subtract

基本功能

语法结构

寄存器 - 寄存器形式

寄存器 - 立即数形式

使用场景

示例代码

注意事项

3.分支跳转指令

B - Branch

基本概念

语法格式

使用场景

注意事项

BX - Branch and Exchange

基本功能

语法结构

指令集切换规则

使用场景

代码示例

代码解释

注意要点

BL - Branch with Link calls

基本概念

语法格式

工作原理

使用场景

示例代码

注意事项

BLX - Branch with Link and Exchange

基本功能

语法格式(寻址方式)

立即数寻址

寄存器寻址

使用示例

指令用途

4.协处理器指令

MRC - Move Register from Coprocessor

指令功能

语法结构

使用场景

代码示例

代码解释

注意事项

MCR - Move to Coprocessor Register

指令功能

语法结构

使用场景

代码示例

代码解释

注意事项

5.功能指令

CPSID - Change Processor State (Interrupt Disable)

基本功能

语法结构

使用场景

代码示例

代码解释

注意事项

CPSIE - Change Processor State(Interrupt Enable)

基本功能

语法结构

使用场景

代码示例

代码解释

注意事项

CPS - Change PE State

指令功能

语法结构

使用场景

代码示例

代码解释

注意事项

DSB - Data Synchronization Barrier

指令功能

语法结构

使用场景

代码示例

代码解释

注意事项

ISB - Instruction Synchronization Barrier

指令功能

语法结构

使用场景

代码示例

代码解释

注意事项


1.数据传输指令

MRS - Move from Status Register

MRS(Move from Status Register)指令用于将程序状态寄存器(CPSR,Current Program Status Register)或备份程序状态寄存器(SPSR,Saved Program Status Register)中的值传送到通用寄存器中。

指令用途

在 ARM 架构里,CPSR 包含了当前处理器的状态信息,例如条件标志位、中断使能位、处理器模式等。

SPSR 则是在异常发生时,用于保存 CPSR 的备份寄存器,以便异常处理完成后能恢复处理器的状态。

MRS 指令可让用户读取这些状态寄存器的值,进而依据处理器状态开展相应的操作,比如在编写异常处理程序时,就需要读取 SPSR 来恢复异常发生前的处理器状态。

指令语法

MRS {cond} Rd, <psr>
  • {cond}:可选的条件码,用于指定指令执行的条件。只有当条件码满足时,指令才会执行。像 EQ(相等)、NE(不相等)等。

  • Rd:目标寄存器,用于存放从状态寄存器中读取的值。

  • <psr>:状态寄存器,可以是 CPSR 或者 SPSR_<mode>,其中 <mode> 表示处理器模式,如 SVC(管理模式)、IRQ(中断模式)等。

代码示例

读取 CPSR 到通用寄存器
MRS R0, CPSR ; 将 CPSR 的值读取到寄存器 R0 中

这个例子里,MRS 指令把 CPSR 的值传送到寄存器 R0 中,之后就可以操作 R0,从而检查处理器的状态。

在异常处理程序中读取 SPSR
; 假设当前处于中断处理模式
MRS R1, SPSR_IRQ ; 将 SPSR(中断模式)的值读取到寄存器 R1 中

异常处理程序中,借助 MRS 指令读取 SPSR 的值,就能够获取异常发生前的处理器状态,方便后续恢复。

使用场景

  • 异常处理:在异常处理程序开始时,使用 MRS 指令读取 SPSR 的值,以保存异常发生前的处理器状态。异常处理结束时,再通过 MSR(Move to Status Register)指令将保存的状态写回到 CPSR 中,以此恢复处理器状态。

  • 状态检查:通过读取 CPSR 的值,能够检查处理器的条件标志位(如 N、Z、C、V),进而判断之前的运算结果,实现条件分支等操作。

MSR - Move to Status Register

MSR(Move to Status Register)指令用于将通用寄存器里的值或立即数,写入程序状态寄存器(CPSR)或备份程序状态寄存器(SPSR)。

指令语法

MSR {cond} <psr_fields>, <Rm | #immed_8r>
  • {cond}:可选的条件码,指令只有在条件码满足时才会执行。常见条件码有 EQ(相等)、NE(不相等)、GT(大于)等。

  • <psr_fields>:规定了要写入状态寄存器的位域,有以下几种情况:

    • cpsr 或 spsr:对整个状态寄存器进行操作。

    • cpsr_f 或 spsr_f:仅操作标志位域(N、Z、C、V)。

    • cpsr_c 或 spsr_c:操作控制位域(像处理器模式、中断使能位)。

    • cpsr_x 或 spsr_x:操作扩展位域。

    • cpsr_s 或 spsr_s:操作状态位域。

  • <Rm | #immed_8r>:可以是通用寄存器(如 R0 - R15),也可以是 8 位的立即数(#immed_8r)。

使用场景

  • 异常处理:异常处理程序结束时,需要把之前保存的 SPSR 值写回 CPSR,以恢复处理器在异常发生前的状态。

  • 中断控制:通过设置 CPSR 里的中断使能位(I 和 F),能够开启或者关闭中断。

  • 处理器模式切换:根据需求把处理器切换到不同的模式,例如用户模式、系统模式、管理模式等。

示例代码

改变处理器模式为管理模式
; 将处理器模式设置为管理模式(SVC),同时禁用 IRQ 和 FIQ 中断
MOV R0, #0xD3  ; 0xD3 对应的二进制为 11010011
               ; 其中 10011 表示管理模式,I = 1(禁用 IRQ),F = 1(禁用 FIQ)
MSR CPSR_c, R0 ; 将 R0 的值写入 CPSR 的控制位域
设置条件标志位
MOV R1, #0x10  ; 准备一个值用于设置标志位
MSR CPSR_f, R1 ; 将 R1 的值写入 CPSR 的标志位域
异常处理结束后恢复状态
; 假设异常处理结束,恢复状态
MRS R2, SPSR_IRQ  ; 读取 SPSR(中断模式)的值到 R2
MSR CPSR_cxsf, R2 ; 将 R2 的值写回到 CPSR,恢复状态
禁用IRQ中断
; 禁用 IRQ 中断
MOV R3, #0x80  ; 0x80 对应的二进制为 10000000,I 位为 1
MSR CPSR_c, R3 ; 将 R3 的值写入 CPSR 的控制位域,禁用 IRQ 中断

注意事项

  • 权限问题:修改 CPSR 或 SPSR 一般需要特定权限,通常只有在特权模式下才能操作。

  • 位域选择:明确指定要操作的位域,防止意外修改其他重要的状态信息。

  • 影响范围:对状态寄存器的修改会立即影响处理器的行为,例如改变中断使能位可能会导致中断响应的变化。

LDR - Load Register

LDR(Load Register)指令用于从内存中读取数据到寄存器

基本概念

LDR 指令的核心功能是把内存中的数据加载到指定的寄存器中。

计算机系统中,数据通常存储在内存中,而处理器要对这些数据进行操作,就需要先把数据从内存加载到寄存器里。LDR 指令正是实现此类数据传输的关键指令。

语法格式(寻址方式)

LDR 指令有多种语法格式(寻址方式),常见的如下:

  • 立即数偏移寻址:适用于偏移量固定的情况。例如,从数组中按固定偏移量读取元素。

  • 寄存器偏移寻址:适用于偏移量动态变化的情况。例如,通过循环变量来访问数组元素。

  • 文字池寻址:常用于加载常量或者全局变量的地址。

立即数偏移寻址
LDR <Rt>, [<Rn>, #<offset>]
  • <Rt>:目标寄存器,用于存放从内存中读取的数据。

  • <Rn>:基地址寄存器,包含内存操作的基地址。

  • #<offset>:立即数偏移量,它会和基址寄存器的值相加,得到最终的内存地址。

代码示例:

    MOV R0, #0x1000  ; 设置基地址
    LDR R1, [R0, #4] ; 从地址 0x1004 处读取数据到 R1 寄存器
寄存器偏移寻址
LDR <Rt>, [<Rn>, <Rm>]
  • <Rt>、<Rn> 含义同上。

  • <Rm>:偏移寄存器,其值会和基址寄存器的值相加,得到最终的内存地址。

代码示例:

    MOV R0, #0x1000  ; 设置基地址
    MOV R2, #4       ; 设置偏移量
    LDR R1, [R0, R2] ; 从地址 0x1004 处读取数据到 R1 寄存器
文字池寻址
LDR <Rt>, =<expr>
  • <Rt>:目标寄存器。

  • <expr>:表达式,可以是一个常量或者标号。这种寻址方式会在文字池中查找 expr 的地址,并将该地址处的数据加载到目标寄存器中。

代码示例:

LDR R0, =0x1234  ; 将常量 0x1234 的地址处的数据加载到 R0 寄存器

使用场景

  • 读取数组元素:在处理数组时,可使用 LDR 指令按索引读取数组中的元素。

  • 访问全局变量:通过 LDR 指令读取全局变量的值。

  • 加载常量:使用文字池寻址加载常量到寄存器中。

注意事项

  • 内存访问权限:要确保目标内存地址具有可读权限,否则会导致内存访问错误。

  • 寻址方式选择:根据具体需求选择合适的寻址方式,不同的寻址方式适用于不同的场景。

  • 数据类型:LDR 指令默认加载 32 位数据。如果需要加载 8 位或 16 位数据,可以使用 LDRB(加载字节)或 LDRH(加载半字)指令。

STR - Store Register

STR(Store Register)指令用于将寄存器中的数据存储到内存中的指令,与 LDR 指令的加载功能相对。

基本概念

计算机系统中,处理器对数据进行运算和处理时通常在寄存器中操作,但最终结果可能需要保存到内存里。STR 指令的作用就是把寄存器中的数据存储到指定的内存地址,从而实现数据从寄存器到内存的传输。

语法格式(寻址方式)

STR 指令有多种常见的语法格式:

  • 立即数偏移寻址:适用于偏移量固定的情况。例如,将数据存储到数组中固定的位置。

  • 寄存器偏移寻址:适用于偏移量动态变化的情况。例如,通过循环变量将数据依次存储到数组的不同元素中。

立即数偏移寻址
STR <Rt>, [<Rn>, #<offset>]
  • <Rt>:源寄存器,里面存放着要存储到内存的数据。

  • <Rn>:基地址寄存器,包含内存操作的基地址。

  • #<offset>:立即数偏移量,它会和基地址寄存器的值相加,得到最终的内存地址。

代码示例:

MOV R0, #0x1000  ; 设置基地址
MOV R1, #0xABCD  ; 将数据 0xABCD 存入 R1 寄存器
STR R1, [R0, #4] ; 将 R1 中的数据存储到地址 0x1004 处
寄存器偏移寻址
STR <Rt>, [<Rn>, <Rm>]
  • <Rt>、<Rn> 的含义同上。

  • <Rm>:偏移寄存器,其值会和基址寄存器的值相加,得到最终的内存地址。

代码示例:

MOV R0, #0x1000  ; 设置基地址
MOV R1, #0xABCD  ; 将数据 0xABCD 存入 R1 寄存器
MOV R2, #4       ; 设置偏移量
STR R1, [R0, R2] ; 将 R1 中的数据存储到地址 0x1004 处

使用场景

  • 保存数组元素:在处理数组时,可使用 STR 指令将数据按索引存储到数组的相应位置。

  • 更新全局变量:通过 STR 指令将计算结果保存到全局变量所在的内存地址。

  • 栈操作:在函数调用过程中,使用 STR 指令将寄存器的值保存到栈中,以保存现场。

注意事项

  • 内存访问权限:要确保目标内存地址具有可写权限,否则会导致内存访问错误。

  • 寻址方式选择:根据具体需求选择合适的寻址方式,不同的寻址方式适用于不同的场景。

  • 数据类型:STR 指令默认存储 32 位数据。如果需要存储 8 位或 16 位数据,可以使用 STRB(存储字节)或 STRH(存储半字)指令。

PUSH

PUSH 指令属于栈操作指令,用于将寄存器中的数据压入栈中。

指令功能

PUSH 指令的主要功能是把一个或多个寄存器的值保存到栈中。

在程序执行过程中,当需要调用子程序或者处理中断时,常常需要保存当前寄存器的值,以防止在子程序或中断处理过程中这些寄存器的值被修改。PUSH 指令可以将这些寄存器的值按顺序压入栈中,等子程序或中断处理完毕后,再使用 POP 指令将这些值从栈中恢复出来。

语法结构

PUSH 指令的语法格式如下:

PUSH {<registers>}
  • <registers>:这是一个寄存器列表,用于指定要压入栈中的寄存器。可以是单个寄存器,也可以是多个寄存器,多个寄存器之间用逗号分隔。例如,{R0} 表示只将寄存器 R0 的值压入栈中;{R0 - R3} 表示将寄存器 R0、R1、R2 和 R3 的值按顺序压入栈中;{R0, R2, R4} 表示将寄存器 R0、R2 和 R4 的值按顺序压入栈中。

使用场景

  • 子程序调用:在调用子程序之前,使用 PUSH 指令将需要保存的寄存器的值压入栈中,在子程序返回之前,使用 POP 指令将这些值从栈中恢复出来,以保证子程序不会影响主程序中寄存器的值。

  • 中断处理:在中断处理程序中,使用 PUSH 指令将当前寄存器的值压入栈中,在中断处理程序返回之前,使用 POP 指令将这些值从栈中恢复出来,以保证中断处理不会影响主程序中寄存器的值。

代码示例

以下是一个简单的 ARM 汇编代码示例,展示了 PUSH 指令在子程序调用中的使用:

    ; 主程序
    MOV R0, #10          ; 将立即数 10 赋值给寄存器 R0
    MOV R1, #20          ; 将立即数 20 赋值给寄存器 R1
    PUSH {R0, R1}        ; 将 R0 和 R1 的值压入栈中
    BL SUBROUTINE        ; 调用子程序
    POP {R0, R1}         ; 从栈中恢复 R0 和 R1 的值
    B END_PROGRAM        ; 跳转到程序结束处

SUBROUTINE:
    ; 子程序
    MOV R0, #30          ; 将立即数 30 赋值给寄存器 R0
    MOV R1, #40          ; 将立即数 40 赋值给寄存器 R1
    BX LR                ; 返回主程序

END_PROGRAM:
    B .                  ; 进入无限循环

代码解释

  1. 主程序部分:

    1. 将立即数 10 赋值给寄存器 R0,将立即数 20 赋值给寄存器 R1,然后使用 PUSH {R0, R1} 指令将 R0 和 R1 的值压入栈中。

    2. 接着调用子程序 SUBROUTINE,在子程序返回后,使用 POP {R0, R1} 指令从栈中恢复 R0 和 R1 的值。

  2. 子程序部分:在子程序中,将立即数 30 赋值给寄存器 R0,将立即数 40 赋值给寄存器 R1,然后使用 BX LR 指令返回主程序。

  3. 程序结束部分:跳转到 END_PROGRAM 处,然后进入无限循环。

注意事项

  • 栈的操作需要遵循后进先出(LIFO)的原则,因此在使用 PUSH 和 POP 指令时,要确保寄存器的顺序一致,否则可能会导致数据混乱。

  • 栈指针(通常是 SP 寄存器)的值会随着 PUSH 和 POP 指令的执行而自动调整,因此不需要手动修改栈指针的值。但是,要确保栈空间足够,避免栈溢出的问题。

POP

POP 指令与 PUSH 指令相对应,是用于栈操作的重要指令,主要功能是从栈中弹出数据并恢复到寄存器。

指令功能

POP 指令的核心功能是从栈中按照后进先出(LIFO)的顺序取出数据,并将这些数据依次恢复到指定的寄存器中。

在程序执行过程中,当使用 PUSH 指令将寄存器的值保存到栈中后,在合适的时机就需要使用 POP 指令将这些值从栈中恢复出来,以保证程序的正确执行。

语法结构

POP 指令的语法格式如下:

POP {<registers>}
  • <registers>:这是一个寄存器列表,用于指定从栈中弹出的数据要恢复到哪些寄存器。可以是单个寄存器,也可以是多个寄存器,多个寄存器之间用逗号分隔。例如,{R0} 表示只从栈中弹出一个数据并恢复到寄存器 R0;{R0 - R3} 表示依次从栈中弹出 4 个数据,分别恢复到寄存器 R0、R1、R2 和 R3;{R0, R2, R4} 表示依次从栈中弹出 3 个数据,分别恢复到寄存器 R0、R2 和 R4。

使用场景

  • 子程序返回:在调用子程序之前,使用 PUSH 指令将需要保存的寄存器的值压入栈中,在子程序返回时,使用 POP 指令将这些值从栈中恢复出来,以保证子程序不会影响主程序中寄存器的值。

  • 中断处理返回:在中断处理程序中,使用 PUSH 指令将当前寄存器的值压入栈中,在中断处理程序返回之前,使用 POP 指令将这些值从栈中恢复出来,以保证中断处理不会影响主程序中寄存器的值。

代码示例

以下是一个简单的 ARM 汇编代码示例,展示了 POP 指令在子程序调用中的使用:

    ; 主程序
    MOV R0, #10          ; 将立即数 10 赋值给寄存器 R0
    MOV R1, #20          ; 将立即数 20 赋值给寄存器 R1
    PUSH {R0, R1}        ; 将 R0 和 R1 的值压入栈中
    BL SUBROUTINE        ; 调用子程序
    POP {R0, R1}         ; 从栈中恢复 R0 和 R1 的值
    B END_PROGRAM        ; 跳转到程序结束处

SUBROUTINE:
    ; 子程序
    MOV R0, #30          ; 将立即数 30 赋值给寄存器 R0
    MOV R1, #40          ; 将立即数 40 赋值给寄存器 R1
    BX LR                ; 返回主程序

END_PROGRAM:
    B .                  ; 进入无限循环

代码解释

  1. 主程序部分:

    1. 首先将立即数 10 赋值给寄存器 R0,将立即数 20 赋值给寄存器 R1。

    2. 然后使用 PUSH {R0, R1} 指令将 R0 和 R1 的值压入栈中,接着调用子程序 SUBROUTINE。

    3. 在子程序返回后,使用 POP {R0, R1} 指令从栈中恢复 R0 和 R1 的值,这样 R0 和 R1 就恢复到了调用子程序之前的值。

  2. 子程序部分:在子程序中,将立即数 30 赋值给寄存器 R0,将立即数 40 赋值给寄存器 R1,然后使用 BX LR 指令返回主程序。

  3. 程序结束部分:跳转到 END_PROGRAM 处,然后进入无限循环。

注意事项

  • 栈操作顺序:栈的操作遵循后进先出(LIFO)原则,所以使用 PUSH 和 POP 指令时,寄存器的顺序必须一致。例如,PUSH {R0, R1} 后,必须使用 POP {R0, R1} 来恢复寄存器的值,否则会导致数据混乱。

  • 栈指针与栈溢出:栈指针(通常是 SP 寄存器)的值会随着 PUSH 和 POP 指令的执行而自动调整,一般不需要手动修改。但要确保栈空间足够,避免栈溢出的问题。如果栈溢出,可能会覆盖其他重要的数据,导致程序崩溃。

  • PC 寄存器特殊处理:在某些情况下,如果需要将栈中的数据恢复到程序计数器 PC 寄存器(如子程序返回),可以使用 POP {PC} 来实现。但要注意栈中存储的返回地址必须是合法的,否则会导致程序跳转到错误的地址。

2.数据处理指令

BIC - Bit Clear

BIC(Bit Clear)指令用于对寄存器中的指定位进行清零操作。

指令语法

BIC {cond}{S} Rd, Rn, <Operand2>
  • {cond}:可选的条件码,用于指定指令执行的条件。只有当条件码满足时,指令才会执行。常见的条件码如 EQ(相等)、NE(不相等)、GT(大于)等。

  • {S}:可选后缀,如果指定了该后缀,则会根据操作结果更新 CPSR(当前程序状态寄存器)中的条件标志位(如 N、Z、C、V)。

  • Rd:目标寄存器,用于存放操作结果。

  • Rn:第一个操作数寄存器,提供要进行位清除操作的原始数据。

  • <Operand2>:第二个操作数,可以是立即数或者寄存器。该操作数的位为 1 的位置,会对应将 Rn 寄存器中相同位置的位清零。

操作原理

BIC 指令通过将 Rn 寄存器的值与 Operand2 的反码进行按位与(AND)操作来实现位清除。具体的:

  • 对于 Operand2 中值为 1 的位,Rn 中对应位置的位会被清零;

  • 而 Operand2 中值为 0 的位,Rn 中对应位置的位保持不变。

使用场景

  • 清除特定标志位:当需要清除寄存器中某些特定的标志位时,可以使用 BIC 指令。例如,在状态寄存器中清除某些状态标志。

  • 数据预处理:在进行其他操作之前,可能需要清除数据中的某些无效位,以确保后续操作的正确性。

示例代码

清除寄存器中特定的位
; 假设 R0 中存储了一个 32 位的数据,现在要清除第 2 位和第 5 位
MOV R1, #(1 << 2) | (1 << 5) ; R1 的值为 0000 0000 0000 0000 0000 0000 0010 0100
BIC R0, R0, R1               ; 清除 R0 中第 2 位和第 5 位

上述示例中,首先将需要清除的位对应的位置为 1,存储在 R1 中。然后使用 BIC 指令将 R0 中对应 R1 为 1 的位清零。

清除标志位并更新条件标志
; 假设 R2 中存储了状态信息,要清除第 0 位,并更新条件标志
MOV R3, #1      ; R3 的值为 0000 0000 0000 0000 0000 0000 0000 0001
BICS R2, R2, R3 ; 清除 R2 中第 0 位,并根据结果更新 CPSR 的条件标志位

在这个示例中,使用 BICS 指令清除 R2 中的第 0 位,并根据操作结果更新 CPSR 中的条件标志位,方便后续进行条件判断。

ORR - Logical Inclusive OR

ORR(Logical Inclusive OR,逻辑或)指令的功能是对两个操作数进行按位逻辑或运算,然后把结果存于目标寄存器。

指令语法

ORR {cond}{S} Rd, Rn, <Operand2>
  • {cond}:可选的条件码,用于规定指令执行的条件。只有条件码满足时,指令才会执行。常见的条件码有 EQ(相等)、NE(不相等)、GT(大于)等。

  • {S}:可选后缀,若指定了该后缀,就会依据操作结果更新 CPSR(当前程序状态寄存器)里的条件标志位(像 N、Z、C、V)。

  • Rd:目标寄存器,用来存放逻辑或运算的结果。

  • Rn:第一个操作数寄存器,提供进行逻辑或运算的一个操作数。

  • <Operand2>:第二个操作数,可以是立即数或者寄存器。

操作原理

ORR 指令会对 Rn 寄存器的值和 Operand2 的值逐位进行逻辑或运算。

逻辑或运算的规则是:只要两个对应位中有一个为 1,结果位就为 1;只有当两个对应位都为 0 时,结果位才为 0。

使用场景

  • 设置特定标志位:当需要把寄存器里的某些特定位置为 1 时,可以使用 ORR 指令。例如,在状态寄存器里设置某些状态标志。

  • 组合数据:可以把多个数据的不同位组合到一个寄存器中。

示例代码

设置寄存器中特定的位
; 假设 R0 中存储了一个 32 位的数据,现在要设置第 2 位和第 5 位
MOV R1, #(1 << 2) | (1 << 5) ; R1 的值为 0000 0000 0000 0000 0000 0000 0010 0100
ORR R0, R0, R1               ; 设置 R0 中第 2 位和第 5 位为 1

在这个例子里,先把需要设置的位对应的位置为 1,存于 R1 中。接着使用 ORR 指令将 R0 中对应 R1 为 1 的位设置为 1。

设置标志位并更新条件标志
; 假设 R2 中存储了状态信息,要设置第 0 位,并更新条件标志
MOV R3, #1      ; R3 的值为 0000 0000 0000 0000 0000 0000 0000 0001
ORRS R2, R2, R3 ; 设置 R2 中第 0 位为 1,并根据结果更新 CPSR 的条件标志位

此示例使用 ORRS 指令将 R2 中的第 0 位设置为 1,并且依据操作结果更新 CPSR 里的条件标志位,以便后续进行条件判断。

ADD - Add

ADD 指令用于执行加法运算的基础指令。

指令功能

ADD 指令的核心功能是对两个操作数进行加法运算,并将结果存储到目标寄存器中。

与 ADDS 指令不同,ADD 指令执行加法运算时不会更新程序状态寄存器(CPSR)中的标志位,而 ADDS 指令在执行加法运算的同时会更新标志位。

语法结构

ADD 指令有多种语法形式,常见的如下:

寄存器 - 寄存器形式

ADD <Rd>, <Rn>, <Rm>
  • <Rd>:目标寄存器,用于存放加法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • <Rm>:第二个操作数所在的寄存器。

寄存器 - 立即数形式

ADD <Rd>, <Rn>, #<immediate>
  • <Rd>:目标寄存器,用于存放加法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • #<immediate>:立即数,即一个常量值。

寄存器移位形式

ADD <Rd>, <Rn>, <Rm>, <shift>
  • <Rd>:目标寄存器,用于存放加法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • <Rm>:第二个操作数所在的寄存器。

  • <shift>:移位操作,对 <Rm> 进行移位操作后再与 <Rn> 相加。常见的移位操作有 LSL(逻辑左移)、LSR(逻辑右移)等。

使用场景

  • 数值计算:在进行数学计算时,若需要对两个数值进行加法运算,可使用 ADD 指令。例如,计算两个变量的和。

  • 地址计算:在处理内存地址时,可能需要对地址进行偏移计算,此时使用 ADD 指令能方便地实现地址的加法操作。

  • 计数器更新:在循环或计数操作中,可使用 ADD 指令对计数器进行递增操作。

代码示例

以下是几个使用 ADD 指令的示例:

示例 1:寄存器 - 寄存器形式

    MOV R0, #5          ; 将立即数 5 赋值给寄存器 R0
    MOV R1, #3          ; 将立即数 3 赋值给寄存器 R1
    ADD R2, R0, R1      ; R2 = R0 + R1,即 R2 = 5 + 3 = 8

上述代码中,把 5 赋值给 R0,3 赋值给 R1,然后用 ADD 指令计算 R0 + R1 的结果,将结果 8 存到 R2 中。

示例 2:寄存器 - 立即数形式

    MOV R0, #10         ; 将立即数 10 赋值给寄存器 R0
    ADD R1, R0, #5      ; R1 = R0 + 5,即 R1 = 10 + 5 = 15

上述代码中,把 10 赋值给 R0,使用 ADD 指令计算 R0 + 5 的结果,将结果 15 存到 R1 中。

示例 3:寄存器移位形式

    MOV R0, #2          ; 将立即数 2 赋值给寄存器 R0
    MOV R1, #3          ; 将立即数 3 赋值给寄存器 R1
    ADD R2, R0, R1, LSL #2  ; 先将 R1 逻辑左移 2 位(R1 = 3 << 2 = 12),然后 R2 = R0 + 12,即 R2 = 2 + 12 = 14

上述代码中,把 2 赋值给 R0,3 赋值给 R1,先将 R1 逻辑左移 2 位得到 12,再用 ADD 指令计算 R0 + 12 的结果,将结果 14 存到 R2 中。

注意事项

  • 操作数范围:对于立即数形式的 ADD 指令,立即数的取值范围是有限制的,要保证使用的立即数在合法范围内,不然可能会导致指令无法正确执行。

  • 寄存器的选择:要选择合适的寄存器来存放操作数和结果,避免寄存器冲突造成数据丢失或错误。

ADDS - Add

ADDS 指令是 ADD 指令的变体。

指令功能

ADDS 指令的主要功能是对两个操作数执行加法运算,然后把结果存到目标寄存器中。

与 ADD 指令不同的是,ADDS 指令在完成加法运算后,还会依据运算结果更新程序状态寄存器(CPSR)里的标志位。这些标志位能够反映运算结果的特性,像结果是否为零、是否产生进位等,后续的条件分支指令可以依据这些标志位来决定是否执行相应操作。

语法结构

ADDS 指令有多种常见的语法形式:

寄存器 - 寄存器形式

ADDS <Rd>, <Rn>, <Rm>
  • <Rd>:目标寄存器,用于存放加法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • <Rm>:第二个操作数所在的寄存器。

寄存器 - 立即数形式

ADDS <Rd>, <Rn>, #<immediate>
  • <Rd>:目标寄存器,用于存放加法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • #<immediate>:立即数,也就是一个常量值。

寄存器移位形式

ADDS <Rd>, <Rn>, <Rm>, <shift>
  • <Rd>:目标寄存器,用于存放加法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • <Rm>:第二个操作数所在的寄存器。

  • <shift>:移位操作,对 <Rm> 进行移位操作后再与 <Rn> 相加。常见的移位操作有 LSL(逻辑左移)、LSR(逻辑右移)等。

使用场景

  • 数值计算与状态判断:在进行数值计算时,若需要根据加法运算结果的状态来进行后续操作,就可以使用 ADDS 指令。例如,判断两个数相加的结果是否为零,以此来决定程序的执行流程。

  • 条件分支:结合条件分支指令(如 BEQ、BNE、BCS 等)使用,根据 ADDS 指令更新的标志位来决定程序的执行方向。比如,在循环中判断计数器是否达到某个值,从而决定是否退出循环。

代码示例

以下是几个使用 ADDS 指令的示例:

示例 1:寄存器 - 寄存器形式

    MOV R0, #5          ; 将立即数 5 赋值给寄存器 R0
    MOV R1, #3          ; 将立即数 3 赋值给寄存器 R1
    ADDS R2, R0, R1     ; R2 = R0 + R1,即 R2 = 5 + 3 = 8
    ; 根据结果更新标志位,这里 Z 标志位为 0(结果不为零)

上述代码中,把 5 赋值给 R0,3 赋值给 R1,然后用 ADDS 指令计算 R0 + R1 的结果,将结果 8 存到 R2 中。由于结果不为零,CPSR 中的 Z 标志位会被置为 0。

示例 2:结合条件分支指令

    MOV R0, #10         ; 将立即数 10 赋值给寄存器 R0
    MOV R1, # - 10       ; 将立即数 -10 赋值给寄存器 R1
    ADDS R2, R0, R1     ; R2 = R0 + R1,即 R2 = 10 + (-10) = 0
    BEQ EQUAL           ; 如果 Z 标志位为 1(结果为零),跳转到 EQUAL 标签处
    B NOT_EQUAL         ; 否则,跳转到 NOT_EQUAL 标签处

EQUAL:
    ; 处理相等的情况
    MOV R3, #1          ; 可以在这里进行一些操作,比如将 R3 赋值为 1
    B END_PROGRAM

NOT_EQUAL:
    ; 处理不相等的情况
    MOV R3, #0          ; 将 R3 赋值为 0
    B END_PROGRAM

END_PROGRAM:
    B .                 ; 进入无限循环

上述代码中,把 10 赋值给 R0, - 10 赋值给 R1,使用 ADDS 指令计算 R0 + R1 的结果,结果为 0。因为结果为零,CPSR 中的 Z 标志位会被置为 1,BEQ 指令会根据 Z 标志位的值决定是否跳转到 EQUAL 标签处执行相应操作。

注意事项

  • 标志位的使用:要清楚 ADDS 指令更新的标志位(如 N、Z、C、V 标志位)的含义,并且在后续使用条件分支指令时,要根据具体需求正确使用这些标志位。

  • 操作数范围:对于立即数形式的 ADDS 指令,立即数的取值范围是有限制的,需要确保使用的立即数在合法范围内,否则可能会导致指令无法正确执行。

  • 寄存器的选择:要确保选择合适的寄存器来存放操作数和结果,避免寄存器冲突导致数据丢失或错误。

SUB - Subtract

SUB 指令用于执行减法运算的基础指令。

指令功能

SUB 指令的主要功能是对两个操作数进行减法运算,把结果存到目标寄存器中。

它和 SUBS 指令不同,SUB 指令不会更新程序状态寄存器(CPSR)里的标志位,而 SUBS 指令在执行减法运算的同时会更新标志位。

语法结构

SUB 指令有多种语法形式,下面是常见的几种:

寄存器 - 寄存器形式

SUB <Rd>, <Rn>, <Rm>
  • <Rd>:目标寄存器,用于存放减法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • <Rm>:第二个操作数所在的寄存器,该操作数会从 <Rn> 中减去。

寄存器 - 立即数形式

SUB <Rd>, <Rn>, #<immediate>
  • <Rd>:目标寄存器,用于存放减法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • #<immediate>:立即数,该立即数会从 <Rn> 中减去。

寄存器移位形式

SUB <Rd>, <Rn>, <Rm>, <shift>
  • <Rd>:目标寄存器,用于存放减法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • <Rm>:第二个操作数所在的寄存器。

  • <shift>:移位操作,对 <Rm> 进行移位操作后再从 <Rn> 中减去。常见的移位操作有 LSL(逻辑左移)、LSR(逻辑右移)等。

使用场景

  • 数值计算:在进行数值计算时,若不需要根据减法运算结果更新标志位,就可以使用 SUB 指令。例如,计算两个变量的差值。

  • 地址计算:在进行地址计算时,可能需要对地址进行减法操作,这时使用 SUB 指令能方便地实现。

代码示例

以下是几个使用 SUB 指令的示例:

示例 1:寄存器 - 寄存器形式

    MOV R0, #10         ; 将立即数 10 赋值给寄存器 R0
    MOV R1, #3          ; 将立即数 3 赋值给寄存器 R1
    SUB R2, R0, R1      ; R2 = R0 - R1,即 R2 = 10 - 3 = 7

上述代码中,把 10 赋值给 R0,3 赋值给 R1,然后用 SUB 指令计算 R0 - R1 的结果,将结果 7 存到 R2 中。

示例 2:寄存器 - 立即数形式

    MOV R0, #20         ; 将立即数 20 赋值给寄存器 R0
    SUB R1, R0, #5      ; R1 = R0 - 5,即 R1 = 20 - 5 = 15

上述代码中,把 20 赋值给 R0,使用 SUB 指令计算 R0 - 5 的结果,将结果 15 存到 R1 中。

示例 3:寄存器移位形式

    MOV R0, #16         ; 将立即数 16 赋值给寄存器 R0
    MOV R1, #2          ; 将立即数 2 赋值给寄存器 R1
    SUB R2, R0, R1, LSL #2  ; 先将 R1 逻辑左移 2 位(R1 = 2 << 2 = 8),然后 R2 = R0 - 8,即 R2 = 16 - 8 = 8

上述代码中,把 16 赋值给 R0,2 赋值给 R1,先将 R1 逻辑左移 2 位得到 8,再用 SUB 指令计算 R0 - 8 的结果,将结果 8 存到 R2 中。

注意事项

  • 操作数范围:对于立即数形式的 SUB 指令,立即数的取值范围是有限制的,要保证使用的立即数在合法范围内,不然可能会导致指令无法正确执行。

  • 寄存器的选择:要选择合适的寄存器来存放操作数和结果,避免寄存器冲突造成数据丢失或错误。

SUBS - Subtract

SUBS 指令属于算术运算指令,本质上是减法指令 SUB 的变体。

基本功能

SUBS 指令的主要功能是执行减法运算,将一个操作数从另一个操作数中减去,并且会根据运算结果更新程序状态寄存器(CPSR)中的标志位。这些标志位可以反映运算结果的各种特性,例如结果是否为零、是否产生进位或借位等,后续的条件分支指令可以根据这些标志位来决定是否执行相应的操作。

语法结构

SUBS 指令有几种不同的语法形式,常见的如下:

寄存器 - 寄存器形式
SUBS <Rd>, <Rn>, <Rm>
  • <Rd>:目标寄存器,用于存放减法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • <Rm>:第二个操作数所在的寄存器,该操作数将从 <Rn> 中减去。

寄存器 - 立即数形式
SUBS <Rd>, <Rn>, #<immediate>
  • <Rd>:目标寄存器,用于存放减法运算的结果。

  • <Rn>:第一个操作数所在的寄存器。

  • #<immediate>:立即数,该立即数将从 <Rn> 中减去。

使用场景

  • 数值计算:在进行数值计算时,需要进行减法运算并根据结果的状态来进行后续操作,就可以使用 SUBS 指令。例如,判断两个数是否相等,可以用一个数减去另一个数,然后根据结果是否为零(通过 Z 标志位判断)来确定。

  • 条件分支:结合条件分支指令(如 BEQ、BNE 等)使用,根据 SUBS 指令更新的标志位来决定程序的执行流程。比如,在循环中判断计数器是否减到零,从而决定是否退出循环。

示例代码

以下是几个使用 SUBS 指令的示例:

示例 1:寄存器 - 寄存器形式

    MOV R0, #5          ; 将立即数 5 赋值给寄存器 R0
    MOV R1, #3          ; 将立即数 3 赋值给寄存器 R1
    SUBS R2, R0, R1     ; R2 = R0 - R1,即 R2 = 5 - 3 = 2
    ; 根据结果更新标志位,这里 Z 标志位为 0(结果不为零)

示例 2:结合条件分支指令

    MOV R0, #10         ; 将立即数 10 赋值给寄存器 R0
    MOV R1, #10         ; 将立即数 10 赋值给寄存器 R1
    SUBS R2, R0, R1     ; R2 = R0 - R1,即 R2 = 10 - 10 = 0
    BEQ EQUAL           ; 如果 Z 标志位为 1(结果为零),跳转到 EQUAL 标签处
    B NOT_EQUAL         ; 否则,跳转到 NOT_EQUAL 标签处

EQUAL:
    ; 处理相等的情况
    MOV R3, #1          ; 可以在这里进行一些操作,比如将 R3 赋值为 1
    B END_PROGRAM

NOT_EQUAL:
    ; 处理不相等的情况
    MOV R3, #0          ; 将 R3 赋值为 0
    B END_PROGRAM

END_PROGRAM:
    B .                 ; 进入无限循环

注意事项

  • 标志位的使用:要清楚 SUBS 指令更新的标志位(如 N、Z、C、V 标志位)的含义,并且在后续使用条件分支指令时,要根据具体需求正确使用这些标志位。

  • 操作数范围:对于立即数形式的 SUBS 指令,立即数的取值范围是有限制的,需要确保使用的立即数在合法范围内,否则可能会导致指令无法正确执行。

  • 寄存器的选择:要确保选择合适的寄存器来存放操作数和结果,避免寄存器冲突导致数据丢失或错误。

3.分支跳转指令

B - Branch

B 指令用于实现程序跳转。

基本概念

B 指令的主要功能是改变程序的执行流程,使程序跳转到指定的目标地址继续执行。它属于无条件跳转指令,也就是说,一旦执行 B 指令,程序就会立即跳转到指定地址,而不会考虑其他条件。

语法格式

B 指令的语法格式较为简单:

B <label>

其中,<label> 是一个标号,代表要跳转的目标地址。标号在汇编代码中用于标记特定的位置,通过 B 指令可以直接跳转到该位置。

代码示例:

    MOV R0, #10
    B subroutine  ; 跳转到子程序
    ; 子程序返回后不会执行到这里,因为没有保存返回地址
    MOV R1, #20

subroutine:
    ADD R0, R0, #5  ; 子程序代码
    B end_program   ; 跳转到程序结束处

end_program:
    MOV R7, #1
    SWI 0  ; 退出程序

使用场景

  • 子程序调用:在程序中调用子程序时,可以使用 B 指令跳转到子程序的入口地址。不过需要注意的是,B 指令本身不会保存返回地址,所以在使用 B 指令调用子程序后,需要额外的操作来实现返回原程序的功能。

  • 条件分支:结合条件判断指令(如 CMP),可以实现条件分支。例如,先进行条件判断,根据判断结果决定是否执行 B 指令进行跳转。

  • 循环结构:在实现循环结构时,使用 B 指令可以使程序跳转到循环体的起始位置,从而实现循环执行的功能。

注意事项

  • 返回地址处理:由于 B 指令不会保存返回地址,在使用 B 指令调用子程序时,需要手动保存和恢复返回地址,否则程序无法正确返回原位置继续执行。如果需要自动保存返回地址,可以使用 BL 指令。

  • 标号作用域:要确保跳转的标号在当前作用域内是有效的。如果是全局标号,要保证在整个程序中是唯一的;如果是局部标号,要注意其作用域仅限于当前文件或代码段。

  • 跳转范围:B 指令的跳转范围是有限的,通常只能在相对较近的地址范围内进行跳转。如果需要跳转到较远的地址,可以使用其他跳转指令或采用间接跳转的方式。

BX - Branch and Exchange

BX(Branch and Exchange)指令用于实现程序分支跳转,同时还能完成指令集状态的切换。

基本功能

  • 程序跳转:BX 指令最主要的功能是改变程序的执行流程,使程序跳转到指定的地址继续执行。这类似于其他架构中的跳转指令,比如 x86 架构的 JMP 指令。

  • 指令集切换:BX 指令还可以在 ARM 指令集和 Thumb 指令集之间进行切换。在 ARM 架构中,有两种指令集模式,ARM 指令集使用 32 位指令,而 Thumb 指令集使用 16 位指令,BX 指令可以根据目标地址的最低位来决定是否切换指令集。

语法结构

BX 指令的基本语法格式如下:

BX <Rm>

其中,<Rm> 是一个通用寄存器,该寄存器的值就是跳转的目标地址。

指令集切换规则

  • 当 <Rm> 的最低位(即 bit[0])为 0 时,处理器会以 ARM 指令集模式执行目标地址处的代码。

  • 当 <Rm> 的最低位(即 bit[0])为 1 时,处理器会切换到 Thumb 指令集模式,并在目标地址处开始执行代码。同时,该位在跳转时会被忽略,实际跳转的地址是 <Rm> 去掉最低位后的地址。

使用场景

  • 子程序调用返回:在子程序调用结束后,通常会使用 BX 指令返回到调用处继续执行。例如,在汇编语言编写的函数中,函数执行完毕后会将返回地址存储在某个寄存器中,然后使用 BX 指令跳转到该地址。

  • 指令集模式切换:当需要在 ARM 指令集和 Thumb 指令集之间进行切换时,可以使用 BX 指令。例如,在某些对代码密度要求较高的场景下,可能会使用 Thumb 指令集;而在需要执行复杂操作的场景下,可能会使用 ARM 指令集。

代码示例

以下是一个简单的 ARM 汇编代码示例,展示了 BX 指令的使用:

    ; 主程序
    MOV R0, #10          ; 将立即数 10 赋值给寄存器 R0
    BL SUBROUTINE        ; 调用子程序
    B END_PROGRAM        ; 跳转到程序结束处

SUBROUTINE:
    ADD R0, R0, #1       ; R0 = R0 + 1
    MOV PC, LR           ; 保存返回地址到 PC,这里相当于简单的返回操作
    BX LR                ; 跳转到返回地址,同时可进行指令集切换(如果需要)

END_PROGRAM:
    B .                  ; 进入无限循环

代码解释

  1. 主程序部分:将立即数 10 赋值给寄存器 R0,然后使用 BL 指令调用子程序 SUBROUTINE。

  2. 子程序部分:在子程序中,将 R0 的值加 1,然后使用 MOV PC, LR 保存返回地址到程序计数器 PC,接着使用 BX LR 指令跳转到返回地址,同时如果 LR 的最低位为 1,还会进行指令集切换。

  3. 程序结束部分:跳转到 END_PROGRAM 处,然后进入无限循环。

注意要点

  • 在使用 BX 指令进行跳转时,要确保目标地址是合法的,否则可能会导致程序崩溃。

  • 当进行指令集切换时,要确保目标地址处的代码是使用相应指令集编写的,否则会产生错误的执行结果。

BL - Branch with Link calls

BL(Branch with Link)指令用于实现子程序(函数)调用

基本概念

BL 指令本质上是一种特殊的跳转指令,它不仅能让程序跳转到指定的子程序入口地址开始执行子程序,还会自动保存返回地址,以便子程序执行完毕后能返回到原程序继续执行。

语法格式

BL 指令的语法较为简单:

BL <label>

其中,<label> 是一个标号,代表要跳转的子程序的入口地址。标号在汇编代码里用于标记特定的位置,BL 指令会使程序跳转到该位置开始执行子程序。

工作原理

  1. 保存返回地址:在执行 BL 指令时,处理器会将当前 PC(程序计数器)的值加上一个偏移量(通常是 4 字节,因为 ARM 指令通常是 4 字节对齐),得到下一条指令的地址,然后将该地址保存到链接寄存器 LR(Link Register,即 R14)中。

  2. 跳转执行:程序跳转到 <label> 所指定的地址,开始执行子程序的代码。

  3. 返回原程序:在子程序执行完毕后,通过 BX 指令将 LR 寄存器中的返回地址加载到 PC 中,使程序返回到原程序中 BL 指令的下一条指令处继续执行。

使用场景

  • 模块化编程:大型程序开发中,为了提高代码的可维护性和复用性,通常将不同的功能封装成子程序。BL 指令可以方便地调用这些子程序,实现模块化编程。

  • 函数调用:编写汇编程序时,经常需要调用各种函数来完成特定的任务。BL 指令是实现函数调用的关键指令。

示例代码

    MOV R0, #10       ; 传递参数,假设子程序需要一个参数
    BL subroutine     ; 调用子程序
    MOV R1, R0        ; 子程序返回后,将返回值存到 R1
    B end_program     ; 跳转到程序结束处

subroutine:
    ADD R0, R0, #5    ; 子程序代码,将参数加 5
    BX LR             ; 返回原程序

end_program:
    MOV R7, #1        ; 系统调用号,表示退出程序
    SWI 0             ; 执行系统调用,退出程序

在这个示例中,主程序将参数 10 存入 R0 寄存器,然后使用 BL 指令调用 subroutine 子程序。子程序将 R0 中的值加 5,最后使用 BX LR 指令返回主程序。主程序将子程序的返回值存到 R1 寄存器,然后结束程序。

注意事项

  • 返回地址管理:在子程序中,要确保在合适的位置使用 BX LR 指令返回原程序。如果在子程序中修改了 LR 寄存器的值,需要在返回前恢复其原始值,否则程序将无法正确返回。

  • 参数传递和返回值:在使用 BL 指令调用子程序时,需要遵循一定的参数传递和返回值约定。通常,前几个参数通过寄存器 R0 - R3 传递,返回值也通过 R0 寄存器返回。

  • 栈的使用:如果子程序需要保存更多的寄存器值或者使用局部变量,可能需要使用栈来进行存储。在子程序开始时,将需要保存的寄存器值压入栈中,在子程序结束前,将这些值从栈中弹出恢复。

BLX - Branch with Link and Exchange

BLX(Branch with Link and Exchange)指令主要用于子程序调用,同时还能实现 ARM 状态和 Thumb 状态之间的切换。

基本功能

  • BLX 指令将下一条指令的地址保存到链接寄存器 LR(R14)中,这是为了子程序执行完后能返回到调用处。

  • 它会跳转到指定的目标地址去执行子程序。

  • 该指令能够根据目标地址的最低位来决定是否进行 ARM 状态和 Thumb 状态的切换。

    • 若目标地址最低位为 0,就继续保持 ARM 状态;

    • 若为 1,则从 ARM 状态切换到 Thumb 状态,或者从 Thumb 状态切换到 ARM 状态。

语法格式(寻址方式)

BLX 指令有两种语法格式(寻址方式):

  • 立即数寻址;

  • 寄存器寻址;

立即数寻址
BLX <target_address>

其中,<target_address> 是目标地址,一般是一个标号。指令会根据该地址的最低位来判断是否要切换状态。

寄存器寻址
BLX <Rm>

其中,<Rm> 是一个通用寄存器,寄存器中的值为目标地址。同样,会依据该值的最低位决定是否进行状态切换。

使用示例

下面是一段简单的 ARM 汇编代码示例,展示了 BLX 指令的使用:

    ; 主程序
    MOV R0, #10      ; 将立即数 10 存入 R0
    BLX Subroutine  ; 调用子程序,可能会切换状态
    ; 子程序返回后继续执行这里
    MOV R7, #1       ; 设置系统调用号为 1(退出程序)
    SWI 0            ; 执行系统调用

; 子程序
Subroutine
    ADD R0, R0, #5   ; R0 = R0 + 5
    BX LR            ; 返回主程序

指令用途

  • 子程序调用:BLX 是实现子程序调用的关键指令,能够在不同状态的代码间进行切换调用。

  • 状态切换:在 ARM 架构里,程序可以在 ARM 状态(32 位指令集)和 Thumb 状态(16 位指令集)之间切换,BLX 指令能依据目标地址最低位来实现这种状态切换。

4.协处理器指令

MRC - Move Register from Coprocessor

MRC(Move Register from Coprocessor)指令用于从协处理器向 ARM 处理器传送数据。

指令功能

MRC 指令的主要功能是让 ARM 处理器从协处理器那里读取数据。

在 ARM 架构里,协处理器能辅助主处理器完成特定任务,像浮点运算、内存管理等。

借助 MRC 指令,主处理器可以获取协处理器内部寄存器的值,从而了解协处理器的状态或者获取计算结果。

语法结构

MRC 指令的语法格式如下:

MRC{<cond>} <p#, <opcode_1>, <Rd>, <CRn>, <CRm>{, <opcode_2>}
  • 参数解释:

    • <cond>:这是可选的条件码,用于指定指令执行的条件。只有当条件满足时,指令才会执行。常见的条件码有 EQ(相等)、NE(不相等)等。

    • <p#>:表示协处理器编号,范围是 p0 - p15。它用来指定要与哪个协处理器进行通信。

    • <opcode_1>:是第一个操作码,为一个 4 位的无符号整数,用于指定协处理器操作的类型。

    • <Rd>:是 ARM 处理器的目标寄存器,从协处理器读取的数据会被存到这个寄存器中。

    • <CRn>:是协处理器的寄存器编号,指定要从协处理器的哪个主寄存器读取数据。

    • <CRm>:是协处理器的附加寄存器编号,用于进一步指定协处理器操作的参数。

    • <opcode_2>:是可选的第二个操作码,同样为 4 位无符号整数,用于对协处理器操作进行更详细的指定。

使用场景

  • 获取协处理器状态:主处理器可以通过 MRC 指令读取协处理器的状态寄存器,以此了解协处理器的工作状态,例如是否出现错误、是否完成特定计算等。

  • 读取计算结果:当协处理器完成特定计算后,主处理器可以使用 MRC 指令从协处理器的结果寄存器中读取计算结果。

代码示例

以下是一个简单的示例,展示了如何使用 MRC 指令从协处理器 p15 读取寄存器 c1 的值:

; 从协处理器 p15 的 c1 寄存器读取数据到 ARM 处理器的 R0 寄存器
MRC p15, 0, R0, c1, c0, 0

; 后续可以对 R0 中的数据进行处理
MOV R1, R0          ; 将 R0 的值复制到 R1

代码解释

  1. MRC p15, 0, R0, c1, c0, 0:从协处理器 p15 的 c1 寄存器读取数据,并将其存储到 ARM 处理器的 R0 寄存器中。这里的 opcode_1 为 0,opcode_2 也为 0。

  2. MOV R1, R0:将 R0 中的数据复制到 R1 寄存器,以便后续处理。

注意事项

  • 要保证协处理器已经正确初始化,并且支持所使用的操作码和寄存器访问。

  • 在使用 MRC 指令时,要确保协处理器编号、操作码和寄存器编号的设置正确,否则可能会导致读取的数据不正确或者产生错误。

MCR - Move to Coprocessor Register

MCR(Move to Coprocessor Register)指令用于将数据从 ARM 处理器传送到协处理器。

指令功能

MCR 指令的核心功能是让 ARM 处理器向协处理器发送数据。

在 ARM 系统中,协处理器可以辅助主处理器完成特定的任务,如浮点运算、内存管理、向量处理等。

通过 MCR 指令,主处理器能够将控制信息、数据等传递给协处理器,以控制协处理器的操作或者为其提供计算所需的数据。

语法结构

MCR 指令的语法格式如下:

MCR{<cond>} <p#>, <opcode_1>, <Rd>, <CRn>, <CRm>{, <opcode_2>}
  • 参数解释:

    • <cond>:可选的条件码,用于指定指令执行的条件。只有当条件满足时,指令才会执行。常见的条件码如 EQ(相等)、NE(不相等)、GT(大于)等。

    • <p#>:协处理器编号,范围是 p0 - p15,用于指定要与哪个协处理器进行通信。

    • <opcode_1>:第一个操作码,是一个 4 位的无符号整数,用于指定协处理器操作的类型。

    • <Rd>:ARM 处理器的源寄存器,要传送给协处理器的数据就存放在这个寄存器中。

    • <CRn>:协处理器的寄存器编号,指定要将数据写入协处理器的哪个主寄存器。

    • <CRm>:协处理器的附加寄存器编号,用于进一步指定协处理器操作的参数。

    • <opcode_2>:可选的第二个操作码,同样是 4 位无符号整数,用于对协处理器操作进行更详细的指定。

使用场景

  • 协处理器初始化:在使用协处理器之前,需要对其进行初始化设置。通过 MCR 指令,主处理器可以将初始化参数传递给协处理器的相应寄存器,使协处理器进入合适的工作状态。

  • 控制协处理器操作:协处理器工作过程中,主处理器可使用 MCR 指令向协处理器发送控制命令,如启动计算、停止计算、设置计算模式等。

代码示例

以下是一个简单的示例,展示如何使用 MCR 指令将 ARM 处理器寄存器 R0 中的数据传送到协处理器 p15 的寄存器 c1 中:

; 将 R0 中的数据传送到协处理器 p15 的 c1 寄存器
MOV R0, #0x1234      ; 将立即数 0x1234 赋值给 R0
MCR p15, 0, R0, c1, c0, 0

; 后续可以进行其他操作
B .                  ; 进入无限循环

代码解释

  1. MOV R0, #0x1234:将立即数 0x1234 赋值给 ARM 处理器的寄存器 R0,作为要传送给协处理器的数据。

  2. MCR p15, 0, R0, c1, c0, 0:使用 MCR 指令将 R0 中的数据传送到协处理器 p15 的 c1 寄存器。这里的 opcode_1 为 0,opcode_2 也为 0。

  3. B .:进入无限循环,程序继续执行其他操作。

注意事项

  • 要确保协处理器已经正确初始化,并且支持所使用的操作码和寄存器访问。

  • 在使用 MCR 指令时,要保证协处理器编号、操作码和寄存器编号的设置正确,否则可能会导致数据写入错误或者产生其他异常。

  • 不同的协处理器对操作码和寄存器的使用方式可能不同,需要参考相应协处理器的文档进行正确配置。

5.功能指令

CPSID - Change Processor State (Interrupt Disable)

CPSID(Change Processor State,Interrupt Disable)指令用于控制处理器中断屏蔽状态。

基本功能

CPSID 指令的核心功能是禁用特定类型的中断,让处理器在执行关键代码时不会被中断干扰,以此保证代码的原子性和执行的完整性。

执行该指令后,特定类型的中断请求会被处理器忽略,直至使用 CPSIE(Change Processor State,Interrupt Enable)指令重新启用中断。

语法结构

CPSID 指令的语法格式如下:

CPSID {<if>} [, #<mode>]
  • 参数解释:

    • <if>:这是一个必选参数,它有以下几种取值:

      • i:屏蔽 IRQ(Interrupt ReQuest)中断,也就是普通中断。在 ARM 系统里,大多数外部设备产生的中断都属于 IRQ 中断。

      • f:屏蔽 FIQ(Fast Interrupt ReQuest)中断,即快速中断。FIQ 中断具有比 IRQ 中断更高的优先级,通常用于对响应时间要求极高的场合。

      • if:同时屏蔽 IRQ 和 FIQ 中断。

    • #<mode>:这是一个可选参数,用于指定处理器模式。不过在实际运用中,一般不指定该参数。

使用场景

  • 临界区保护:在对共享资源进行操作时,为了防止多个中断服务程序或者任务同时访问这些资源从而引发数据不一致的问题,可使用 CPSID 指令屏蔽中断,保证操作的原子性。

  • 实时任务执行:在实时系统中,部分任务对时间要求极为严格,不允许被中断打断。在执行这类任务时,就可以使用 CPSID 指令暂时屏蔽中断,等任务执行完毕后再恢复中断。

代码示例

以下是一段 ARM 汇编代码示例,展示了如何使用 CPSID 指令屏蔽中断:

; 屏蔽 IRQ 和 FIQ 中断
CPSID if

; 关键代码段开始
; 这里进行对共享资源的操作
LDR R0, =0x55AA     ; 将数据 0x55AA 加载到寄存器 R0
STR R0, [R1]        ; 把 R0 中的数据存储到内存地址 R1 处
; 关键代码段结束

; 恢复 IRQ 和 FIQ 中断
CPSIE if

; 程序继续执行
B .                 ; 进入无限循环

代码解释

  1. CPSID if:屏蔽 IRQ 和 FIQ 中断,确保后续关键代码段的执行不会被中断打断。

  2. 关键代码段:把数据 0x55AA 加载到寄存器 R0 中,然后将其存储到内存地址 R1 处。这部分代码是需要保护的关键操作。

  3. CPSIE if:恢复 IRQ 和 FIQ 中断,允许处理器响应中断请求。

  4. B .:进入无限循环,程序继续执行。

注意事项

  • 在使用 CPSID 指令屏蔽中断后,要及时使用 CPSIE 指令恢复中断,不然系统将无法响应中断请求,可能会致使系统失去实时性。

  • 在多任务系统中,频繁使用 CPSID 指令可能会对系统的性能和稳定性产生影响,所以需要谨慎使用。

CPSIE - Change Processor State(Interrupt Enable)

CPSIE(Change Processor State, Interrupt Enable)指令用于控制处理器中断使能状态,与 CPSID 指令相对,CPSID 用于禁用中断,而 CPSIE 用于启用中断。

基本功能

CPSIE 指令的主要功能是允许处理器响应特定类型的中断。当使用 CPSID 指令屏蔽中断后,可通过 CPSIE 指令恢复中断的响应,使得系统能够继续处理外部设备或内部事件产生的中断请求。

语法结构

CPSIE 指令的语法格式如下:

CPSIE {<if>} [, #<mode>]
  • 参数解释:

    • <if>:这是必选参数,有以下几种取值:

      • i:启用 IRQ(Interrupt ReQuest)中断,即普通中断。大多数外部设备(如定时器、串口等)产生的中断属于 IRQ 中断。

      • f:启用 FIQ(Fast Interrupt ReQuest)中断,即快速中断。FIQ 中断具有比 IRQ 中断更高的优先级,通常用于对响应时间要求极高的场景,如高速数据采集。

      • if:同时启用 IRQ 和 FIQ 中断。

    • #<mode>:可选参数,用于指定处理器模式。在实际应用中,通常不指定该参数。

使用场景

  • 临界区结束:在使用 CPSID 指令屏蔽中断来保护临界区(对共享资源的操作)后,当临界区代码执行完毕,需要使用 CPSIE 指令恢复中断,以允许系统继续响应外部事件。

  • 实时任务完成:在实时系统中,若某个任务在执行期间不允许被中断打断,使用 CPSID 屏蔽中断;任务完成后,使用 CPSIE 恢复中断,使系统恢复正常的中断处理机制。

代码示例

以下是一个 ARM 汇编代码示例,展示了 CPSID 和 CPSIE 指令的配合使用:

; 屏蔽 IRQ 和 FIQ 中断
CPSID if

; 关键代码段开始
; 这里进行对共享资源的操作
LDR R0, =0xABCD     ; 将数据 0xABCD 加载到寄存器 R0
STR R0, [R2]        ; 把 R0 中的数据存储到内存地址 R2 处
; 关键代码段结束

; 恢复 IRQ 和 FIQ 中断
CPSIE if

; 程序继续执行
MOV R3, #0          ; 将立即数 0 赋值给寄存器 R3
B .                 ; 进入无限循环

代码解释

  1. CPSID if:屏蔽 IRQ 和 FIQ 中断,确保关键代码段执行时不会被中断干扰。

  2. 关键代码段:将数据 0xABCD 加载到寄存器 R0 中,并存储到内存地址 R2 处,这部分代码是需要保护的操作。

  3. CPSIE if:恢复 IRQ 和 FIQ 中断,使处理器能够继续响应中断请求。

  4. 后续代码:将立即数 0 赋值给寄存器 R3,然后进入无限循环,程序继续执行。

注意事项

  • 在使用 CPSIE 恢复中断时,要确保关键代码段已经执行完毕,否则可能会导致数据不一致或程序出错。

  • 频繁地启用和禁用中断可能会影响系统的性能和稳定性,因此需要合理安排中断的屏蔽和恢复操作。

CPS - Change PE State

CPS(Change Processor State)指令是 ARM 架构里用于更改处理器状态的指令,它能对处理器的模式、中断屏蔽状态等进行控制。

指令功能

CPS 指令的主要功能是对处理器的状态进行改变,具体涵盖以下方面:

  • 中断屏蔽状态控制:可以屏蔽或者使能 IRQ(普通中断)和 FIQ(快速中断)。

  • 处理器模式切换:能够让处理器在不同的工作模式间进行切换,像用户模式、系统模式、管理模式等。

语法结构

CPS 指令的语法格式如下:

CPS{<cond>} <#op><#I><#F><#A> [, <mode>]
  • 参数解释:

    • <cond>:这是可选的条件码,用于指定指令执行的条件。只有当条件满足时,指令才会执行。常见的条件码有 EQ(相等)、NE(不相等)等。

    • <#op>:操作类型,有以下两种取值:

      • I:表示修改中断屏蔽状态。

      • M:表示修改处理器模式。

    • <#I>:控制 IRQ 中断屏蔽状态,有以下两种取值:

      • I:屏蔽 IRQ 中断。

      • i:使能 IRQ 中断。

    • <#F>:控制 FIQ 中断屏蔽状态,有以下两种取值:

      • F:屏蔽 FIQ 中断。

      • f:使能 FIQ 中断。

    • <#A>:控制异步异常(如调试异常)屏蔽状态,有以下两种取值:

      • A:屏蔽异步异常。

      • a:使能异步异常。

    • <mode>:可选参数,用于指定要切换到的处理器模式。常见的模式有:

      • #16:用户模式(User)。

      • #17:快速中断模式(FIQ)。

      • #18:普通中断模式(IRQ)。

      • #19:管理模式(Supervisor)。

      • #22:中止模式(Abort)。

      • #23:未定义指令模式(Undefined)。

      • #31:系统模式(System)。

使用场景

  • 中断管理:在执行关键代码时,为避免中断干扰,可以使用 CPS 指令屏蔽中断;关键代码执行完毕后,再使能中断。

  • 特权模式切换:在某些情况下,需要从用户模式切换到特权模式(如管理模式)来执行一些特权操作,这时可以使用 CPS 指令进行模式切换。

代码示例

以下是几个简单的示例,展示了 CPS 指令的使用:

示例 1:屏蔽 IRQ 和 FIQ 中断

    CPSID IF  ; 屏蔽 IRQ 和 FIQ 中断
    ; 关键代码段
    ; ...
    CPSIE IF  ; 使能 IRQ 和 FIQ 中断

示例 2:从用户模式切换到管理模式

    MRS R0, CPSR  ; 将当前程序状态寄存器(CPSR)的值读取到 R0
    BIC R0, R0, #0x1F  ; 清除模式位
    ORR R0, R0, #0x13  ; 设置为管理模式
    MSR CPSR_c, R0  ; 将修改后的值写回 CPSR
    ; 或者使用 CPS 指令直接切换
    CPS #19  ; 切换到管理模式

代码解释

  • 示例 1:

    • CPSID IF 指令用于屏蔽 IRQ 和 FIQ 中断,保证关键代码段的执行不会被中断干扰;

    • CPSIE IF 指令用于使能 IRQ 和 FIQ 中断,让系统恢复正常的中断处理。

  • 示例 2:

    • 第一种方法通过 MRS、BIC、ORR 和 MSR 指令来修改程序状态寄存器(CPSR)的值,从而实现模式切换;

    • 第二种方法直接使用 CPS 指令将处理器模式切换到管理模式(模式编号为 19)。

注意事项

  • 在使用 CPS 指令进行模式切换时,要确保了解不同模式的权限和用途,避免因模式切换不当而导致系统异常。

  • 频繁地屏蔽和使能中断可能会影响系统的性能和稳定性,需要合理使用。

DSB - Data Synchronization Barrier

DSB(Data Synchronization Barrier)指令用于确保内存操作顺序和数据一致性的重要同步指令。

指令功能

在 ARM 处理器中,为了提高性能,内存操作(如读、写操作)可能会被乱序执行,即实际执行顺序可能与程序中编写的顺序不同。此外,不同的处理器核心或者硬件单元在访问内存时也可能存在缓存不一致等问题。

DSB 指令的主要功能就是作为一个同步屏障,确保在 DSB 指令之前的所有内存访问操作(包括数据读、写操作)都完成之后,才会执行 DSB 指令之后的内存访问操作。它可以保证数据的一致性和内存操作的顺序性,防止因内存操作乱序而导致的错误。

语法结构

DSB 指令的语法比较简单,其基本格式为:

DSB {<option>}
  • <option>:是可选参数,用于指定同步的范围和条件。常见的选项有:

    • SY:表示系统范围的同步,确保所有的内存访问操作(包括普通内存和设备内存)都完成。这是最常用的选项,如果不指定 <option>,默认就是 SY。

    • ISH:表示内部共享范围的同步,确保在同一个内部共享域(通常是同一个多核处理器中的所有核心)内的内存访问操作完成。

    • ISHST:表示内部共享范围的存储同步,只确保存储(写)操作完成。

使用场景

  • 设备驱动编程:在与硬件设备进行交互时,需要确保对设备寄存器的写操作已经完成,才能进行后续的操作。例如,在向设备发送控制命令后,使用 DSB 指令确保命令已经写入设备寄存器,然后再读取设备的状态寄存器。

  • 多核系统编程:在多核处理器系统中,不同核心之间可能会共享内存。当一个核心对共享内存进行写操作后,为了让其他核心能够及时看到更新后的数据,需要使用 DSB 指令确保写操作完成,然后再通过其他同步机制(如内存屏障指令)通知其他核心。

  • 中断处理:在中断处理程序中,可能会对共享数据进行操作。为了确保中断处理程序中的内存操作与主程序中的内存操作顺序正确,需要使用 DSB 指令进行同步。

代码示例

以下是一个简单的 ARM 汇编代码示例,展示了 DSB 指令在设备驱动编程中的使用:

    ; 向设备寄存器写入控制命令
    LDR R0, =0x40000000  ; 设备寄存器地址
    MOV R1, #0x01        ; 控制命令
    STR R1, [R0]         ; 将控制命令写入设备寄存器

    ; 使用 DSB 指令确保写操作完成
    DSB SY

    ; 读取设备状态寄存器
    LDR R2, [R0, #4]     ; 读取设备状态寄存器的值
    CMP R2, #0x02        ; 比较状态值
    BEQ SUCCESS          ; 如果状态值等于 0x02,跳转到 SUCCESS 标签

    ; 错误处理
    B ERROR

SUCCESS:
    ; 处理成功情况
    B END_PROGRAM

ERROR:
    ; 处理错误情况
    B END_PROGRAM

END_PROGRAM:
    B .                  ; 进入无限循环

代码解释

  1. 写操作:将控制命令 0x01 写入设备寄存器 0x40000000。

  2. DSB SY:使用 DSB 指令确保写操作已经完成,这样后续读取设备状态寄存器时,能够得到正确的结果。

  3. 读操作:读取设备状态寄存器的值,并与 0x02 进行比较。

  4. 后续处理:根据比较结果跳转到相应的处理标签进行处理。

注意事项

  • DSB 指令会带来一定的性能开销,因为它需要等待之前的内存操作完成。因此,在使用时需要权衡性能和数据一致性的需求,避免不必要的使用。

  • 不同的 option 选项适用于不同的场景,需要根据具体的系统架构和需求选择合适的选项。

ISB - Instruction Synchronization Barrier

ISB(Instruction Synchronization Barrier)指令用于确保指令执行顺序和指令缓存一致性的同步指令。

指令功能

在 ARM 处理器中,为了提高指令执行效率,会采用指令流水线、指令预取等技术,这可能会让指令的实际执行顺序和程序编写的顺序有所不同。另外,处理器的指令缓存(Instruction Cache)可能会存储旧的指令副本。

ISB 指令的主要功能是充当同步屏障,保证在 ISB 指令之后的指令从指令缓存或者内存中重新获取,并且按顺序执行,也就是确保 ISB 之后的指令不会受 ISB 之前指令执行顺序的影响。

语法结构

ISB 指令的语法较为简单,基本格式为:

ISB {<option>}
  • <option>:是可选参数,用于指定同步的范围和条件。常见选项有:

    • SY:表示系统范围的同步,确保所有指令都从指令缓存或内存中重新获取,这是最常用的选项。若不指定 <option>,默认就是 SY。

    • ISH:表示内部共享范围的同步,保证在同一个内部共享域(通常是同一个多核处理器里的所有核心)内指令的同步。

使用场景

  • 指令集切换:当从 ARM 指令集切换到 Thumb 指令集,或者反之,使用 ISB 指令可以确保新指令集的指令能正确获取和执行。

  • 程序流改变:在执行如 BX、BLX 这类改变程序执行流程的指令后,使用 ISB 指令能保证新的指令序列按顺序执行。

  • 内存映射寄存器更新:对内存映射的控制寄存器进行写操作后,若该操作会影响指令的执行,就需要使用 ISB 指令确保后续指令能正确响应这些改变。

代码示例

以下是一个简单的 ARM 汇编代码示例,展示了 ISB 指令在指令集切换时的使用:

    ; 假设当前处于 ARM 指令集模式
    MOV R0, #1           ; 加载立即数 1 到 R0
    ORR R0, R0, #1       ; 设置 R0 的最低位为 1,用于切换到 Thumb 模式
    BX R0                ; 切换到 Thumb 指令集

    ; 使用 ISB 指令确保后续指令从新的指令集获取
    ISB SY

    ; 以下是 Thumb 指令代码
    MOVS R1, #2          ; Thumb 指令:加载立即数 2 到 R1
    ADDS R1, R1, #1      ; Thumb 指令:R1 = R1 + 1

    B END_PROGRAM        ; 跳转到程序结束处

END_PROGRAM:
    B .                  ; 进入无限循环

代码解释

  1. 指令集切换:借助 BX 指令,把 R0 的最低位设置为 1,从而切换到 Thumb 指令集。

  2. ISB SY:使用 ISB 指令确保后续的 Thumb 指令能从指令缓存或内存中重新获取,按顺序执行。

  3. Thumb 指令执行:执行 Thumb 指令,将立即数 2 加载到 R1,然后让 R1 的值加 1。

  4. 程序结束:跳转到 END_PROGRAM 标签,进入无限循环。

注意事项

  • ISB 指令会带来一定的性能开销,因为它需要清空指令流水线并重新获取指令。所以在使用时要权衡性能和指令执行顺序的需求,避免不必要的使用。

  • 不同的 <option> 选项适用于不同的场景,要根据具体的系统架构和需求选择合适的选项。


网站公告

今日签到

点亮在社区的每一天
去签到