任何人都可以帮我解释WinDbg中这个简单的反汇编吗?

我得到了以下简单的C ++代码:

#include  int main(void) { ::printf("\nHello,debugger!\n"); } 

从WinDbg,我得到了以下反汇编代码:

 SimpleDemo!main: 01111380 55 push ebp 01111381 8bec mov ebp,esp 01111383 81ecc0000000 sub esp,0C0h 01111389 53 push ebx 0111138a 56 push esi 0111138b 57 push edi 0111138c 8dbd40ffffff lea edi,[ebp-0C0h] 01111392 b930000000 mov ecx,30h 01111397 b8cccccccc mov eax,0CCCCCCCCh 0111139c f3ab rep stos dword ptr es:[edi] 0111139e 8bf4 mov esi,esp 011113a0 683c571101 push offset SimpleDemo!`string' (0111573c) 011113a5 ff15b0821101 call dword ptr [SimpleDemo!_imp__printf (011182b0)] 011113ab 83c404 add esp,4 011113ae 3bf4 cmp esi,esp 011113b0 e877fdffff call SimpleDemo!ILT+295(__RTC_CheckEsp) (0111112c) 011113b5 33c0 xor eax,eax 011113b7 5f pop edi 011113b8 5e pop esi 011113b9 5b pop ebx 011113ba 81c4c0000000 add esp,0C0h 011113c0 3bec cmp ebp,esp 011113c2 e865fdffff call SimpleDemo!ILT+295(__RTC_CheckEsp) (0111112c) 011113c7 8be5 mov esp,ebp 011113c9 5d pop ebp 011113ca c3 ret 

我完全理解它有些困难。 什么是SimpleDemo!ILT在这里做的事情是什么?

011113c0比较ebp和esp的指令有什么意义

由于我在main()函数中没有任何局部变量,为什么在01111383的loacation中还有一个子esp, 0C0h

非常感谢。

更新1

虽然我仍然不知道ILT的含义,但__RTC_CheckESP用于运行时检查。 可以通过在main()函数之前放置以下pragma来消除这些代码。

 #pragma runtime_checks( "su", off ) 

参考:

http://msdn.microsoft.com/en-us/library/8wtf2dfz.aspx

http://msdn.microsoft.com/en-us/library/6kasb93x.aspx

更新2

sub esp,0C0h指令在堆栈上分配另一个0C0h字节的额外空间。 然后EAX填充0xCCCCCCCC,这是4个字节,因为ECX = 30h,4 * 30h = 0C0h,所以指令rep stos dword ptr es:[edi]用0xCC填充额外的空格。 但堆栈上的这个额外空间是什么? 这是安全带吗? 另外我注意到,如果我关闭运行时检查,如更新1所示,堆栈上仍有这样的额外空间,但要小得多。 这个空间没有填充0xCC。

没有运行时检查的汇编代码如下所示:

 SimpleDemo!main: 00231250 55 push ebp 00231251 8bec mov ebp,esp 00231253 83ec40 sub esp,40h <-- Still extra space allocated from stack, but smaller 00231256 53 push ebx 00231257 56 push esi 00231258 57 push edi 00231259 683c472300 push offset SimpleDemo!`string' (0023473c) 0023125e ff1538722300 call dword ptr [SimpleDemo!_imp__printf (00237238)] 00231264 83c404 add esp,4 00231267 33c0 xor eax,eax 00231269 5f pop edi 0023126a 5e pop esi 0023126b 5b pop ebx 0023126c 8be5 mov esp,ebp 0023126e 5d pop ebp 0023126f c3 ret 

