user breakpoint called from code at 0x...

来源:互联网 发布:c语言课程coursera 编辑:程序博客网 时间:2024/05/17 20:22

处理在NTDll中意外的用户断点

很久没有写东西了,这次是为了完善很久很久以前写的一个培训ppt(VC的使用与调试技巧),才想起来写点东西的。
下面的文章参考了http://www.debuginfo.com/tips/userbpntdll.html,但不是翻译,偶英语太烂了。


我们在调试程序的过程中,有时会突然的显示一个对话框,上面显示这样一条信息:
User breakpoint called from code at 0x77fa018c
或者是
Unhandled exception at 0x77f767cd (ntdll.dll) in myapp.exe: User breakpoint.
不过我遇到过的都是第一条信息,没有遇到过第二条信息。

怎么回事?我们没有设置断点呀!为什么会有一个用户断点?
并且这个问题看起来并没有那么严重,不在调试状态下,程序正常运行,即使在调试状态下我们把这个对话框按了确定后,再继续F5,好像什么事情也没有发生,程序仍然在正常运行!

隐患!千万不要忽视她!这个信息告诉我们,程序中某个地方已经开始溃烂,如果你频繁的碰到这个对话框,就说明溃烂已经扩大了。

如果你够仔细,你会发现在你点了这个对话框的确定按钮之后,会在Output窗口中发现多了一行信息:
HEAP[DebugInfo2.exe]: Heap block at 00030FD8 modified at 00031010 past requested size of 30

查看堆栈窗口,里面显示这样的信息:
NTDLL! 7c921230()
NTDLL! 7c97db9c()
NTDLL! 7c98cd11()
NTDLL! 7c980af8()
KERNEL32! 7c85e7af()
_CrtIsValidHeapPointer(const void * 0x00031000) line 1697
_free_dbg(void * 0x00031000, int 0x00000001) line 1044 + 9 bytes
operator delete(void * 0x00031000) line 49 + 16 bytes
WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00151f15, int 0x00000001) line 13 + 15 bytes
WinMainCRTStartup() line 198 + 54 bytes
KERNEL32! 7c816d4f()

重现这样的现场很容易的,只需几行代码就可以了
char *p = new char[4];
lstrcpy(p, "this is a test");
delete p;

为什么会有这样的信息消息框出现呢?这是因为如果我们在调试状态下,操作系统用DebugWin32Heap来替代正常的heap分配内存空间。在这个堆上的任何操作,debug的堆管理器会检查堆的数据完整性,如果它发现了一个错误,就会报告一个消息上来。

那么,我们怎么样才能知道造成错误的原因呢?如果只是类似上面的演示代码,不用任何技巧都能发现并且定位的。但是,绝大多数情况下,Output窗口和CallStack窗口告诉我们的信息都不能让我们精确定位。

用BoundsChecker做运行期的检查可以查到这个错误的原因,并且我个人认为在程序发布之前,在BoundsChecker下完整的跑一遍,检查内存和资源是非常有必要的。但是每次都用BoundsChecker是比较麻烦的。

还有一个办法,可以让这种定位更准确一点。

那就是windows 2000 SP2之后提供的PageHeap机制。

下面的这段文字是别人写的,我抄的,:)
当一个应用程序的PageHeap机制被激活时,该应用程序的所有的堆分配被放到内存中,这样堆的 边界就与虚拟内存的边界排在一起了。与堆相邻的虚拟内存页面被设置为NO_ACCESS。在该应用程序中对堆后面的空间的访问就会立刻引起错误,这就可以 在一个调试工具中被捕获。在释放堆时,过程与之类似。PageHeap修改释放的应用程序虚拟页面为NO_ACCESS,这样,如果应用程序试图读写该内 存时就会发生访问错误。如果为一个应用程序运行PageHeap特性,应用程序要比正常时运行得慢,并且需要更多的虚拟内存,因为每一个堆的分配都需要两 个完整的虚拟内存页面。随着应用程序对堆的使用的增加,可能需要增加系统的虚拟内存的大小,否则会出现虚拟内存不够的错误信息。除非系统有相当大的虚拟内 存,否则建议不要同时运行两个以上的激活了PageHeap特性的应用程序。

