Advanced Breakpoints and How to Use Them

[Previous] [Next]

Setting a breakpoint on a source line in the Visual C++ debugger with a Win32 Debug or Win32 Unicode Debug project configuration is simple. Just load the source file, put the cursor on the line you want to stop on, roll the mouse to the Insert/Remove Breakpoint button, and click. In debugger terms, this process is called setting a location breakpoint. When the code for that line executes, the debugger will stop at that location. The ease of setting a location breakpoint belies its importance: the location breakpoint on a specific source code line is what separates the modern age of debugging from the debugging dark ages.

In the early days of computing, breakpoints simply didn't exist. Your only "strategy" for finding a bug was to run your program until it crashed and then wade through page after page of hexadecimal core-dump printouts of the state of memory looking for the problem. The only debuggers in the debugging dark ages were trace statements and faith. In the renaissance age of debugging, made possible by the introduction of higher-level languages, developers could set breakpoints but had to debug only at the assembly-language level. The higher-level languages still had no provisions for viewing local variables or seeing a program in source form. As the languages evolved into more sophisticated tools, the debugging modern age began, and developers were able to set a breakpoint on a line of source code and see their variables in a display that interpreted the variables' values into the exact type they specified. This simple location breakpoint is still extremely powerful, and with just it alone, you can solve 99.46 percent of your debugging problems.

However wonderful, though, location breakpoints can get tedious very quickly. What would happen if you set the breakpoint on a line inside a for loop that executes from 1 to 10,000 and the bug turned up on the 10,000th iteration? Not only would you give yourself carpal tunnel syndrome from pressing the key assigned to the Go command, but you would also spend hours waiting to get to the iteration that produced the bug. Wouldn't it be nice if there were some way to tell the debugger that you want the breakpoint to execute 9,999 times before stopping?

Fortunately, there is a way: welcome to the realm of advanced breakpoints. In essence, advanced breakpoints allow you to program some smarts into breakpoints, letting the debugger handle the menial chores involved in tracking down bugs and minimizing the time and effort you have to spend in the debugger. Some of the various conditions you can add with advanced breakpoints include having the breakpoint skip for a certain count, break when an expression is true, and break when a variable or memory address changes. The advanced breakpoint capabilities have finally moved debuggers solidly into the modern age, allowing developers to do in minutes what used to take hours with simple location breakpoints.

Advanced Breakpoint Syntax and Location Breakpoints

Before jumping into all the great ways you can use advanced breakpoints, I need to spend a little time going over the advanced breakpoint syntax. This syntax is important because the Breakpoints dialog box displays the breakpoints in this format. Fortunately, the syntax is relatively straightforward. Later in the chapter (Listing 5-1), I've included a project, AdvancedBP, that demonstrates each type of advanced breakpoint. This project is also on the companion CD, so you might want to open the project in Visual Studio and refer to it as you're working through this section.

The advanced breakpoint syntax is composed of two parts. The first part is the context portion, and the second part is the location, expression, variable, or Microsoft Windows message condition. You can think of the context portion just as you do the scope of a variable when programming. The context simply provides the debugger with an unambiguous location for your breakpoint.

In debugger terms, the function, the source file, and the binary module specify the context, and the context is delineated in advanced breakpoint syntax as {[function],[source file],[binary module]}. You need to specify only enough context information to get the breakpoint set. In your run-of-the-mill location breakpoint, all the information the debugger needs is the name of the source file. You've probably seen the simple location breakpoint format in the Breakpoints dialog box, perhaps without realizing what it is. If you set a location breakpoint on line 20 of TEST.CPP, for example, the Breakpoints dialog box displays it as {,TEST.CPP,}.20.

