栈和函数调用

def:栈是一种后入先出的数据结构,天然适合用来保存需要函数调用等需要保存的信息。在windows的用户进程中都包含用户栈和内核栈两个栈。

每个线程都至少包含有一个栈,每个栈都对应内核中的一个_KTHREAD结构:


在线程开始运行之前需要创建这个线程的栈,创建过程如下:




栈作为一个存储数据的结构,在函数调用的过程中执行CALL和RET指令时分别以如下方式使用:



而用户态调用内核态的过程如下(引用自连接):


栈中除了存储跳转的地址之外,还可以存储和在函数内定义的局部变量,这样随着函数退出就不再需要清理内存了。因为ESP经常变化,无法直接的表达局部变量的地址,所以CPU中引入了一个新的寄存器EBP,记录栈中的某一个地址为基础地址(通常为函数入口处的ESP地址),局部变量使用EBP的偏移来表示,从EBP到当前ESP的栈内容成为当前函数的栈帧,到上个EBP的栈内容称为调用函数的栈帧。根据这些EBP的内容我们可以查找到函数调用的关系这是实现栈回溯的基础。

有一些优化为了减少目标程序的体积,提高运行数度会去掉栈指针的建立和维护,而在符号文件中记录帧指针省略的信息,我们无法通过EBP方法来实现栈回溯,只能通过符号文件来实现,如果没有符号文件则无法知道函数的调用关系。

函数调用和返回时ESP的值保持不变,我们称为栈平衡。如果ESP被意外破坏而指向了别的地址,(debug版)可以在函数返回时插入检查函数_chkesp检查是否被破坏。

我们根据函数调用时入栈顺序,是否使用寄存器和谁负责清理约定不同的函数调用方式:


用户栈的大小系统默认为1M,但为了节约内存并不会一次为所有线程分配1M的物理内存,开始的时候提交连个内存页,一个栈内容一个保护页,当栈增长到保护页的时候触发一次访问异常,则系统把保护页设置为栈内存,再提交一个页为保护页,这样就实现了栈的自动增长。保护页会一直存在每次访问保护页都会触发一次访问异常,如果栈已经增长到最大值则不再提交新的内存,而抛出一个栈溢出异常(当还有最后一个页的空间的时候也会抛出异常,但可以继续运行)。而对于一次超出一个页面大小的内存申请编译器需要插入检查函数_chkstk分多次提交,知道得到足够大的内存。

对于栈中内存的读写,如果超出了内存边界则有可能会破坏栈帧的内容,如果函数的返回地址被恶意修改到某个特定的位置,在这个位置中加入一段带有恶意目的的代码,这样的方式成为缓冲区溢出攻击。

为了避免缓冲区溢出的情况,编译器通常在debug版本中为每个变量增加cookie保护,并记录每个变量的大小,在函数返回时不止检查返回地址,同时也检查变量保护的cookie是否被破坏,来减少缓冲区溢出的情况及早发现错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值