寄存器
寄存器有哪些
32位寄存器有16个,分别是:
4个数据寄存器(EAX、EBX、ECX、EDX)。
2个变址和指针寄存器(ESI和EDI);2个指针寄存器(ESP和EBP)。
6个段寄存器(ES、CS、SS、DS、FS、GS)。
1个指令指针寄存器(EIP);1个标志寄存器(EFlags)。
寄存器分别有什么作用
简单的汇编指令
ebp、esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。每一个函数调用,(相当于领域展开)都要在栈区创建一个空间。(我的记忆方法是sb表示高低顺序(手动狗头))
上实操!
代码
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 10; int b = 20; int c = 0; c = Add(a, b); printf("%d\n", c); return 0; }
函数栈帧讲解
main函数的函数栈帧创建
首先打破大家一个固有观点,main函数也是被其他函数调用的,具体如下图(call就是调用)
在创建main函数函数栈帧前esp和ebp是维护tmianCRTStartup函数的。
(下图是没有创建main函数前的esp和ebp位置)
首先是push ebp将ebp指针存放地址进行压栈操作,esp向上移动指向ebp压栈,就是下图蓝色小矩形,(每一次pushy,esp都会向上移动)是将tmianCRTStartup函数的栈底指针存放起来,便于销毁时能够找寻(销毁时会讲具体原理),然后是mov ebp,esp,意思是将esp的地址赋值给ebp,然后是sub esp,0E4h,意思是将esp减去一个16进制的数0E4h,esp往上移(往低地址移),这个16进制的数可以看成是main函数函数栈帧的大小。
一顿操作后就是这个样子
接下来push三连击,每压一次压栈,esp都会向上移动,然后是lea edi,[edp-24h]指令,意思是将地址edp-24加载到edi中去。
接下来意思是将edi以下的9个空间的值都赋为0CCCCCCCCh(这里就解释了当你不初始化变量时为什么是随机值)
一顿操作后就是下面这个样子
调用函数的函数栈帧创建过程
函数变量
mov三连击,分别将0Ah、14h和0的值放到[ebp-8]、[ebp-14h]和[ebp-20h]三个地址内也就是给这三个变量盖房子。
Add函数函数栈帧的创建
这里讲解的就是函数的传参问题,也就是解释了为什么传int这种变量时无法对实参的值进行修改,先打个预防针(形参是实参的临时拷贝);
在创建Add函数的函数栈帧前进行传参操作
mov eax,dword ptr [ebp-14h]
push eax
mov ecx,dword ptr [ebp-8]
push ecx
这几条指令的意思就是将[ebp-14h]和[ebp-8]中存放的值分别赋给eax和ecx,并它们压在栈顶。(这里有一个小细节就是先传b,再传a);
传参之后就是在原图加上上面的ecx eax
在进入函数前我们压入函数调用完执行的下一条代码的地址,以便进入函数内部后再出来后能够找到所需要执行的下一条代码
接下来进入函数内部和main函数的函数栈帧创建一样,大家自行理解,如果有不懂得可以评论区留言或者是私信我’
一顿操作下面就是add的函数栈帧
接下来执行Add函数内部的代码
函数返回值
将0存放到[ebp-8]的空间中去,然后将[ebp+8]存放的值(a=10)赋给eax,再然后给eax加上[ebp+0Ch]存放的值(b=20),此时eax的值为30,再将eax的值存放到[ebp-8]的空间中去,而[ebp-8]这个地址存放的就是z。最后执行return z语句,相应的汇编代码就是mov eax,dword ptr [ebp-8],将[ebp-8]存放的值赋给eax,现在eax的值就是30了。重点(eax就是存放函数返回值的寄存器)
函数栈帧销毁
Add函数执行完毕后就开始销毁
pop三连击,将edi,esi,ebx弹出栈,然后mov esp,ebp意思是将ebp指针赋值给esp,然后弹出ebp,这里ebp就回到了main函数的栈底,
然后执行ret返回到call执行的下一条语句
回来之后,给esp加上8,那么esp就向下移,此时形参x和y的空间也销毁了。接下来的指令是,将eax的值放到[ebp-20h]的空间中去,而[ebp-20]这个空间存放的就是变量c,相当于将eax的值赋给c,那么Add函数的返回值就成功地带回来了。最后就是将c的值打印在屏幕上了。
然后就是main函数的销毁,大家也是自行理解,我就不做赘述了
最后总结几个问题
1.局部变量是如何创建的?
当main函数的函数栈帧创建好之后,然后在函数栈帧中给局部变量开辟一个空间。
2.为什么局部变量的值是随机值?
因为编译器会给函数栈帧里的空间放入随机值,而局部变量也是在函数栈帧中开辟空间的,所以局部变量的值是随机值。
3.函数是如何传参的?传参的顺序是怎么样的?
将b和a的值分别存在寄存器eax、ecx,然后把eax和ecx压在栈顶。再通过地址找到eax和ecx,也就是形参,将形参加起来,就这样完成了函数的传参。传参的顺序是从右往左传。
4.形参和实参是什么什么关系?
形参和实参的值是相同的,空间上是独立的,形参只是实参的一份临时拷贝,对形参的修改不会影响实参。
5.函数调用是怎么做的呢?
通过esp和ebp的移动来为调用的函数开辟函数栈帧,然后再将执行函数里的代码。
6.函数调用结束后是怎么返回的?
在调用函数之前,就把call指令的下一条指令的地址压在栈顶了,再将main函数的ebp也进行压栈。调用完函数之后,pop main函数的ebp,ebp就会重新指向main函数的栈底。然后通过call指令的下一条指令的地址找回到call的下一条指令,继续往后执行,至此被调用函数的函数栈帧就被销毁了,通过函数的返回值也通过寄存器eax带回来了。
ok今天的分享到这里就结束了,希望大家三连一波狠狠的支持博主一下!!!感谢