基础
对栈进行一个介绍:栈是一种特殊的数据结构
特点:
栈是先进后出(FIRST INT LAST OUT)简称为FILO
栈是限定仅在表头进行插入和删除操作的线性表
具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:
看个图片了解一下:
假设栈的空间是由下面向上面使用的
实际在内存中栈是由上面到下面使用的,本次介绍栈帧用栈的空间的使用由下面到上面使用是为了可以明显的看出反汇编的理解
由上图可以看到出现了esp与ebp寄存器,这两个寄存器控制了栈的大小
首先介绍两个寄存器esp和ebp(这两个寄存器里面存放的是地址)
esp存放的是栈顶的地址,ebp存放的是栈底的地址
lea的意思是加载有效地址
rep(重复) stos(存储)
dword(double word)四个字节
call(调用)
push(入栈)pop(出栈)
测试
在程序测试的时候,main函数也是被调用的,由_mainCRTStartup函数调用,_mainCRTStartup函数也是被调用的,所以main函数与_CRRTStartup函数均在执行的时候为其分配栈空间
如图函数栈的调用:
由图可以看到main函数与mainCRTStsrtup函数均被调用,为其分配栈空间
接下来看一个程序,并且观看反汇编,看其如何使用栈帧:
#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", c); return 0; }
进行调试转到反汇编:
刚开始函数的栈帧
main函数内部
第一步push ebp表示将ebp压栈(只要进行了入栈操作esp就会移动到栈顶)所以第一,二步的esp均指向栈顶,而不再是mainCRTStartup函数的栈顶了
第二步mov ebp esp表示将esp的地址赋值给ebp
第三步sub esp - 4Ch表示将esp的地址减少4CH,即代表将esp向上移动4CH的地址,那就是分配了栈帧,为main函数分配了空间
第四步push ebx push esi push edi并且esp的地址变换了,跑到了栈顶的位置
第五步lea edi [ebp - 4Ch]表示将ebp - 4Ch的地址放到edi中
第六步move eax,0CCCCCCCCh将0CCCCCCCCh放入eax中
第七步rep stos dword ptr [edi]意思,从edi这个起始地址,重复ecx次,每次储存dword字节,储存的是eax(储存即可以用说是初始化)
以上才是将main函数里面的栈帧空间申请好,并且全部赋值。
接下来就开始进行用户所写的代码进行实现了
<1>int a = 10;
mov dword ptr [ebp - 4],0Ah这个的意思是将0Ah(就是10)这个值放到[ebp - 4]这个地址所指向的空间里,占用的空间为dword(double word)4个字节因为一个字占用2个字节.一个汉字占用几个字节?
<2>int b = 20;
mov dword ptr [ebp - 8],14h这个的意思是将14h(就是20)放到[ebp - 8]这个地址所指向的空间里,占用的空间为dword(double word)4个字节因为一个字占用2个字节。
mov dword ptr [ebp - 0Ch],0这个的意思是将14h(就是20)放到[ebp - 8]这个地址所指向的空间里,占用的空间为dword(double word)4个字节因为一个字占用2个字节。
初始化完成之后进行函数的调用
<3>c = Add(a, b);
mov eax,dword ptr [ebp - 8]意思是将[ebp - 8]的这个地址开始的4个字节放到eax中,其实就是将
b = 20;
这个值放到eax中push eax进行压栈
mov ecx,dword ptr [ebp -4]意思是将[ebp - 8]的这个地址开始的4个字节放到eax中,其实就是将
a = 10;
这个值放到ecx中push ecx进行压栈
就是进行函数的传参,但是是从右向左传参
<4> call @ITL + 25(_Max) (0040101e)该语句一运行的话,call指令的内存里面放的是下一条指令的地址0040D7CA,并且自己进行压栈,由内存中可以看到。这句代码的意思是调用我们的Add函数。
从图中可以看到运行了call指令后,栈帧10上面的那一块内存上面存放的是call指令下一条指令的地址,并且已经进行压栈操作了。
则此时栈帧变为:
运行call指令进入到Add函数当中,最后运行完之后,还是要回到call指令的下一条指令,在main函数中运行
3.Add函数
第一步push ebp将main函数的ebp进行压栈操作
第二步move ebp,esp将esp的地址复制给ebp则ebp就会到Add函数的栈里
第三步sub esp,44h意思是对esp进行减去44h,即开辟Add函数内部的栈帧
第四步push ebx push esi push edi将这三个寄存器进行压栈操作,为了下一步rep tops dword ptr [edi]做基础
第五步rep tops dword ptr [edi]意思是从[edi]开始将eax的值循环11h次赋初值,为刚才开辟的Add函数的栈帧
<1>int z = 10;
mov dword ptr [ebp - 4],0这个的意思是将0这个值放到[ebp - 4]这个地址所指向的空间里,占用的空间为dword(double word)4个字节因为一个字占用2个字节。
<2>z = x + y;
mov eax,dword ptr [ebp + 8]就是将[ebp + 8] (就是10)这个值付给eax
add eax,dword ptr [ebp + 0Ch]就是将[ebp + 0Ch] (就是20)加到eax上面去,那么eax就变成了30
mov dword ptr [ebp - 4],eax将eax的值给[ebp - 4] (那么z就是30了)
<3>return z;
mov eax,doward ptr [ebp - 4]将[ebp - 4]的值赋给eax
pop edi pop esi pop ebx分别将edi,esi,ebx进行出栈操作
mov esp,ebp将ebp的值赋给了esp,那么直接就对Add函数进行了栈帧的释放
pop ebp将ebp进行一个出栈操作,出栈是将ebp进行出栈并且将出栈的ebp放到ebp中,并且esp的值加4,那么在Add函数中的main函数的ebp就有返回到main函数当中了。现在esp中存放的就是call指令,那就是下一条指令的地址
ret结束Add函数返回到main函数
add esp,8表示销毁了刚才传的两个实参
mov dword ptr [ebp - 0Ch],eax将eax(也就将Add中求得30)放到[ebp - 0Ch] (就是main函数中a,b的哪一个内存空间)中。
mov edx,dword ptr [ebp - 0Ch]最后将[ebp - 0Ch]放到edx中,最后输出edx,程序结束。
如有不足:请告诉小凯,会及时更新与修改的,谢谢大家
github感兴趣可以到我的github中看一下.