一、内存分布
一般我们把进程的地址空间分为:栈区(heap)、堆区(stack)、未初始化静态全局区、已初始化静态全局区、字符常量区、代码区。如图:
二、栈帧的建立与撤销
我们知道每一次函数调用的过程都要为函数开辟栈空间,用于本次函数调用中临时变量的保存,现场保护。这块栈空间称之为函数栈桢。每一个函数都有自己的栈帧空间,并且独占自己的栈帧空间。
系统提供两个特殊的寄存器用于标识栈桢的顶部和底部。
ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶,即栈顶寄存器。
EBP:基址指针寄存器(extended base pointer),即栈底寄存器。
除了与栈相关的寄存器外,我们还需要记住另一个至关重要的寄存器。 EIP:指令寄存器(extended instruction pointer),也称程序计数器。其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。
首先栈是自高地址向低地址即向下生长的。我们通过如下程序了解栈帧建立过程:
int myadd(int _a, int _b)
{
int _z = _a + _b;
return _z;
}
int main()
{
int a = 0xAAAAAAAA;
int b = 0xBBBBBBBB;
int c=myadd(a, b);
printf("you should run here:%d\n",c);
system("pause");
return 0;
首先main 函数调用myadd函数,其反汇编代码如下:
在调用函数之前,形成的函数栈桢是这样的:
这时寄存器状态为:
之后调用函数call指令做了两件事情:
1.将当前汇编指令的下一条指令地址进行保存(入栈保存),即将00F13EA9入栈保存。保存的目的是为了回到main函数,执行call指令的下面的指令。
2.跳转至目标函数的入口地址处(jmp),即修改eip寄存器为myadd函数首地址00F11570。如图所示:
此时寄存器和内存显示为:
此时栈桢状态为;
此时可以看出形参实例化是从右到左的。
这时已经进入了myadd函数,因此会形成一个新的栈桢:
在进行完成加法运算后,将返回值放到了eax寄存器中。进行一些出栈操作后,
mov esp,ebp之后,此时ebp指向main函数的ebp,esp向高地址处移动。
进行ret指令:
1,将之前保存的函数值的返回地址进行出栈。
2,将之前保存的地址放回到eip寄存器当中,即将00F13EA9放入到eip寄存器中。
此时栈桢状态为:
执行完ret指令后回到了main函数中:
而此时可以看到将eax的内容放入到了c中,即把返回值赋给了c.
此时main函数调用myadd函数全部完成。
练习
在上面例子的基础上,若只知变量a,如何通过变量a修改变量b的值?
int myadd(int _a, int _b)
{
int *p = &_a;
p++;
*p = 10;
int _z = _a + _b;
return _z;
}
此博客只供借鉴,若有什么问题和建议,可以留言,望大家多多包涵!!!