目录
准备阶段
栈顶指针和栈底指针
函数栈帧的创建与销毁:
1、大体分析思路(汇编代码)
2、main函数的创建
具体过程:
画图:
3、储存变量
具体过程
画图
3、 ADD函数的创建与销毁
传参
ADD函数的创建
变量的创建
ADD函数的销毁
4、打印c的值
具体过程
画图
5、main函数的销毁
具体过程
画图
如果想上手操作并看到过程,不建议跳过准备阶段部分,仔细阅读这个部分(直接从寄存器看,也可以)。
2019版本 - VS编译器
用到VS编译器中的:反汇编窗口、内存窗口、监视窗口。
按键盘上的F10(逐过程调试) —— 点击上方的调试 —— 窗口 —— 反汇编 —— 内存 —— 监视
把反汇编、内存、和监视窗口打开后是下面这样:
接下来按F10来一步一步的调试,观察内存窗口和监视窗口来分析执行的步骤。
寄存器:
有eax,ebx,ecx,edx,今天重点的寄存器是:ebp(栈底指针),esp(栈顶指针),这两个寄存器是用来维护函数栈帧的。
注意:寄存器是放在cpu中的,不在main函数中
在不同的编译器下,函数调用的过程中栈帧的创建是略有差异的,具体的细节取决于编译器的实现。
在观察函数栈帧的创建与销毁的过程中有两个非常重要的指针 esp(栈顶指针)和ebp(栈底指针),他们两个是用来维护函数栈帧的。观察时,主要看这两个指针(esp,ebp),在指针esp上方的内存是可以存放数据的内存。
用一段简单的代码分析函数栈帧的创建与销毁
下面的代码是上面代码的汇编代码(更容易观察栈帧创建和销毁的过程):
ADD函数的创建与销毁
int main()
{
005518D0 push ebp //压栈:把ebp的放在栈的上面,然后esp上移一个整型
005518D1 mov ebp,esp //把ebp的值改成esp的值
005518D3 sub esp,0E4h //把esp的值改成(esp – 0E4h)(0E4h为228)的值
005518D9 push ebx //压栈:把ebx的放在栈的上面,然后esp上移一个整型
005518DA push esi //压栈:把esi的放在栈的上面,然后esp上移一个整型
005518DB push edi //压栈:把edi的放在栈的上面,然后esp上移一个整型
005518DC lea edi,[ebp-24h] //edi中的值变成:ebp-24h
005518DF mov ecx,9 //ecx中的值变成:9
005518E4 mov eax,0CCCCCCCCh //eax中的值变成:0CCCCCCCh
005518E9 rep stos dword ptr es:[edi] //从edi存放的地址到ebp存放的地址全部初始化为0CCCCCCCh
005518EB mov ecx,55C003h //ecx中的值变成:55C003h
005518F0 call 0055131B //
说明:
main函数也是一个函数,既然是函数那么就有函数栈帧的创建与销毁
Push——压栈(给栈顶放一个元素)、move——把后一位的值赋予前一位、sub——减去一个数、dword —— 一个整型
int a = 10;
005518F5 mov dword ptr [ebp-8],0Ah //把ebp-8中的值变成0Ah
int b = 20;
005518FC mov dword ptr [ebp-14h],14h //把ebp-14h中的值变成14h
int c = 0;
00551903 mov dword ptr [ebp-20h],0 //把ebp-20h中的值变成0
c = ADD(a, b);
具体过程
c = ADD(a, b);
0055190A mov eax,dword ptr [ebp-14h] //eax中放ebp-14h的值
0055190D push eax //压栈:
0055190E mov ecx,dword ptr [ebp-8] //ecx中放ebp-8的值
00551911 push ecx //压栈:
00551912 call 005513B6 //压栈:放005512B6
画图
具体过程
int ADD(int x, int y)
{
00551770 push ebp //压栈:
00551771 mov ebp,esp //把ebp的值改成esp的值
00551773 sub esp,0CCh //把esp的值改成(esp – 0CCh)(0CCh为204)的值
00551779 push ebx //压栈:
0055177A push esi //压栈:
0055177B push edi //压栈:
0055177C lea edi,[ebp-0Ch] //edi中的值变成:ebp-0Ch
0055177F mov ecx,3 //ecx中的值变成:3
00551784 mov eax,0CCCCCCCCh //eax中的值变成:0CCCCCCCh
00551789 rep stos dword ptr es:[edi]//edi存放的地址到ebp存放的地址全部初始化成0CCCCCCCh
0055178B mov ecx,55C003h //ecx中的值变成:55C003h
00551790 call 0055131B //
其中的 005513B6 是call指令的下一条地址
具体过程
int z = 0;
00551795 mov dword ptr [ebp-8],0 //为z开辟空间:把ebp-8改成0
z = x + y;
0055179C mov eax,dword ptr [ebp+8] //把ebp+8(形参x)的中的值放在eax中
0055179F add eax,dword ptr [ebp+0Ch]//eax加上ebp+0Ch(形参y)中的值
005517A2 mov dword ptr [ebp-8],eax //把eax(两个数相加的值)放入ebp-8(z)中
return z;
005517A5 mov eax,dword ptr [ebp-8] //把返回值(ebp-8)放入eax中
画图
具体过程
005517A8 pop edi //pop——出栈:放出edi
005517A9 pop esi //出栈:放出esi
005517AA pop ebx //出栈:放出ebx
005517AB add esp,0CCh //esp加上0CCh(16进制)
005517B1 cmp ebp,esp //
005517B3 call 00551244 //
005517B8 mov esp,ebp //esp的值变成ebp
005517BA pop ebp //出栈:弹出栈顶的ebp(main)
005517BB ret //回到地址005512B6(call指令的下一条指令)
画图
00551917 add esp,8 //esp 加 8
0055191A mov dword ptr [ebp-20h],eax //ebp-20(c)的值改成eax中的值
printf("%d\n", c);
0055191D mov eax,dword ptr [ebp-20h] //eax中的值改成ebp-20h(c)中的值
00551920 push eax //压栈:
00551921 push 557B30h //压栈:
00551926 call 005510CD //进入printf函数(和ADD函数的创建和销毁一样)
//这个函数执行的是在屏幕上打印C的值
0055192B add esp,8 //esp加8
return 0;
0055192E xor eax,eax //
}
00551930 pop edi //出栈
00551931 pop esi //出栈
00551932 pop ebx //出栈
00551933 add esp,0E4h //esp加上0E4h(228)
00551939 cmp ebp,esp //
0055193B call 00551244 //
00551940 mov esp,ebp //esp的值改成ebp的值
00551942 pop ebp //出栈:把ebp弹出来
00551943 ret //回到call的下一条语句
谢谢观看(如有错误欢迎指正)