ARM汇编指令基础

时间:2024-03-20 21:15:31
1.二种指令集
CISC复杂指令集 
RISC精简指令集
2.二种架构
冯诺依曼结构:程序和数据都放在内存中, 且不彼此分离。
哈佛结构:程序和数据分开独立放在不同的内存块中,彼此完全分离。
3.ARM 约定:
Byte : 8 bits
Halfword : 16 bits (2 byte)
Word : 32 bits (4 byte)
4.ARM 处理器工作模式:
User : 非特权模式, 大部分任务执行在这种模式
FIQ : 当一个高优先级(fast) 中断产生时将会进入这种模式
IRQ : 当一个低优先级(normal) 中断产生时将会进入这种模式
Supervisor :当复位或软中断指令执行时将会进入这种模式
Abort : 当存取异常时将会进入这种模式
Undef : 当执行未定义指令时会进入这种模式
System : 使用和 User 模式相同寄存器集的特权模式
5.ARM 总共有 37 个寄存器, 但是每种模式下最多只能看到 18 个寄存器。37 个寄存器中 30 个为“通用” 型, 1 个固定用作 PC, 一个固定用作 CPSR, 5 个固定用作 5 种异常模式下的 SPSR。
ARM汇编指令基础
ARM汇编指令基础
整个 CPU 中只有一个 PC(CPSR 也只有一个, 但 SPSR 有 5 个)
ARM汇编指令基础
6.ARM 的异常处理机制:
(1).当异常产生时, ARM core:
--拷贝 CPSR 到 SPSR_<mode>
--设置适当的 CPSR 位:
-改变处理器状态进入 ARM 态
-改变处理器模式进入相应的异常模式
-设置中断禁止位禁止相应中断 (如果需要)
--保存返回地址到 LR_<mode>
--设置 PC 为相应的异常向量
(2).返回时, 异常处理需要:
--从 SPSR_<mode>恢复 CPSR
--从 LR_<mode>恢复 PC
--Note:这些操作只能在 ARM 态执行.
ARM汇编指令基础
7.ARM 汇编指令集 :
    ARM CPU 和内存数据交换
    ldr(load register) 指令将内存内容加载入通用寄存器。
    str(store register) 指令将寄存器内容存入内存空间中。