The ability to specify the context for a location breakpoint allows you to solve a particularly nasty type of debugging problem. Consider the case in which you have a source file with a diagnostic function, CheckMyMem, used by two dynamic-link libraries (DLLs), A.DLL and B.DLL, and the function appears in both DLLs by static linking. Because you're doing lots of proactive programming, you're calling the function a great deal from both DLLs. However, you're experiencing a random crash only in B.DLL. If you set a standard location breakpoint in the CheckMyMem source code, "{,CHECKMYMEM.CPP,}.27," the breakpoint will trigger in both DLLs even though you just want to see the calls made in B.DLL. To specify that you want the location breakpoint to trigger only in B.DLL, you would need to use the breakpoint context "{,CHECKMYMEM.CPP,B.DLL}.27."

On the Visual C++ debugger Breakpoints dialog box Location tab, you can type the context syntax directly into the Break At edit box; however, it's easier just to use the Advanced Breakpoint dialog box, shown in Figure 5-1. Click the arrow button to the right of the edit control to bring up the menu. On the menu, select Advanced, and then in the Context group box, type the appropriate information for your breakpoint.

click to view at full size.

Figure 5-1 Specifying the breakpoint context in the Advanced Breakpoint dialog box

The real muscle of advanced breakpoints is found in the second part of the advanced breakpoint syntax, in which you specify the location, expression, variable, or Windows message to break on. I introduced the simple location breakpoint syntax by showing a line location breakpoint. However, you can also break on other types of locations. If you want to break on an absolute address that you know, in the Breakpoints dialog box just type the address in the Break At edit box. For example, as I write this, I'm using the AdvancedBP program, which I compiled for you and which is available on the companion CD. The entry point of the application, mainCRTStartup, is 0x401210, so that's the address I'm using for the breakpoint. (Keep in mind that you must include the 0x prefix in the debugger to indicate a hexadecimal number.)

Quickly Breaking on Any Function

Because the Visual C++ debugger is smart enough to evaluate expressions, you have many interesting options for setting breakpoints. Here's one technique I find useful in the midst of my debugging battles: if I know the function I want to break on, instead of hunting all over the source code for the function, I just type the function name in the Break At edit box. If the symbol exists in one of the loaded modules, the debugger will put a breakpoint on the first instruction in the function. If your program is stopped in the debugger and the function you typed is wrong, the debugger will display a message box telling you so. When working with C++ code, type in the class qualifier as well. For example, to break on the base Microsoft Foundation Class (MFC) library CDialog class OnOK method, you would type CDialog::OnOK.

The debugger is also smart enough to know about overloaded class members and will prompt you for a specific version of the function. For example, in an MFC application, if you type CString::CString as the function on which to set the breakpoint, the debugger won't know which version of the constructor you're interested in and will prompt you with the Resolve Ambiguity dialog box, shown in Figure 5-2. The Resolve Ambiguity dialog box lists, in advanced breakpoint syntax, the eight CString constructors, from which you choose the appropriate version.

click to view at full size.

Figure 5-2 The Resolve Ambiguity dialog box

The easiest way to set breakpoints on complicated functions, such as class operators, is to type just enough information so that the debugger displays the Resolve Ambiguity dialog box. For example, the MFC CString class has overloaded assignment operators, so you would type just CString::operator= to see a list of them. You can also specify parameters to the function, if you know them, to set the breakpoint directly. With the CString::operator= example, you could type CString::operator=(const char *) and bypass the Resolve Ambiguity dialog box altogether.

Breakpoints on System or Exported Functions

This technique of setting a location breakpoint on the first instruction of a function is very powerful. If you try to set a breakpoint on a function that your program imports from a DLL, however, you'll be disappointed. It doesn't work. There's nothing wrong with the debugger; you just need to give it some context information about where it can find the function. Additionally, one other small detail is important: the function name depends on whether symbols for the DLL are loaded. Keep in mind that you can set a breakpoint on a system DLL function only in Microsoft Windows 2000. The lack of copy-on-write protection (discussed in Chapter 4) is the reason you can't set breakpoints on system functions in Windows 98 that load above the 2-GB memory line. To get this technique to work in Windows 2000, you must have Common Object File Format (COFF) and export loading turned on in the debugger. In Visual C++, on the Debug tab of the Options dialog box, make sure Load COFF & Exports is checked.

