问题
有哪些跨修改代码的想法可以在x86或x86-x64系统上触发意外行为,其中跨修改代码中所有内容都正确,除了在执行修改后的代码之前在执行处理器上执行序列化指令?
如下所述,我有一台Core 2 Duo E6600处理器可供测试,该处理器明确提到存在与此相关的问题。我将在此机器上测试任何与我分享的想法并提供更新。
背景
在x86和x64系统上,编写跨修改代码的官方指导是进行以下操作:
; Action of Modifying Processor
Store modified code (as data) into code segment;
Memory_Flag ← 1;
; Action of Executing Processor
WHILE (Memory_Flag ≠ 1)
Wait for code to update;
ELIHW;
Execute serializing instruction; (* For example, CPUID instruction *)
Begin executing modified code;
序列化指令在某些处理器的勘误表中被明确提及为必要操作。例如,英特尔 Core 2 Duo E6000 系列有以下勘误:(来自http://www.mathemainzel.info/files/intelX6800andintelE6000.pdf)
引起一个处理器或系统总线主机将数据写入第二个处理器当前正在执行的代码段,并希望第二个处理器将该数据作为代码执行的行为称为交叉修改代码(XMC)。不强制第二个处理器在执行新代码之前执行同步指令的 XMC 称为非同步 XMC。
使用非同步 XMC 来修改处理器的指令字节流的软件可能会看到执行修改后代码的处理器出现意外或不可预测的执行行为。
关于为什么如果不使用序列化指令就可能出现意外执行行为的原因,存在一些推测,详见http://linux.kernel.narkive.com/FDc9TB0d/patch-linux-kernel-markers。
当i-fetch完成并且micro-ops被存储在跟踪缓存中时,原始机器指令和微操作之间不再有直接的对应关系,这是由于优化所导致的。例如(为了说明人工构造的):mov eax, ebx;mov memory, eax;mov eax, 1。在跟踪缓存中,将不会有任何微操作来更新eax与ebx的值。在运行时将“mov eax, ebx”更改为“mov ecx, ebx”将实效优化的跟踪缓存,因此唯一的解决方案是一个GPF。如果修改不使跟踪缓存失效,则不会发生GPF。问题是:“我们能否预测跟踪缓存没有失效的情况”,一般而言答案是否定的,因为微体系结构不是公开的。但是,可以猜测使用中断指令int3修改单个字节的操作码不会导致无法处理的不一致性。这就是英特尔确认的情况,可以存储int3而无需同步(即强制刷新跟踪缓存)。
此外,https://sourceware.org/ml/systemtap/2005-q3/msg00208.html上还有更多信息:
当我们意识到这个问题时,我和英特尔微架构团队进行了长时间的讨论。事实证明,这个错误(顺便说一句,英特尔不打算修复)的原因是跟踪缓存 - 指令解释产生的微操作流 - 不能保证有效。在行文之间,我认为这个问题出现是因为跟踪缓存中进行了优化,不再可能识别原始指令边界。如果CPU发现由于未同步的交叉修改而使跟踪缓存无效,则指令执行将被中止并显示GPF。与英特尔的进一步讨论表明,用int3替换第一个操作码字节不会受到这个错误的影响。
除了我在互联网上发布的内容外,关于这个问题没有太多其他的信息。此外,在使用x86和x86-64系统上的交叉修改代码时,我没有找到任何公开的人们遇到指令执行失败的例子。
我有一台电脑,运行着Intel Core 2 Duo E6600处理器,这个处理器明确记录了存在这个问题,并且我还没有写出能够触发这个问题的代码。编写代码是我个人的好奇心。在生产代码中,我只会遵循规则,但我认为通过复现这个问题,可能还有些东西我需要学习。