8.八种寻址方式:
    寄存器寻址 mov r1, r2
    立即寻址 mov r0, #0xFF00
    寄存器移位寻址 mov r0, r1, lsl #3 //r1 左移 3 位给 r0 --> r0=r1*8
    寄存器间接寻址 ldr r1, [r2] //r2 存放的内容指向的地址的值给 r1
    基址变址寻址 ldr r1, [r2, #4] //r2 存放的内容加 4 指向的地址的值给 r1
    多寄存器寻址 ldmia r1!, {r2-r7, r12} //将 r1 内容指向的地址(相当于数组的首地址)依次加载到 r2 到 r7 和 r12 寄存器
    堆栈寻址 stmfd sp!, {r2-r7, lr} //从栈里面依次连续访问多个字节放的寄存器了(即用于压栈操作)
    相对寻址 beq flag //跳转到标号处
    flag: //标号(跳转点)

9.指令后缀:
    同一指令经常附带不同后缀, 变成不同的指令。 经常使用的后缀有:
    B(byte) 功能不变, 操作长度变为 8 位
    H(half word) 功能不变, 长度变为 16 位
    S(signed) 功能不变, 操作数变为有符号
    如 ldr ldrb ldrh ldrsb ldrsh
    S(S 标志) 功能不变, 影响 CPSR 标志位(条件位 N、 Z、 C、 V)
    如 mov 和 movs movs r0, #0 (这里条件位取决于 r0)

10.条件执行后缀:
ARM汇编指令基础
moveq r0, r1 @ 如果 eq 后缀成立, 则直接执行 mov r0, r1; 如果 eq不成立则本句代码直接作废, 相当于没有  @ 类似于 C 语言中 if (eq){r0 = r1;}
条件后缀执行注意 2 点:
1、 条件后缀是否成立, 不是取决于本句代码, 而是取决于这句代码之前的代码运行后的结果(CPSR 标志位)。
2、 条件后缀决定了本句代码是否被执行, 而不会影响上一句和下一句代码是否被执行。

11.多级指令流水线:
PC 指向正被取指的指令, 而非正在执行的指令.
若 PC 指向的指令地址是 0x3000_0008 则正在执行的指令地址是0x3000_0000(所以 PC 指向某指令则该指令之前的第二条指令正在执行)。

12.数据处理指令:
数据传输指令 mov mvn
算术指令 add sub rsb adc sbc rsc
逻辑指令 and orr eor bic
比较指令 cmp cmn tst teq
乘法指令 mvl mla umull umlal smull smlal
前导零计数 clz
mov r1, r0 @两个寄存器之间数据传递
mov r1, #0xff @ 将立即数赋值给寄存器
mvn 和 mov 用法一样, 区别是 mov 是原封不动的传递, 而 mvn是按位取反后传递


and 逻辑与
orr 逻辑或
eor 逻辑异或
bic 位清除指令
bic r0,r1,#0x1f @ 将 r1 中的数的 bit0 到 bit4 清零后赋值给 r0


13.比较指令:
cmp 例如cmp r0, r1 等价于 sub r2, r0, r1 (r2 = r0 - r1),并根据结果设置CPSR的标志位。
cmn 例如cmn r0, r1 等价于 add r0, r1,并根据结果设置CPSR的标志位
tst 例如tst r0, #0xf @测试 r0 的 bit0~bit3 是否全为 0
teq 测试等价(注意: teq 是对 2 个数, 进行 eor(异或) )
比较指令用来比较 2 个寄存器中的数
注意: 比较指令不用后加 s 后缀就可以影响 cpsr 中的标志位。
实例:
CMP R1,R0 ;将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR的标志位
CMP R1,#100 ;将寄存器R1的值与立即数100相减,并根据结果设置CPSR的标志位
CMN R1,R0 ;将寄存器R1的值与寄存器R0的值相加,并根据结果设置CPSR的标志位
CMN R1,#100 ;将寄存器R1的值与立即数100相加,并根据结果设置CPSR的标志位


14.cpsr 访问指令:
mrs 用来读 psr, msr 用来写 psr
cpsr 和 spsr 的区别和联系:
cpsr 是程序状态寄存器, 整个 SoC 中只有 1 个; 而 spsr 有 5 个, 分别在 5 种异常模式下。
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
等价于
msr cpsr_c, #0xd3 @I & F disable, Mode: 0x13 - svc
说明:
msr cpsr_cxsf,r1 ;这里的 cxsf 表示从低到高分别占用的 4 个 8bit 的数据域
CPSR 有 4 个 8 位区域:
标志域(F)、状态域(S)、扩展域(X)、控制域(C)
指令中有时还有出现 cpsr_cf, cpsr_all, cpsr_c 等, 这里:
c 指 CPSR 中的 control field ( PSR[7:0])
f 指 flag field (PSR[31:24])
x 指 extend field (PSR[15:8])
s 指 status field ( PSR[23:16])
其中 cpsr 的位表示为:
31 30 29 28 --- 7 6 - 4 3 2 1 0
N Z C V I F M4 M3 M2 M1 M0

15.跳转(分支)指令:
b  直接跳转(就没打开算返回)
bl (branch and link), 跳转前把返回地址放入 lr 中, 以便返回, 以便用于函数调用
bx 跳转同时切换到 ARM 模式, 一般用于异常处理的跳转。

16.访存指令:
ldr/str & ldm/stm & swp
单个字/半字/字节访问 ldr/str
例: ldr ldrb ldrh
多字批量访问 ldm/stm
swp 内存与寄存器互换指令,一边读一边写
例:
swp r1, r2, [r0] @把 r0 中内存的地址的值给 r1,同时把 r2 寄存器的值写入内存。
swp r1, r1, [r0] @把内存中的值给 r1, 同时把 r1 原来的值给内存


17.汇编函数调用方法:
   mov r14, r15  //保存函数返回地址到lr
   ldr r15, show //pc指针赋值执行函数
   mov r0,r0
show:
   .word 0x33f94aa8   //假设这个地址存放printf函数
   
   
18.合法立即数与非法立即数:
ARM汇编立即数构成规则:必须是一个8位的二进制数,前面补上24位二进制0,扩展为32位;然后将这个32位的扩展数首尾相连循环偶数位得到。
给定一个立即数,判断其是否合法可以分三步:
首先将给定的立即数写成32位二进制的形式;然后看能不能用一个8位的二进制数包括所有含1的部分,如不能则非法;最后看这个8位二进制数能不能循环移动偶数位得到给定的立即数,不能数则非法。 1的个数超过8个一定不合法。
举例如下:
0xff=00000000 00000000 00000000 11111111:相当于8位二进制11111111循环右移0位得到,合法;
0x104=00000000 00000000 00000001 00000100:相当于8位二进制01000001循环右移2位得到,合法;
0x101=00000000 00000000 00000001 00000001:包含所有1的部分是100000001,无法用一个8位二进制表示,非法;
0x102=00000000 00000000 00000001 00000010:包含所有1的部分是10000001,但10000001只能循环右移奇数位得到给定的数,非法;
0xfC000002 =11111100 00000000 00000000 00000010:相当于8位二进制10111111循环移动6位得到,合法。
0x8000007E=10000000 00000000 00000000 01111110:包含所有1的部分是11111101,但10000001只能循环移动奇数位得到给定的数,非法。
   
19.软中断指令:
swi(software interrupt):
软中断指令用来实现操作系统中系统调用。


20.协处理器 cp15 操作指令:
mcr & mrc
mrc 用于读取 CP15 中的寄存器
mcr 用于写入 CP15 中的寄存器
协处理器和 MMU、 cache、 TLB 等处理有关, 功能上和操作系统的虚拟地址映射、 cache 管理等有关。


21.MRC & MCR 的使用方法:
mcr{<cond>} p15, <opcode_1>, <Rd>, <Crn>, <Crm>, {<opcode_2>}
opcode_1: 对于 cp15 永远为 0
Rd: ARM 的普通寄存器
Crn: cp15 的寄存器, 合法值是 c0~c15
Crm: cp15 的寄存器, 一般均设为 c0
opcode_2: 一般省略或为 0
例:
/***disable MMU stuff and caches*****/
mrc p15, 0, r0, c1, c0, 0 @将 CP15 的寄存器 C1 的值读到 r0 中
bic r0, r0, #0x00002000 @clear bits 13 (--v-)
bic r0, r0, #0x00000007 @clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0 @将 r0 的值写到 CP15 的寄存器 C1 中

22.CP15 的寄存器 C1
访 问主标识符寄存器的指令格式如下所示:
mrc p15, 0, r0, c1, c0{, 0} ; 将 CP15 的寄存器 C1 的值读到 r0 中
mcr p15, 0, r0, c1, c0{, 0} ; 将 r0 的值写到 CP15 的寄存器 C1 中

CP15 中的寄存器 C1 的编码格式及含义说明如下:
31 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
附 加 L4 RR V I Z F R S B L D P W C A M

位 说 明:
M 0 : 禁止 MMU 或者 PU ; 1 : 使能 MMU 或者 PU
A 0 : 禁止地址对齐检查; 1 : 使能地址对齐检查
C 0 : 禁止数据 / 整个 cache ; 1 : 使能数据 / 整个 cache
W 0 : 禁止写缓冲; 1 : 使能写缓冲
P 0 : 异常中断处理程序进入 32 位地址模式; 1 : 异常中断处理程序进入 26 位地址模式
D 0 : 禁止 26 位地址异常检查; 1 : 使能 26 位地址异常检查
L 0 : 选择早期中止模型; 1 : 选择后期中止模型
B 0 : little endian ; 1 : big endian
S 在基于 MMU 的存储系统中, 本位用作系统保护
R 在基于 MMU 的存储系统中, 本位用作 ROM 保护
F 0 : 由生产商定义
Z 0 : 禁止跳转预测功能; 1 : 使能跳转预测指令
I 0 : 禁止指令 cache ; 1 : 使能指令 cache
V 0 : 选择低端异常中断向量 0x0~0x1c ; 1 : 选择高端异常中断向量 0xffff0000~ 0xffff001c
RR 0 : 常规的 cache 淘汰算法, 如随机淘汰; 1 : 预测性淘汰算法,如 round-robin 淘汰算法
L4 0 : 保持 ARMv5 以上版本的正常功能; 1 : 将 ARMv5 以上版本与以前版本处理器 兼容, 不根据跳转地址的 bit[0] 进行 ARM 指令和 Thumb 状态切换: bit[0] 等于 0 表示 ARM 指令, 等于 1 表示 Thumb 指令。

举例(来自于 uboot)
/********使能 MMU*********/
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1 @M 位置 1
mcr p15, 0, r0, c1, c0, 0

23.多寄存器访问指令:
ldr/str 每周期只能访问 4 字节内存, 如果需要批量读取、 写入内存时太慢, 解决方案是 stm/ldm。
sdm (load register mutiple)
stm (store register mutiple)
举例(uboot start.S 537 行) :
stmia sp, {r0 - r12}
--将r0存入sp 指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008 ),将r2存入该地址······直到r12内容放入(0x30001030),指令完成。
--一个访存周期同时完成13个寄存器的读写。