让我们的程序启用Full Page Heap机制,只需在注册表中
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Image File Execution Options/YourAppName.exe下增加如下设置:
GlobalFlag,字符串类型,值是x02200000
PageHeapFlags,字符串类型,值是0x3
VerifierFlags,DWORD类型,值是00000001
其中YourAppName.exe换成你的程序的名字,不要带路径。

如果嫌麻烦,你可以到http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx下载Debugging Tools for Windows,安装后,有一个gflags工具,使用下面的命令:
gflags –p /enable YourAppName.exe /full
这个工具帮你写好了上面的注册表项目。
这个工具是一个GUI的程序,你可以双击并且设置相关调试入口。

如果设置了Full PageHeap模式,我们在调试运行的时候,那么就会在弹出那个令人费解的断点对话框之前出现一个断言对话框,哈哈,这个对话框可是有一个Retry按钮的,点击之后进入CallStack,你可以看到此时发生了什么了。

如果你编写的是一个dll,也可以使用这个机制的,不过注册表项下的值就要设置成如下:
GlobalFlag,字符串类型,值是0x02000000"
PageHeapTargetDlls,字符串类型,值是调试的dll名称,不带路径
VerifierFlags,DWORD类型,值是00000001
PageHeapFlags,字符串类型,0x403

或者使用命令行
gflags –p /enable YourAppName.exe /full /dlls YourDll.dll

--------------------原文--------------------------------------------

unexpected user breakpoint in ntdll.dll

Updated: 01.09.2005

We were in the middle of the debugging session, when suddenly the debugger displayed a message similar to the following:

User breakpoint called from code at 0x77fa018c

or the following:

Unhandled exception at 0x77f767cd (ntdll.dll) in myapp.exe: User breakpoint.

What happened? We did not even set any breakpoints, so why there is a user breakpoint? The problem starts looking even stranger when we notice that the application works fine when we start it outside of the debugger! But under debugger it keeps showing the message about user breakpoint, so that we cannot effectively debug our application anymore.

Searching for an answer, we look at Debug Output window. And yes, there is another message! For example:

HEAP[myapp.exe]: Heap block at 00360F78 modified at 00360F90 past requested size of 10

Aha, it looks like the heap is corrupted. The contents of Call Stack window also point to the heap:

NTDLL! DbgBreakPoint@0 address 0x77fa018c
NTDLL! RtlpBreakPointHeap@4 + 38 bytes
NTDLL! RtlpCheckBusyBlockTail@4 + 106 bytes
NTDLL! RtlpValidateHeapEntry@12 + 146975 bytes
NTDLL! RtlDebugFreeHeap@12 + 191 bytes
NTDLL! RtlFreeHeapSlowly@12 + 70765 bytes
NTDLL! RtlFreeHeap@12 + 4078 bytes
MYAPP! free + 102 bytes
MYAPP! operator delete(void *) + 9 bytes
main(int 1, char * * 0x003610b0) line 21 + 15 bytes
MYAPP! mainCRTStartup + 180 bytes
KERNEL32! BaseProcessStart@4 + 130958 bytes

It turns out that when we start the application under debugger, the operating system uses a special kind of heap – Debug Win32 heap – instead of the normal heap. Whenever an operation on the heap is performed (such as allocating or freeing memory), the debug heap manager checks integrity of the heap entries and internal structures. If it founds a corruption, it notifies us via a hard-coded breakpoint, which is reported by the debugger.

How can we find the reason of the heap corruption? We can of course employ a specialized runtime checker application (such as BoundsChecker, Purify or Memory Validator), but I prefer to use PageHeap, because it is part of the operating system (since Windows 2000 SP2) and is easy to use. Detailed technical information about PageHeap can be found in KB286470.

We can enable Full PageHeap with the help of the following Registry entries:

HKLM/Software/Microsoft/Windows NT/CurrentVersion/Image File Execution Options/yourapp.exe
GlobalFlag = REG_DWORD 0x2000000
PageHeapFlags = REG_DWORD 0x3

(replace yourapp.exe with the real name of your executable)

If you have Debugging Tools for Windows installed, there is another way to achieve the same – run the following command:

gflags –p /enable yourapp.exe /full

