指令集、立即数和伪指令
文章目录
- 一、介绍
- 二、指令格式转换
- 2.1.ARM 与 Thumb
- 2.2.Thumb-2
- 三、立即数和伪指令
- 3.1.立即数
- 3.2.伪指令
一、介绍
ARM 公司发布两类指令集:
- ARM 指令集:32位,高效但占据空间
- Thumb 指令集:16位,节省空间
在 RISC 中对于内存只有读写操作,计算是在 ALU 里面的,因此汇编可以分为下面几类:
- 内存的读写操作
- 运算
- 跳转 / 分支:调用函数等
- 比较:if 语句等
这些就是所谓的指令集。
二、指令格式转换
2.1.ARM 与 Thumb
ARM 指令和 Thumb 指令之间的切换,取决于地址的 bit 0 位, 因为地址本身是对齐的(字对齐或半字对齐),所以 bit 0 在计算实际地址时会被硬件忽略。它纯粹用于状态控制。举个例子,假设 thumb_routine 的物理地址是 0x00002000。这是一个半字(16位)对齐的地址,其 bit 0 是 0。:
;这是由ARM状态切换到Thumb状态
LDR R0, =0x00002001 ; 将地址 0x2000 + 1 加载到 R0,bit 0 被显式设置为 1
BX R0 ; 处理器看到 bit 0 = 1,会切换到Thumb状态,并跳转到 0x2000 执行。
同理,由 Thumb 状态切换到 ARM 状态,假设 arm_routine 的物理地址是 0x00003000,这是一个字对齐的地址,其 bit 0 是 0。
LDR R1, =arm_routine ; 直接加载ARM例程的地址 0x00003000,bit 0 = 0
BX R1 ; 处理器看到 bit 0 = 0,会切换到ARM状态,并跳转到 0x3000 执行。
2.2.Thumb-2
为了提高 32 位和 16 位切换的效率,ARM 公司又提出了 Thumb-2 指令集,使得两种不同格式的指令混合使用。举例:要将寄存器 R1 的值左移 2 位:
- 在传统 Thumb 中: 可能需要多条 16 位指令才能完成(比如先用一条指令将移位值 2 放入另一个寄存器,再用一条指令进行移位)
- 在 ARM 中: 一条指令搞定:MOV R0, R1, LSL #2 (32位指令)
- 在 Thumb-2 中: 同样一条指令搞定:LSLS R0, R1, #2。关键是,这条指令在 Thumb-2 中是一条 32 位指令,但它可以在 Thumb 状态下直接执行,无需切换到 ARM 状态
Thumb-2 的指令会影响程序状态寄存器的 EPSR 寄存器,该寄存器里的 T 就是说明了该指令是 ARM 指令还是 Thumb 指令,置 1 就是 Thumb 指令,置 0 就是 ARM 指令,如下图所示:
三、立即数和伪指令
3.1.立即数
MOV R0, #VAL ;将VAL值存放到R0寄存器里,这个值可以是任意数吗?
不可以,VAL 里面的数必须是立即数,立即数就是一个数值通过一个指令直接放在寄存器里面,CPU 访问的时候直接可以知道这个数值,不需要再去访问其他地址。如下图所示,在一条 32 位的 ARM 指令中,不可能用一个完整的 32 位来存放一个任意的 32 位立即数,因为还需要空间来编码操作码、目标寄存器、条件码等。
解释一下以上图片:
- cond:条件执行字段,它决定这条指令在什么条件下执行,例如:两数相比,相等 EQ,不相等 NE
- opcode:操作码字段,用于区分数据处理指令,并指定具体操作,例如:MOV、ADD、AND 等
- Rd:目标寄存器,指令结果存放的地方,例如:R0 ~ R15 寄存器
- 0 ~ 11 位:这里的 12 位被进一步划分为两个部分:Immed_8:一个 8 位的基础数(0 ~ 255);Rotate_imm:一个 4 位的循环右移值(0 ~ 15)
判断一个数是否是立即数,下面有一条公式:实际立即数 = Immed_8 循环右移 (2 × Rotate_imm) 位
:取 Immed_8(一个 8 位的数);将它扩展为一个 32 位的数(高 24 位用 0 填充);将这个 32 位数循环右移。右移的位数是 Rotate_imm 字段的值 乘以 2;因为 Rotate_imm 是 4 位,所以范围是 0 ~ 15;乘以2后,可能的移位位数是 0, 2, 4, 6, …, 30(所有偶数);最终得到的结果,就是指令可以使用的 32 位立即数。
例如:假设指令中的立即数字段编码是:Rotate_imm = 4, Immed_8 = 0b00000010 (即十进制2):
取出基础值: Immed_8 = 2, 32位表示为 0x00000002。计算移位位数: 2 × Rotate_imm = 2 × 4 = 8 位。执行循环右移8位:将 0x00000002 (二进制 0000 0000 ... 0000 0010) 循环右移8位。结果是 0x02000000 (二进制 0000 0010 0000 0000 ... 0000 0000)。所以,这条指令使用的实际立即数是 0x02000000。
3.2.伪指令
LDR R0, =VAL ;使用等于号就说明了这是一条伪指令
伪指令就是假的指令,一条不存在的指令,编译器会将这个伪指令替换成真实的指令,如果 VAL 的值是立即数 0x12,编译器就会将该指令变成:
MOV R0, #0x12
如果这个值不是立即数,而是没有遵循立即数规律的数:0x12345678,编译器会将其替换为:
Label DCD 0x12345678 ;先在某个地方保存这个值
LDR R0, [pc,#offset] ;再使用Load Register读内存指令读出这个值,offset是链接程序时确定的