为什么 x86 jump/call 指令使用相对位移而不是绝对目标?
Why do x86 jump/call instructions use relative displacements instead of absolute destinations?
我正在学习8086,有一个特别的问题困扰着我,我还没有找到满意的答案。
我知道 CPU 按顺序执行代码,如果想要更改代码流,我们希望 IP 指向我们感兴趣的代码所在的 new/old 地址。
现在,我的问题是,为什么我们(我的意思是CPU)在遇到跳转指令时,不直接去把IP更新为标签对应的地址?
遇到跳转指令需要在IP上加一个位移是什么意思?
我认为
- 计算位移(即跳转标签到跳转后下一条指令的距离)和
- 然后采取位移2的恭维,
- 它最终被添加到 IP,以便 IP 指向由标签
指向的 address/instruction
对我来说,这听起来比用与标签对应的地址更新 IP 还要多一些工作。但是,我确信事情的完成方式一定是有原因的,只是我不知道。
在 8086 中选择这种设计的原因是什么?
您大大高估了 CPU 解码相对跳跃的复杂性的成本。
- calculating the displacement(i.e the distance from the jump label to the next instruction after the jump)
- then taking that displacements 2's compliment,
机器代码必须包含步骤 2 的结果(有符号整数相对位移),因此所有这些都在 assemble 时完成。在 assembler 中,减去两个整数地址已经为您提供了所需的有符号 2 的补码位移。
使用相对位移有真正的优势,因此仅仅为了简化 assembler 的编写而使 ISA 变得更糟是没有任何意义的。您只需要编写 assembler 一次,但 在机器上运行的所有内容 都受益于更紧凑的代码和位置独立性。
相对分支位移是完全正常的,并且也用于大多数其他体系结构(例如 ARM:https://community.arm.com/processors/b/blog/posts/branch-and-call-sequences-explained,其中固定宽度指令无论如何都不可能进行直接绝对分支编码)。 如果 不 使用相对分支编码,它会使 8086 成为奇数。
update: 也许不完全是奇数。 MIPS 使用 rel16 << 2
代替 beq
/ bne
(MIPS 指令固定为 32 位宽并且始终对齐)。但是对于无条件j
(跳转)指令,它interestingly it uses a pseudo-direct encoding. It keeps the high 4 bits of PC, and directly replaces the PC[27:2]
bits with the value encoded in the instruction. (Again, low 2 bits of the program counter are always 0
.) So within the same 1/16th of address space, j
instructions are direct jumps, and don't give you position-independent code. This applies to jal
(jump-and-link = call
), making function calls from PIC code less efficient :( Linux-MIPS used to require PIC binaries, but apparently now it doesn't(但共享库仍然必须是PIC)。
当CPU运行eb fe
时,它所要做的只是将位移添加到IP
而不是替换IP
。由于非跳转指令已经通过添加指令长度更新IP
,加法器硬件已经存在。
请注意,sign-extending 8 位位移到 16 位(或 32 位或 64 位)在硬件中是微不足道的:2 的补码符号扩展只是复制符号位,这不需要任何逻辑门,只是将一位连接到其余部分的电线。 (例如 0xfe
变为 0xfffe
,而 0x05
变为 0x0005
。)
8086 非常重视代码密度,提供许多常用指令的简短形式。这是有道理的,因为代码获取是 8086 上最重要的瓶颈之一,所以更小的代码通常是更快的代码。
例如,存在两种形式的相对 jmp
,一种是 rel8(短),一种是 rel16(近)。 (在后来 CPU 引入的 32 位和 64 位模式中,E9
操作码是 jmp rel32
而不是 rel16
,但 EB
仍然是 jmp rel8
因为函数内的跳转通常在 -128/+127).
但是 call
没有特殊的缩写,因为它在大多数情况下用处不大。那么为什么它仍然困扰于相对位移而不是绝对位移?
嗯,x86 确实有绝对跳转,但仅限于间接跳转或 far 跳转。 (到不同的代码段)。例如,EA
操作码是 jmp ptr16:16
: "Jump far, absolute, address given in operand".
要进行绝对近距离跳跃,只需 mov ax, target_label
/ jmp ax
。 (或者在 MASM 语法中,mov ax, OFFSET target_label
)。
相对位移与位置无关
对该问题的评论提出了这个问题。
考虑一个机器代码块(已经 assembled),在块内有一些跳转。如果将整个块复制到不同的起始地址(或更改 CS
基地址,以便可以在段的不同偏移量访问同一块),则只有相对跳转会继续工作。
对于 要解决相同的问题,代码必须使用不同的 ORG
指令重新 assembled。显然,当您使用远跳转更改 CS 时,这不可能即时发生!
我正在学习8086,有一个特别的问题困扰着我,我还没有找到满意的答案。
我知道 CPU 按顺序执行代码,如果想要更改代码流,我们希望 IP 指向我们感兴趣的代码所在的 new/old 地址。
现在,我的问题是,为什么我们(我的意思是CPU)在遇到跳转指令时,不直接去把IP更新为标签对应的地址?
遇到跳转指令需要在IP上加一个位移是什么意思?
我认为
- 计算位移(即跳转标签到跳转后下一条指令的距离)和
- 然后采取位移2的恭维,
- 它最终被添加到 IP,以便 IP 指向由标签 指向的 address/instruction
对我来说,这听起来比用与标签对应的地址更新 IP 还要多一些工作。但是,我确信事情的完成方式一定是有原因的,只是我不知道。
在 8086 中选择这种设计的原因是什么?
您大大高估了 CPU 解码相对跳跃的复杂性的成本。
- calculating the displacement(i.e the distance from the jump label to the next instruction after the jump)
- then taking that displacements 2's compliment,
机器代码必须包含步骤 2 的结果(有符号整数相对位移),因此所有这些都在 assemble 时完成。在 assembler 中,减去两个整数地址已经为您提供了所需的有符号 2 的补码位移。
使用相对位移有真正的优势,因此仅仅为了简化 assembler 的编写而使 ISA 变得更糟是没有任何意义的。您只需要编写 assembler 一次,但 在机器上运行的所有内容 都受益于更紧凑的代码和位置独立性。
相对分支位移是完全正常的,并且也用于大多数其他体系结构(例如 ARM:https://community.arm.com/processors/b/blog/posts/branch-and-call-sequences-explained,其中固定宽度指令无论如何都不可能进行直接绝对分支编码)。 如果 不 使用相对分支编码,它会使 8086 成为奇数。
update: 也许不完全是奇数。 MIPS 使用 rel16 << 2
代替 beq
/ bne
(MIPS 指令固定为 32 位宽并且始终对齐)。但是对于无条件j
(跳转)指令,它interestingly it uses a pseudo-direct encoding. It keeps the high 4 bits of PC, and directly replaces the PC[27:2]
bits with the value encoded in the instruction. (Again, low 2 bits of the program counter are always 0
.) So within the same 1/16th of address space, j
instructions are direct jumps, and don't give you position-independent code. This applies to jal
(jump-and-link = call
), making function calls from PIC code less efficient :( Linux-MIPS used to require PIC binaries, but apparently now it doesn't(但共享库仍然必须是PIC)。
当CPU运行eb fe
时,它所要做的只是将位移添加到IP
而不是替换IP
。由于非跳转指令已经通过添加指令长度更新IP
,加法器硬件已经存在。
请注意,sign-extending 8 位位移到 16 位(或 32 位或 64 位)在硬件中是微不足道的:2 的补码符号扩展只是复制符号位,这不需要任何逻辑门,只是将一位连接到其余部分的电线。 (例如 0xfe
变为 0xfffe
,而 0x05
变为 0x0005
。)
8086 非常重视代码密度,提供许多常用指令的简短形式。这是有道理的,因为代码获取是 8086 上最重要的瓶颈之一,所以更小的代码通常是更快的代码。
例如,存在两种形式的相对 jmp
,一种是 rel8(短),一种是 rel16(近)。 (在后来 CPU 引入的 32 位和 64 位模式中,E9
操作码是 jmp rel32
而不是 rel16
,但 EB
仍然是 jmp rel8
因为函数内的跳转通常在 -128/+127).
但是 call
没有特殊的缩写,因为它在大多数情况下用处不大。那么为什么它仍然困扰于相对位移而不是绝对位移?
嗯,x86 确实有绝对跳转,但仅限于间接跳转或 far 跳转。 (到不同的代码段)。例如,EA
操作码是 jmp ptr16:16
: "Jump far, absolute, address given in operand".
要进行绝对近距离跳跃,只需 mov ax, target_label
/ jmp ax
。 (或者在 MASM 语法中,mov ax, OFFSET target_label
)。
相对位移与位置无关
对该问题的评论提出了这个问题。
考虑一个机器代码块(已经 assembled),在块内有一些跳转。如果将整个块复制到不同的起始地址(或更改 CS
基地址,以便可以在段的不同偏移量访问同一块),则只有相对跳转会继续工作。
对于 ORG
指令重新 assembled。显然,当您使用远跳转更改 CS 时,这不可能即时发生!