Team LiB   Previous Section   Next Section

3.3 Observing State

The ability to watch the progress of a program's execution line by line is important, but debugging would be much harder if we were not also able to examine the program's state. Visual Studio .NET therefore provides us with a range of tools for examining a process's memory. We can access global variables, the stack (which contains local variables and parameters of the currently executing method and its callers), and raw memory.

3.3.1 Displaying Variables and Expressions

Several windows can be used to display variables and expressions while single-stepping through code in the debugger. They all work in more or less the same way, displaying the name, value, and type of a number of expressions. They are all dockable tool windows. They all keep their value displays up-to-date as you single-step through the code, highlighting any changed values in red. The only difference between these various windows is the exact selection of expressions displayed.

3.3.1.1 Watch windows

A watch window is a grid into which you can type arbitrary expressions. These will be evaluated whenever code is halted in the debugger and updated as you single-step. All expressions are evaluated with respect to the scope of the current line of code.

Figure 3-20 shows a watch window with two expressions. (New expressions are added by typing into the Name column in the blank line at the bottom of the grid.) The first expression, this, illustrates that the watch window allows objects to be expanded so that the individual fields can be shown. The second expression, ((Button) sender).Text, illustrates that we are not restricted to simple variable names—this is a snippet of C# that performs a cast on a variable and then retrieves a property.

Figure 3-20. A watch window
figs/mvs_0320.gif

Remember that in .NET, properties are really functions, so the implication is that expressions in watch windows are able to cause code to be executed. This is indeed the case, and you can even include method calls inside the expressions that you want to be evaluated. You should exercise caution when doing this—in particular, you don't want the presence of your watch window expression to have side effects that modify the program's operation.

If the ability to execute code as a side effect of evaluating a watch expression makes you nervous, you can disable this facility. Open the Options dialog with Tools Options..., and select the Debugging folder. If you uncheck the "Allow property evaluation in variable windows" option, this will prevent Visual Studio .NET from calling functions in watch windows. It disallows all function calls, not just those required to evaluate properties, despite what the text seems to imply.

The watch window in Figure 3-20 is labeled Watch 1. You can have up to four watch windows open. These can be opened from the Debug Windows Watch menu. Expressions will stay in the windows until you delete them; they persist across debug sessions. If you write an expression that makes sense in only a particular scope, Visual Studio .NET will display an error message in that line of the watch window, but this is easily ignored. It doesn't do anything disruptive like opening an error dialog, so it is common practice to leave useful but context-specific expressions in place and to ignore the errors when debugging in a different context.

