(1)地址空间与物理内存是两个完全不同的概念,真正的代码及数据都存在物理内存中。物理储存器是指实际存在的具体储存器芯片,CPU在操纵物理储存器的时候都把他们当做内存来对待,把他们看成由若干个储存单元组成的逻辑储存器,这个逻辑储存器就是我们所说的地址空间。地址空间大小与逻辑储存器大小不一定相等。
(2)进程的地址空间分布 进程的地址空间包括:栈区(heap)、共享区、堆区(stack)、未初始化静态全局区、已初始化静态全局区、静态只读区、代码段。如图:
首先要明白几个地方:每一个函数都有自己的栈帧空间,并且独占自己的栈帧空间, 当前正在运行的函数的栈帧总是在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。
「注:函数栈帧的大小并不固定,一般与其对应函数的局部变量多少有关。函数运行过程中,其栈帧大小也是在不停变化的。除了与栈相关的寄存器外,我们还需要记住另一个至关重要的寄存器。」
EIP:指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。
首先栈是自高地址向低地址即向下生长的。我们通过如下程序了解栈帧建立过程:
int fun(int a,int b)
{
int sum=0;
sum=a+b;
return sum;
}
int main()
{
fun(3,4);
printf("haha");
return 0;
}
这里main是调用者(caller);fun是被调用者(Callee)在函数调用前,main正在用ESP和EBP寄存器指示它自己的栈帧。main把EAX,ECX和EDX压栈。这是一个可选的步骤,只在这三个寄存器内容需要保留的时候执行此步骤。接着,main把传递给fun的参数一一进栈,最后的参数最先进栈,这里也就解释了函数在压栈过程中是从右往左,即先压4,再压3。
(1)这里首先main函数建立自己的栈帧结构;main()函数是由__tCRTStartup()
函数调用的,所以mainCRTStratup()
函数调用__tmainCRTStra()
函数的时候就会从栈上为__tmainCRTStra()
分配类似图中这么一块空间,因为我们现在要调用main()函数了,所以当然要先把__tmainCRTStartup()
函数的运行状态保存下来,这样main()函数才能返回的时候才能找得到!。然后继续执行下一条语句:mov ebp,esp
即把esp
的值赋给ebp
,这样,ebp
也就指向了现在esp
的位置 然后sub esp 0C0h
这样就为main函数开辟了一段空间然后将ebx、esi、edi
寄存器压栈就形成如图所示:
紧接着将局部变量及实参压栈,并执行call
指令,main
用call
指令调用子函数:call fun
当call指令执行的时候,EIP指令指针寄存器的内容被压入栈中。因为EIP寄存器是指向main中的下一条指令,所以现在返回地址就在栈顶了。在call指令执行完之后,下一个执行周期将从名为fun的标记处开始. 图一
当函数fun,也就是被调用者取得程序的控制权,它必须做3件事:建立它自己的栈帧,为局部变量分配空间,最后,如果需要,保存寄存器EBX,ESI和EDI的值。首先fun必须建立它自己的栈帧。EBP寄存器现在正指向main的栈帧中的某个位置,这个值必须被保留,因此,EBP进栈。然后ESP的内容赋值给了 EBP。这使得函数的参数可以通过对EBP附加一个偏移量得到,而栈寄存器ESP便可以空出来做其他事情。如此一来,几乎所有的c函数都由如下两个指令开 始:
push ebp
mov ebp, esp
下一步,fun必须为它的局部变量分配空间,同时,也必须为它可能用到的一些临时变量分配 空间。比如,foo中的一些C语句可能包括复杂的表达式,其子表达式的中间值就必须得有地方存放。这些存放中间值的地方同城被称为临时的,因为他们可以为 下一个复杂表达式所复用 现在,局部变量和临时存储都可以通过基准指针EBP加偏移量找到了。最后,如果fun用到EBX,ESI
和EDI
寄存器,则它必须在栈里保存它们。这里写图片描述
fun的函数体现在可以执行了。这其中也许有进栈、出栈的动作,栈指针ESP也会上下移动,但EBP是保持不变的。这意味着我们可以一直用[EBP+…]
找到第一个参数,而不管在函数中有多少进出栈的动作。函数fun的执行也许还会调用别的函数,甚至递归地调用foo本身。然而,只要EBP寄存器在这些子调用返回时被恢复,就可以继续用EBP加上偏移量的方式访问实际参数,局部变量和临时存储。紧接着当被调用者执行完毕时将消除栈帧结构,调用pop指令。
在把程序控制权返还给调用者前,被调用者foo必须先把返回值保存在EAX寄存器中。其次,foo必须恢复EBX,ESI和EDI寄存器的值。进栈和出栈操作的次数必须保持平衡。
在程序控制权返回到调用者main)后,这时,传递给fun的参数通常已经不需要了。我们可以把参数一起弹出栈,这可以通过把栈指针实现:add esp, 8
此时fun函数调用结束栈帧结构恢复至图一。如果在函数调用前,EAX,ECX
和EDX
寄存器的值被保存在栈中,调用者main函数现在可以把它们弹出。这个动作之后,栈顶就回到了我们开始整个函数调用过程前的位置。这样整个函数的调用就结束了
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有