Inv*_*ion 5 compiler-construction x86 assembly
在调试某些软件时,我注意到在很多情况下,子程序之间插入了INT3指令.
我假设这些技术上并没有在'之间'插入函数,而是在它们之后,为了暂停执行,如果子程序retn在最后没有执行,无论出于何种原因.
我的假设是否正确?如果不是,这些说明的目的是什么?
不正确的假设.
他们在功能之间填充,而不是之后.并且随机决定跳过指令的CPU被破坏并且应该被丢弃.
原因INT 3是双重的.它是一个单字节指令,这意味着即使只有一个字节的空间也可以使用它.绝大多数说明都不合适,因为它们太长了.此外,它是"调试中断"指令.这意味着调试器可以捕获在函数之间执行代码的尝试.这不是由忽略引起的retn,而是出于更简单的原因,例如使用未初始化的函数指针.
在Linux上,gcc和clang pad用0x90(NOP)来对齐函数.(当链接.o到不均匀尺寸的部分时,甚至连接器也会这样做).
通常没有任何特别的优点,除非CPU在函数结束时没有对RET指令进行分支预测.在这种情况下,NOP不会从发现正确分支目标时需要时间恢复的任何事情上启动CPU.
函数的最后一条指令可能不是RET; 它可能是间接JMP(例如通过函数指针进行尾调用).在这种情况下,分支预测更有可能失败.(CALL/RET对由返回栈特别预测.注意RET是伪装的间接JMP;它基本上是a jmp [rsp]和a add rsp, 8).
间接JMP或CALL的默认预测(当没有可用的分支目标缓冲区预测时)是跳转到下一条指令.(显然没有预测和停止,直到知道正确的目标是不是一个选项,或者默认预测对于跳转表是足够可用的.)
如果默认预测导致推测性地执行CPU不能轻易中止的事情,例如FP sqrt或可能是微编码的东西,则会增加分支误预测惩罚.更糟糕的是,如果推测执行的指令导致TLB未命中,触发硬件页面行走或以其他方式污染高速缓存.
像INT 3这样只生成异常的指令不能有任何这些问题.CPU不会尝试在它应该之前执行INT,因此不会发生任何坏事.IIRC,如果下一个指令的默认预测没用,建议在间接JMP之后放置类似的东西.
对于函数之间的随机垃圾,即使对包含RET的16B机器代码块进行预解码也会减慢速度.现代CPU以4个指令为一组并行解码,因此在下面的指令已经解码之后,它们才能检测到RET.(这与推测性执行不同).在无条件分支(如RET)之后的字节中避免慢速解码长度变化前缀是有用的,因为这会延迟分支的解码.
LCP停顿仅影响Intel CPU:AMD在其L1缓存中标记指令边界,并在较大的组中进行解码.(英特尔使用解码后的高速缓存来获得高吞吐量,而无需每次在循环中实际解码的电源成本.)
请注意,在Intel CPU中,指令长度查找发生在比实际解码更早的阶段.例如,Sandybridge前端看起来像这样:
(图片复制自David Kanter的Haswell撰写.虽然我与他的Sandybridge写作有关.他们都很优秀.)
另请参阅Agner Fog的microarch pdf,以及x86标签wiki 中的更多链接,了解我在此答案中描述的内容(以及更多内容).
| 归档时间: |
|
| 查看次数: |
870 次 |
| 最近记录: |