我已经注释了汇编程序,希望这会对你有所帮助。 以’d’开头的行是调试代码行,以’r’开头的行是运行时检查代码行。 我还提出了我认为没有运行时检查版本和发布版本的调试。

  ; The ebp register is used to access local variables that are stored on the stack, ; this is known as a stack frame. Before we start doing anything, we need to save ; the stack frame of the calling function so it can be restored when we finish. push ebp ; These two instructions create our stack frame, in this case, 192 bytes ; This space, although not used in this case, is useful for edit-and-continue. If you ; break the program and add code which requires a local variable, the space is ; available for it. This is much simpler than trying to relocate stack variables, ; especially if you have pointers to stack variables. mov ebp,esp d sub esp,0C0h ; C/C++ functions shouldn't alter these three registers in this build configuration, ; so save them. These are stored below our stack frame (the stack moves down in memory) r push ebx r push esi r push edi ; This puts the address of the stack frame bottom (lowest address) into edi... d lea edi,[ebp-0C0h] ; ...and then fill the stack frame with the uninitialised data value (ecx = number of ; dwords, eax = value to store) d mov ecx,30h d mov eax,0CCCCCCCCh d rep stos dword ptr es:[edi] ; Stack checking code: the stack pointer is stored in esi r mov esi,esp ; This is the first parameter to printf. Parameters are pushed onto the stack ; in reverse order (ie last parameter pushed first) before calling the function. push offset SimpleDemo!`string' ; This is the call to printf. Note the call is indirect, the target address is ; specified in the memory address SimpleDemo!_imp__printf, which is filled in when ; the executable is loaded into RAM. call dword ptr [SimpleDemo!_imp__printf] ; In C/C++, the caller is responsible for removing the parameters. This is because ; the caller is the only code that knows how many parameters were put on the stack ; (thanks to the '...' parameter type) add esp,4 ; More stack checking code - this sets the zero flag if the stack pointer is pointing ; where we expect it to be pointing. r cmp esi,esp ; ILT - Import Lookup Table? This is a statically linked function which throws an ; exception/error if the zero flag is cleared (ie the stack pointer is pointing ; somewhere unexpected) r call SimpleDemo!ILT+295(__RTC_CheckEsp)) ; The return value is stored in eax by convention xor eax,eax ; Restore the values we shouldn't have altered r pop edi r pop esi r pop ebx ; Destroy the stack frame r add esp,0C0h ; More stack checking code - this sets the zero flag if the stack pointer is pointing ; where we expect it to be pointing. r cmp ebp,esp ; see above r call SimpleDemo!ILT+295(__RTC_CheckEsp) ; This is the usual way to destroy the stack frame, but here it's not really necessary ; since ebp==esp mov esp,ebp ; Restore the caller's stack frame pop ebp ; And exit ret ; Debug only, no runtime checks push ebp mov ebp,esp d sub esp,0C0h d lea edi,[ebp-0C0h] d mov ecx,30h d mov eax,0CCCCCCCCh d rep stos dword ptr es:[edi] push offset SimpleDemo!`string' call dword ptr [SimpleDemo!_imp__printf] add esp,4 xor eax,eax mov esp,ebp pop ebp ret ; Release mode (I'm assuming the optimiser is clever enough to drop the stack frame when there's no local variables) push offset SimpleDemo!`string' call dword ptr [SimpleDemo!_imp__printf] add esp,4 xor eax,eax ret 

第一,您的代码的main()forms不正确。 它不会返回你承诺会返回的int。 纠正这个缺陷,我们得到:

 #包括 
 int main(int argc,char * argv [])
 {
     ::输出( “\ nHello,调试器\ n!”);
    返回0;
 }

另外,在C ++程序中看到#include 是非常奇怪的。 我相信你想要#include

在所有情况下,必须在堆栈上为参数和返回值创建空间。 main()的返回值需要堆栈空间。 在调用printf()期间要保存的main()上下文需要堆栈空间。 printf()的参数需要堆栈空间。 printf()的返回值需要堆栈空间。 这就是0c0h字节堆栈帧正在做的事情。

首先发生的事情是传入的bas指针被复制到堆栈的顶部。 然后将新的堆栈指​​针复制到基指针中。 我们稍后会检查以确保堆栈从它开始的地方回来(因为你打开了运行时检查)。 然后我们构建(0C0h字节长)堆栈帧以在调用printf()期间保存我们的上下文和printf()的参数。 我们跳转到printf()。 当我们回来时,我们跳过你没有在你的代码中检查的返回值(它的框架上唯一的东西),并确保调用后的堆栈与调用之前的位置相同。 我们从堆栈中弹出我们的上下文。 然后我们检查最后的堆栈指针是否与我们在前面保存的值相匹配。 然后我们将基指针的先前值从堆栈顶部弹出并返回。

这是在使用运行时检查(/ RTC)构建时由编译器插入的代码。 禁用这些选项,它应该更清晰。 / GZ也可能导致这种情况,具体取决于您的VS版本。

对于任何被调用或随后调用的函数,40字节是最坏情况的堆栈分配。 这里有详细解释。

堆栈顶部保留的空间是什么? 首先,为任何局部变量创建空间。 在这种情况下,FunctionWith6Params()有两个。 但是,这两个局部变量仅占0x10字节。 与堆栈顶部创建的其余空间有什么关系?

在x64平台上,当代码准备堆栈以调用另一个函数时,它不会使用推送指令将参数放在堆栈上,这通常是x86代码中的情况。 相反,堆栈指针通常对于特定function保持固定。 编译器查看当前函数调用的代码中的所有函数,找到具有最大参数数量的函数,然后在堆栈上创建足够的空间以容纳这些参数。 在这个例子中,FunctionWith6Params()调用printf()传递8个参数。 由于这是具有最大参数数量的被调用函数,因此编译器在堆栈上创建8个插槽。 然后,堆栈上的前四个插槽将成为FunctionWith6Params()调用的任何函数使用的主空间。

为了记录,我怀疑ILT意味着“增量链接Thunk”。

增量链接(和编辑和继续)的工作方式如下:链接器通过在可执行文件开头分组的thunks为每个调用添加一个间接层,并在它们之后添加一个巨大的保留空间。 这样,当您重新链接更新的可执行文件时,它可以将任何新的/更改的代码放入保留区域并仅修补受影响的thunk,而不更改其余代码。