指令概述

  • 程序:计算机执行过程的序列,编写程序就是编写这个过程序列
  • 指令集的规模
    • 不宜过小:通过指令的排列组合,能达到高级编程语言的所有效果
    • 不宜过大:指令集规模应精简,以免增加处理器电路的负担

指令格式

基本格式

<opcode> {<cond>}{S} <Rd>,<Rn>,{<Op2>}	; comment

  • <> 必须
  • {} 可选

指令存储形式

汇编代码格式

一个完整的 ARM 汇编由两部分组成:声明代码段

声明

  • 声明:在一个程序之前先要进行声明
    • 声明代码段:用 AREA 指令定义一个段,说明所定义段的相关属性(段的名字和段的属性等)
    • 声明 ARM 指令:用 CODE 来声明程序为 ARM 指令
    • 声明程序入口:用 ENTRY 指令标识程序的入口点
  • 在程序完成后要用 END 指令声明程序结束。每一个汇编程序段都必须有一条 END 指令,指示代码段的结束。

代码段

  • :在 ARM 汇编语言程序中,以程序段为单位组织代码。段是相对独立的指令或数据序列,具有特定的名称。
    • 代码段:代码段的内容为执行代码
    • 数据段:数据段存放代码运行时需要用到的数据
  • 段具有:READONLYREADWRITE 等属性。
  • 注意:一个汇编程序至少有一个代码段。
    • 如果程序较长时,可以分割为多个代码段和数据段。
    • 多个段在程序编译连接时最终形成一个可执行的映像文件。

使用 ; 进行注释,标号要顶格写

示例

指令详解

存储器访问指令

数据传送分为两种:存储加载

  • 将数据从寄存器到存储器的传送叫作存储
  • 将数据从存储器到寄存器的传送叫作加载

N 表示不支持这种代码,Y 表示支持这种代码。以上所有指令都不影响标志位

LDM / STM 加载/存储寄存器组

寄存器组加载/存储指令:用于在寄存器和存储器之间进行成组的数据传送

  • LDM:Load Multiple,把存储器(内存)中的成组数据加载到寄存器组
  • STM:Store Multiple,把一组寄存器中的数据存储到存储区(内存),使用一个寄存器做基址
LDM<addressing_mode> <Rn>{!}, <registers>
LDMFD SP!, {R0-R3, PC}	; 从堆栈弹出数据到R0-R3和PC(函数返回)
LDMDB R5!, {R0-R4}

STM<addressing_mode> <Rn>{!}, <registers>
STMFD SP!, {R0-R3, LR}	; 满递减堆栈,压栈R0-R3和LR,并更新SP
STMIA R3!, {R0, R4-R12, LR}

寻址模式后缀

后缀 名称 堆栈类型 地址变化
FD Full Descending 满递减 先存数据,地址递减
FA Full Ascending 满递增 先存数据,地址递增
ED Empty Descending 空递减 先递减地址,再存数据
EA Empty Ascending 空递增 先递增地址,再存数据
IA Increment After 递增 先访问,后加地址
IB Increment Before 递增 先加地址,再访问
DA Decrement After 递减 先访问,后减地址
DB Decrement Before 递减 先减地址,再访问

!:写回

带  !(写回):

STMFD SP!, {R0-R3}
  • 将 R0–R3 存入堆栈;
  • SP 自动更新(递减,因为是满递减堆栈);
  • 相当于:先压栈,再修改 SP 指向新的栈顶。

不带  !

STMFD SP, {R0-R3}
  • 数据仍从当前 SP 地址开始存储;
  • 但 SP 的值不会改变,后续若继续用 SP 访问堆栈,可能覆盖刚存的数据。

总结:

  • !  的作用是让基址寄存器自动更新为操作后的最终地址
  • 在堆栈操作或连续内存块访问中,通常需要加  !,以确保指针正确移动;
  • 这是 ARM 前索引寻址(pre-indexed addressing) 的一部分。

LDR / STR 加载/存储单个寄存器

加载/存储单个寄存器指令 LDRSTR 用于在寄存器和存储器之间进行半字以及字节传送。

  • LDR 用于把存储器中的 32 位数据加载到寄存器
  • STR 用于把寄存器中的 32 位数据存储到存储器中
LDR{T}, Rd, <address>
STR{T}, Rd, <address>
  • {T} 是可选传送后缀
  • <address> 为地址偏移量

