背景:windows开发的老表们,你是否遇到过这类问题,程序崩溃了,也抓到了dmp,windbg打开dmp文件,执行!analyze -v指令,傻眼了,显示的调用堆栈被破坏了,执行kb,看下调用堆栈,也是显示的被破坏的调用堆栈。怎么办老表,你往下看.......
一、函数调用时的栈帧结构
如果你知道这些废话,直接跳过这段,往下看,不要浪费时间, 调查windows栈溢出此类的问题,首先要明白函数的调用原理,实际上,函数的调用是在栈上进行的,栈的生长方向是由高地址向低地址,即栈底为高地址,栈顶为低地址。每个函数在被调用时都对应着各自一个栈帧,用来记录函数自身的一些信息(返回地址、局部变量…),因此栈帧也叫“过程活动记录”,为了衡量栈帧的范围,就需要用到两个寄存器:ESP(Extend Stack Pointer)和EBP(Extend Base Pointer)。ESP就是,也叫栈指针,ESP中始终存放着指向当前栈帧顶部的指针,时刻指向栈帧顶部,即当压入数据时,ESP-=4;弹出数据时ESP+=4。EBP时刻指向当前栈帧的底部(并非栈底),局部变量,参数,返回地址、的访问都是以EBP寄存器为参考点。
表哥,明白下面三个寄存器的作用,你就可以阅读了,以下说的都是x86架构。
寄存器名字 |
英文缩写 |
功能 |
备注 |
EBP |
Extend Base Pointer |
指向栈最上面一个栈帧的底部,用它可直接存取栈中的数据,包括,参数,局部变量,返回地址。 |
EBP和ESP是一对 |
ESP |
Extend Stack Pointer |
始终指向栈顶。 压栈:ESP-4 出栈:ESP_4 |
EBP和ESP是一对 |
EIP |
Instruction Pointer |
EIP寄存器保存CPU将要执行的下一句指令地址。每次CPU执行完相应的汇编指令之后,EIP寄存器的值就会增加。EIP指向即将执行的代码地址。 |
|
二、函数调用堆栈被破坏bug分析
1、遍历所有线程,找到出问题的线程。
拿到dump文件,输入命令~*kbn查看所有线程信息,发现异常出现在14号线程,输入命令~14s切换到出问题的线程,下面是14号线程的调用栈。一般来说,对付“kernel32!UnhandledExceptionFilter+0x1af”的方法是输入命令“.cxr poi(0xXXXXXXXX+4)”,(其中0xXXXXXXXX为第一个参数,即红色标出位置的值)即可看到发生异常前的调用栈。但是在本实例中这个值为0x00000000,是个无效地址,我们已经不能用常规方法看到调用栈了
2、恢复调用栈
首先要观察下,栈溢出到什么程度,我给他大致分四种情况。
a)栈中保存的父函数的ebp被覆盖。
当前栈帧的ebp指向父函数的ebp(看下图)
dd ebp,当前帧的ebp指向父函数的ebp,可执行dd ebp指令查看父函数的ebp的值,如果不正常,那就是被覆盖了。观察下父函数ebp寄存器的值和esp寄存器的值相差大不大,如果两者相差不大,如果明显相差很大,完全不在一个区间,证明父函数ebp寄存器被覆盖 了。父函数的ebp被覆盖了,会导致返回父函数时,定位局部变量,参数时发生错误。另外还可以输入命令!teb查看线程的栈空间范围,看下esp和ebp是不是在[StackLimit StackBase]范围之内。
b)栈中保存的返回地址(调用子函数时push eip)被覆盖
返回地址:当前栈帧ebp+4(看下图)
观察下栈中保存的返回地址,是不是在正常的代码段地址返回内,如果明显不合法的代码地址,那么就是返回地址被覆盖了,在当前函数执行完毕后,返回到父函数时,会把返回地址弹出栈到EIP寄存器中,如果栈中保存的返回地址是错误的,那么pop到EIP寄存器的返回地址也就是错误的。会执行到不可访问或者非法的代码地址处。
c)当前执行函数的参数被覆盖
当前执行的函数的第一个参数位置处:ebp + 8(看下图)
当前执行的函数的第二个参数位置处:ebp + 12(看下图)
d)覆盖到父函数的栈帧空间(局部变量)
恢复调用栈方法,
第一种方法:输入命令dps esp L1000,查看调用栈,下面是其中的一些关键部分。
第二种方法,如果esp寄存器被破坏,可以可以输入命令!teb查看线程的栈空间范围,
然后执行命令dps StackLimit StackBase查看堆栈上的符号。
通过查找ebp便可以恢复调用栈了(从0fadff94(从函数__RtlUserThreadStart的ebp往上递推) 向上找到0fadff88 再向上找到0fadd724,依次类推) 而最后的0fadd674 再也找不到存储它的地方了,说明这里已经是调用栈的尽头。那么破坏栈的地方也就是发生在这里了
VFPrintOpr!CVFPrintMgrWnd::Refresh+0x25 [E:\Daily_Build\code\src\Set_Nuclear\EngStationFunctions\VFPrintMgr\VFPrintMgrWnd.cpp @ 165]
通过进一步分析代码查看变量值便可以找到问题的所在
(通过ebp追踪被破坏的调用堆栈)
(堆栈布局)
一般的追踪思路,因为每个线程的创建函数为ntdll!__RtlUserThreadStart,可以通过ntdll!__RtlUserThreadStart函数的ebp往上追踪,具体的追踪思路
如下:
更多【Windbg调试栈溢出】相关视频教程:www.yxfzedu.com