Alb*_*ert 4 x86 assembly cpu-architecture x86-16
我正在学习 8086,有一个特别的问题困扰着我,我还没有找到任何令人满意的答案。
我知道 CPU 按顺序执行代码,如果想要更改代码流,我们希望 IP 指向我们感兴趣的代码所在的新/旧地址。
现在,我的问题是为什么我们(我的意思是 CPU)在遇到跳转指令时不只是使用与标签对应的地址来更新 IP?
当我们遇到跳转指令时,需要在IP中添加位移吗?
在我看来
对我来说,这听起来像是更多的工作,然后只需使用与标签对应的地址更新 IP。但是,我确信事情的处理方式一定是有原因的,只是我不知道。
在 8086 中选择这种设计的原因是什么?
您大大高估了解码相对跳跃的 CPU 复杂性成本。
- 计算位移(即跳转标签到跳转后下一条指令的距离)
- 然后取那个位移 2 的恭维,
机器码必须包含第 2 步的结果(有符号整数相对位移),因此所有这些都在汇编时完成。在汇编程序中,减去两个整数地址已经为您提供了所需的有符号 2 的补码位移。
使用相对位移确实有好处,因此仅仅为了简化编写汇编程序而使 ISA 变得更糟是没有任何意义的。您只需要编写一次汇编程序,但在机器上运行的所有内容都受益于更紧凑的代码和位置独立性。
相对分支位移是完全正常的,也用于大多数其他架构(例如 ARM:https : //community.arm.com/processors/b/blog/posts/branch-and-call-sequences-explained,其中固定 -宽度指令无论如何都无法进行直接的绝对分支编码)。 它会使 8086 成为不使用相对分支编码的奇数。
更新:也许不完全是奇怪的。MIPSrel16 << 2
用于beq
/ bne
(MIPS 指令固定为 32 位宽且始终对齐)。但是对于无条件j
(跳转)指令,有趣的是它使用了伪直接编码。保留PC的高4位,直接用PC[27:2]
指令中编码的值替换这些位。(同样,程序计数器的低 2 位始终为0
。)因此,在地址空间的相同 1/16 内,j
指令是直接跳转,并且不会为您提供与位置无关的代码。这适用于jal
(jump-and-link = call
),使从 PIC 代码调用函数的效率降低:( Linux-MIPS 过去需要 PIC 二进制文件,但显然现在不是(但共享库仍然必须是 PIC)。
当 CPU 运行时eb fe
,它所要做的就是将位移添加到IP
而不是替换IP
。由于非跳转指令已经IP
通过添加指令长度进行更新,因此加法器硬件已经存在。
请注意,将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 之内)。
但是 for 并没有特别的缩写call
,因为它在大多数情况下用处不大。那么为什么它仍然需要相对位移而不是绝对位移呢?
x86 确实有绝对跳转,但仅适用于间接或远跳转。(到不同的代码段)。例如,EA
操作码是jmp ptr16:16
:“跳远,绝对,操作数中给出的地址”。
要进行绝对近跳,只需mov ax, target_label
/ jmp ax
。(或在 MASM 语法中,mov ax, OFFSET target_label
)。
相对位移与位置无关
对该问题的评论提出了这一点。
考虑一个机器代码块(已经组装),在块内有一些跳转。如果您将整个块复制到不同的起始地址(或更改CS
基地址,以便可以在段的不同偏移量处访问同一块),那么只有相对跳转会继续工作。
对于标签 + 绝对地址来解决同样的问题,必须使用不同的ORG
指令重新组装代码。显然,当您使用远 jmp 更改 CS 时,这不会立即发生!