To illustrate how to set a breakpoint on a system DLL, I'll set a breakpoint on the KERNEL32.DLL LoadLibrary function. Because you already know how to set the context for a location breakpoint, you won't be surprised to see that the first part of the breakpoint is {,,KERNEL32.DLL}, to identify the function's module. The Visual C++ debugger follows a hierarchical symbol information approach in which more complete symbols take precedence over the less complete ones. Program Database (PDB) files, which have all the source line, function, variable, and type information possible, always take precedence over COFF/DBG files, which hold only public function symbols, and COFF/DBG files take precedence over exported names, which are a sort of pseudo symbol. If you want to confirm that the debugger loads symbols for a DLL, you need to monitor the Debug tab of the Output window. If the Output window says "Loaded symbols for 'DLL name'," you have full symbols for your DLL. Conversely, if it says "Loaded 'DLL name', no matching symbolic information found" or "Loaded exports for 'DLL name'," no symbols were loaded.

Since we're on the subject of symbols, I'll mention that you should always install the Windows 2000 symbols. They won't help you completely reverse engineer the operating system because they contain symbol names only for public symbols. If the symbols are loaded, however, you'll at least be able to see what function you're in when you're looking at the stack or the Disassembly window. Be aware that you need to update the operating system symbols each time you apply an operating system service pack. For Windows 2000, the symbols are on the Customer Support Diagnostics CD. For Windows NT 4, the Visual Studio setup includes a program, Windows NT Symbols Setup, that installs the symbols for you.

If symbols aren't loaded, the location string you'll use is the name exported from the DLL. You can check the name by running the DUMPBIN utility on the DLL: DUMPBIN /EXPORTS DLL Name. If you run DUMPBIN on KERNEL32.DLL, you won't see a LoadLibrary function but rather two similarly named functions, LoadLibraryA and LoadLibraryW. Suffixes indicate the character set used by the function; the A suffix stands for ANSI and the W stands for Wide, or Unicode. Windows 2000 uses Unicode internally for internationalization. If you compiled your program with _UNICODE defined, you'll want to use the LoadLibraryW version. If you didn't, you can use LoadLibraryA. However, LoadLibraryA is just a wrapper that allocates memory to convert the ANSI string to Unicode and calls LoadLibraryW, so technically you could use LoadLibraryW as well. If you know for sure that your program is going to call only one of these functions, you can just set the breakpoint on that function. If you're not sure, set breakpoints on both functions. If symbols aren't loaded, the breakpoint syntax for breaking on LoadLibrary is "{,,KERNEL32.DLL}LoadLibraryA" or "{,,KERNEL32.DLL}LoadLibraryW."

If your application is targeting only Windows 2000, you should use Unicode throughout. You can get a nice performance boost. Matt Pietrek, in his December 1997 "Under the Hood" column in Microsoft Systems Journal, reported that the ANSI wrappers had a sizable performance hit associated with them. In addition to having a faster program, you'll be several steps closer to full internationalization by using Unicode.

If symbols are loaded, you need to do some calculations because you'll need to match the decorated symbol name. What you need to know is the calling convention of the exported function and the function prototype. I'll get into much more detail about calling conventions in Chapter 6. For the LoadLibrary function, the prototype from WINBASE.H with some macros expanded for clarity, is as follows:

 __declspec (dllimport) HMODULE __stdcall LoadLibraryA(     LPCSTR lpLibFileName     ); 

The WINBASEAPI macro expands into the standard call calling convention, __stdcall, which, by the way, is the calling convention for all system application programming interface (API) functions. Standard call functions are decorated with an underbar prefix and suffixed with an "@" sign followed by the number of bytes pushed on the stack. Fortunately, calculating the number is easy; it's the sum of the parameter byte count. With the Intel Pentium family of CPUs, you can just count the number of parameters and multiply by 4. In the case of LoadLibrary, which takes one parameter, the final name is _LoadLibraryA@4. Here are some examples that will give you an idea of what final names look like: CreateProcess, which has 10 parameters, is _CreateProcessA@40, and TlsAlloc, which has no parameters, is _TlsAlloc@0. Even if a function doesn't have any parameters, you must keep the "@#" format. As is the case when symbols aren't loaded, the ANSI and Unicode conditions still apply. If symbols are loaded, the breakpoint syntax for breaking on LoadLibrary is "{,,KERNEL32.DLL}_LoadLibraryA@4" or "{,,KERNEL32.DLL}_LoadLibraryW@4."

