- 浏览: 25554 次
最新评论
转】函数调用 堆栈
#include <stdio.h>
long test(int a,int b)
{
a = a + 3;
b = b + 5;
return a + b;
}
int main(int argc, char* argv[])
{
printf("%d",test(10,90));
return 0;
}
先来看一个概貌
16: int main(int argc, char* argv[])
17: {
00401070 push ebp
00401071 mov ebp,esp
00401073 sub esp,40h
00401076 push ebx
00401077 push esi
00401078 push edi
00401079 lea edi,[ebp-40h]
0040107C mov ecx,10h
00401081 mov eax,0CCCCCCCCh
00401086 rep stos dword ptr [edi]
18: printf("%d",test(10,90));
00401088 push 5Ah
0040108A push 0Ah
0040108C call @ILT+0(test) (00401005)
00401091 add esp,8
00401094 push eax
00401095 push offset string "%d" (0042201c)
0040109A call printf (004010d0)
0040109F add esp,8
19: return 0;
004010A2 xor eax,eax
20: }
下面来解释一下
开始进入Main函数 esp=0x12FF84 ebp=0x12FFC0
完成椭圆形框起来的部分
00401070 push ebp ebp的值入栈,保存现场(调用现场,从test函数看,如红线所示,即保存的0x12FF80用于从test函数堆栈返回到main函数)
00401071 mov ebp,esp 此时ebp=0x12FF80 此时ebp就是“当前函数堆栈”的基址 以便访问堆栈中的信息;还有就是从当前函数栈顶返回到栈底
00401073 sub esp,40h
函数使用的堆栈,默认64个字节,堆栈上就是16个横条(密集线部分)此时esp=0x12FF40
在上图中,上面密集线是test函数堆栈空间,下面是Main的堆栈空间 (补充,其实这个就叫做 Stack Frame)
00401076 push ebx
00401077 push esi
00401078 push edi 入栈
00401079 lea edi,[ebp-40h]
0040107C mov ecx,10h
00401081 mov eax,0CCCCCCCCh
00401086 rep stos dword ptr [edi]
初始化用于该函数的栈空间为0XCCCCCCCC 即从0x12FF40~0x12FF80所有的值均为0xCCCCCCCC
18: printf("%d",test(10,90));
00401088 push 5Ah 参数入栈 从右至左 先90 后10
0040108A push 0Ah
0040108C call @ILT+0(test) (00401005)
函数调用,转向eip 00401005
注意,此时仍入栈,入栈的是call test 指令下一条指令的地址00401091 下一条指令是add esp,8
@ILT+0(?test@@YAJHH@Z):
00401005 jmp test (00401020)
即转向被调函数test
另
1. 如果函数调用方式是__stdcall 不同之处在于
main函数call 后面没有了 add esp, 8
test函数最后一句 是 ret 8 (由test函数清栈, ret 8意思是执行ret后,esp+8)
2. 运行过程中0x12FF28 保存了指令地址 00401091是怎么保存的?
栈每个空间保存4个字节(粒度4字节) 例如下一个栈空间0x12FF2C保存参数10
因此
0x12FF28 0x12FF29 0x12FF2A 0x12FF2B
91 10 40 00
little-endian 认为其读的第一个字节为最小的那位上的数
3. char a[] = "abcde"
对局部字符数组变量(栈变量)赋值,是利用寄存器从全局数据内存区把字符串“abcde”拷贝到栈内存中的
4. int szNum[5] = { 1, 2, 3, 4, 5 }; 栈中是如何分布的?
00401798 mov dword ptr [ebp-14h],1
0040179F mov dword ptr [ebp-10h],2
004017A6 mov dword ptr [ebp-0Ch],3
004017AD mov dword ptr [ebp-8],4
004017B4 mov dword ptr [ebp-4],5
可以看出来 是从右边开始入栈,所以是 5 4 3 2 1 入栈
int *ptrA = (int*)(&szNum+1);
int *ptrB = (int*)((int)szNum + 1);
std::cout<< ptrA[-1] << *ptrB << std::endl;
结果如何?
28: int *ptrA = (int*)(&szNum+1);
004017BB lea eax,[ebp]
004017BE mov dword ptr [ebp-18h],eax
&szNum是指向数组指针;加1是加一个数组宽度;&szNum+1指向移动5个int单位之后的那个地方, 就是把EBP的地址赋给指针
ptrA[-1]是回退一个int*宽度,即ebp-4
29: int *ptrB = (int*)((int)szNum + 1);
004017C1 lea ecx,[ebp-13h]
004017C4 mov dword ptr [ebp-1Ch],ecx
如果上面是指针算术,那这里就是地址算术,只是首地址+1个字节的offset,即ebp-13h给指针
实际保存是这样的
01 00 00 00 02 00 00 00
ebp-14h ebp-13h ebp-10h
注意是int*类型的,最后获得的是 00 00 00 02
由于Little-endian, 实际上逻辑数是02000000 转换为十进制数就为33554432
最后输出533554432
8: long test(int a,int b)
9: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,40h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-40h]
0040102C mov ecx,10h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi] //这些和上面一样
10: a = a + 3;
00401038 mov eax,dword ptr [ebp+8] //ebp=0x12FF24 加8 [0x12FF30]即取到了参数10
0040103B add eax,3
0040103E mov dword ptr [ebp+8],eax
11: b = b + 5;
00401041 mov ecx,dword ptr [ebp+0Ch]
00401044 add ecx,5
00401047 mov dword ptr [ebp+0Ch],ecx
12: return a + b;
0040104A mov eax,dword ptr [ebp+8]
0040104D add eax,dword ptr [ebp+0Ch] //最后的结果保存在eax, 结果得以返回
13: }
00401050 pop edi
00401051 pop esi
00401052 pop ebx
00401053 mov esp,ebp //esp指向0x12FF24, test函数的堆栈空间被放弃,从当前函数栈顶返回到栈底
00401055 pop ebp //此时ebp=0x12FF80, 恢复现场 esp=0x12FF28
00401056 ret ret负责栈顶0x12FF28之值00401091弹出到指令寄存器中,esp=0x12FF30
因为win32汇编一般用eax返回结果 所以如果最终结果不是在eax里面的话 还要把它放到eax
注意,从被调函数返回时,是弹出EBP,恢复堆栈到函数调用前的地址,弹出返回地址到EIP以继续执行程序。
从test函数返回,执行
00401091 add esp,8
清栈,清除两个压栈的参数10 90 调用者main负责
(所谓__cdecl调用由调用者负责恢复栈,调用者负责清理的只是入栈的参数,test函数自己的堆栈空间自己返回时自己已经清除,靠!一直理解错)
00401094 push eax 入栈,计算结果108入栈,即printf函数的参数之一入栈
00401095 push offset string "%d" (0042201c) 入栈,参数 "%d" 当然其实是%d的地址
0040109A call printf (004010d0) 函数调用 printf("%d",108) 因为printf函数时
0040109F add esp,8 清栈,清除参数 ("%d", 108)
19: return 0;
004010A2 xor eax,eax eax清零
20: }
main函数执行完毕 此时esp=0x12FF34 ebp=0x12FF80
004010A4 pop edi
004010A5 pop esi
004010A6 pop ebx
004010A7 add esp,40h //为啥不用mov esp, ebp? 是为了下面的比较
004010AA cmp ebp,esp //比较,若不同则调用chkesp抛出异常
004010AC call __chkesp (00401150)
004010B1 mov esp,ebp
004010B3 pop ebp //ESP=0X12FF84 EBP=0x12FFC0 尘归尘 土归土 一切都恢复最初的平静了
004010B4 ret
原文地址:http://www.cnblogs.com/dylanwind/archive/2008/12/08/1349822.html
#include <stdio.h>
long test(int a,int b)
{
a = a + 3;
b = b + 5;
return a + b;
}
int main(int argc, char* argv[])
{
printf("%d",test(10,90));
return 0;
}
先来看一个概貌
16: int main(int argc, char* argv[])
17: {
00401070 push ebp
00401071 mov ebp,esp
00401073 sub esp,40h
00401076 push ebx
00401077 push esi
00401078 push edi
00401079 lea edi,[ebp-40h]
0040107C mov ecx,10h
00401081 mov eax,0CCCCCCCCh
00401086 rep stos dword ptr [edi]
18: printf("%d",test(10,90));
00401088 push 5Ah
0040108A push 0Ah
0040108C call @ILT+0(test) (00401005)
00401091 add esp,8
00401094 push eax
00401095 push offset string "%d" (0042201c)
0040109A call printf (004010d0)
0040109F add esp,8
19: return 0;
004010A2 xor eax,eax
20: }
下面来解释一下
开始进入Main函数 esp=0x12FF84 ebp=0x12FFC0
完成椭圆形框起来的部分
00401070 push ebp ebp的值入栈,保存现场(调用现场,从test函数看,如红线所示,即保存的0x12FF80用于从test函数堆栈返回到main函数)
00401071 mov ebp,esp 此时ebp=0x12FF80 此时ebp就是“当前函数堆栈”的基址 以便访问堆栈中的信息;还有就是从当前函数栈顶返回到栈底
00401073 sub esp,40h
函数使用的堆栈,默认64个字节,堆栈上就是16个横条(密集线部分)此时esp=0x12FF40
在上图中,上面密集线是test函数堆栈空间,下面是Main的堆栈空间 (补充,其实这个就叫做 Stack Frame)
00401076 push ebx
00401077 push esi
00401078 push edi 入栈
00401079 lea edi,[ebp-40h]
0040107C mov ecx,10h
00401081 mov eax,0CCCCCCCCh
00401086 rep stos dword ptr [edi]
初始化用于该函数的栈空间为0XCCCCCCCC 即从0x12FF40~0x12FF80所有的值均为0xCCCCCCCC
18: printf("%d",test(10,90));
00401088 push 5Ah 参数入栈 从右至左 先90 后10
0040108A push 0Ah
0040108C call @ILT+0(test) (00401005)
函数调用,转向eip 00401005
注意,此时仍入栈,入栈的是call test 指令下一条指令的地址00401091 下一条指令是add esp,8
@ILT+0(?test@@YAJHH@Z):
00401005 jmp test (00401020)
即转向被调函数test
另
1. 如果函数调用方式是__stdcall 不同之处在于
main函数call 后面没有了 add esp, 8
test函数最后一句 是 ret 8 (由test函数清栈, ret 8意思是执行ret后,esp+8)
2. 运行过程中0x12FF28 保存了指令地址 00401091是怎么保存的?
栈每个空间保存4个字节(粒度4字节) 例如下一个栈空间0x12FF2C保存参数10
因此
0x12FF28 0x12FF29 0x12FF2A 0x12FF2B
91 10 40 00
little-endian 认为其读的第一个字节为最小的那位上的数
3. char a[] = "abcde"
对局部字符数组变量(栈变量)赋值,是利用寄存器从全局数据内存区把字符串“abcde”拷贝到栈内存中的
4. int szNum[5] = { 1, 2, 3, 4, 5 }; 栈中是如何分布的?
00401798 mov dword ptr [ebp-14h],1
0040179F mov dword ptr [ebp-10h],2
004017A6 mov dword ptr [ebp-0Ch],3
004017AD mov dword ptr [ebp-8],4
004017B4 mov dword ptr [ebp-4],5
可以看出来 是从右边开始入栈,所以是 5 4 3 2 1 入栈
int *ptrA = (int*)(&szNum+1);
int *ptrB = (int*)((int)szNum + 1);
std::cout<< ptrA[-1] << *ptrB << std::endl;
结果如何?
28: int *ptrA = (int*)(&szNum+1);
004017BB lea eax,[ebp]
004017BE mov dword ptr [ebp-18h],eax
&szNum是指向数组指针;加1是加一个数组宽度;&szNum+1指向移动5个int单位之后的那个地方, 就是把EBP的地址赋给指针
ptrA[-1]是回退一个int*宽度,即ebp-4
29: int *ptrB = (int*)((int)szNum + 1);
004017C1 lea ecx,[ebp-13h]
004017C4 mov dword ptr [ebp-1Ch],ecx
如果上面是指针算术,那这里就是地址算术,只是首地址+1个字节的offset,即ebp-13h给指针
实际保存是这样的
01 00 00 00 02 00 00 00
ebp-14h ebp-13h ebp-10h
注意是int*类型的,最后获得的是 00 00 00 02
由于Little-endian, 实际上逻辑数是02000000 转换为十进制数就为33554432
最后输出533554432
8: long test(int a,int b)
9: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,40h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-40h]
0040102C mov ecx,10h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi] //这些和上面一样
10: a = a + 3;
00401038 mov eax,dword ptr [ebp+8] //ebp=0x12FF24 加8 [0x12FF30]即取到了参数10
0040103B add eax,3
0040103E mov dword ptr [ebp+8],eax
11: b = b + 5;
00401041 mov ecx,dword ptr [ebp+0Ch]
00401044 add ecx,5
00401047 mov dword ptr [ebp+0Ch],ecx
12: return a + b;
0040104A mov eax,dword ptr [ebp+8]
0040104D add eax,dword ptr [ebp+0Ch] //最后的结果保存在eax, 结果得以返回
13: }
00401050 pop edi
00401051 pop esi
00401052 pop ebx
00401053 mov esp,ebp //esp指向0x12FF24, test函数的堆栈空间被放弃,从当前函数栈顶返回到栈底
00401055 pop ebp //此时ebp=0x12FF80, 恢复现场 esp=0x12FF28
00401056 ret ret负责栈顶0x12FF28之值00401091弹出到指令寄存器中,esp=0x12FF30
因为win32汇编一般用eax返回结果 所以如果最终结果不是在eax里面的话 还要把它放到eax
注意,从被调函数返回时,是弹出EBP,恢复堆栈到函数调用前的地址,弹出返回地址到EIP以继续执行程序。
从test函数返回,执行
00401091 add esp,8
清栈,清除两个压栈的参数10 90 调用者main负责
(所谓__cdecl调用由调用者负责恢复栈,调用者负责清理的只是入栈的参数,test函数自己的堆栈空间自己返回时自己已经清除,靠!一直理解错)
00401094 push eax 入栈,计算结果108入栈,即printf函数的参数之一入栈
00401095 push offset string "%d" (0042201c) 入栈,参数 "%d" 当然其实是%d的地址
0040109A call printf (004010d0) 函数调用 printf("%d",108) 因为printf函数时
0040109F add esp,8 清栈,清除参数 ("%d", 108)
19: return 0;
004010A2 xor eax,eax eax清零
20: }
main函数执行完毕 此时esp=0x12FF34 ebp=0x12FF80
004010A4 pop edi
004010A5 pop esi
004010A6 pop ebx
004010A7 add esp,40h //为啥不用mov esp, ebp? 是为了下面的比较
004010AA cmp ebp,esp //比较,若不同则调用chkesp抛出异常
004010AC call __chkesp (00401150)
004010B1 mov esp,ebp
004010B3 pop ebp //ESP=0X12FF84 EBP=0x12FFC0 尘归尘 土归土 一切都恢复最初的平静了
004010B4 ret
原文地址:http://www.cnblogs.com/dylanwind/archive/2008/12/08/1349822.html
发表评论
-
Xcode9解决exportArchive: requires a provisioning profile
2017-09-23 15:15 974https://stackoverflow.com/quest ... -
缺少编译器要求的成员“ystem.Runtime.CompilerServices.ExtensionAttribute..ctor”
2017-09-09 15:05 819//缺少编译器要求的成员“ystem.Runtime.Com ... -
[转]帧同步和状态同步
2017-03-06 12:22 591转自:http://www.gameres.c ... -
非对称加密和数字证书原理
2017-03-01 17:34 445http://www.cnblogs.com/JeffreyS ... -
Unity模糊查找MonoBehaviour
2016-11-23 17:32 1049using System; using System.Co ... -
Unity问题备份
2016-10-09 11:19 6021. Could not find file " ... -
链接-Unity Assetbundle使用填坑
2016-09-08 11:53 533http://www.cnblogs.com/ybgame/p ... -
协程执行顺序测试备份
2016-09-01 18:28 436using UnityEngine;using Syste ... -
[转]简单粗暴的so加解密实现
2016-08-12 17:03 736http://bbs.pediy.com/showthread ... -
[转]android解决apk编译方法数超过64k的问题
2016-08-05 20:59 455如果你是一个android开发者,你至少听说过的Dalvik的 ... -
svn 413 Entity too large
2016-06-29 20:58 676在apache配置conf文件中添加 LimitXMLRequ ... -
[转]gradle打不同渠道的包
2016-06-27 18:29 622美团Android自动化之旅— ... -
IPv4 regex
2016-06-07 12:12 376private static Regex ipv4Reg = ... -
[转]unity3d使用winphone平台的问题
2016-05-25 12:18 406最近在移植u3d的游戏到WindowsPhone8上,Wind ... -
[转]Deep link是什么
2016-05-23 12:06 615http://www.open-open.com/lib/vi ... -
苹果和google play审核时注意的问题
2016-05-16 15:18 692苹果测试沙盒充值不能用越狱手机来测 google play测试 ... -
C# MSDN地址备忘
2016-05-14 19:20 417https://msdn.microsoft.com/zh-c ... -
Windows API错误码
2016-05-14 19:19 291https://msdn.microsoft.com/en-u ... -
java 坑收集
2016-05-06 20:15 3091. Strng.getBytes() 一定要指明用的是什么编 ... -
[转]iphone不越狱抓包
2016-04-27 20:42 936Remote Virtual Interface 在iOS ...
相关推荐
C++ 获取函数调用堆栈的 高效实现代码
详细解释了函数调用过程中堆栈的处理过程,主要对新手来说可以通过这个提升一下自己对函数调用过程的了解
通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈
函数递归调用堆栈分析.doc
Win32环境下函数调用的堆栈之研究,一个好的学习资料
webpack loader,记录JavaScript函数调用堆栈到字符串window.dxj中,该堆栈可格式化进一步对比分析 也可保存不同时刻的对象进行对比分析
函数调用,参数堆栈等学习资料收集 关于函数调用时堆栈的变化分析(转自Jim's blog) - H_S_的学习总结与心得 - 博客园.mht ...函数调用堆栈分析.doc 转贴:关于函数调用的深入分析 百度空间_应用平台.mht
【应聘笔记系列】堆栈、栈帧与函数调用过程分析,C-C++堆栈指引
函数调用堆栈变化分析[参考].pdf
详细介绍了main函数如何调用子函数的过程,非常的经典
linux C用户态调试追踪函数调用堆栈以及定位段错误.docx
本文实例讲述了JavaScript实现显示函数调用堆栈的方法。分享给大家供大家参考,具体如下: 许多大型的JavaScript应用程序间的函数调用关系是非常复杂的,在开发或者调试过程中,经常需要跟踪某个函数是由哪些函数...
自动生存期:局部变量和函数形参一般都具有自动生存期,它们的内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时内存被收回。 动态生存期:具有动态生存期的变量的生存...
Visual C/C++的编译器提供了几种函数调用约定,了解这些函数调用约定的含义及它们之间的区别可以帮助我们更好地调试程序。在这篇文章里,我就和大家共同探讨一些关于函数调用约定的内容。 Visual C/C++的编译器支持...
堆栈帧,函数调用,堆栈切换 函数调用过程种种细节分析
gdb调试中,具有调用关系的函数在堆栈中的关系。
CSDN 博文,对栈帧的概念以及函数调用过程栈的变化做了详细的分析!
函数调用中堆栈的个人理解共4页.pdf.zip