在C++中调用free()函数会在调试模式下触发ntdll!DbgBreakPoint(),但在发布模式下会导致崩溃。

5
我有一个单线程程序,在非调试模式下,在某些点上经常在调用free()之后崩溃。然而,在调试模式下,即使没有设置断点,调试器也会在调用free()的那一行中断。当我尝试再次跳到下一行时,调试器再次在同一行中断。再次单步执行后,正常恢复执行,没有崩溃、段错误或其他异常。
编辑-1:与我上面写的相反,在非调试模式下崩溃是不一致的,这让我认为我正在某个不应该写入的地方写入了一些内容(但调试模式下仍然一致)。
在断点处的调用堆栈显示在调用free()语句之后调用了一些Windows库函数。我不知道如何解释它们。因此,在这种情况下,我也不知道如何进行调试。
下面是断点处的调用堆栈信息。能否指导我可以解决这个问题的方向?是什么原因导致调试器模式下断点?
该程序在Windows Vista上运行,使用gcc 4.9.2编译,调试器使用gdb。假设不是双重释放(我使用::operator new::operator delete重载来捕获它)。即使没有这些重载,所描述的情况也是一样的。
请注意,崩溃(或调试器中的非自愿休息)是稳定的。每次在相同的执行点发生。下面是初始断点处的调用堆栈:(请注意,free_wrapper()是包含导致崩溃/中断的free()语句的函数。)
#0 0x770186ff   ntdll!DbgBreakPoint() (C:\Windows\system32\ntdll.dll:??)
#1 0x77082edb   ntdll!RtlpNtMakeTemporaryKey() (C:\Windows\system32\ntdll.dll:??)
#2 0x7706b953   ntdll!RtlImageRvaToVa() (C:\Windows\system32\ntdll.dll:??)
#3 0x77052c4f   ntdll!RtlQueryRegistryValues() (C:\Windows\system32\ntdll.dll:??)
#4 0x77083f3b   ntdll!RtlpNtMakeTemporaryKey() (C:\Windows\system32\ntdll.dll:??)
#5 0x7704bcfd   ntdll!EtwSendNotification() (C:\Windows\system32\ntdll.dll:??)
#6 0x770374d5   ntdll!RtlEnumerateGenericTableWithoutSplaying() (C:\Windows\system32\ntdll.dll:??)
#7 0x75829dc6   KERNEL32!HeapFree() (C:\Windows\system32\kernel32.dll:??)
#8 0x75a99c03   msvcrt!free() (C:\Windows\system32\msvcrt.dll:??)
#9 0x350000 ?? () (??:??)
--> #10 0x534020    free_wrapper(pv=0x352af0) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\Unrelated\MemMgmt.cpp:282)
#11 0x407f74    operator delete(pv=0x352af0) (C:\dm\bin\codes\CodeBlocks\ProjTemp\main.cpp:1002)
#12 0x629a74    __gnu_cxx::new_allocator<char>::deallocate(this=0x22f718, __p=0x352af0 "\nÿÿÿÿÿÿº\r%") (C:/Program Files/CodeBlocks/MinGW/lib/gcc/mingw32/4.9.2/include/c++/ext/new_allocator.h:110)
#13 0x6c2257    std::allocator_traits<std::allocator<char> >::deallocate(__a=..., __p=0x352af0 "\nÿÿÿÿÿÿº\r%", __n=50) (C:/Program Files/CodeBlocks/MinGW/lib/gcc/mingw32/4.9.2/include/c++/bits/alloc_traits.h:383)
#14 0x611940    basic_CDataUnit<std::allocator<char> >::~basic_CDataUnit(this=0x22f714, __vtt_parm=0x781df4 <VTT for basic_CDataUnit_TDB<std::allocator<char> >+4>, __in_chrg=<optimized out>) (include/DataUnit/CDataUnit.h:112)
#15 0x61dfa1    basic_CDataUnit_TDB<std::allocator<char> >::~basic_CDataUnit_TDB(this=0x22f714, __in_chrg=<optimized out>, __vtt_parm=<optimized out>) (include/DataUnit/CDataUnit_TDB.h:125)
#16 0x503898    CTblSegHandle::UpdateChainedRowData(this=0x353cf8, new_row_data=..., old_row_fetch_res=..., vColTypes=..., block_hnd=...) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\SegHandles\CTblSegHandle.cpp:912)
#17 0x502fcc    CTblSegHandle::UpdateRowData(this=0x353cf8, new_row_data=..., old_row_fetch_res=..., vColTypes=..., block_hnd=...) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\SegHandles\CTblSegHandle.cpp:764)
#18 0x443272    UpdateRow(row_addr=..., new_data_unit=..., vColTypes=..., block_hnd=..., seg_hnd=...) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\DbUtilities.cpp:910)
#19 0x443470    UpdateRow(row_addr=..., vColValues=..., vColTypes=...) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\DbUtilities.cpp:935)
#20 0x4023e3    test_RowChaining() (C:\dm\bin\codes\CodeBlocks\ProjTemp\main.cpp:234)
#21 0x4081c6    main() (C:\dm\bin\codes\CodeBlocks\ProjTemp\main.cpp:1034)

