文章目录
线索栏
- 为什么指令 pushq %rsp和 popq %rsp的行为会产生歧义?具体是哪两个步骤的执行顺序导致了不确定性?
- 如何通过编写测试程序来确定x86-64处理器上这两条指令的实际行为?
- 对于 pushq %rsp,测试程序 pushtest返回0,这揭示了x86-64的哪种行为?(压入原始值还是更新后的值?)
- 对于 popq %rsp,测试程序 poptest返回0xabcd,这揭示了x86-64的哪种行为?(弹出值直接覆盖,还是先更新栈指针?)
- Intel x86体系结构的历史中,关于PUSH SP指令的行为在不同处理器型号(如8086 vs.IA-32)间有何不一致?这带来了哪两个主要缺点?
- 从x86的这段历史不一致中,我们可以得到关于处理器体系结构设计的什么重要教训?
笔记栏
1.指令歧义与实验验证
1)歧义分析
(1)pushq %rsp:指令需执行两个操作:
①%rsp <- %rsp - 8;
②将%rsp的原值写入新栈顶 M[%rsp]。歧义在于:写入内存的%rsp值是步骤1之前的"原始值",还是步骤1之后的"新值"?
(2)popq %rsp:指令需执行:
①从当前栈顶 M[%rsp]读取值 val;
②%rsp <- %rsp + 8。歧义在于:%rsp最终被设置为读取到的值val,还是%rsp+8?
2)实验确定(x86-64行为,Y86-64将遵循)
(1)测试1: pushq %rsp(pushtest):
c
pushtest:
movq %rsp, %rax # 1. 保存原始的%rsp值到%rax
pushq %rsp # 2. 执行有歧义的pushq %rsp
popq %rdx # 3. 将刚压入的值弹出到%rdx
subq %rdx, %rax # 4. 比较原始值(%rax)和弹出值(%rdx)
ret # 5. 返回差值
①结果:函数总是返回 0。这意味着 %rdx(弹出的值) 等于 %rax(原始的%rsp)。
②结论:在x86-64上,pushq %rsp压入的是%rsp的原始值(即在栈指针递减之前的值)。
(2)测试2: popq %rsp(poptest):
c
poptest:
movq %rsp, %rdi # 1. 保存原始栈指针
pushq $0xabcd # 2. 压入一个测试常数
popq %rsp # 3. 执行有歧义的popq %rsp
movq %rsp, %rax # 4. 将popq后的%rsp值作为返回值
movq %rdi, %rsp # 5. 恢复栈指针
ret
①结果:函数总是返回 0xabcd。这意味着 popq %rsp执行后,%rsp被直接设置为了从内存弹出的值 0xabcd。
②结论:在x86-64上,popq %rsp将%rsp设置为从内存中弹出的值(即,它用弹出的值直接覆盖%rsp,而不是先递增栈指针)。
(3)其他有相同行为的指令:popq %rsp的行为与 movq (%rsp), %rsp后接 addq $8, %rsp不同。它等价于 movq (%rsp), %rsp吗?不完全是,因为popq还隐含释放栈空间。可以说,popq %rsp的效果是用栈顶的值替换%rsp,并隐式地"丢弃"该栈槽。
2.历史教训与设计启示
(1)历史不一致性:Intel文档指出,对于PUSH栈指针指令,不同型号的x86处理器行为不同:
①Intel 8086处理器:PUSH SP将SP寄存器的新值(即减去2之后的值)压入栈中。
②从Intel 286开始的IA-32处理器:PUSH ESP将ESP寄存器的原始值(指令执行前的值)压入栈中。
(2)不一致性带来的缺点:
①降低代码可移植性:依赖此类指令细节的程序在不同型号处理器上可能有不同行为,即使这种情况罕见,潜在的不兼容性后果严重。
②增加文档复杂性:需要特别的说明来澄清这些差异,使得本就复杂的x86文档更加臃肿。
(3)设计启示:提前明确细节,在体系结构设计中保持完全的一致性,从长远看能避免大量麻烦。 这是优秀系统设计的重要原则。
总结栏
本节聚焦于两条特殊指令的微妙细节,并通过历史案例深刻揭示了处理器体系结构设计中"一致性"原则的重要性。
- 魔鬼在细节中:pushq %rsp和 popq%rsp的歧义源于指令的"复合"操作(修改指针并访问以其为地址的内存)。处理这种"自指"操作必须精确定义子步骤的顺序。x86-64的实际行为是:pushq%rsp压入旧值,popq %rsp用弹出值覆盖指针。
- 实验是检验真理的可靠方法:当文档晦涩或存在歧义时,编写简单的测试程序是探究硬件实际行为的有效手段。教材通过两个巧妙的测试,清晰地确定了x86-64的约定,Y86-64将遵循此约定以保证教学模型与真实世界的一致性。
- 历史是前车之鉴:Intel 8086与后续IA-32在PUSHSP行为上的不一致,是一个真实的历史教训。它表明,早期体系结构设计中未明确的细节,会成为后续兼容性的负担,损害可移植性并增加生态系统的复杂性。
- 一致性的价值:旁注的结论具有普适性。无论是在设计新的指令集(如Y86-64),还是在定义任何系统接口时,力求明确、一致、无歧义,能为整个生态(程序员、编译器作者、硬件实现者)节省巨大的长期成本。Y86-64明确此类细节,正是为了避免重蹈x86的覆辙。
最终启示:学习处理器体系结构,不仅要理解主流的数据通路和控制流,也要关注这些看似边缘的"角落案例"。它们往往最能考验一个设计的严谨性,也最能体现优秀工程实践与历史包袱之间的差别。理解这一点,对从事任何系统设计工作都大有裨益。