堆栈的基本概念

  • 栈:由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

  • 堆: 一般由程序员分配释放, 在C语言中使用malloc函数分配的内存会被操作系统会智能的释放,但如果程序一直运行则会造成内存泄露。

  • ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。

  • EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

堆栈空间从栈顶开始使用


相关指令

PUSH指令

PUSH指令将源操作数压入堆栈,可以是寄存器的值或者立即数或者内存中的值,并且减少ESP的值,数值相当于数据的长度

例如

1
PUSH AX

以上命令将AX寄存器中的值压入栈中,并且ESP减2,相当于以下两条指令,PUSH指令会自动调整ESP的值

1
2
MOV WORD PTR DS:[ESP],AX
SUB ESP,2

POP指令

数据压入栈后要弹出可以对ESP加上需要弹出的长度

例如

1
2
3
4
PUSH ECX
MOV ECX,10
MOV DWORD PTR DS:[ESP],ECX
ADD ESP,4

以上指令可以直接简化成POP ECX,此指令将栈顶4字节内存数据储存到ECX,并且ESP+4


堆栈平衡

如果要返回父程序,在堆栈进行堆栈的操作的时候,要保证RET这条指令之前,ESP指向的是压入栈中的地址

例如:

1
2
3
4
CALL 0X0041009C

PUSH 1
RET

以上代码在返回时栈顶内存为1,RET指令将1写入到EIP寄存器,导致下一步指令找不到了


如果通过堆栈传递参数了,那么在函数执行完毕后,要平衡参数导致的堆栈变化

对于传参之后的堆栈平衡有两种方法,例如有如下代码

1
2
3
4
5
6
7
8
PUSH 1
PUSH 2
CALL 0X0041009C


MOV EAX,DWORD PTR SS:[ESP+8]
ADD EAX,DOWRD PTR SS:[ESP+4]
RET

以上代码在函数调用完毕后,压入堆栈的2个立即数没有释放,有两种平衡方法:

  1. 外平栈:在函数调用后将ESP寄存器 + 8

  2. 内平栈:在函数内部平衡堆栈,直接使用RET 8指令,这条指令会在返回后将ESP额外+8,一共+12,还有函数地址占用的4字节


EBP寻址

使用ESP寻址的缺点:在函数中如果要使用堆栈的话ESP会发生变化,此时使用参数和局部变量会收到影响,EBP寻址可以拟补此缺点

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUSH 1
PUSH 2
CALL 0X0041009C


PUSH EBP
MOV EBP,ESP
SUB ESP,10
MOV EAX,DWORD PTR SS:[EBP+0XC]
ADD EAX,DWORD PTR SS:[EBP+0X8]


MOV ESP,EBP
POP EBP
RET 8

在跳转后将EBP的值压入栈中,将ESP的值存放到EBP,然后将ESP-10,相当于一个新的堆栈空间,原有的EBP储存在栈中,ESP储存在EBP中

要寻找参数时只需要以EBP为基准寻址即可,EBP相当于修改前的ESP,但要注意的是调用函数后除了函数地址,还把EBP压栈了,所有第一个参数的偏移量应该是0X8

返回时只需要将两个堆栈指针寄存器还原即可,先还原ESP,因为EBP保存着以前ESP的值

调用完毕后ESP和EBP的值保持不变