场景介绍
有时候当你收到一个dump
后,大多数情况可以通过k
命令查找到导致栈溢出的函数。但是本文要讲的是,曾经碰到过的栈溢出(*
), 却无法直接通过k
命令查看到当前的函数调用栈。 下面将介绍一个简单的方法,找到导致栈溢出的函数。
样例代码
先声明下,因为产品的实际分析不能够通过网络分享。下面的样例代码,实际上不会导致在上一章场景介绍
中提到的问题,可以直接通过crash的栈查找到代码,本文只是通过这个例子来讲解如下场景的分析思路: 如果栈溢出了,通过k
命令却无法查找到函数调用栈。
#include <iostream>
void Func(const char* pcsPara1, const char* pcsPara2)
{
char csTmpStr[1000] = "good";
std::cout << pcsPara1 << pcsPara2 << csTmpStr << std::endl;
Func(pcsPara1, pcsPara2);
}
int main()
{
Func("Hello ", "* ");
return 0;
}
样例分析
采用Windbg
对dump进行分析。第一步
通过!teb
命令查看当前线程的基本信息, 大家应该都清楚,每一个线程都有自己的函数调用栈。这里着重看一下,这个线程的起始地址为StackBase
(000000bfd6b00000
), 栈的上限是StackLimit
(000000bfd6a01000
), 这里同样可以看出来栈是向低地址生长的,而且栈的大小大约为1MB
.
0:000> !teb
TEB at 000000bfd69b4000
ExceptionList: 0000000000000000
StackBase: 000000bfd6b00000
StackLimit: 000000bfd6a01000
SubSystemTib: 0000000000000000
FiberData: 0000000000001e00
ArbitraryUserPointer: 0000000000000000
Self: 000000bfd69b4000
EnvironmentPointer: 0000000000000000
ClientId: 0000000000005fd8 . 0000000000000a98
RpcHandle: 0000000000000000
Tls Storage: 0000023ec056fdd0
PEB Address: 000000bfd69b3000
LastErrorValue: 0
LastStatusValue: c000003a
Count Owned Locks: 0
HardErrorMode: 0
第二步
通过命令dps
查看栈空间上值对应的符号信息。使用这个命令,是因为调用函数的时候会记录函数返回后要执行的下一条指令的地址,那么栈上就保留了函数的信息,通过这个命令就可以查找到对应的函数。(如果对函数调用过程还不熟悉的同学可以参考阅读<<你了解函数调用过程吗?>>)
0:000> dps 000000bfd6a01000 000000bfd6b00000
......
000000bf`d6afd148 00007ff6`5a461755 *!Func+0xa5
......
000000bf`d6afd588 00007ff6`5a461755 *!Func+0xa5
......
000000bf`d6afd9c8 00007ff6`5a461755 *!Func+0xa5
......
第三步
,可以通过栈上的值对应的符号信息,查找到可能的函数调用过程,比如这个例子看到多处调用*!Func
, 则可以对相应的代码处进行审查,得知这是一个栈溢出问题。
建议
其实对于栈溢出来说,很多情况都是函数非正常的循环调用导致, 此时修复这个bug即可。但也有可能是函数的局部变量空间过大,函数调用层数深。比如在一些领域禁止使用在堆上分配/使用内存,很多时候只能在栈上去申请,这个时候提高栈空间的默认值(1M),如下图将栈空间调整为4M。
最后是个人微信公众号,文章CSDN和微信公众号都会发,欢迎一起讨论。