传送后缀

  • S:有符号操作
  • SBSH 不可用于 STR
后缀 作用
B 字节传送
H 半字传送
SB 带符号字节传送
SH 带符号半字传送
  • LDRB:把存储器中的 8 位数据字节加载到寄存器
  • STRB:把寄存器中的低字节存储到存储器
  • LDRH:把存储器中的半字加载到存储器
  • STRH:把寄存器中的低半字存储到存储器

区分:字、半字、字节

寻址

  • LDR/STR 指令寻址非常灵活,由两部分组成:
    • 一部分是一个基址寄存器,它可以是任一个通用寄存器;
    • 另一部分是地址偏移量
  • 地址偏移量通常有 3 种形式:
    • 立即数偏移
    • 寄存器偏移
    • 寄存器及移位常数
LDR R2, [R5]	; 将R5指向地址的字数据存入R2
LDR R1, [R0, #0x12]	; 将R0+0x12地址处的数据读出,保存到R1中
STR R1, [R0, #0x04]	; 将R1的数据存储到R0+0x04地址

LDRB R3, [R2], #1	; 将R2指向地址的字节数据存入R3,并令R2=R2+1
STRB R6, [R7]		; 将R7指向地址的字节数据存入R6
LDRH R6, [R2], #2	; 将R2指向地址的半字节数据存入R6,并令R2=R2+2;半字数据高位用0扩展

STRH R1, [R2, #2]	; 将R1的半字数据保存到R2+2地址中,只修改低2字节数据,并令R2=R2+1

LDRSB R1, [R0, R3]	; 将R0+R3地址上的字节数据存入R1,高24位用符号扩展

无特权访问

LDRT Rd, [Rb, #imme]
STRT Rd, [Rb, #imme]

T 后缀的含义是:指令在特权模式下对存储器的访问,将被存储器看成是用户模式(无特权)的访问。这条指令在特权模式下不享有特权

互斥访问

互斥访问总是成对出现,每个 LDREX 语句后面总是跟随一个 STREX 语句

  • 可以实现半字和字节访问:可以使用 BH 后缀
  • 不可以实现带符号数据的存储和加载:不可以使用 SHSB
LDREX Rd, [Rb, #imme]
STREX Rt, Rd, [Rb, #imme]	; Rt记录指令执行结果

互斥清除指令CLREX

互斥清除指令用来清除由指令 LDREX 标注的互斥状态,在执行这条指令以后,内核不再处于互斥状态,后面的指令 STREX 不能正确执行,Rt 返回 1

堆栈指令 PUSH 和 POP

这两条指令可以对寄存器组实现操作,是以 SP 为基址的加载和存储指令

  • 堆栈指针 SP 是隐含的地址基址
  • 堆是满栈递减堆栈,堆栈向下增长,堆栈指针总是指向最后入栈的数据
  • 使用入栈指令 PUSH 时,每传送一个数据,堆栈指针就自动减 4;使用出栈指针 POP 时,每传送一个数据,堆栈指针就自动加 4,属于后索引形式
  • 指令执行完后,堆栈指针的指向已经变化。
PUSH {register_list}
POP {register_list}

通用数据处理指令

数据处理指令是所有微处理器中最基本的指令,用于在寄存器和存储器之间进行数据的双向传输

![[images/Pasted image 20260109133021.png]]

加减法指令

  • ADD:两个操作对象相加
  • SUB:两个操作对象相减
  • ADC:两个操作对象带进位加
  • SBC:两个操作对象带借位减
  • RSB:两个操作对象反减
  • ADDW:包含 12 位立即数的加法操作
  • SUBW:包含 12 位立即数的减法操作
<op>{S} Rd, Rn, <op2>
<op>{S} Rd, Rn, #imme

ADD R1, R2, R0		; R5 = R2 + R0
ADDS R1, R1, #1		; R1 = R1 + 1
SUB R4, R1, R0		; R4 = R1 - R0
SUBS R0, R0, #1		; R0 = R0 - 1
SUBS R2, R1, R2		; R2 = R1 - R2
RSB R3, R1, #0xFF00	; R3 = 0XFF00 - R1
RSBS R1, R2, R2, LSL #2 ; R1 = (R2 << 2) - R2

乘除法指令

乘法指令

  • MUL:32 位乘法运算
  • MLA:32 位乘加运算
  • MLS:32 位乘减运算
MUL {cond}{S} Rd, Rs, Rt
MUL R0, R1, R2			; R0 = R1 * R2


MLA/MLS {cond}{S} Rd, Rs, Rt, Rm
MLA R0, R1, R2, R3		; R0 = R3 + R1 * R2
MLS R4, R5, R6, R7		; R4 = R7 - R5 * R6

长乘法指令

  • UMULL:64 位无符号乘法运算
  • UMLAL:64 位无符号长乘加运算
  • SMULL:64 位带符号长乘法运算
  • SMLAL:64 位带符号长乘加运算
OP {条件} {S} 目的寄存器低字节, 目的寄存器高字节, 操作数1, 操作数2

除法指令

  • UDIV:无符号除法运算
  • SDIV:带符号除法运算
SDIV/UDIV Rd, Rm, Rs
SDIV/UDIV Rd, Rm

SDIV R0, R5, R7		; R0 = R5 / R7
UDIV R8, R2, R3		; R8 = R2 / R3
  • Rd:目标寄存器,也可存放被除数
  • Rm:存放被除数的寄存
  • Rs:存放除数的寄存器

这是 32 位运算指令,结果是一个 32 位的数据,存储在目标寄存器中。如果不能被整除,余数将丢失

逻辑操作指令

  • AND:两个操作对象相与
  • ORR:两个操作对象相或
  • EOR:两个操作对象异或
  • BIC:对寄存器按位清 0
  • ORN:对寄存器进行非或,先对第 2 操作数进行非运算,再和第 1 操作数进行或运算
<op>{cond}{S} Rd, Rn, <Op2>

AND R1, R2, #0x80		; R8 = R4 & 0x80
AND R5, R6				; R5 = R5 & R6
ANDS R0, R0, #0x01		; R0 = R0 & 0x01,取出最低位数据
ORR R6, #0x40			; R6 = R6 | 0x40
ORR R0, R0, #0x0F 		; R0 = R0 | 0x0F,将R0的低4位置为1
ORR R3, R1, R3, LSL #8	; R3 = R1 | (R3 << 8)
EOR R9, R6, R5			; R9 = R6 ^ R5
EOR R1, R1, #0x0F		; R1 = R1 ^ 0x0F,将R1的低4位取反
BIC R1, R1, #0x0F		; R1的低4位清零
ORN R1, R2, R3			; R1 = (!R3) | R2

位移操作指令

  • LSL:逻辑左移(Logical Shift Left),寄存器中字的低端空出的有效位补 0
  • LSR:逻辑右移(Logical Shift Right),寄存器中字的高端空出的有效位补 0
  • ASR:算术右移(Arithmetic Shift Right),算术移位的对象是带符号数,在移位过程中必须保持操作数的符号位不变
    • 若源操作数为正,则字的高端空出的有效位补 0
    • 若源操作数为负数,则字的高端空出的有效位补 1
  • ROR:循环右移(Rotate Right),从字的最低端移出的有效位依次填入空出字的高端有效位
  • RRX:扩展为 1 的循环右移(Rotate Right Extended),按操作数所指定的数量向右循环移位,空位用原来 C 标志位填充

![[images/Pasted image 20260109135808.png]]

<op> Rd, {Rn}, Rs/#n
  • op 指的是 LSL/LSR/ASR/ROR/RRX
  • Rd 是指目标寄存器;
  • Rn 是指移位寄存器;
  • Rs 是指移位长度寄存器,其数值是 Rn 移位的位数,范围是 $[0 ,255]$
  • n 是指移位长度,ASRLSRROR 是 $[1 ,31]$,LSL 是 $[0 ,31]$

比较操作指令

  • CMP:两个操作对象的比较操作,用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新 CPSR(Current Program Status Register,程序状态寄存器)中条件标志位的值
  • CMN:两个操作对象的取反比较操作,用于把一个寄存器的内容和另一个寄存器的内容或立即数取反后进行比较,同时更新 CPSR 中条件标志位的值
CMP/CMN{cond} Rs, Rt

CMP R1, R0		; if (R1 - R0 > 0) CPSR: N = 0 else if (R1 - R0 == 0) CPSR: Z = 1 else CPSR: N = 1
CMP R1, #100	; R1 - 100

CMN R1, R0		; R1 + R0
CMN R1, #100	; R1 + 100

数据传送指令

这是一组只有两个操作对象的指令

  • MOV:把第 2 操作对象内容传送到第 1 操作对象
  • MVN:把第 2 操作对象内容的传送到第 1 操作对象
  • MOVW:把 16 位立即数放到寄存器的低 16 位,高 16 位清 0
  • MOVT:把 16 位立即数放到寄存器的高 16 位,低 16 位不影响
<op>{cond}{S} Rd, Rs

MOV R1, R0				; R1 = R0
MOV R1, R0, LSL #3		; R1 = R0 << 3
MOV R7, PC				; PC + 4, R7 = PC
MOVW R7, #0xFF12		; 0xFF12传送到R7的低16位,高16位清零
MOVT R3, #0xF123		; 0xF123传送到R3的高16位,低16位不影响
MVN R0, #0				; R0 = !0,将立即数0取反后赋值给R0,即R0 = -1
MVN R4, R6				; R4 = !R6
MOVS R1, R2, LSL #2		; R1 = R2 << 2,条件标志位刷新
  • {S}:表示是否更新 CPSR 的条件码标志到状态寄存器的条件标志位

分支控制指令

![[images/Pasted image 20260109144936.png]]

分支指令

  • B / BX:程序简单跳转
  • BL / BLX:分支链接指令,保存跳转处地址
B/BL Label		; 跳转到目标标签
BX/BLX Rm		; Rm是存储转移地址的寄存器
  • 指令 BLBLX 除引导程序跳转外,还把下一条语句的地址记录在链接寄存器 LR 中,以便程序返回使用

比较分支指令

  • CBZ:比较结果为 0 的跳转
  • CBNZ:比较结果非 0 的跳转
CBZ/CBNZ Rn, Label

CBZ R5, Label1		; if R5 == 0: jump to Label1
CBNZ R0, Label2		; if R0 != 0: jump to Label2
  • Rn 是比较寄存器,只能是低位寄存器 R0 ~ R7
  • 这是两条不可以使用条件后缀的指令
  • 这些指令不会改变条件标志位,但是可以判断是否需要跳转
  • 这些指令只能向后跳转,不能向前跳转,跳转的范围限制在 4 ~ 130 字节内

条件分支指令

  • 条件分支指令只有一条即 IT,是 IF-THEN 的简写
  • 指令 IT 必须和一个条件后缀组成一条语句,随后跟随的语句由条件是否成立决定是否执行
    • 如果本条语句的条件成立,则执行随后被标注 T 的语句
    • 如果本条语句的条件不成立,则执行随后被标注 E 的语句
  • 每条 IT 语句最多可以跟随 4 条被执行的语句,每条语句的标注符号 TE 紧随 IT,不适用有其他符号或空格
    • 需要注意的是,指令本身已经有一个 T,这个 T 标注了随后的第 1 条语句是否条件成立

![[images/Pasted image 20260109145611.png]]

IT {x} S
IT {x}{y} S
IT {x}{y}{z} S

CMP R1, R0			; 比较R1和R0,产生条件
ITTEE EQ			; T-T-E-E
ADD					; 相等(EQ)执行-对应T
SUB					; 相等(EQ)执行-对应T
MUL					; 不相等执行-对应E
DIV					; 不相等执行-对应E
  • S 表示条件后缀
  • x、y 和 z 依次表示第 2 条、第 3 条和第 4 条语句的标注,都是可选的,都有 T 和 E 两种
    • T 表示条件成立执行第 i 条语句
    • E 表示条件不成立执行第 i 条语句

汇编语法

格式规范

  • 所有的标号必须顶格写
  • 所有的指令均不能顶个书写,指令前面应该有空格,一般用 Tab 键
  • ARM 汇编器对标识符的大小写敏感,因此书写标志及指令时,大小写要一致,指令、寄存器名可以全部为大写,也可以全部为小写,但是不能大小写混合使用(对于单个指令要保持一致,指令之间不需要)
  • 使用注释时,注释内容由“;”开始一直到此行结束,注释可以顶格写
  • 定义变量、常量时,其标识符必须顶格书写,否则编译器报错
  • 函数名需要顶格写
AREA Reset, DATA, READONLY
START
	AND
	ADD
NEXT
	sub
	orr