函数声明后面加个stdcall是什么意思

来源:互联网 发布:善领dsa最新数据 编辑:程序博客网 时间:2024/06/02 10:28

首先先要清楚汇编中堆栈的原理 才能了解 stdcall的约束
    
这里 是我通过ollydbg汇编代码调试去理解

比如 

我们看到这个方法的调用位置为 00401378 找到这个地址的调用代码为

00401378  |.  E8 BD000000   call 0040143A (这里等价于 call MessageBoxA)

进入 0040143A

0040143A   $- FF25 AC314000      jmp 77D507EA 

我们看到 无条件跳转到 77D507EA  看这个位置的代码
我们看到进入了user32模块代码如下

77D507EA >    8BFF               mov edi,edi
77D507EC  /.  55                 push ebp
77D507ED  |.  8BEC               mov ebp,esp
77D507EF  |.  833D BC14D777 00   cmp dword ptr ds:[0x77D714BC],0x0
77D507F6  |.  74 24              je XUSER32.77D5081C
77D507F8  |.  64:A1 18000000     mov eax,dword ptr fs:[0x18]
77D507FE  |.  6A 00              push 0x0
77D50800  |.  FF70 24            push dword ptr ds:[eax+0x24]
77D50803  |.  68 241BD777        push USER32.77D71B24
77D50808  |.  FF15 C412D177      call dword ptr ds:[<&KERNEL32.Interlocke>;  kernel32.InterlockedCompareExchange
77D5080E  |.  85C0               test eax,eax
77D50810  |.  75 0A              jnz XUSER32.77D5081C
77D50812  |.  C705 201BD777 0100>mov dword ptr ds:[0x77D71B20],0x1
77D5081C  |>  6A 00              push 0x0                                 ; /LanguageID = 0 (LANG_NEUTRAL)
77D5081E  |.  FF75 14            push [arg.4]                             ; |Style
77D50821  |.  FF75 10            push [arg.3]                             ; |Title
77D50824  |.  FF75 0C            push [arg.2]                             ; |Text
77D50827  |.  FF75 08            push [arg.1]                             ; |hOwner
77D5082A  |.  E8 2D000000        call USER32.MessageBoxExA                ; \MessageBoxExA
77D5082F  |.  5D                 pop ebp
77D50830  \.  C2 1000            retn 0x10

在 77D507EA 断点后

我们调用了MessageBoxA方法 看到堆栈中 信息为 

0012FE8C   0040137D  /CALL 到 MessageBoxA 来自 CRACKME.00401378
0012FE90   000206F6  |hOwner = 000206F6 ('CrackMe v1.0',class='No need to disasm the code!')
0012FE94   00402169  |Text = "No luck there, mate!"
0012FE98   00402160  |Title = "No luck!"
0012FE9C   00000030  \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL

第一列是堆栈的地址 也就是 esp的地址

说明从堆栈地址 0012FE9C   到0012FE8C   压入了 5个4字节的数据 通过右边可以看出 这些数据中4个4字节是MessageBoxA 的四个参数

最上面的1个4字节压入的 子程序返回 要执行的代码的地址

第二列存放的地址信息 实际是ds的地址 通过这个地址 找到找到的就是找到的数据 

比如00402160  到00402169的9个字节  存放的是No luck!的2进制数据 可以看到  这里可以看到No luck!为8个字节 其实还有最后一字节 为‘\0’表示字符串的结束

我们知道 call了某个地址 需要通过 retn指令结束子程序的运行  retn指令将esp对应的地址 pop出来 从而获得 call完成后的下一个运行代码的地址

我们看到 0012FE8C   0040137D  /CALL 到 MessageBoxA 来自 CRACKME.00401378 

说明 retn之后 下一个执行的代码是 0040137D  这个地址

但是因为 堆栈中 pop了 

0012FE90   000206F6  |hOwner = 000206F6 ('CrackMe v1.0',class='No need to disasm the code!')
0012FE94   00402169  |Text = "No luck there, mate!"
0012FE98   00402160  |Title = "No luck!"
0012FE9C   00000030  \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL

这从0012FE9C到0012FE90这 16个字节的数据 都是子程序使用的 所有在retn的同时还要讲该数据清除掉 

所以在retn的时候 会pop掉0012FE8C   0040137D  /CALL 到 MessageBoxA 来自 CRACKME.00401378  这里

使esp的地址指向0012FE90   也就是0012FE8C+4 

我们看到

77D5082F  |.  5D                 pop ebp
77D50830  \.  C2 1000            retn 0x10

最后一句是retn 0x10 也就是 10是 10进制的16 也是 retn 16个字节 

除了 retn的4个字节 还要加上后面参数的 16个字节 所以 我们可以看到

retn 0x10 后  esp指向了(0012FE9C   00000030  \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL)0012FE9C   的后一个地址

也就是

0012FEA0   0040124A  返回到 CRACKME.0040124A 来自 CRACKME.00401362


这就是参数入栈的原理

__stdcall----参数从右向左入栈

我们看到

0012FE8C   0040137D  /CALL 到 MessageBoxA 来自 CRACKME.00401378
0012FE90   000206F6  |hOwner = 000206F6 ('CrackMe v1.0',class='No need to disasm the code!')
0012FE94   00402169  |Text = "No luck there, mate!"
0012FE98   00402160  |Title = "No luck!"
0012FE9C   00000030  \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL

MessageBox的四个参数

int WINAPI MessageBox(  _In_opt_  HWND hWnd,  _In_opt_  LPCTSTR lpText,  _In_opt_  LPCTSTR lpCaption,  _In_      UINT uType);
我们看到栈顶是地址0012FE8C 栈顶也就是最后压入的参数 最先压入的参数也就是栈底的是   
0012FE9C   00000030  \Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL 
也就是最先压入的是UINT uType 说明是从右到左压入参数 是stdcall约定
这里是其他blog转载来的 只是说明 除了stdcall还有其他约定
 在Win32汇编中,我们经常要和Api打交道,另外也会常常使用自己编制的类似于Api 的带参数的子程序,本文要讲述的是在子程序调用的过程中进行参数传递的概念和分析。一般在程序中,参数的传递是通过堆栈进行的,也就是说,调用者把要传递给子程序(或者被调用者)的参数压入堆栈,子程序在堆栈取出相应的值再使用,比如说,如果你要调用 SubRouting(Var1,Var2,Var3),编译后的最终代码可是      
push Var3 
push Var2 
push Var1 
call SubRouting 
add esp,12 

      也就是说,调用者首先把参数压入堆栈,然后调用子程序,在完成后,由于堆栈中先前压入的数不再有用,调用者或者被调用者必须有一方把堆栈指针修正到调用前的状态。参数是最右边的先入堆栈还是最左边的先入堆栈、还有由调用者还是被调用者来修正堆栈都必须有个约定,不然就会产生不正确的结果,而Stdcall就是参数从右到左压入栈,由被调用的子程序来修正堆栈的指针。      __cdecl----参数从右向左入栈,调用者清除栈
     __stdcall----参数从右向左入栈,被调用者清除栈       函数func(int   a, int b) 
      补充一点: 
__cdecl函数被编译成:_func 
__stdcall函数被编译成:_func@8 (8 为参数的字节数)  Directive Parameter order Clean-up Passes parameters in registers?
register Left-to-right Routine Yes 
pascal Left-to-right Routine No 
cdecl Right-to-left Caller No 
stdcall Right-to-left Routine No 

safecall Right-to-left Routine No

VC里面:PASCAL==CALLBACK==WINAPI==__stdcall        _stdcall是Pascal程序的缺省调用方式,通常用于Win32  Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。    _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。         关于PASCAL,其实你只要弄明白一点就行了:声明为这种调用约定的函数都是由它本身来清栈,而__cdecl的函数都是由调用者来清栈。  


原创粉丝点击