观察栈帧和EBP、ESP寄存器
本文将用WinDbg观察栈帧以及EBP、ESP寄存器的变化过程。
首先我们先写一段简单的代码进行实验。
int main()
{
int m1 = 7;
int m2 = A(m1);
return 0;
}
int A(int a)
{
return a;
}
禁用VisualStudio的自动优化,然后编译成release版本,然后用WinDbg打开可执行文件。
观察main函数的反汇编代码。
0:000> uf main
TestStack!main [c:\users\xiaohaitao\documents\visual studio 2008\projects\teststack\teststack\test.cpp @ 7]:
7 01391000 55 push ebp
7 01391001 8bec mov ebp,esp
7 01391003 83ec08 sub esp,8
8 01391006 c745fc07000000 mov dword ptr [ebp-4],7
9 0139100d 6a07 push 7
9 0139100f e81c000000 call TestStack!A (01391030)
9 01391014 83c404 add esp,4
9 01391017 8945f8 mov dword ptr [ebp-8],eax
10 0139101a 8b45f8 mov eax,dword ptr [ebp-8]
11 0139101d 8be5 mov esp,ebp
11 0139101f 5d pop ebp
11 01391020 c3 ret
在main开始处设置断点,然后执行到断点处。
0:000> bp main
0:000> g
Breakpoint 0 hit
eax=003c19f8 ebx=00000000 ecx=6f7bb6f0 edx=00000000 esi=00000001 edi=01393374
eip=01391000 esp=002afaf0 ebp=002afb30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
TestStack!main:
01391000 55 push ebp
观察此时的栈帧
0:000> kb
ChildEBP RetAddr Args to Child
002afaec 013911ae 00000001 003c2c70 003c19f8 TestStack!main [c:\users\xiaohaitao\documents\visual studio 2008\projects\teststack\teststack\test.cpp @ 7]
002afb30 77571194 7ffd6000 002afb7c 7797b495 TestStack!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 582]
002afb3c 7797b495 7ffd6000 77e5d1ed 00000000 kernel32!BaseThreadInitThunk+0xe
002afb7c 7797b468 013912f6 7ffd6000 00000000 ntdll!__RtlUserThreadStart+0x70
002afb94 00000000 013912f6 7ffd6000 00000000 ntdll!_RtlUserThreadStart+0x1b
此时的栈帧基址为:002afaec
观察此处的内容:
0:000> dd 002afaec l4
002afaec 00000001 013911ae 00000001 003c2c70
我们知道,每一个栈帧基址的内容应该是上一层函数的栈帧基址,也就是地址002afaec处的内容应该是002afb30才对,而现在这个值为00000001,第二个值013911ae为函数返回值,第三值00000001为传递给该函数的第一个参数。和kb打印的结果对比,函数返回值和参数是没有错的。但是第一个值00000001是什么呢?
观察ebp,esp寄存器。
0:000> r ebp, esp
ebp=002afb30 esp=002afaf0
我们发现ebp的值是上一个栈帧的基址,而不是本栈帧的地址。而esp的值002afaf0,比当前栈帧基址002afaec要高4个字节。我们知道ESP是始终指向栈顶的,而栈空间是由高地址向低地址分配,当前栈帧基址002afaec比栈顶002afaf0还低4个字节,说明当前栈帧基址指向未使用的栈空间,是一个随机值。
然后执行main函数的开始前两句汇编。
0:000> u main
TestStack!main [c:\users\xiaohaitao\documents\visual studio 2008\projects\teststack\teststack\test.cpp @ 7]:
01391000 55 push ebp
01391001 8bec mov ebp,esp
01391003 83ec08 sub esp,8
01391006 c745fc07000000 mov dword ptr [ebp-4],7
0139100d 6a07 push 7
0139100f e81c000000 call TestStack!A (01391030)
01391014 83c404 add esp,4
01391017 8945f8 mov dword ptr [ebp-8],eax
0:000> bp 01391003 //在01391003 83ec08 sub esp,8 处设置断点
0:000> g //继续执行
Breakpoint 1 hit
eax=003c19f8 ebx=00000000 ecx=6f7bb6f0 edx=00000000 esi=00000001 edi=01393374
eip=01391003 esp=002afaec ebp=002afaec iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
TestStack!main+0x3:
01391003 83ec08 sub esp,8
此时再观察ESP, EBP寄存器
0:000> r esp, ebp
esp=002afaec ebp=002afaec
0:000> dd 002afaec l4
002afaec 002afb30 013911ae 00000001 003c2c70
此时ebp=002afaec的内容已经由00000001变成了002afb30, 由前面的栈回溯可以看出002afb30正是上一级函数的栈帧地址,说明执行完基址入栈以后,main函数的栈帧基址的内容和EBP的内容都已经被正确设置。
总结:
esp是栈指针寄存器,总是指向栈顶。
ebp是栈帧基址寄存器,不是栈基址寄存器,指向栈帧的基址。
在一个函数的最开始处,以下两条指令未执行时:
00a01000 55 push ebp
00a01001 8bec mov ebp,esp
ebp指向的上一个栈帧的基址。
esp指向栈顶,比该函数的栈基址高4个字节。
打印该函数栈基址的内容,由于上一个栈帧的基址还未入栈,所以此时栈帧基址指向的为随即内容。
最近,看一下EBP的概念:
(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部