在 C++ 中调用 free() 在调试中触发 ntdll!DbgBreakPoint() 但在发布时崩溃
Posted
技术标签:
【中文标题】在 C++ 中调用 free() 在调试中触发 ntdll!DbgBreakPoint() 但在发布时崩溃【英文标题】:Calling free() in C++ triggers ntdll!DbgBreakPoint() in debug but crashes in release 【发布时间】:2017-07-18 09:08:04 【问题描述】:我有一个单线程程序,当在 非调试 模式下运行时,在调用 free()
之后,该程序会在某些点持续崩溃。
然而,在 debug 模式下,调试器会在调用free()
的行上中断,即使没有设置断点。当我尝试再次进入下一行时,调试器再次在同一行中断。再次步进恢复执行为正常。没有崩溃,没有段错误,什么都没有。
EDIT-1:与我上面写的相反,在 非调试 模式下崩溃 结果是在一致,这让我觉得我是不知何故 在我不应该写的某处。 (debug 模式下的中断是 不过还是一致的。)
中断处的调用堆栈显示了在调用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)
【问题讨论】:
看起来你在堆中打乱了,或者为一个已经删除的对象调用了 delete。有点难找的东西。从代码中删除 more'n'more 直到错误消失,或者将所有指向对象的指针设置为 0,直到删除相应的指针。然后你使用特殊的删除/释放函数来检查非 0 参数。这可能会有所帮助,但如果你复制一个指针,你会错过它。 这种错误报告通常意味着以下三种情况之一:1) 双重释放,2) 释放从未实际分配的指针,或 3) 堆已损坏。看看你试图释放的指针指向的内存;如果它包含以下链接中提到的“签名”值之一,您可能会知道出了什么问题:***.com/a/370362/12711 即使您使用的是 GCC,因为它依赖于msvcrt
DLL,以下文章可能会有所帮助:docs.microsoft.com/en-us/visualstudio/debugger/…
您可以使用`gflags`在它自己的页面上设置每个堆分配,这可能有助于触发调试器在发生损坏时得到通知,而不是事后检测到它:docs.microsoft.com/en-us/windows-hardware/drivers/debugger/…跨度>
@MichaelBurr :我发现了这个错误。缓冲区溢出如预期。我以前从未使用过 Visual Studio,因此实际上我花了更长的时间来启动它并在我的项目中运行,而不是找到错误。但是没有它,肯定会麻烦很多。立即报告堆损坏。从那时起,正如您所建议的,这只是几分钟的签名内存值检查。谢谢你的建议。
【参考方案1】:
当我看到一个看起来像你的调用堆栈时,最常见的原因是堆损坏。双重释放或尝试释放从未分配过的指针可能具有类似的调用堆栈。由于您将崩溃描述为不一致,这使得堆损坏更有可能成为候选者。双重释放和释放未分配的指针往往会在同一个地方持续崩溃。为了寻找这样的问题,我通常:
-
为 Windows 安装调试工具
使用提升的权限打开命令提示符
将目录更改为安装 Windows 调试工具的目录。
通过运行 gflags.exe -p /enable applicationName.exe /full 启用整页堆
启动附加了调试器的应用程序并重新创建问题。
通过运行 gflags.exe -p /disable applicationName.exe 禁用应用程序的整页堆
使用全页堆运行应用程序会在每次分配的末尾放置一个不可访问的页面,以便程序在访问超出分配的内存时立即停止。这是根据页面GFlags and PageHeap。如果缓冲区溢出导致堆损坏,则此设置应导致调试器在溢出发生时中断..
确保在完成调试后禁用页面堆。在整页堆下运行会极大地增加应用程序的内存压力,因为每次堆分配都会占用一整页。
【讨论】:
【参考方案2】:您可以使用 valgrind 检查您的 CODE 中是否有任何无效的读/写或任何无效的空闲。 valgrind -v --leak-check=full --show-reachable=yes --log-file=log_valgrind ./Process
log_valgrind 将包含无效的读/写。
【讨论】:
我不知道 Valgrind for Windows 的最新端口。 在删除调用之前放置断点并打印您要释放的对象的值。以上是关于在 C++ 中调用 free() 在调试中触发 ntdll!DbgBreakPoint() 但在发布时崩溃的主要内容,如果未能解决你的问题,请参考以下文章