我正在查看一些编译器输出,当调用一个函数时,它通常会开始设置调用堆栈,如下所示:
PUSH EBP
MOV EBP, ESP
PUSH EDI
PUSH ESI
PUSH EBX
Run Code Online (Sandbox Code Playgroud)
因此,我们将调用例程的基指针保存在堆栈中,向上移动我们自己的基指针,然后将几个寄存器的内容存储在堆栈中.然后在例程结束时将它们恢复为原始值,如下所示:
LEA ESP, [EBP-0Ch]
POP EBX
POP ESI
POP EDI
POP EBP
RET
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.但是,我注意到在一个例程中,设置调用堆栈的代码看起来有点不同.事实上,它看起来像这样:
IN AL, DX
PUSH EDI
PUSH ESI
PUSH EBX
Run Code Online (Sandbox Code Playgroud)
出于多种原因,这非常令人困惑.首先,方法结束代码与上面引用的另一种方法相同,特别是期望在堆栈上可以获得保存的EBP副本.
另一方面,如果我理解正确,该命令IN AL, DX
读入AL
寄存器,这与EAX
寄存器相同,并且因为它发生了,这里的下一个命令是
XOR EAX, EAX
Run Code Online (Sandbox Code Playgroud)
因为程序想要将它在堆栈上分配的一些东西归零.
问题:我想知道这里到底发生了什么,我不明白.被翻译的机器代码IN AL, DX
是单字节EC
,而指令对PUSH EBP MOV EBP,ESP将对应于三个字节55 88 EC
.反汇编程序是否以某种方式误读了这个?或者是依赖于我不理解的副作用的东西?
如果有人好奇,这个机器代码是由CLR的JIT编译器生成的,我正在用Visual Studio调试器查看它.这是C#中的最小再现:
class C {
string s = "";
public void f(string s) {
this.s = s;
}
}
Run Code Online (Sandbox Code Playgroud)
但请注意,这似乎是不确定的; 有时我似乎得到了IN AL, DX
版本,而其他时候有一个PUSH EBP
跟着一个MOV EBP, ESP
.
编辑:我开始强烈怀疑反汇编程序错误 - 我只是在另一种情况下显示IN AL, DX
(操作码EC
)和内存中的前两个字节55 88
.因此,反汇编程序可能只是对方法的切入点感到困惑.(虽然我仍然想知道为什么会发生这种情况!)
听起来像你在使用VS2015.你的结论是正确的,它的调试引擎有很多 bug.是的,地址不对.不是唯一的问题,它不能正确恢复断点,你很容易在代码中看到INT3指令.当抖动重新生成代码并替换存根调用时,它无法正确刷新反汇编.你不能相信你看到的任何东西.
我建议您使用工具>选项>调试>常规,并勾选"使用托管兼容模式"复选框.这迫使调试器使用较旧的调试引擎VS2010 vintage.它更加稳定.
您将丢失此引擎的某些功能,例如返回值检查和64位编辑+继续.进行这种调试时不会错过.然而,您将看到假代码地址,这在以前一直很常见,因此所有CALL地址都是错误的,您无法轻松识别到CLR的调用.来回翻转发动机是一种解决方法,但当然是一个很大的烦恼.
这也没有奏效,我看到更新中没有任何改进.但毫无疑问,它们有一个很大的错误列表可以解决,VS2015在它完成之前就已经发布了.希望VS2017更好,我们很快就会发现.