Location Breakpoint Modifiers

After that brief sojourn into how to calculate the correct location of exported functions, you can now set a location breakpoint anywhere in your application with aplomb. Location breakpoints are great, but as I indicated at the opening of this section, you can also add some real smarts to them so that you can use the debugger even more efficiently. The vehicles for these "smarts" are skip counts, conditional expressions, and variable changes.

Skip Counts

The simplest modifier applicable to location breakpoints is a skip count. A skip count tells the debugger that it should put the breakpoint in but not stop on it until the breakpoint executes a specific number of times. With this modifier, breaking inside loops at the appropriate time is trivial.

Adding a skip count to a location breakpoint is easy. First set a regular location breakpoint and bring up the Breakpoints dialog box. Highlight the location breakpoint in the Breakpoints list box, and click the Condition button. Then in the bottom edit control on the Breakpoint Condition dialog box, enter the number of times you want the breakpoint skipped.

What makes skip counts so useful is that when you're stopped in the debugger, the debugger will tell you how many times the breakpoint has executed. If you have a loop that's crashing but you don't know which iteration is crashing, add a location breakpoint to a line in the loop and add a skip count modifier that is larger than the total number of loop iterations. When your program crashes, bring up the Breakpoints dialog box and look at the breakpoint indicated in the Breakpoints list box. After the breakpoint syntax string, you'll see the number of hits remaining. Subtract that number from the skip count to find out how many times the loop executed. The Breakpoints dialog box shown in Figure 5-3 displays the remaining skip count after a crash. Keep in mind that the remaining count works only when your program is running at full speed. Single-stepping over a breakpoint doesn't update the skip count.

Figure 5-3 An example of remaining skip count breakpoint executions

Conditional Expressions

The second modifier for location breakpoints is a conditional expression. A location breakpoint that has a conditional expression triggers only if its expression evaluates to true. A conditional expression is a powerful weapon for gaining control exactly when you need it. The debugger can handle just about any expression you throw at it. To add a conditional expression to your breakpoint, select the breakpoint in the Breakpoints dialog box, click the Condition button, and then enter the expression in the first edit control in the Breakpoint Condition dialog box. The only rules you need to keep in mind are these three:

  • You can use only C-style comparison operators.
  • The breakpoint conditional expression can't call any functions.
  • The breakpoint conditional expression can't contain any macro values.

Because the expression evaluator knows how to evaluate your variables to values, you can use variables directly. Additionally, you can manipulate pointers and do casting if needed.

As you can see, the conditional expression modifier is extremely powerful. Knowing the different pseudoregisters that allow you to access register values and special values and being a little creative are two of the tricks to using conditional expressions. For example, although the Visual C++ debugger doesn't have an explicit method for setting a location breakpoint that fires only in a specific thread under Windows 2000, if you set the expression to "@TIB==Thread Information Block Linear Address," you'll break only on the specified thread. The first step is to enter the @TIB pseudoregister into the Watch window and find the thread information block linear address for the thread you want to break on. You might need to use the debugger's Threads dialog box to set the active thread to the one you're interested in checking. If, for example, the thread you want to break on @TIB evaluated to 0x7FFDE000, the expression is "@TIB==0x7FFDE000." For Windows 98, you'll need to look at the Intel CPU FS register, which is unique for each thread, and you can use the expression "@FS==thread specific value."