(gflags.exe can be found in the installation directory of Debugging Tools)

After we have enabled Full PageHeap, we should run the application under debugger and wait for access violations or hard-coded breakpoints. After an access violation occurred or a breakpoint has been hit, examination of the current source line and the call stack usually allows us to immediately see who is corrupting the heap.

Note also that it is recommended to link the application with release version of CRT library (that is, use /ML, /MT or /MD compiler option (or the corresponding IDE equivalent) instead of /MLd, /MTd or /MDd option). This is because debug version of CRT library uses its own heap checking infrastructure (by the way, not as powerful as Full PageHeap), which can overlap with PageHeap and hide some errors instead of exposing them.

Another situation that worth talking about is when we are debugging a DLL that is loaded by a large application (often a 3rd party application). Since there is probably no point in checking the whole application with PageHeap, we can ask it to check only the heap entries that are allocated by our DLL:

gflags –p /enable yourapp.exe /full /dlls yourdll.dll
 
摘自:http://hi.baidu.com/futurecreator/blog/item/636ab4081665ce226b60fbe7.html

 

解释二:

在调试程序中遇到提示“user  breakpoint  called  from  code  at  0x......(地址)”时,这并不一定是因没用户设置了断点的关系,而是因为系统执行了一个硬编码断点操作(hard  coded  breakpoint  instruction)。   
 
例如在Windows  NT下当正被调试的应用程序获得焦点时,如果F12键按下,
 

则Windows  NT调用一个类似于DebugBreak()函数的函数,这个被调用

的函数执行一个hard  coded  breakpoint  instruction,于是调试器捕捉到这个操作产生的例外,所以中断并给出上述的提示(User  breakpoint  called  from  code  at  <address>)或者是给出Break  caused  by  hard  coded  breakpoint  instruction.,在这样的情况下,只要让调试继续进行即可(按下F5键)。   
 
值得注意的是当程序涉及到全局性的共享内存(对象)时,如CShareFile,这些对象维护着一个全局性的内存块,如果因为程序的疏忽对仍然锁定的对象 (locked  object)进行释放内存的话,则在NT下也会出现User  breakpoint  called  from  code  at  <address>这样的提示。   
 
此外,在低版本的Visual  C++调试器中,在不少地方(例如一些有关打印设备的操作)都用到GlobalLock,编程不当也会导致上述的提示出现,不过在win32中,很多这样的调用已经不再必要了。   
 
希望这些信息能对您有所帮助,建议您检查一些是不是程序当中有一些涉及到释放全局性内存的操作。

 

摘自:http://blog.csdn.net/jiahehao/archive/2007/11/06/1869891.aspx

解释三:

    在调试程序中遇到提示“user  breakpoint  called  from  code  at  0x......(地址)”时,这并不一定是因为用户设置了断点的关系,而是因为系统执行了一个硬编码断点操作(hard  coded  breakpoint  instruction)。 
     例如在Windows  NT下当正被调试的应用程序获得焦点时,如果F12键按下,则Windows  NT调用一个类似于DebugBreak()函数的函数,这个被调用的函数执行一个hard  coded  breakpoint  instruction,于是调试器捕捉到这个操作产生的例外,所以中断并给出上述的提示(User  breakpoint  called  from  code  at <address>)或者是给出Break  caused  by  hard  coded  breakpoint  instruction.,在这样的情况下,只要让调试继续进行即可(按下F5键)。 
     值得注意的是当程序涉及到全局性的共享内存(对象)时,如CShareFile,这些对象维护着一个全局性的内存块,如果因为程序的疏忽对仍然锁定的对象 (locked  object)进行释放内存的话,则在NT下也会出现User  breakpoint  called  from  code  at  <address>这样的提示。 
     此外,在低版本的Visual  C++调试器中,在不少地方(例如一些有关打印设备的操作)都用到GlobalLock,编程不当也会导致上述的提示出现,不过在win32中,很多这样的调用已经不再必要了。

     希望这些信息能对您有所帮助,建议您检查一些是不是程序当中有一些涉及到释放全局性内存的操作。
 
摘自:http://face-recognition.spaces.live.com/blog/cns!CEDE688463F4BF2A!196.entry