Chapter 48
Figure 48-10
Processes
Building multi-tier applications can be quite complex, and it is often necessary to have all the tiers running. To do this, Visual Studio 2005 can start multiple projects at the same stage, enabling true end-to-end debugging. Alternatively, you can attach to other processes to debug running applications. Each time Visual Studio attaches to a process, that process is added to the list of attached processes. Figure 48-11 shows a solution containing two Windows applications and a web application. To debug the web application, Internet Explorer is started and Visual Studio 2005 attaches to that process in addition to the ASP.NET process.
Figure 48-11
The toolbar at the top of the Processes window enables you to detach or terminate a process that is currently attached, or attach to another process.
Memor y Windows
The next series of windows are typically used for low-level debugging when all other alternatives have been exhausted. Stepping into memory locations, using a disassembler, or looking at registry values erodes any potential benefits of a managed environment, so the following windows should be used sparingly.
Memory Windows 1–4
Similar to the watch windows, four memory windows can be used to view the contents of memory at a particular address. Each window can examine different memory addresses to simplify debugging your application. Figure 48-12 shows an example of the information that can be seen using this window. The scrollbar on the right of the window can be used to navigate forward or backward through the memory addresses to view information contained in neighboring addresses.
Using the Debugging Windows
Figure 48-12
Disassembly
Interesting debates arise periodically over the relative performance of two different code blocks. Occasionally this discussion degrades to talking about which MSIL instructions are used, and why one code block is faster because it generates one fewer instruction. Clearly, if you are calling that code block millions of times, disassembly might give your application a significant benefit. However, more often than not, a bit of high-level refactoring saves much more time and with much less arguing. Figure 48-13 shows the Disassembly window for a button click — the runtime is about to make a call to the method testMethod. You can see the memory address and the MSIL instruction.
Figure 48-13
You can see from Figure 48-13 that a breakpoint has been set on the call to testMethod and that the execution point is at this breakpoint. While still in this window you can step through the lines of MSIL and review what instructions are being executed.
Registers
Using the Disassembly window to step through MSIL instructions can become very difficult to follow as different information is loaded, moved, and compared using a series of registers. The Registers window, shown in Figure 48-14, enables the contents of the various registers to be monitored. Changes in a register value are highlighted in red, making it easy to see what happens as each line is stepped through in the Disassembly window.
Figure 48-14
Chapter 48
Exceptions
Visual Studio 2005 has a brand-new exception handler that provides you with more useful information. Figure 48-15 shows the exception assistant dialog that is shown when an exception is raised. In addition to providing more information, it also displays a series of actions. The Actions list varies depending on the type of exception being thrown. In this case, the two options are to view details of the exception or copy it to the clipboard.
Figure 48-15
If you select the View Detail action item from the exception, you are presented with a modal dialog that provides a breakdown of the exception that was raised. Figure 48-16 shows the attributes of the exception, including the StackTrace, which can be viewed in full by clicking the down arrow to the right of the screen.
Figure 48-16
Of course, there are times when exceptions are used to control the execution path in an application. For example, some user input may not adhere to a particular formatting constraint, and instead of using a regular expression to determine whether it matches, a parse operation has been attempted on the string. When this fails, it raises an exception, which can easily be trapped without stopping the entire application.
Using the Debugging Windows
By default, all exceptions are trapped by the debugger, as they are assumed to be exceptions to the norm that shouldn’t have happened. In special cases, such as invalid user input, it may be important to ignore specific types of exceptions. This can be done via the Exceptions window, accessible from the Debug menu.
Figure 48-17 shows the Exceptions window, which lists all the exception types that exist in the .NET Framework. For each exception there are two debugging options. The debugger can be set to break when an exception is thrown regardless of whether it is handled. If the Just My Code option has been enabled, checking the User-unhandled box causes the debugger to break for any exception that is not handled within a user code region. More information on Just My Code is provided in Chapter 50, which examines debugging attributes.
Figure 48-17
Unfortunately, the Exceptions window doesn’t pick up any exception types that you may have created, but you can add them manually using the Add button in the lower-right corner of the window. You need to ensure that you provide the full class name, including the namespace; otherwise, the debugger will not break on handled exceptions. Clearly, unhandled exceptions will still cause the application to crash.
Customizing the Exception Assistant
As with a lot of the configurable parts within Visual Studio 2005, the information displayed by the Exception Assistant is stored in an XML file (C:\Program Files\Microsoft Visual Studio 8\ Common7\IDE\ExceptionAssistantContent\1033\DefaultContent.xml). This file can be modified either to alter the assistant information for existing exception types or to add your own custom exception types. If you have your own exception types, it is better practice to create your own XML document. Simply placing it in the same directory as the DefaultContent.xml is sufficient to register it with Visual Studio for the next time your application is debugged. An example XML file is provided in the following code snippet:
<?xml version=”1.0” encoding=”utf-8” ?>
<AssistantContent Version=”1.0” xmlns=”urn:schemas-microsoft-com:xml- msdata:exception-assistant-content”>
Chapter 48
<ContentInfo>
<ContentName>Additional Content</ContentName> <ContentID>urn:exception-content-microsoft-com:visual-studio-7-default-
content</ContentID>
<ContentFileVersion>1.0</ContentFileVersion>
<ContentAuthor>DeveloperNews</ContentAuthor>
<ContentComment>Additional Exception Assistant Content for Visual Studio 8.0.</ContentComment>
</ContentInfo>
<Exception>
<Type>DeveloperNews.MyException</Type>
<Tip HelpID=”http://www.developernews.com/MyExceptionHelp.htm”> <Description>Silly error, you should know better......</Description>
</Tip>
</Exception>
</AssistantContent>
This example registers help information for the exception type MyException. The HelpID attribute is used to provide a hyperlink for more information about the exception. When this exception is raised, the debugger displays the window (see Figure 48-18).
Figure 48-18
Unwinding an Exception
In Figure 48-18 there is an additional item in the Actions list, which is to enable editing. This is effectively the capability to unwind the execution of the application to just before the exception was raised. In other words, you can effectively debug your application without having to restart your debugging session.
An alternative way to unwind the exception is to select the Unwind to This Frame item from the rightclick context menu off the Call Stack window after an exception has been raised.
As with many of the debugging features, both the Exception Assistant and the capability to unwind exceptions can be disabled via the Debugging tab of the Options window.
Using the Debugging Windows
Summar y
This chapter has described each of the debugging windows in detail so you can optimize your debugging experience. Although the number of windows can seem somewhat overwhelming at first, they each perform an isolated task or provide access to a specific piece of information about the running application. As such, you will easily learn to navigate between them, returning to those that provide the most relevant information for you.
The following chapter provides more detail about how you can customize the debugging information. This includes changing the information displayed in the DataTip and visualizing more complex variable information.
Debugging Breakpoints
Long gone are the days where debugging an application involved adding superfluous output statements to track down where an application was failing. Visual Studio 2005 provides a rich debugging experience that includes breakpoints and the Edit and Continue feature. This chapter covers how you can use these features to debug your application.
Breakpoints
A breakpoint is used to pause, or break, an application at a particular point of execution. An application that has been paused is said to be in Break mode, causing a number of the Visual Studio 2005 windows to become active. For example, the Watch window can be used to view variable values. Figure 49-1 shows a breakpoint that has been added to the constructor of the Customer class. The application will break on this line if the Customer class constructor is called.
Figure 49-1
Setting a Breakpoint
Breakpoints can be set either through the Debug menu, using the Breakpoint item from right-click context menu or by using the keyboard shortcut, F9. The Visual Studio 2005 code editor also provides a shortcut for setting breakpoint using a single mouse click.
Chapter 49
An application can only be paused on a line of executing code. This means that a breakpoint set on either a comment or a variable declaration will be repositioned to the next line of executable code when the application is run.
Simple Breakpoints
A breakpoint can be set on a line of code by placing the cursor on that line and enabling a breakpoint using any of the following:
Selecting Toggle Breakpoint from the Debug menu
Selecting Insert Breakpoint from the Breakpoint item on the right-click context menu
Pressing F9
Clicking once in the margin of the code window with the mouse. Figure 49-1 shows the location of the mouse immediately after a breakpoint has been set using the mouse.
Selecting Location from the Breakpoint item on the right-click context menu for the line of code with the breakpoint set displays the File Breakpoint dialog, shown in Figure 49-2. Here you can see that the break point is set at line 4 of the Customer.vb file. There is also a character number, which provides for special cases in which multiple statements appear on a single line.
Figure 49-2
Function Breakpoints
Another type of breakpoint that can be set is a function breakpoint. The usual way to set a breakpoint on a function is to select the function signature and either press F9 or use the mouse to create a breakpoint. This will create a file breakpoint as you did previously. In the case of multiple overloads, this would require you to locate all the overloads and add the appropriate breakpoints. Setting a function breakpoint enables you to set a breakpoint on one or more functions by specifying the function name.
To set a function breakpoint, select Break at Function from the Breakpoint item on the Debug menu. This loads the New Breakpoint dialog shown in Figure 49-3, in which you can specify the name of the function on which to break. There is a toggle to enable IntelliSense checking for the function name. The recommendation is to leave this checked, as it becomes almost impossible to set a valid breakpoint without this support.
Unfortunately, the IntelliSense option doesn’t give you true IntelliSense as you type, unlike other debugging windows. However, if you select the name of the function in the code window before creating the breakpoint, the name of the function is automatically inserted into the dialog.
Debugging Breakpoints
Figure 49-3
When setting a function breakpoint, you can specify either the exact overload you wish to set the breakpoint on or just the function name. In Figure 49-3, the overload with a single Integer parameter has been selected. Notice that unlike a full method signature, which requires a parameter name, to select a particular function overload, you should only provide the parameter type. If you omit the parameter information, and there are multiple overloads, you will be prompted to select the overloads on which to place the breakpoint, as illustrated in Figure 49-4.
Figure 49-4
Address Breakpoints
Another way to set a breakpoint is via the Call Stack window. When the application is in Break mode, the call stack shows the current list of function calls. After selecting any line in the call stack, a breakpoint can be set in the same way as a file breakpoint, as described earlier (toggle Breakpoint from the Debug menu, use the F9 keyboard shortcut, or use Insert Breakpoint from the context menu). Figure 49-5 shows a short call stack with a new breakpoint set within the constructor for the Customer class.
Figure 49-5
The call stack is generated using function addresses. As such, the breakpoint that is set is an address breakpoint. This type of breakpoint is only useful within a single debugging session, as function addresses are likely to change when an application is modified and rebuilt.