当我步入下一行代码时,调试器再次中断执行并显示了此时的调用堆栈:

最后一次

#0 0x770186ff   ntdll!DbgBreakPoint() (C:\Windows\system32\ntdll.dll:??)
#1 0x77082edb   ntdll!RtlpNtMakeTemporaryKey() (C:\Windows\system32\ntdll.dll:??)
#2 0x77052c7f   ntdll!RtlQueryRegistryValues() (C:\Windows\system32\ntdll.dll:??)
#3 0x77083f3b   ntdll!RtlpNtMakeTemporaryKey() (C:\Windows\system32\ntdll.dll:??)
#4 0x7704bcfd   ntdll!EtwSendNotification() (C:\Windows\system32\ntdll.dll:??)
#5 0x770374d5   ntdll!RtlEnumerateGenericTableWithoutSplaying() (C:\Windows\system32\ntdll.dll:??)
#6 0x75829dc6   KERNEL32!HeapFree() (C:\Windows\system32\kernel32.dll:??)
#7 0x75a99c03   msvcrt!free() (C:\Windows\system32\msvcrt.dll:??)
#8 0x350000 ?? () (??:??)
--> #9 0x534020 free_wrapper(pv=0x352af0) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\Unrelated\MemMgmt.cpp:282)
#10 0x407f74    operator delete(pv=0x352af0) (C:\dm\bin\codes\CodeBlocks\ProjTemp\main.cpp:1002)
#11 0x629a74    __gnu_cxx::new_allocator<char>::deallocate(this=0x22f718, __p=0x352af0 "\nÿÿÿÿÿÿº\r%") (C:/Program Files/CodeBlocks/MinGW/lib/gcc/mingw32/4.9.2/include/c++/ext/new_allocator.h:110)
#12 0x6c2257    std::allocator_traits<std::allocator<char> >::deallocate(__a=..., __p=0x352af0 "\nÿÿÿÿÿÿº\r%", __n=50) (C:/Program Files/CodeBlocks/MinGW/lib/gcc/mingw32/4.9.2/include/c++/bits/alloc_traits.h:383)
#13 0x611940    basic_CDataUnit<std::allocator<char> >::~basic_CDataUnit(this=0x22f714, __vtt_parm=0x781df4 <VTT for basic_CDataUnit_TDB<std::allocator<char> >+4>, __in_chrg=<optimized out>) (include/DataUnit/CDataUnit.h:112)
#14 0x61dfa1    basic_CDataUnit_TDB<std::allocator<char> >::~basic_CDataUnit_TDB(this=0x22f714, __in_chrg=<optimized out>, __vtt_parm=<optimized out>) (include/DataUnit/CDataUnit_TDB.h:125)
#15 0x503898    CTblSegHandle::UpdateChainedRowData(this=0x353cf8, new_row_data=..., old_row_fetch_res=..., vColTypes=..., block_hnd=...) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\SegHandles\CTblSegHandle.cpp:912)
#16 0x502fcc    CTblSegHandle::UpdateRowData(this=0x353cf8, new_row_data=..., old_row_fetch_res=..., vColTypes=..., block_hnd=...) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\SegHandles\CTblSegHandle.cpp:764)
#17 0x443272    UpdateRow(row_addr=..., new_data_unit=..., vColTypes=..., block_hnd=..., seg_hnd=...) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\DbUtilities.cpp:910)
#18 0x443470    UpdateRow(row_addr=..., vColValues=..., vColTypes=...) (C:\dm\bin\codes\CodeBlocks\ProjTemp\src\DbUtilities.cpp:935)
#19 0x4023e3    test_RowChaining() (C:\dm\bin\codes\CodeBlocks\ProjTemp\main.cpp:234)
#20 0x4081c6    main() (C:\dm\bin\codes\CodeBlocks\ProjTemp\main.cpp:1034)