If you need to break based on a specific last error code, you can use the @ERR pseudoregister. For example, to break after an API call that could have a last error of ERROR_FILE_NOT_FOUND according to the Platform SDK documentation, the expression would be "@ERR==2." I looked up ERROR_FILE_NOT_FOUND in WINERROR.H to get its value. Table 5-1 lists all the pseudoregisters.

Finally, because you can't call functions in your expressions, breaking on a string with a specific value is difficult. In that case, just set up an expression that checks each character, such as (szBuff[0]=='P')&&(szBuff[1]=='a')&& (szBuff[2]=='m').

The other trick you can use with conditional expressions is to combine them with skip counts. That way, you can easily break on the nth time the expression is true.

Table 5-1 Expression and Watch Window Pseudoregisters

Pseudoregister Description
@ERR Last error value; the same value returned by the GetLastError API function
@TIB Thread information block for the current thread; necessary because the debugger doesn't handle the "FS:0" format
@CLK Undocumented clock register; usable only in the Watch window
@EAX, @EBX, @ECX, @EDX, @ESI, @EDI, @EIP, @ESP, @EBP, @EFL Intel CPU registers
@CS, @DS, @ES, @SS, @FS, @GS Intel CPU segment registers
@ST0, @ST1, @ST2, @ST3, @ST4, @ST5, @ST6, @ST7 Intel CPU floating-point registers

Variable Changes

The final location breakpoint modifier is breaking when a variable changes. The important caveat to remember with this type of location breakpoint modifier is that the variable is checked only when the location breakpoint executes. This modifier comes in handy when you know a memory overwrite is happening in a higher level function but you're trying to narrow down which lower level function is doing the clobbering. When I'm trying to resolve such a situation, I'll set the location breakpoints after each function call and have them check whether the variable value has changed. One benefit of using this variable change is that you can have the debugger look at an entire buffer if needed.

Adding this breakpoint modifier is just like adding the others in that you use the Breakpoint Condition dialog box to set the conditional parameters you want. The only possible confusion is that the edit control you use for entering the variable to watch is the same edit control you use to enter a conditional expression. In the middle edit control of the Breakpoint Condition dialog box, you tell the debugger how many items in an array or memory location you want to watch. If the value you want to break on is a pointer dereference, such as *pMyData, you enter the number of bytes to watch.

Global Expression and Conditional Breakpoints

Up to this point, I've been talking about a single type of breakpoint—the location breakpoint—and its modifiers. When I originally mentioned the second part of breakpoint syntax, I referred to three other types in addition to the location breakpoint: expressions, variables, and Windows messages. Expression and variable breakpoints are similar to the location breakpoint modifiers except that they are global in scope. The same rules for their use apply, however. On Intel CPUs, both of these breakpoint types will try to use a hardware breakpoint through one of the CPU's special debug registers.

The debug registers are limited to monitoring an address and 1 byte, 2 bytes, or 4 bytes at that address. If you construct your expression or data change breakpoint so that the debugger can store it in one of the debug registers, your program will be able to run at full speed until it meets your condition or the data changes. If the expression or data can't use the debug registers, however, the debugger will single-step every assembly-language instruction and check the condition after stepping. If your program is forced to single-step at this level of detail, it will run extremely slowly—probably so slowly that it's unusable.

Consequently, no matter whether you're interested in an expression or a data change, the best way for you to ensure that the debugger utilizes the debug registers is to use actual address values for your expressions and data change locations. In the AdvancedBP program in Listing 5-1, I wanted to set a global expression breakpoint to trigger if the first character of the global variable g_szGlobal changed to G. In the program, I looked up the address for g_szGlobal, 0x00404594, and set the expression breakpoint on the Breakpoints dialog box Data tab to "*(char*)0x00404594=='G'." I have to admit that I had to tinker a little to get the expression correct. If you want to see the debugger single-step each instruction, the incorrect expression "WO(0x00404594)=='G' " will show you. I haven't found the global expression breakpoint that useful because it's very hard to find an expression the debugger will accept. The global variable breakpoint is much handier.

