编译器在设置调用堆栈时生成意外的“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中没有解决这个问题。
我正在查看一些编译器输出,当函数被调用时,它通常会像这样开始设置调用堆栈:
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
.
0x55 0x8B
)
注意:Hans提到的'Use managed compatibility mode'在VS2019中没有解决这个问题。