2
看起来你的堆被搞乱了,或者对已经删除的对象调用了delete。这种问题很难找到。从代码中逐渐删除更多内容,直到错误消失,或将所有指向对象的指针设置为0,直到删除相应的指针。然后使用特殊的delete/free函数来检查非0参数。这可能有所帮助,但如果你复制了一个指针,你会错过它。 - harper
1
这种类型的 Bug 报告通常意味着以下三种情况之一:1)双重释放,2)释放一个实际上从未分配的指针,或者3)堆已被破坏。请查看您正在尝试释放的指针指向的内存;如果它包含以下链接中提到的“签名”值之一,您可能会得到有关出了什么问题的提示:https://dev59.com/ZXRC5IYBdhLWcg3wOeWB#370362 - Michael Burr
2
即使您正在使用GCC,由于它依赖于“msvcrt” DLL,因此以下文章可能会有所帮助:https://learn.microsoft.com/en-us/visualstudio/debugger/crt-debug-heap-details - Michael Burr
2
你可以使用 gflags 来将每个堆分配单独放置在它自己的页面上,这有助于触发调试器在腐败发生时立即收到通知,而不是事后检测:https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags-and-pageheap - Michael Burr
1
@MichaelBurr:我发现了这个 Bug。正如预期的那样,是缓冲区溢出。我之前从未使用过 Visual Studio,因此将其与我的项目配合使用花费的时间比找到 Bug 更长。但如果没有它,那肯定会更麻烦。我立即报告了堆栈损坏。从那时起,就像你建议的那样,只需要几分钟的签名内存值检查。感谢你的建议。 - Kemal
显示剩余7条评论
2个回答

4
当我看到像你这样的调用堆栈时,最常见的原因是堆损坏。双重释放或尝试释放从未分配的指针可能会导致类似的调用堆栈。由于您将崩溃描述为不一致,这使得堆损坏成为更有可能的候选项。双重释放和释放未分配的指针往往会在同一位置一致地崩溃。为了查找此类问题,我通常会执行以下操作:
  1. 安装Windows调试工具
  2. 使用提升的权限打开命令提示符
  3. 将目录更改为已安装Windows调试工具的目录。
  4. 通过运行gflags.exe -p /enable applicationName.exe /full启用完整页面堆。
  5. 使用附加了调试器的应用程序并重新创建问题启动应用程序。
  6. 通过运行gflags.exe -p /disable applicationName.exe禁用应用程序的完整页面堆。
使用完整页面堆运行应用程序会在每个分配的末尾放置一个无法访问的页面,因此如果程序访问超出分配的内存,则会立即停止。这是根据页面GFlags and PageHeap的规定。如果缓冲区溢出导致堆破坏,则此设置应该会导致调试器在溢出发生时中断。
确保在调试完成后禁用页面堆。在完整页面堆下运行会大大增加应用程序的内存压力,因为每个堆分配都会消耗整个页面。

哇,这真的帮了我大忙。在启用该标志后,我在我搞砸内存释放逻辑的地方确实遇到了崩溃,而不是一些与问题无关的随机混乱地方! - ScienceDiscoverer

-1
你可以使用valgrind检查你的代码中是否存在任何无效读/写操作或者无效释放内存的情况。命令如下:
valgrind -v --leak-check=full --show-reachable=yes --log-file=log_valgrind ./Process
日志文件log_valgrind将包含无效读/写操作信息。

我不知道是否有最新的Valgrind Windows端口。 - Paul Floyd
在删除调用之前设置断点,并打印您尝试释放的对象的值。 - Rohit

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