我正在学习8086,有一个特别的问题困扰着我,我还没有找到任何满意的答案。
我知道CPU会按顺序执行代码,如果想要更改代码流,我们希望IP指向我们感兴趣的代码所在的新/旧地址。
现在,我的问题是为什么我们(我的意思是CPU)在遇到跳转指令时不要用标签对应的地址更新IP?
当我们遇到跳转指令时,需要有一个添加到IP的位移是什么?
在我看来
对我而言,这听起来更像是工作,只需使用与标签对应的地址更新IP。但是,我确信必须有一个理由来完成事情,只是我不知道。
8086中这种设计选择的原因是什么?
答案 0 :(得分:3)
你过分估计解码相对跳跃的CPU复杂性成本。
- 计算位移(即跳转后从跳转标签到下一条指令的距离)
- 然后采取这种位移2的赞美,
醇>
机器代码必须包含步骤2的结果(有符号整数相对位移),因此所有这些都是在汇编时完成的。在汇编程序中,减去两个整数地址已经为您提供了所需的带符号2的补码位移。
使用相对位移有很大的优势,因此使ISA更简单以简化编写汇编程序是没有任何意义的。您只需编写一次汇编程序,但在机器上运行的所有都可以从更紧凑的代码和位置独立性中受益。
相对分支位移是完全正常的,并且在大多数其他体系结构中也使用(例如ARM:https://community.arm.com/processors/b/blog/posts/branch-and-call-sequences-explained,其中固定宽度指令使得直接绝对分支编码无论如何都是不可能的)。 这将使8086成为奇怪的而不是使用相对分支编码。
更新:也许不完全是奇怪的。 MIPS将rel16 << 2
用于beq
/ bne
(MIPS指令固定为32位宽且始终对齐)。但对于无条件j
(跳转)指令,它interestingly it uses a pseudo-direct encoding。它保持PC的高4位,并直接用指令中编码的值替换PC[27:2]
位。 (同样,程序计数器的低2位总是0
。)因此,在相同的1/16地址空间内,j
指令是直接跳转,并且不提供与位置无关的代码。这适用于jal
(jump-and-link = call
),making function calls from PIC code less efficient :( Linux-MIPS用于要求PIC二进制文件,but apparently now it doesn't(但共享库仍需要是PIC)。
当CPU运行eb fe
时,它所要做的就是将位移添加到IP
,而不是替换IP
。由于非跳转指令已经通过添加指令长度来更新IP
,因此加法器硬件已经存在。
请注意,sign-extending 8位到16位(或32位或64位)的位移在硬件中是微不足道的:2的补码符号扩展只是复制符号位,不需要任何逻辑门,只是连接一点到其余的电线。 (例如0xfe
变为0xfffe
,而0x05
变为0x0005
。)
例如,存在两种形式的相对jmp
,一种具有rel8(短),另一种具有rel16(近)。 (在后来的CPU中引入的32位和64位模式中,E9
操作码是jmp rel32
而不是rel16
,但EB
仍然是jmp rel8
,因为跳转函数内通常在-128 / + 127之内。
但call
并没有特别的缩写,因为它在大多数情况下都没用多少。那么为什么它仍然困扰相对位移而不是绝对?
x86确实有绝对跳跃,但仅适用于间接或远跳转。 (到不同的代码段)。例如,EA
操作码为jmp ptr16:16
: "Jump far, absolute, address given in operand".
要进行绝对近距离跳跃,只需mov ax, target_label
/ jmp ax
。 (或者使用MASM语法mov ax, OFFSET target_label
)。
相对位移与位置无关
对这个问题的评论提出了这个问题。
考虑一个机器代码块(已经组装好),在块内部有一些跳转。如果将整个块复制到不同的起始地址(或更改CS
基址,以便可以在与段不同的偏移处访问相同的块),则只有相对跳转才能继续工作。
要使labels + absolute addresses解决同样的问题,必须使用不同的ORG
指令重新组装代码。显然,当你用远jmp更改CS时,不会发生这种情况!