系统栈的工作原理

来源:互联网 发布:小仙女的网络意思 编辑:程序博客网 时间:2024/05/01 11:55
进程使用的内存按功能大致可以分为以下4部分:
1.代码区:机器会到这个位置取之指并执行。
2.数据区:存储全局变量或静态数据
3.堆区:进程可以在堆区动态请求一块一定大小的内存,并在使用完之后归还给堆区。动态分配与回收特点。
4.栈区:用于动态存储函数之间的调用关系,以保证被调用函数在函数执行完成之后返回母函数中继续执行。

windows下,高级语言经过编译链接,所形成的这样的格式就是windows下的PE文件格式。当PE文件被装载进内存之后,就形成了进程。

PE文件中包含的二进制的机器码会被装入到内存中的代码区,CPU会在这个区域内一条一条的取出操作数和指令,送入算术逻辑单元进行运算。
如果代码中请求开辟动态内存,则在堆区中分配一块合适大小的内存区域,返回给代码区的代码使用。
在函数调用发生时,函数调用关系等信息会被动态得保存在系统的栈区,在函数调用结束后返回给母函数。
系统栈的工作原理

栈的2种操作
Push
pop
栈的2中数据
top
base

内存的栈区就是系统栈,系统栈由系统自动调用。
根据操作系统、编译器、编译选项的不同,同一文件的不同函数在内存的代码区中的分布可能是相邻的,也可能相隔甚远;可能有先后顺序,也可能是无序的;但是都存在于PE文件所映射的一个区域中。
在函数调用发生时,系统栈会为这个函数开辟一个新的栈帧,并压入栈中。这个栈帧的内存空间被它所属的函数独占,正常情况下是不会与别的函数共享的。在函数调用结束时,系统栈会弹出该函数所使用的栈帧。
系统栈的工作原理

以上操作调用时,系统栈的中的操作。
1.main函数调用func_A函数的时候,先将自己的返回地址压入栈中,再将func_A函数的栈帧压入栈中。
2.func_A函数中调用func_B函数的时候,func_A函数将自己的返回地址压入栈中,再将func_B函数的栈帧压入栈中。
3.func_B函数在执行完毕的时候,系统将func_B函数的栈帧出栈,func_A函数的返回地址裸露在栈顶,此时处理器按照这个返回地址跳转到func_A函数的空间中继续执行。
4.func_A函数在执行完毕的时候,系统将fun_A函数的栈帧出栈,main函数的返回地址裸露在栈顶,此时处理器按照这个返回地址转到main函数的空间中继续执行。
main函数不是第一个被调用的函数,在调用main函数之前还会有系统的一些函数进行调用。

寄存器与函数栈帧:
在实际的内存当中并没有这样的物理分段,实现分段原理是CPU中的寄存器,用存放的指针来看出这一块位置。

每一个函数独占自己的栈帧空间,当前正在运行的函数的栈帧总是在栈顶。CPU中有两个寄存器标志系统栈栈顶的栈帧。
(1)Esp:指向最上层栈帧的栈顶(与系统栈的栈顶是同一位置)
(2)Ebp:指向最上层栈帧的栈底(不是系统栈的栈底)
系统栈的工作原理

函数栈帧:Esp和Ebp之间的内存为当前函数的栈帧,Ebp标志当前函数的栈底,Esp标志当前函数的栈顶。
函数的栈帧当中所包含的信息:
(1)局部变量:为函数局部变量开辟内存空间
(2)栈帧状态值:保存前的栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的底部可以通过堆栈平衡来得到),在当前栈帧被弹出后可以恢复出上一个栈帧。
(3)函数的返回地址:保存当前函数调用前的“断点”

Eip:指令寄存器永远指向下一条准备执行的指令地址。控制了Eip的内存,就控制了进程。

函数调用约定
函数调用约点描述了函数传递参数方式和栈协同工作的技术细节。
函数调用约定的差别在于参数传递方式,参数入栈是从左向右压入栈中还是从右向左压如栈中和函数返回时在堆栈平衡操作是在子函数中还是在母函数中完成。

C(Cdecl)调用约定: 函数参数从右向左入栈,在母函数中进行堆栈平衡操作。
SysCall调用约定:函数参数从右向左入栈,在子函数中进行堆栈平衡操作。
StdCall调用约定:函数参数从右向左入栈,在子函数中进行堆栈平衡操作。
BASIC调用约定:函数参数从左向右入栈,在子函数中进行堆栈平衡操作。
FORTRAN调用约定:函数参数从左向右入栈,在子函数中进行堆栈平衡操作。
PASCAL调用约定:函数参数从左向右入栈,在子函数中进行堆栈平衡操作。

VC支持3中函数调用约定,为C调用约定,stdcall调用约定,fastcall调用约定。默认为stdcall调用约定。
参数传递也会有不同,在windows平台下C++类成员函数的this指针由ECX寄存器存储,GCC编译器会作为最后一个参数压入栈中。

函数调用大致步骤(stdcall调用约定下):
(1)将参数从右向左压入系统栈中。
(2)返回地址入栈将代码区调用指令的下一条指令的地址压入栈中,供函数返回时继续执行。
(3)代码区跳转:处理器从当前代码区跳转到调用函数入口处。
(4)栈帧调整:
     保存当前栈帧状态值,以备后面回复本栈帧时使用(Ebp入栈)
     将当前栈帧切换到新栈帧(Esp装入Ebp,更新栈帧底部)
     给新栈帧分配空间(Esp减去所需空间的大小,抬高栈顶)
stdcall调用约定下函数调用时的指令序列:
push 第三个参数           //三个参数入栈
push 第二个参数
push 第一个参数
call 函数地址             //call完成的工作:将函数当前位置压入栈中,保存返回地址,跳转到函数入口处
push ebp                  //将ebp的值压入栈中,保存当前栈帧的ebp的值,就是当前栈帧的栈底
mov ebp, esp              //设置新栈帧的栈底,栈帧切换
sub esp, XXX              //设置新栈帧的栈顶,抬高栈顶,开辟内存空间。
系统栈的工作原理
函数返回的步骤:
1.返回函数的返回值,将函数的返回值保存在EAX中。
2.弹出当前栈帧,恢复出上一个栈帧。
  在堆栈平衡的基础上,给esp加上栈帧的大小,降低栈顶,恢复当前栈的空间。
  将当前栈帧底部保存的前栈帧ebp的值传入ebp中,恢复出上一个栈帧
  将函数返回地址传入eip寄存器当中
3.按函数的返回地址跳回母函数中继续执行。
C语言和win32平台下,函数返回时的相关指令序列如下:
add esp,XXX     //降低栈顶,回收当前栈的空间
pop ebp         //将前ebp的值出栈,传入ebp中,回复出上一个栈帧
retn            //弹出栈顶元素,既弹出返回地址,让处理器跳转到弹出的返回地址,恢复调用前的代码区
系统栈的工作原理
来自《0DAY安全》栈溢出章解首部分的学习
原创粉丝点击