24.8 种后缀:
ia(increase after)  先传输,再地址+4。  空增栈
ib(increase before) 先地址+4,再传输。 满增栈
da(decrease after)  先传输,再地址-4。  空减栈
db(decrease before) 先地址-4,再传输。 满减栈
fd(full decrease)   满递减堆栈。
ed(empty decrease)  空递减堆栈。
fa(·······) 满递增堆栈。
ea(·······) 空递增堆栈。

25.四种栈:
空栈: 栈指针指向空位, 每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出。
满栈: 栈指针指向栈中最后一格数据, 每次存入时需要先移动栈指针一格再存入;取出时可以直接取出, 然后再移动栈指针。
增栈: 栈指针移动时向地址增加的方向移动的栈。
减栈: 栈指针移动时向地址减小的方向移动的栈。


26.! 的作用:
ldmia r0, {r2 - r3}//因为 ld 是读内存(r0 是内存指向的地址) ia 是先传输, 再地址加 4, 即先把 r0 指向的地址里的内容放到 r2,再地址加 4, 后地址里的内容读到 r3 (注意: 因为不加“!” 所以地址加 4 的结果不存放在 r0 中, 是临时变量不保存)
ldmia r0! , {r2 - r3}
(注意:“!” 的作用是把地址变化的结果存放在 r0 中保存)
感叹号的作用:就是 r0 的值在 ldm 过程中发生的增加或者减少最后写回到 r0 去, 也就是说 ldm 时会改变 r0 的值。


