stdcall调用约定分析,以c语言为分析对象
来源:互联网 发布:隔离和防晒的顺序 知乎 编辑:程序博客网 时间:2024/05/14 02:57
stdcall调用约定分析,以c语言为分析对象
yckyck2001
stdcall调用约定分析,以c语言为分析对象(一)
-、编译平台
本文中C代码的编译平台为VC6.0,编译版本为Debug版本。
二、研究的C代码
int __stdcall stdcallFun(int Num1 , int Num2)
{
if (Num1 > Num2)
return Num1 ;
else
return Num2 ;
}
void main(int argc, char *argv[])
{
int x1 , x2 , nBigger;
x1 = 5 ;
x2 = 6 ;
nBigger = stdcallFun(x1,x2) ;
return ;
}
三、利用VC调试时,查看Debug版本的汇编代码(如看不懂该部分代码可直接跳到第四点的分析部分)
stdcallFun函数
stdcallFun:
004010A0 push ebp ;保存ebp,
004010A1 mov ebp,esp
004010A3 sub esp,40h ; stdcallFun分配临时变量
004010A6 push ebx
004010A7 push esi
004010A8 push edi ;保护现场
004010A9 lea edi,[ebp-40h]
004010AC mov ecx,10h
004010B1 mov eax,0CCCCCCCCh
004010B6 rep stos dword ptr [edi] ;初始化临时变量
004010B8 mov eax,dword ptr [ebp+8] ;mov eax, Num1
004010BB cmp eax,dword ptr [ebp+0Ch] cpm Num1,Num2
004010BE jle stdcallFun+25h (004010c5)
004010C0 mov eax,dword ptr [ebp+8]
004010C3 jmp stdcallFun+28h (004010c8)
004010C5 mov eax,dword ptr [ebp+0Ch]
004010C8 pop edi
004010C9 pop esi
004010CA pop ebx
004010CB mov esp,ebp
004010CD pop ebp
004010CE ret 8
main函数
main:
004010EC mov ecx,13h
004010F1 mov eax,0CCCCCCCCh
004010F6 rep stos dword ptr [edi]
004010F8 mov dword ptr [ebp-4],5 ;x1 = 5
004010FF mov dword ptr [ebp-8],6 ;x2 = 6
00401106 mov eax,dword ptr [ebp-8]
00401109 push eax ;变量x2的值入栈
0040110A mov ecx,dword ptr [ebp-4]
0040110D push ecx ;变更x1的值入栈
0040110E call @ILT+25(_stdcallFun@8) (0040101e)
00401113 mov dword ptr [ebp-0Ch],eax
00401116 pop edi
00401117 pop esi
00401118 pop ebx
00401119 add esp,4Ch
0040111C cmp ebp,esp
0040111E call __chkesp (00401280) ;调试代码
00401123 mov esp,ebp
00401125 pop ebp
00401126 ret
四、阶段分析
1.理解stdcall参数传递规则的关键是C语言的变量内存分配规则。对于全局变量和static变量分配的内存空间为全局内存空间;对于函数的局部变量执行函数时分配,退出执行函数收回分配空间。变量的内存空间为栈(stack)。而程序执行中程序员动态审请的内存则位于全局的堆(heap)中。所以我们以下分析函数调用传递规则时,将结合栈的变化图分析。
2.阶段分析
a.main函数局部变量分配初始化
void main()
{
int x1 , x2 , nBigger;
程序执行到此处
对应汇编代码为:
00401060 push ebp
00401061 mov ebp,esp
00401063 sub esp,4Ch ;main函数分配局部变量在stack里边分配
00401066 push ebx
00401067 push esi
00401068 push edi ;保护现场
00401069 lea edi,[ebp-4Ch]
0040106C mov ecx,13h
00401071 mov eax,0CCCCCCCCh
00401076 rep stos dword ptr [edi] ;用缺省值0CCCCCCCH初始化局部变量,
局部变量的分配是通过,改变栈指针ESP的值实现的,上述代码实现后,栈空间分配如下
……
ESP
0CCCCCCCH
……
0CCCCCCCH
nBigger = 0CCCCCCCH
x2 = 0CCCCCCCH
x1 = 0CCCCCCCH
EBP
……
13h个临时变量,占4Ch个字节空间
每个函数调用都有当时具体的EBP值,函数通过EBP和偏移值访问该函数的局部变量,如该main函数,通过当前main函数的EBP值, [ebp-4]访问main函数的局部变量x1,[ebp-8]访问main函数的局部变量x2, [ebp-0Ch]访问main函数的局部变量nBigger。为了便于描述,我们记现在main函数此时的ebp值为 Value_ebp_main。
stdcall调用约定分析,以c语言为分析对象(二)
b.代码继续执行,x1,x2赋值
x1 = 5 ;
x2 = 6 ;
对应汇编代码
004010F8 mov dword ptr [ebp-4],5 ;x1 = 5
004010FF mov dword ptr [ebp-8],6 ;x2 = 6
对应栈图
……
ESP
0CCCCCCCH
……
0CCCCCCCH
nBigger = 0CCCCCCCH
x2 = 6
x1 = 5
EBP
……
c.调用stdcallFun函数
nBigger = stdcallFun(x1,x2) ;
对应汇编代码
00401106 mov eax,dword ptr [ebp-8]
00401109 push eax ;变量x2的值入栈
0040110A mov ecx,dword ptr [ebp-4]
0040110D push ecx ;变更x1的值入栈
0040110E call @ILT+25(_stdcallFun@8) (0040101e)
对应栈空间中
…… ESP EIP Num1=5 Num2 =6 0CCCCCCCH …….. 0CCCCCCCH nBigger = 0CCCCCCCH x2 = 6 x1 = 5 EBP ……..
我们可以看到main函数中的局部变量的值,x1的值,x2的值通过栈传递给了函数stdcallFun,注意以上我们的用词,局部变量的值,也就是说stdcall参数传递规则下的C参数传递是值传送,也就是说我们调用stdcallFun(x1,x2),无论函数怎么操作也不会改变到main函数局部变量x1,x2的,调用stdcallFun(x1,x2)如同调用stdcallFun(5,6)。从以上栈变化图也可以看出stdcall的局部变量Num1,Num2通过main函数的push操作,内存空间得到审请,并赋值。也就是说以上栈图红色部分(Num1,Num2)的空间是属于子函数而不属于main函数的,到时要合理的释放。局部变量值的入栈顺序顺序为自右至左(先x2,后x1)。
d.我们继续单步进去执行stdcallFun函数
stdcallFun:
004010A0 push ebp ;
004010A1 mov ebp,esp
对应栈空间图:
……
EBP,ESP
Value_ebp_main
ECS:EIP
Num1=5
Num2 =6
0CCCCCCCH
……
0CCCCCCCH
nBigger = 0CCCCCCCH
x2 = 6
x1 = 5
……
我们以上讨论到函数通过EBP和偏移值访问该函数的局部变量,每个函数执行时都有其特定的EBP值,进入stdcallFun后,我们要更改EBP值使其更方便的操作stdcallFun的局部变量,退出stdcallFun函数调用时,我们要恢复调用函数main的EBP值,所以改变stdcallFun的EBP值前必须把main函数的EBP值Value_ebp_main入栈,然后把当前的ESP值赋于EBP。
e.stdcallFun分配并初始化局部变量空间和保护现场
004010A3 sub esp,40h ; stdcallFun分配临时变量
004010A6 push ebx
004010A7 push esi
004010A8 push edi ;保护现场
004010A9 lea edi,[ebp-40h]
004010AC mov ecx,10h
004010B1 mov eax,0CCCCCCCCh
004010B6 rep stos dword ptr [edi] ;初始化临时变量
对应栈空间图
……
ESP
ESI
EBI
EBX
0CCCCCCCH
……
0CCCCCCCH
EBP
Value_ebp_main
ECS:EIP
Num1=5
Num2 =6
0CCCCCCCH
……
0CCCCCCCH
nBigger = 0CCCCCCCH
x2 = 6
x1 = 5
……
10h个临时变量,占40h个字节空间
stdcall调用约定分析,以c语言为分析对象(三)
f.执行stdcallFun函数里边的代码
if (Num1 > Num2)
return Num1 ;
else
return Num2 ;
对应汇编代码
004010B8 mov eax,dword ptr [ebp+8] ;mov eax, Num1
004010BB cmp eax,dword ptr [ebp+0Ch] ;cmp Num1,Num2
004010BE jle stdcallFun+25h (004010c5) ;if Num1<=Num2
004010C0 mov eax,dword ptr [ebp+8] ;返回值Num1保存在eax中
004010C3 jmp stdcallFun+28h (004010c8)
004010C5 mov eax,dword ptr [ebp+0Ch] ;返回值Num2保存在eax中
我们可以看到stdcallFun中,对于传进来的参数Num1,Num2的访问,通过当前EBP的值(属于stdcallFun函数)和正偏移来访问,而在函数内部定义的局部变量则是通过函数执行时当前的EBP值和负偏移来访问的,如main函数的对其x1,x2,nBigger 局部变量的访问。而stdcallFun的返回值存储在eax中。
g.stdcallFun恢复现场,释放局部变量空间
004010C8 pop edi
004010C9 pop esi
004010CA pop ebx
004010CB mov esp,ebp
004010CD pop ebp
以上代码中mov esp,ebp ,执行后,esp值加40H,也就是说在stdcallFun内部审请的10c个临时局部变量40H个字节的空间释放。pop ebp,恢复调用函数main执行时的ebp值。此时栈空间图如下:
……
Value_ebp_main
ESP
ECS:EIP
Num1=5
Num2 =6
0CCCCCCCH
……
0CCCCCCCH
nBigger = 0CCCCCCCH
x2 = 6
x1 = 5
EBP
……
以上为无效值
紧接着stdcallFun返回调用者main,
004010CE ret 8
我们注意到 ret 后面还带了一个8,该语句不单单把cs,ip指针出栈,而且还把当初main函数(调用函数)传递参数值进来审请的空间释放了。此时栈空间图如下
……
Value_ebp_main
ECS:EIP
Num1=5
Num2 =6
ESP
0CCCCCCCH
……..
0CCCCCCCH
nBigger = 0CCCCCCCH
x2 = 6
x1 = 5
EBP
……..
以上为无效值
h.返回main函数继续执行
00401113 mov dword ptr [ebp-0Ch],eax ;返回值赋于nBigger
00401116 pop edi
00401117 pop esi
00401118 pop ebx
00401119 add esp,4Ch
0040111C cmp ebp,esp
0040111E call __chkesp (00401280) ;调试代码
00401123 mov esp,ebp
00401125 pop ebp
00401126 ret
3.阶段分析总结
由以上分析可知,stdcall参数传递约定,传进调用函数的参数空间由主调函数push操作的方式审请,并同时传进了参数值,传参数时遵循从右到左的的传递参数的顺序。被调用函数负责释放调用函数审请的空间,在返回调用处时释放。由于在返回调用时释放的汇编代码对于具体函数来说是特定的,(如本例中,由于stdcallFun有两个int 型的参数,故其返回代码为ret 8),所以stdcall参数传递约定不能用来实现参数个数可变的函数,如printf等函数。
stdcall参数传递约定,规定被调用函数的返回值存取在eax中。
五、其他
我们可以看到main函数中有两个参数传进,但main函数返回调用处的汇编代码却是
ret ,由以上的分析可知main函数不是stdcall参数传递规则的函数。如果我们把main函数写成:
void __stdcall main(int argc, char *argv[])
{
int x1 , x2 , nBigger;
x1 = 5 ;
x2 = 6 ;
nBigger = stdcallFun(x1,x2) ;
return ;
}
发现编译可通过,链接时提示
LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
事实上main函数的参数传递规则是__cdecl,VC6.0创建工程的缺省参数传递规则为__cdecl,
LIBCD.lib是已经编译好的库,它是按调用__cdecl参数方式的main函数来编译。而__cdecl和__stdcall函数名编译解析的名字是不同的
__stdcall:
An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list. Therefore, the function declared as int func( int a, double b ) is decorated as follows: _func@12
__cdecl:
Underscore character (_) is prefixed to names
所以以上才会出现提示找不到_main函数的错误,void __stdcall main(int argc, char *argv[])
解析为_main@8,而void __cdecl main(int argc, char *argv[])解析为_main,LIBCD.lib调用的是_main。
- stdcall调用约定分析,以c语言为分析对象
- C语言函数调用约定-stdcall&cdecl&thiscall
- stdcall调用分析
- stdcall调用约定
- C语言的函数调用约定(stdcall+cdecl+thiscall+fastcall)
- 函数调用约定 stdcall, cdecl
- 调用约定:cdecl,stdcall,fastcall
- 关于STDCALL、CDECL、PASCAL调用分析
- C语言函数调用约定
- C语言函数调用约定
- C语言函数调用约定
- C语言函数调用约定
- C语言函数调用约定
- C语言函数调用约定
- C语言函数调用约定
- C语言函数调用约定
- C语言函数调用约定
- C语言函数调用约定
- 定义和使用全局变量
- ASCII 完整码表及简介
- 使用mshflexgrid控件
- 回归花剑
- sql2000挂起
- stdcall调用约定分析,以c语言为分析对象
- Apache APR可移植运行库简介(3)
- 中国硕士美女嫁到日本后的悲惨经历
- 上帝掷骰子吗
- 怎样才能静下心来学习--迷茫...
- 微软搜狐面试总结
- 程序开发需要多种技能
- 大头针的减肥行
- windows 2003 COM+组件使用Delphi实现