Watch windows are not read-only—you can change the values of watched expressions. (The expressions must be writable, of course; you can't meaningfully change the value of an expression that calls a method.) This allows you to modify the values of parameters and local variables, which may be useful for experimenting with the behavior of the code you are debugging. This can be especially useful for checking the behavior of error-handling code when it is difficult to generate the error conditions by normal program execution. (Of course, this is no substitute for good unit testing, but it is a useful extra tool to have available.)

Watch Window Format Specifiers

Watch windows allow you to modify the way in which data is presented. By default, they will show values formatted according to their type—integers will be displayed numerically, strings will be shown as text, and so on. However, Visual Studio .NET supports a variety of format specifiers that allow certain types to be displayed in different ways.

Format specifiers are placed after the expression itself, following a comma. For example, you can ask for a variable foo to be displayed in hexadecimal by typing foo,x into the watch window. The standard numeric specifiers are:

Signed decimal (d or i)
Unsigned decimal (u)
Octal and hexadecimal (o and x)
Standard, scientific, and automatic (shortest) floating point (f, e, and g)

You can also ask Visual Studio .NET to interpret integers as being of one of the following types, in which case the value will be displayed as the appropriate text constant:

HRESULT or Win32 error code (hr)

This will look up both the constant, such as E_OUTOFMEMORY, and a textual description of the error if one is available. Note that if a variable's type is HRESULT, the debugger will normally use this format style automatically, so you need to specify only hr when the debugger does not know the value's type (e.g., when examining a CPU register).

Windows Class flag (wc)

This will look up Windows Class constants such as WC_DEFAULTCHAR.

Windows Message (wm)

This will look up the name of a Windows message such as WM_ACTIVATE.

There are also format specifiers for strings:

Single character (c)
String (s)
Unicode string (su)

3.3.1.2 Autos, Locals, and This

Watch windows require you to type in the expressions that you want to evaluate. The Autos, Locals, and This windows are essentially watch windows that provide a useful sets of expressions without the need for you to type anything.

The This window (Debug Windows This) is fairly self-explanatory. (At least it is for C++ and C# developers; for Visual Basic .NET programmers, the Me window might have been a better name.) It is simply a watch window with a single fixed expression, the this (or Me) reference. The Locals window is also straightforward. It is a watch window that shows all local variables and parameters currently in scope.

Although the This and Locals windows are useful, they can often provide information overload. Complex code may have so many locals and object members that you will continually be scrolling these windows to find the values you care about for the current line of code. The Autos window attempts to alleviate this.

The Autos window guesses which expressions in the current line of code would be useful for you to see. It seems to use a heuristic that includes any variables that are used on this line or its immediate neighbors and any variables explicitly modified by the last line that executed. (Implicit side effects are not shown, since these could be arbitrarily extensive if the previous line made any function calls.)

Figure 3-21 shows a typical selection of variables from the Autos window. Both the count and total variables were modified on the previous line, so it has shown these. (It has colored them red, to draw attention to the fact that they have just changed.) It also shows the expressions that will be used on the line about to be executed.

Figure 3-21. The Autos window
figs/mvs_0321.gif

The Autos window is extremely useful. It shows all of the expressions you need to see most of the time. This, in conjunction with the fact that you can evaluate any expression visible in a source code window merely by hovering the mouse over it, means that you will rarely need anything else. (Sometimes the Autos heuristic doesn't guess at all the things you need, in which case watch windows are very useful, but most of the time you will need only Autos.)

3.3.2 Registers

The ability to evaluate expressions while debugging is very powerful. Unfortunately, in some situations expressions cannot be used—the debugger requires a certain amount of symbolic information[3] in order to perform expression evaluation. Sometimes you will find yourself in a situation with no such information available, either because you needed to attach a debugger to a release build or because an error occurred in a third-party or OS component for which source code and symbols are simply unavailable.

[3] Symbolic information is data about named items such as functions, variables, and parameters. Compilers usually discard such information in release builds—executable code deals with raw data and has no need for the symbolic names used to represent the data in source code.

This makes life much harder, but it is still possible to debug code in these circumstances. You must drop back to the old-fashioned techniques of assembly-level debugging, but that is better than nothing. To make sense of single-stepping through assembly language, you will need to examine the contents of the CPU's registers. Visual Studio .NET has a window for precisely this purpose, the Registers window. It can be displayed with Debug Windows Registers (Ctrl-Alt-G). It simply displays the current values of all of the CPU's registers, as Figure 3-22 shows. Old-time developers will appreciate the retro feel of this window (although probably not as much as they will appreciate not having to use it most of the time).

Figure 3-22. The Registers window
figs/mvs_0322.gif

There is a popular reason for wanting to look at register values even when full symbolic information is available to the debugger. If you are debugging some classic unmanaged (non-.NET) Win32 code that has less than thorough error handling, you will often find that the author of the code did not store the return code of an API that you suspect may have failed. The fact that she did not store it in a variable does not, however, prevent you from finding out what it was: you can rely on the fact that the EAX register is used to hold the return value of most methods. So if you suspect that an unchecked error is the cause of your complaints, simply examine the EAX register immediately after the call.

In fact, you don't need to use the Registers window at all to do this. If you are running in native mode (i.e., not .NET), you can simply type EAX into a watch window. Better than that, you can type EAX,hr. This informs the watch window that the value should be interpreted as an error code. Visual Studio .NET will then look up the error number to see if it is either a well-known COM HRESULT, or a standard system error code, and will display some explanatory text for the error. Another useful trick is that you can type @hr, hr, which will display the value returned by the GetLastError API, along with a text explanation when available. These tricks are not available when debugging .NET applications, but since .NET uses exceptions for most error handling, these kinds of problems tend not to arise so often.

3.3.3 The Call Stack

A program's state consists of more than just the location of the next line to be executed and the values of local and global variables. How it got to its current position is also important. Very often when debugging some code, the most interesting questions are not of the form "what is happening here?" but more along the lines of "how did we get to this state in the first place?" Unfortunately, Visual Studio .NET cannot provide you with a complete history of every step of your program's execution, but it can tell you which method called the current method and which called that and so on all the way back up to the start of the thread. It can even take you to the source code location of every call and show the local variables in scope for each call in the chain.

This information is visible in the Call Stack window. This can be displayed with Debug Windows Call Stack (Ctrl-Alt-C). Figure 3-23 shows an example. You can examine the code for any entry on the call stack by double-clicking on it. Visual Studio .NET will take you to the next line that will execute when the code returns to the function in question (i.e., it will highlight the line after the call currently in progress). The lines shown in gray are those for which Visual Studio .NET does not have source code information—if you attempt to show the source code by double-clicking on these, you will instead be shown disassembly for that location. The example here is fairly typical for a Windows Forms application—most of the code is inside the Windows Forms Framework, with the application's main method visible at one end and an event handler at the other.

Figure 3-23. The Call Stack window
figs/mvs_0323.gif

Native Win32 applications don't always display such a complete call stack when symbols are not available. .NET programs run in a managed environment that knows about which methods are called and what types are in use. In Win32, this is not guaranteed, so be prepared for the call stack to be absent, uninformative, or even misleading when it delves into areas outside of your own code—optimized code often doesn't provide all of the information the debugger needs, so Visual Studio .NET is not always able to interpret the entire call stack correctly. (Although be aware that you can download debugging symbols for most Windows system DLLs from Microsoft's web site, which can considerably improve the readability of native call stacks.)

You can obtain debugging symbols in various ways. They are shipped with the MSDN subscription, but the problem with these is that they will go out-of-date as you apply hot fixes and service packs. Information on how to keep your symbols in sync with your OS updates is available at http://www.microsoft.com/ddk/debugging/symbols.asp.

You can also configure VS.NET to automatically download symbols from Microsoft"s public symbol server—see Section 3.5.6" later in this chapter.

As well as double-clicking on entries in the call stack to go to the listed functions, you can select a line in the call stack and press F9 to set a breakpoint. This will create a breakpoint that is positioned so that it gets hit when execution returns to the selected function.

3.3.4 Memory Windows

Just as you will not always have access to source for the code you wish to debug, you may not always have the symbolic information you require to view state using expressions. And just as Visual Studio .NET can drop back to disassembly when the source code is not present, it can also provide you with access to raw memory when you cannot use expressions.

Memory windows simply provide a hexadecimal dump of the memory at the address of your choice. As with watch windows, you can have up to four memory windows open, which can be displayed using Debug Windows Memory.

Figure 3-24 shows a memory window. By default, Visual Studio .NET will display as many bytes as will fit across the window. However, it is often useful to fix the column size to something more regular since this can make it easier to see patterns in the data. So the drop-down list labeled Columns can be used to set an explicit width. It provides a list of various powers of two (2, 4, 8, 16, etc.), which are popular choices, but you can type in any value you like.

Figure 3-24. A memory window
figs/mvs_0324.gif

By default, memory will be shown in 1-byte units. However, it is often useful to group the display into larger units. The window's context menu allows you to group numbers into 2-byte or 4-byte integers. (Since Intel's processors are little-endian, this is useful, because it saves you from reversing the order of the bytes in your head.) It also allows you to make the window interpret the data as 32-bit or 64-bit floating point numbers.

Next to the Address field is a tool button with two small arrows. This button is relevant only if you type an expression (instead of a constant) into the Address field. If the button is not pressed (the default), any expression you type into the Address field will be evaluated just once when you type it. (In fact, the expression will be replaced with its value when you press Return.) If the button is clicked, however, the expression you typed in will remain in the Address field and will be reevaluated each time the debugger halts at a breakpoint or each time you step over a line of code. So if you type in the name of a pointer variable, the window will always display whatever memory the pointer points to, even if the pointer changes.

3.3.5 The Output Window

The various windows Visual Studio .NET supplies for observing your program's state are very useful, but they all suffer from two limitations. First, you can use them only when the program is suspended in the debugger—their contents all vanish when the program is running freely. Second, they cannot show you any historical information—they can show you only the current status.

The Output window does not suffer from either of these restrictions. It is visible during normal execution and can even be viewed after the program has terminated. And once items have been shown in the Output window, they remain there until you clear the window explicitly (or start a new debugging session). The price of this is that the Output window is a little less sophisticated than the other windows we have seen so far—it can show only text. But its ability to function without needing to halt execution makes it an invaluable debugging tool.

Figure 3-25 shows the Output window. Visual Studio .NET itself sends certain messages to this window. For example, here you can see the messages it displays when DLLs are loaded by the program.

Figure 3-25. The Output window
figs/mvs_0325.gif

The final line shown in Figure 3-25 is a custom message generated by the author of the program by including the following code at some appropriate point in the code:

Debug.WriteLine(string.Format("Button1 clicked - count: {0}", count));

This C# code uses the Debug class in the System.Diagnostics namespace. Calls to this API will be compiled into only debug builds. The Trace class allows you to generate output in release builds. So this code will generate debug output in all builds:

Trace.WriteLine(string.Format("Button1 clicked - count: {0}", count));

The Trace class in the System.Diagnostics namespace is unrelated to the ASP.NET tracing facilities.

Note that, unlike the Console.WriteLine method, Debug.WriteLine does not support string formatting with variable length argument lists. If you need to place dynamic information in your output, you must use the String class's Format method as shown here.

Classic Win32 applications can send messages to this window too, using the OutputDebugString API. You would normally use this API indirectly through macros such as MFC's TRACE or ATL's ATLTRACE. As with the .NET Debug class, these macros generate output only in debug builds.

3.3.6 The Modules Window

The Modules window allows you to see which modules (DLLs and EXEs) have been loaded in the current debug session. It also allows you to see which of them Visual Studio .NET has found debug symbols for and to control where it looks for symbol files.

You can display the Modules window with Debug Window Modules (Ctrl-Alt-U). As Figure 3-26 shows, the window displays a considerable amount of information for each loaded module. It shows the filename, the address at which it has been loaded, the file path, the order in which the modules were loaded in this particular process, the version and timestamp of each module, and the process in which the module is loaded (this is used in multiprocess debugging). It also shows whether debug symbols have been loaded for the DLL.

Figure 3-26. The Modules window
figs/mvs_0326.gif

If you are debugging code from just one project, you will probably not need to use the Modules window much, but if your program uses multiple components from many projects, this window is extremely useful. It enables you to find out exactly which components got loaded. (For complex build environments, it is not always trivial to work out exactly where a component will be loaded from, so the ability to find out exactly which one is running is important.)

A common problem that occurs in debugging large componentized applications is that Visual Studio .NET might not be able to locate the debug information it requires for all components automatically. Fortunately, the Modules window enables you to tell Visual Studio .NET where the symbols are—if you right-click on a module and select Reload Symbols..., you will be shown a dialog that lets you choose the .pdb file that contains the symbols. You can even do this with modules for which Visual Studio .NET has already loaded symbols—this is useful because under certain circumstances, the wrong symbols may get loaded.

    Team LiB   Previous Section   Next Section