Listing 5-1 ADVANCEDBP.CPP

 /*001*/ #include <stdio.h> /*002*/ #include <string.h> /*003*/ #include <windows.h> /*004*/ void LocBPFunc ( void ) /*005*/ {   // {,AdvancedBP.cpp,}.6 /*006*/     printf ( "Hello from LocBPFunc\n" ) ; /*007*/ } /*008*/ void SkipLocBPFunc ( void ) /*009*/ {  // {,AdvancedBP.cpp,}.12 skip 99 times(s) /*010*/    for ( int i = 0 ; i < 100 ; i++ ) /*011*/    { /*012*/         printf ( "SkipLocBPFunc iteration = %d\n" , i ) ; /*013*/    } /*014*/ } /*015*/ void ExprTrueLocBP ( void ) /*016*/ {  // {,AdvancedBP.cpp,}.20 when 'j==8' /*017*/    int j = 0 ; /*018*/    for ( int i = 0 ; i < 10 ; i++ ) /*019*/    { /*020*/          j = i * 2 ; /*021*/    } /*022*/ } /*023*/ void DataChangeLocBP ( void ) /*024*/ {  // {,AdvancedBP.cpp,}.26 when szBuff[5](length:1) changes /*025*/    char szBuff[ 10 ] ; /*026*/    strcpy ( szBuff , "String!" ) ; /*027*/ } /*028*/ char g_szGlobal[ 10 ] ; /*029*/ int g_iInt = 0 ; /*030*/ void main ( void ) /*031*/ {   // 0x401210 -> BP @ entry point, _mainCRTStartup /*032*/     LocBPFunc ( ) ; /*033*/     SkipLocBPFunc ( ) ; /*034*/     ExprTrueLocBP ( ) ; /*035*/     DataChangeLocBP ( ) ; /*036*/ /*037*/     //{,,KERNEL32.DLL}_LoadLibraryA@4  <- w/ symbols /*038*/     //{,,KERNEL32.DLL}LoadLibrary      <- w/o symbols /*039*/     LoadLibrary ( "KERNEL32.DLL" ) ; /*040*/ /*041*/     // (char)0x00404594=='G' <- Global expression BP. /*042*/     strcpy ( g_szGlobal , "Global!" ) ; /*043*/ /*044*/     // (long)0x4045A0  <- Global variable BP. /*045*/     g_iInt = 0x42 ; /*046*/ /*047*/     printf ( "Done!\n" ) ; /*048*/ } 

Like the global expression breakpoint, the global variable breakpoint works best if you use the hexadecimal address of the variable, cast the address to a long pointer, and keep the number of elements to watch set to 1. When the memory at that address changes, the debugger stops. To modify the expression "*(char*)0x00404594=='G'" so that it breaks whenever the memory changes, set the breakpoint at the address "*(long*)(0x00404594)." Generally, it takes a try or two to get the right expression so that you can properly use the debug registers. You should never set the number of elements to watch to anything other than 1 when you're doing the long casts. The 1 indicates that you're monitoring for writes to a double-word memory size, and because the hardware debug registers can't handle more than a double-word reference, you'll force the single-step method of checking memory.

Although there are four debug registers, I've gotten only two global breakpoints working at a time. If you try to set a bunch of global breakpoints, you'll guarantee that the debugger will single-step your program.

Global variable breakpoints are an excellent technique for finding one of the most difficult bugs of all, wild writes. As you'll recall from Chapter 2, a wild write occurs when someone writes to memory using an uninitialized pointer, causing corruption to appear in random places. The only time you notice the problem is either when the program crashes or when some of the data becomes corrupted. Once you get the problem duplicated so that you can get the same variable corrupted even part of the time, set the global variable breakpoint so that you can find out whenever someone writes to that memory. Just be prepared for the breakpoint to go off many times on legitimate writes before you come across the first wild write.

Windows Message Breakpoints