27.^的作用:
ldmfd sp!, {r0 - r6, pc} //满递减 ”fd”先传输再减
ldmfd sp!, {r0 - r6, pc}^
^的作用: 在目标寄存器中有 PC 时, 会同时将 SPSR 写入到 CPSR,一般用于从异常模式返回。

28.伪指令的意义:
--伪指令和指令的根本区别是经过编译后会不会生成机器码。
--伪指令的意义在于指导编译过程。

29.gnu 汇编中的一些符号:
@ 用来做注释。 可以在行首也可以在代码后面同一行直接跟, 和 C语言中//类似
# 做注释, 一般放在行首, 表示这一行都是注释而不是代码。
: 以冒号结尾的是标号
. 点号在 gnu 汇编中表示当前指令的地址
# 立即数前面要加#或$, 表示这是个立即数

30.常用 gnu 伪指令:
.global _start @ 给_start 外部链接属性(类似于 c 语言中的 extern 的全局声明)
.section .text @ 指定当前段为代码段
.ascii .byte .short .long .word @用于定义数据变量(常用)
.quad .float .string @ 定义数据变量(不常用)
.align 4 @ 以 16 字节对齐
.balignl 16, 0xabcdefgh @ 16 字节对齐填充
.equ @ 类似于 C 中宏定义(实际上在 arm-gcc 中可以用define 定义所以.equ 不常用)
实例:
IRQ_STACK_START:
.word 0x0badc0de
等价于 unsigned int IRQ_STACK_START = 0x0badc0de;


31.字节对齐

.align 4 @ 16 字节对齐
.align 2 @ 4 字节对齐
.balignl 16, 0xdeadbeef @ 对齐 + 填充
b 表示位填充; align 表示要对齐; l 表示 long, 以 4 字节为单位填充; 16 表示
16 字节对齐; 0xdeadbeef 是用来填充的原料。
例: .balignl 16, 0xdeadbeef 执行后(伪指令编译后会不会生成机器码) 代码指令存放如下:
0x00000008: 上一条指令
0x0000000c 0xdeadbeef
0x00000010: 下一条指令


32.标签使用:
ldr r0, lable    //取标签处的值即88888
ldr r1, =lable   //取标签的地址
lable:
    .word 88888

33.偶尔会用到的 gnu 伪指令:
.end     @标识文件结束
.include @ 头文件包含
.arm / .code32   @声明以下为 arm 指令
.thumb / .code16 @声明以下为 thubm 指令

34.最重要的几个伪指令:
ldr 大范围的地址加载指令(不用考虑是不是合法立即数)。
adr 小范围的地址加载指令。
adrl 中等范围的地址加载指令。
nop 空操作。
ARM 中有一个 ldr 指令, 还有一个 ldr 伪指令。
一般都使用 ldr 伪指令而不用 ldr 指令。
ldr 如果是指令则立即数前是 “#”。
ldr 如果是伪指令则立即数前是“=”, 如果是标号就不用加“=” 了。

例:
ldr 指令: ldr r0, #0xff
伪指令:   ldr r0, =0xfffl @涉及到合法/非法立即数, 涉及到 ARM 文字池

35.adr 与 ldr:
--adr 编译时会被 1 条 sub 或 add 指令替代, 而 ldr 编译时会被一条mov 指令替代或者文字池方式处理;
--adr 总是以 PC 为基准来表示地址, 因此指令本身和运行地址有关,可以用来检测程序当前的运行地址在哪里。
--ldr 加载的地址和链接时给定的地址有关, 由链接脚本决定。

36.adr 和 ldr 的差别:
ldr 加载的地址在链接时确定,而adr加载的地址在运行时确定;所以我们可以通过adr和ldr加载的地址比较来判断当前程序是否在链接时指定的地址运行。