利用内存断点(数据断点)结合windows CRT 定位堆栈溢出问题

来源:互联网 发布:大学生搜题用什么软件 编辑:程序博客网 时间:2024/05/18 18:02

最近几年比较倾心于github+evernote学习方式,所以很少来csdn记录自己学习所得了,今天就堆栈溢出问题写一篇解决办法。

公司司产品经过这三年的迭代已经非常复杂了,代码量超过10万,而且混杂了来自韩国,美国,台湾,北京好几个团队的贡献,加上跨现流行几大移动平台(android, ios)。维护,优化和定位bug已经远不如以前容易了。所以常常借助了比较的工具平台(valgrind, cpplint, intel vtunes)。

上周jekins日常做持续继承的时候报告一个堆栈溢出的问题。问题出在delete某一class实例的时候发生了堆栈溢出。这个class是由另外团队开发,代码量非常大且算法复杂,内部代码细节我们一直没有去读过。一开始通过vs debug方式很久没有找到头绪,因为一旦是堆栈溢出后,函数返回跳转就会出错,很难有正确的堆栈信息让你去排查问题。

后来我想到很久没有用的内存断点,刚好在windows平台可以借助CRT定位了。
问题分析步骤如下(推荐在windows debug模式):

#define BUFFER_SIZE 8class Bug {  public:Bug() {  buffer_ = new char[BUFFER_SIZE];  memset(buffer_, 0x0, BUFFER_SIZE * sizeof(char));}      ~Bug() {  delete[] buffer_;}void copy(const char *src) {  memcpy(buffer_, src, strlen(src));}  private:char* buffer_;/*..... a lot member*/};int main() {  Bug *bug = new Bug();  const char *src = "hello,world";  /*  ...   do a lot things  */  bug->copy(src);  /*  ...   do a lot things  */  delete bug;    return 0;}


1. 缩小排查范围。通过vs SEH等工具,缩小排查范围,在本代码中,vs告诉我们是delete某个实例出错:

实际情况class是非常复杂的一个类,而且在整个程序过程,处理逻辑代码量也是非常的大,手动的去一个一个找到mem*相关的函数完全不可能。

2.  利用windows CRT。我们知道windows程序都是基于CRT和windows API写成,基础函数库基本上都是CRT,内存分配的时候,CRT使用了一个小技巧。

就是在调用内存分配函数后(malloc, new)后,crt会对内存块做标记,表示分配了一块新的内存,如下:

在内存块前后标记4字节fd,用以delete的时候检测这些值,以便于CRT检测到堆栈溢出的问题。

通过delete时候出现堆栈溢出错误可以分析到,可能是前面4字节或者后面4个字节出现问题,那么我们继续走下去看看:

果然可以看到最后4个字节出现了破坏。。


3. 内存断点。在漫长的代码中去排查哪个地方修改了数据是吃力不讨好的工作,这个时候可以借助内存断点工具。

于是重新启动程序,在2中第一幅图new之后得到之前分析到的被破坏的这四个字节内存的地址(0x00AA98D0)

vs 调试->新建断点->新建数据断点

这样就可以在这块内存数据发生改变的时候中断在断点。


4. 然后我们继续运行程序直到程序进入断点。如下:

程序会中断出现数据更新的地方,也就是图中的memcpy处,这个时候发现很奇怪堆栈信息并不是我们直观上想要的结果,这个时候我们只要跳出memcpy函数内部

能看到我们熟悉的堆栈信息:


5. 分析附近的代码。这个时候问题已经定位到很小很小甚至具体到某个系统API调用上(本例是在memcpy的时候),于是稍微分析就能找到问题,在我们的程序中发现memcpy多了一些内存。


6. 总结,类似的问题不一定都是memcpy上,也不一定是如我们例子中的下溢,有可能出现上溢,都可以通过这种方式找到内存方面的问题。

它不光可以作为一个解决内存问题的方法来做,也可以当作条件断点一样来实时观察内存中某一块数据何时发生改变。


遗憾的是就目前为止内存断点能检测的内存大小都不会超过一般的内置类型的大小,当需要查找大范围内存的时候,需要连续设置多个内存断点来找到问题。


1 0
原创粉丝点击