相对跳转指令,在不同CPU架构上的区别
大部分的跳转都是相对地址跳转,即偏移量跳转,然而这个偏移量,在不同的架构上表现是不同的。这篇文章将会对比几种架构来展示其中的细节。
RISC-V
汇编源码(rv.asm):
l1: beq x0, x0, l1
l2: beq x0, x0, l1
编译后进行反汇编看机器码:
riscv64-unknown-elf-as rv.asm -o rv.o && riscv64-unknown-elf-objdump -S rv.o
rv.o: file format elf64-littleriscv
Disassembly of section .text:
0000000000000000 <l1>:
0: 00000063 beqz zero,0 <l1>
0000000000000004 <l2>:
4: fe000ee3 beqz zero,0 <l1>
根据RISC-V指令集手册, beq
指令的编码格式为:
32 25 20 15 12 7 0
+--------------+-----+-----+-----+-------------+---------+
| imm[12|10:5] | rs2 | rs1 | 000 | imm[4:1|11] | 1100011 |
+--------------+-----+-----+-----+-------------+---------+
所以上面两条指令的跳转偏移量分别是0
和一个有符号数0b1111111_1110_0
(值为-4
)。
ARM
汇编源码(arm.asm):
l1: beq l1
l2: beq l1
编译后进行反汇编看机器码:
arm-none-eabi-as arm.asm -o arm.o && arm-none-eabi-objdump -S arm.o
arm.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <l1>:
0: 0afffffe beq 0 <l1>
00000004 <l2>:
4: 0afffffd beq 0 <l1>
根据ARM架构手册,beq
(实际上是b
)指令的编码格式为:
32 28 24 0
+------+-------+---+-----------------+
| cond | 1 0 1 | 0 | signed_immed_24 |
+------+-------+---+-----------------+
两条指令的跳转偏移量分别是FFFFFE
和FFFFFD
,经过符号扩展和移位,这两个偏移量将变为FFFFFFF8
和FFFFFFF4
。
它们也是有符号数,所以是-8
和-12
。
为什么不是0
和-4
呢?
这是因为一些历史原因:
The original ARM design had a 3-stage pipeline (fetch-decode-execute). To simplify the design they chose to have the PC read as the value currently on the instruction fetch address lines, rather than that of the currently executing instruction from 2 cycles ago. Since most PC-relative addresses are calculated at link time, it's easier to have the assembler/linker compensate for that 2-instruction offset than to design all the logic to 'correct' the PC register.
ARM最初的设计有一个三级流水线(预取-解码-执行)。为了简化设计,他们选择将PC的读入值直接指定为当前执行时的预取线上的地址,而不是2个周期前的那个地址 ……
MCS-51
汇编源码(8051.asm):
l1: jc l1
l2: jc l1
编译后进行反汇编看机器码:
sdas8051 -l 8051.lst 8051.asm && head 8051.lst
ASxxxx Assembler V02.00 + NoICE + SDCC mods (Intel 8051), page 1.
Hexadecimal [24-Bits]
000000 40 FE [24] 1 l1: jc l1
000002 40 FC [24] 2 l2: jc l1
根据8051指令集手册,JC
的编码格式是:
16 8 0
+--------+----------+
| OFFSET | 01000000 |
+--------+----------+
所以OFFSET
分别是FE
(-2
)和FC
(-4
)。
和ARM类似,我们拿到的也是指令当前指令时候的PC
值,所以需要减去前一条指令的长度。