编译器在设置调用堆栈时生成意外的“IN AL,DX”(操作码“EC”)

Compiler generated unexpected `IN AL, DX` (opcode `EC`) while setting up call stack

我正在查看一些编译器输出,当函数被调用时,它通常会像这样开始设置调用堆栈:

PUSH EBP
MOV EBP, ESP
PUSH EDI
PUSH ESI
PUSH EBX

所以我们把调用例程的基指针保存在栈上,把自己的基指针上移,然后把几个寄存器的内容存入栈。然后在例程结束时将它们恢复为原始值,如下所示:

LEA ESP, [EBP-0Ch]
POP EBX
POP ESI
POP EDI
POP EBP
RET

到目前为止,还不错。但是,我注意到在一个例程中,设置调用堆栈的代码看起来有点不同。事实上,它看起来像这样:

IN AL, DX
PUSH EDI
PUSH ESI
PUSH EBX

由于多种原因,这很令人困惑。一方面,方法结束代码与上面引用的另一种方法的代码相同,尤其是似乎期望在堆栈上可以使用已保存的 EBP 副本。

另外,如果我理解正确,命令 IN AL, DX 读入 AL 寄存器,这与 EAX 寄存器相同,并且恰好在下一个这里的命令是

XOR EAX, EAX

因为程序想要将它在堆栈上分配的一些东西归零。

问题:我想知道这里到底发生了什么,我不明白。被翻译为 IN AL, DX 的机器码是单字节 EC,而指令对 推EBP MOV EBP,ESP 将对应三个字节55 88 EC。反汇编程序是否以某种方式误读了这个?还是依赖于我不明白的副作用?


如果有人好奇的话,这个机器代码是由 CLR 的 JIT 编译器生成的,我正在使用 Visual Studio 调试器查看它。这是 C# 中的最小复制:

class C {
  string s = "";
  public void f(string s) {
    this.s = s;
  }
}

但是,请注意,这似乎是不确定的;有时我似乎得到 IN AL, DX 版本,而其他时候有 PUSH EBP 后跟 MOV EBP, ESP.


编辑:我开始强烈怀疑反汇编程序错误——我刚遇到另一种情况,它显示 IN AL, DX(操作码 EC)并且内存中的前两个字节是55 88。所以反汇编程序可能只是对方法的入口点感到困惑。 (尽管我仍然想知道为什么会发生 !)

听起来你使用的是 VS2015。你的结论是正确的,它的调试引擎有很多个错误。是的,地址错误。这不是唯一的问题,它没有正确地恢复断点,而且您很容易在代码中看到 INT3 指令。当抖动有 re-generated 代码和替换存根调用时,它无法正确刷新反汇编。你不能相信你看到的任何东西。

我建议您使用“工具”>“选项”>“调试”>“常规”并勾选 "Use Managed Compatibility Mode" 复选框。这会强制调试器使用较旧的调试引擎 VS2010 vintage。稳定多了。

您将失去此引擎的一些功能,例如 return 值检查和 64 位编辑+继续。进行此类调试时不会遗漏。但是,您会看到伪造的代码地址,这在以前很常见,因此所有 CALL 地址都是错误的,您无法轻松识别对 CLR 的调用。翻转引擎 back-and-forth 是一种解决方法,但当然是一个很大的烦恼。

这也没有解决,我没有看到更新有任何改进。但毫无疑问,他们有一个很大的错误列表需要解决,VS2015 在完成之前就已经发布了。希望 VS2017 更好,我们很快就会知道。

正如 Hans 的回答,这是 Visual Studio 中的错误。
为了确认这一点,我使用 IDA 6.5 和 Visual Studio 2019 反汇编了一个二进制文件。这是屏幕截图:

Visual Studio 2019 在考虑 main.

的开始时错过了 2 个字节 (0x55 0x8B)

注意:Hans提到的'Use managed compatibility mode'在VS2019中没有解决这个问题。