The last type of breakpoint is the Windows message breakpoint. You set this breakpoint on the Messages tab of the Breakpoints dialog box. With this type of breakpoint, you can have the debugger break when a window procedure receives a specific Windows message. If you have a straight C SDK-style program, setting this breakpoint is a snap because the Break At WndProc drop-down list contains the window procedures in the program when the debuggee is running. Just select the window procedure and set the message you want to break on, and you're done. If the message you want to break on isn't in the list, just type the message's value into the message combo box.

In today's high-speed world, almost everyone uses C++ class libraries, such as MFC, in which it's just barely possible to use a Windows message breakpoint. The issue is that the Windows message breakpoint requires a function, or address, with the four proper window procedure parameters. You can set a Windows message breakpoint on the main MFC window procedure, "{,,MFC42D.DLL}AfxWndProc," and it will work. Unfortunately, because that's the one window procedure for most windows derived from CWnd, you might be stopping on the breakpoint all the time. If you need to break on a private Windows message that only one of your classes uses, setting the Windows message breakpoint will work, but you'll still need to step out of AfxWndProc to your derived class.

If you need to break on a common message, a better approach is to set a conditional location breakpoint in CWnd::WindowProc, the MFC C++ message method for each class. First find the value of the this pointer for the class you're interested in, and then look up the value of the Windows message you want to stop on in WINUSER.H. With those two values, you can set a location breakpoint in CWnd::WindowProc. I'm running Visual C++ 6.0 Service Pack 3 at the time of this writing, and to break only in my class on a WM_PAINT message, the breakpoint syntax is "{,WINCORE.CPP,} .1584 when (this==0x0012FE74)&&(message==0xF)."

When you're using the location breakpoint with the "this…&&…message" expression, you'll have to be careful because your this pointer will probably change depending on how your class is allocated and on the changes you're making to your code. If you want to get the breakpoint set quickly, you can always add a handler method with the Class Wizard and just set a simple location breakpoint.

I've covered a great deal of ground here with the advanced breakpoints. The AdvancedBP program shows each type of breakpoint except the Windows message, and you might want to experiment a bit with it to see how the various breakpoints work. In the source code, I embedded the breakpoint syntax for each breakpoint so that you can see the syntax without opening the Breakpoints dialog box. I strongly encourage you to take some time to practice using advanced breakpoints in your own code. You'll be amazed at how much you can learn about both the debugger and your project.

Common Debugging Question
Why do my breakpoints disappear or jump around?

To keep your source code breakpoints healthy, it helps to edit your source code in the Visual C++ integrated development environment (IDE) editor because it will keep your breakpoints on the appropriate line. If you do happen to edit your source code outside the IDE and your breakpoints don't correspond to an active source code line, the debugger will respond with the following message: "One or more breakpoints are not positioned on valid lines. These breakpoints have been moved to the next valid line." The debugger always moves the breakpoints down, so if you delete code, you might want to double-check the breakpoints to ensure that they appear on the proper lines.

If the debugger can't set a breakpoint at all, you'll get the message, "One or more breakpoints cannot be set and have been disabled. Execution will stop at the beginning of the program." After you click OK to dismiss the message box, you should bring up the Breakpoints dialog box and look for the unchecked breakpoints. Those are the ones that gave the debugger trouble.

The most common reason for the debugger not setting your breakpoints is that you've set a breakpoint in an explicitly loaded DLL—that is, one loaded with an explicit call to the LoadLibrary API function. All Component Object Model (COM) DLLs are explicitly loaded DLLs, so this problem occurs with an annoying frequency. The debugger has to resolve all breakpoints at startup, so you'll need to add the explicitly loaded DLLs manually to the additional DLLs list to force the debugger to load symbols for those DLLs when you start debugging. In the Project Settings dialog box, on the Debug tab, select Additional DLLs in the Category combo box. In the Modules list, add all the DLLs that your project might ever load.

Setting compiler optimizations can also move or disable your breakpoints. If you do set compiler optimizations, be aware that the compiler might move code in such a way that the debugger can't resolve the breakpoint location.



Debugging Applications
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2000
Pages: 122
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net