Visual Studio - 如何找到堆栈破坏错误的源头

45

我想知道在Visual Studio中,是否有一种好的方法可以找到导致堆栈损坏错误的源代码,只给出写入“外部”的内存地址;

专用(0008)自由列表元素26F7F670的大小不正确(已死亡)

(试图记录如何查找内存错误的笔记)

9个回答

78

首先安装Windbg:

http://www.microsoft.com/whdc/Devtools/Debugging/default.mspx

然后按照以下步骤打开pageheap:

gflags.exe /p /enable yourexecutable.exe /full

这将在每个堆分配后插入一个不可写页面。

之后从windbg内部启动可执行文件,任何堆外的写操作现在都将被该调试器捕获。要关闭pageheap,请使用以下命令:

gflags.exe /p /disable yourexecutable.exe

点击此处获取如何使用pageheap的更多信息。


7
最佳解决方案!救了我的命。同时,直接打开gflags.exe并使用GUI也可以。进入“Image File”,填写exe文件名,并勾选“启用页面堆”。任何调试器都可以使用。 - leoly
3
非常感谢。我花了两天时间才找到这个技巧。我只是勾选了“启用页面堆”,然后像平常一样使用Visual Studio调试器。然后,它会在导致堆破坏错误的代码位置处精准断点。我的bug是由于一个线程不断地提供数据,而另一个线程仅分配足够存储旧数据的内存,这对于存储新数据是不够的。 - khanhhh89
在我的情况下,这并没有帮助。打开此标志使程序正常工作(已测试多次)。一旦关闭该标志,程序将崩溃并显示堆损坏错误。 - mistika
6
使用时要非常小心!!!我曾遇到这样的情况,设置了所有 gflags 但之后忘记禁用它们。花费了整整一周才发现这些标志导致了下一个问题。 - Dominique
1
正在调试我的代码,但第三方dll有问题 - 所以我使用了“/dll DLL [**, DLL]”而不是“/full”,只选择了我的dll。 - Robert Andrzejuk
显示剩余4条评论

12

针对 Windows 10 操作系统,你可以通过 GFlags 工具中的 PageHeap 选项 进行启用。该工具是作为 Windows 调试工具集 的一部分进行提供。

GFlags 中的 Page Heap 选项使你可以选择标准堆验证或完整页面堆验证。需要注意的是,完整堆验证会为每个分配使用一个完整的内存页,因此可能会导致系统内存短缺。

要在 GFlags 中启用 Page Heap:

• 启用 标准页堆验证,标准版本将在每个堆分配的末尾写入一个模式,并在释放分配时检查该模式。

要验证所有进程,请使用以下命令:

gflags /r +hpa

gflags /k +hpa

要验证单个进程,请使用以下命令:

gflags /p /enable ImageFileName

• 启用一个进程的 完整页面堆验证,此选项在每个分配的末尾放置一个不可访问的页面,以便程序如果尝试访问超出分配范围的内存时会立即停止运行。 由于内存消耗较大,因此应只在单个进程上使用该选项。

gflags /i ImageFileName +hpa

gflags /p /enable ImageFileName /full

上述两个命令可以互换使用。

注意:上述所有页堆设置都是全局设置,存储在注册表中(除了 /k)并保持有效,直到你更改它们。 /k 设置是内核标志设置,仅在此会话中生效,当 Windows 关闭时将丢失此设置。

另一个有用的工具是Application Verifier,但它不是 Windows 调试工具的一部分,而是包含在Windows 软件开发工具包 (SDK)中。


5
也许你可以尝试使用微软的Application Verifier。它可以通过在堆操作上启用额外的检查来解决类似问题。我认为地址损坏的随机性是因为堆可能被“微妙地”损坏,而问题直到堆发生重大变化(如大量分配/释放)才会显现出来。

2

您可以在写入内存地址时设置断点。调试器将显示写入位置的代码,但您仍然需要确定哪些写入导致了问题。


2

2

如果您的代码能使用gcc编译并在Linux上运行,那么您可以使用valgrind来找到问题的源头(我不记得具体的标志,但我曾经成功地使用过一次)。


祝你好运,微软已经付出了很大的努力来确保他们的字符串处理命令与尽可能多的标准不兼容。但如果非常简单的话,你可能会得逞。 - Owl

1

我假定使用C++语言。

如果错误是可复现的并且损坏的地址始终相同,您可以设置数据断点以在写入该地址时停止程序。


1
语言是混合的C/C++。每个调试会话中损坏的地址都不同,所以我猜不能使用数据断点。 - Danne
1
你说得很不幸却是对的。 在这些情况下,我的做法是将 #define free/delete 设置为无效。如果问题消失了,我就会将 malloc/new/free/delete 定义为一个记录每个调用的函数,以便找出重复删除或未分配的删除。 - Timores

1
请确保您链接的所有库都与运行的应用程序编译在相同的CLR版本中 - 所有都是Release或所有都是Debug。
当您同时编译Debug和Release时,实际上是针对两个不同版本的C运行时库。这些版本非常不同,并且它们使用不同的内存分配策略和不同的堆。但最重要的是,它们彼此不兼容。
Release C运行时库按预期分配内存,而Debug会添加额外的信息,例如警卫块以跟踪缓冲区溢出和调用分配函数的位置,并因此分配比Release更多的内存。
如果您将应用程序链接到在Release和Debug中构建的DLL混合体,则最有可能尝试在一个CLR中删除在另一个CLR中创建的对象。这意味着您将尝试释放比对象分配的更多或更少的内存,这可能会破坏堆。
您应该构建应用程序并连接到使用相同配置构建的库,即Release或Debug。
特别是在使用不同编译器编译的模块中,可能会出现此问题。

有一种方法可以解决问题,但我不建议使用。如果由于某些原因您仍然需要在不同的模式下构建,则此解决方法将允许从同一共享堆中分配和释放所有内存。API GetProcessHeap将允许您在不同的模块中访问共享堆。通过使用HeapAlloc和HeapFree,您可以在共享堆中分配和释放内存。注意:HeapAlloc和HeapFree应替换应用程序中所有对malloc和free的调用。


我在我的Debug和Release版本中都使用相同版本的第三方库(例如OpenCV)进行编译。据我所知,这只意味着在Debug模式下,我无法进入任何第三方代码,并且它在Debug模式下运行速度也会更快。你认为我错了吗? - ILIA BROUDNO
通常情况下,分发的第三方库会提供一个发布库,供其他人在发布或调试模式下使用。他们通过构建DLL来包含他们自己的C运行时库,并确保不跨库边界共享CRT资源(如堆),以确保动态分配的内存将在边界的同一侧被释放。总之,如果您链接的第三方库已经这样做了,那么在发布和调试中使用它们应该没有问题。 - Merav Kochavi
@ILIABROUDNO 如果这对您有帮助,请点赞 :) - Merav Kochavi

0

它救了我的命,我正在调试X-Plane 12堆栈损坏问题,这已经花费了我一个星期的时间,但没有任何进展。

gflags.exe位于C:\ Program Files \ Debugging Tools for Windows(x64)\ gflags.exe

并且必须以管理员身份运行,然后GUI图像输入使用X-Plane.exe,不要使用X-Plane.exe的路径。

然后点击“启用页面堆”,最后应用。 但是“启用页面堆”对于您的情况来说太强了,因为它会占用大量内存。

好的,然后您可以像往常一样启动X-Plane 12,但是您会发现X-Plane的启动时间比以前长得多,这对于调试目的来说并不是问题。

Global Flags


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接