Chapter 9. Debugging with Visual Studio 2005
IN THIS CHAPTER |
Today's developers may spend as much time debugging their code as they do writing it. This is due in some part to the nature of today's highly dependent and distributed applications. These applications are built to leverage existing functionality, frameworks, building blocks, libraries, and so on. In addition, they often communicate with other applications, services, components, databases, and even data exchanges. In addition, developers also demand more assistance from their debugger to help increase their productivity. Visual Studio 2005 tries to address these needs by offering a number of advancements in the debugging process. Some highlights include
-
Edit and Continue for both VB and C# developers
-
Easier setup for secure remote debugging
-
Visualizers and debugger DataTips
-
Just-my-code debugging
-
Better breakpoints and the addition of tracepoints
-
The Exception Assistant window
-
Debugging support at design time
We will cover all of these features and more in this chapter. Of course, if you are just getting started with .NET, more than
just this list is new to you. The Visual Studio debugger has been evolving since the first release of .NET, which provided a unified debugger
with the capability to debug across languages. In this chapter, we will start by covering the basics of debugging an application. We will then
discuss the Visual Studio 2005 debugger in depth.
Debugging Basics
A typical scenario for a developer writing an application today is to start building a screen or form and build up the code that surrounds it. In addition, the developer may rely on a framework or a few building blocks that provide added functionality. The application may also communicate with a services layer and most definitely a database. Even the most typical applications have a lot of moving parts. These moving parts make the task of finding and eliminating errors in the code all the more complex. The tools that help you track down and purge errors from your code not only have to keep up with this complexity, but also must ease the effort involved with the debugging process. In the following sections, we will cover how a developer would use the tools built into Visual Studio 2005 to debug a typical development scenario.
The Scenario
We want to define an application scenario that we can use to both introduce the basics of debugging as well as function as a base for us to build on throughout the chapter when demonstrating the many features of the debugging tools. In this scenario, imagine you are writing a web page that allows customers to view and edit their profiles. This screen offers new functionality to a larger, existing application. The following are some of the conditions that surround this application scenario:
-
The customers' profiles are stored in a SQL 2005 database.
-
A data access library abstracts access to the database.
-
A web service provides the customers' profile information.
Your task is to write this page using the web service to return customers' profiles and write users' changes to their profiles back to the database using the data access library. The application you will be debugging in this scenario is written in C#. However, the debugging tools in Visual Studio are equally applicable to both C# and Visual Basic. That is, everything we discuss here applies to both languages unless specified as otherwise.
The Many Phases of Debugging
Nearly every time developers open the IDE, they are in some way debugging their code. The line between debugging and writing code, in fact, is becoming more and more blurred. For example, the code editor helps eliminate errors in your code as you write it. It highlights items where errors are present and allows you to fix them. You are then both writing and debugging simultaneously.
In addition, the compiler acts as another debugging tool. The first time you click the Run button, the compiler checks your code and reports a list of errors for you to fix prior to continuing. This is debugging. The steps or phases of the debugging process can be broken into the following:
-
Coding The editor helps you by pointing out issues and possible resolutions.
-
Compiling The compiler checks your code and reports errors prior to continuing.
-
Self-checking You run the application in debug mode and step through screens and code to verify functionality.
-
Unit testing You write and run unit tests to check your application.
-
Responding to issues When an issue has been logged against the code, you must re-create and debug a specific scenario.
In this chapter, we will concentrate on two of these phases: self-checking and responding to issues. These are the two phases in which developers will get the most use of the debugging tools built into Visual Studio. We will therefore assume that the code is written and compiles. Let's start by looking at how to self-check the code.
Debugging the Application (Self-Checking)
In this scenario, you have just started writing a web page to edit a customer's profile. Assume that you've laid out the page, connected to the profile web service, and have written the code to save a user's profile to the database. You now need to start self-checking your work to make sure everything operates as you expect.
The first step is to start your application in debug mode. This will allow you to break into your code if an error occurs. In development, this is typically your default setting. You invoke debug mode by clicking the Run button (the green arrow on the Debug toolbar), making sure your configuration is also set to Debug (the default). Figure 9.1 shows the sample application about to be run in debug mode for the first time.
Figure 9.1. Starting the debugger.
Enabling Debugging on a Website
This example is a web application. As such, it requires you to set up debugging on server-side code whose errors and information are output to a remote client. Of course, you are developing on a single development machine. However, sometimes you may have to debug a process on a test server.
In either case, you have to enable debugging through the configuration file (web.config) for your application. Visual Studio will actually initially prompt you to add a config file and enable debugging. Figure 9.2 shows this prompt. Clicking the OK button adds the configuration file to the application and starts the debugging session.
Figure 9.2. Allowing Visual Studio to enable debugging.
Note
It is important that you turn off debugging prior to deploying to production. Having debugging enabled in a production environment is a security risk. With debugging enabled, ASP.NET writes the details of your errors to a web page. These details provide valuable clues to would-be hackers about how your application is put together. In some instances, the error could include user credentials that are being used to access secured resources.
To turn off debug mode, you must edit the web configuration file. Specifically, you edit the Compilation element under the system.web node. You set debug equal to false (as in off). The following is an example of the XML:
<system.web> <compilation debug="true"/> ... </system.web>
Starting in Debug Mode
The most typical scenario for starting a debug session is just clicking the Run button on the toolbar. This will compile the application and bring up the initial form or page. You also have the option to start without debugging. This capability is useful if you intend to attach to a running process or simply want to run through the application as a user might see it (without breaking into the IDE).
You can also start by stepping into code, line-by-line. This approach is useful if you want to see all of your code as it executes (rather than just errors). You might desire this if you are getting some unexpected behavior. Stepping line-by-line gives you an exact understanding of what is going on with your code (rather than just your assumed understanding).
Stepping into code on a web form is typically done by first opening the main source. You then right-click and select the Run To Cursor option from the shortcut menu. Figure 9.3 shows an example. This command tells Visual Studio to run the application until it gets to this point. At that time, the IDE will open into debug mode, where you can step through each line of code (or continue and so on).
Figure 9.3. After selecting Run to Cursor, you can start debugging.
Breaking on an Error
Not everything you find in debug mode is an error that results in a break in the code. Often, issues arise just because you're looking at the behavior of the application. For example, a control could be out of place, the tab order could be wrong, and so on. For these items, you still have to rely on your eyes. The debugging tools in Visual Studio help you respond to hard errors in your code.
By default, when unhandled exceptions occur in your code, the debugger will break execution and bring up the IDE with the offending code highlighted. The key in that sentence is "unhandled exceptions." They represent places in your code where you do not have try-catch blocks to manage an exception. This is typically a good default setting. However, you often need to see handled exceptions as well.
Fortunately, the errors that result in a break in the IDE are a configurable set. For example, you may handle a specific exception in your code and not want to be dumped to the IDE every time it occurs. Rather, you want to be notified only of those especially exceptional conditions. The Exceptions dialog box allows you to manage the set of exceptions you're concerned with. You access this dialog box by choosing Debug, Exceptions (or pressing Ctrl+D, E). Figure 9.4 shows the dialog box.
Figure 9.4. Determining where Visual Studio breaks.
In the Exceptions dialog box, the various exceptions are categorized for easy access (there is also a Find feature). The two columns of check boxes are of primary interest: one for Thrown and one for User-unhandled. Notice that, by default, the setting for all exceptions in the .NET Framework is User-unhandled. This indicates that the debugger should break execution only when a given exception is thrown and it is not handled by your code.
Adding Thrown to the mix will tell the debugger to break execution even if you have code to handle the exception. The debugger will react by breaking on the line of the exception, before your handler is called.
For the most part, you would simply toggle Thrown at the CLR level. You probably do not need to get more granular, but you can if you need to. However, doing so often results in confusion.
Debugging an Error
The first step in debugging your application is to click that Run button. You are then in debug mode. As it happens, the sample application throws an exception upon the initial run. The debugger responds by breaking into the code and showing the offending line. Figure 9.5 shows a typical view of the editor when it breaks on an error.
Figure 9.5. The debugger breaking execution.
There are a few items to point out about the standard debug session shown in Figure 9.5. First, Visual Studio has highlighted the line on which the error was thrown. You can see this clearly by the arrow and the highlighted text.
Next, notice the window in the upper right of the image. This is the Exception Assistant, which is new to Visual Studio 2005. It provides details on the exception and offers tips for troubleshooting and fixing the given issue. From this window, you can access a few actions, including searching online help for more information on the exception.
At the bottom of the screen are a few additional helpful windows. The Locals window on the left automatically shows the value assigned to all local variables in the code where the exception was thrown. This gives you easy access to key information that might be contributing to the issue. Notice that to the right of this window is a tab called Watch 1. This is a watch window; it keeps track of any custom watch scenarios you set up (more on this later).
The window on the bottom right of the screen is the Call Stack. It shows the order in which various components of your application were called. You can look at the Call Stack to find out how you got to where you are. You can also use it to navigate to any code referenced in the stack. Finally, the tab next to this gives you access to the Immediate window. It allows you to type in code commands and get the results in the editor (more on this to come).
Attaching to the Web Service
After you examine your error, you can see it is being thrown inside the web service process. Unfortunately, the code called by the web service is being run in a separate process than the one you are debugging. When you debug an application, you debug (or shadow) a running process such as an executable (.exe).
To debug code in the web service process, you must both have the source code and attach to the executing process. In this case, all the code for the running application is in the solution. Therefore, you need to attach to the process.
Attaching to the process will enable you to debug code in libraries called by the web service. However, it will not, by default, enable you to debug the web service itself. The reason is that you have not enabled debugging for the web service. Recall Figure 9.2. Here, you turned on debugging for the web application. You need to do the same thing for the web service if you intend to step through code in the web service. You would do so by adding a web.config file to the site and setting the debug attribute to TRue.
In this case, however, do not end the debug session. You can see that the error is in code contained in a library that is called by the web service, and not the web service itself. Therefore, you simply attach to the web service process and debug code in the DataAccessLib project. To attach to the web service process, you choose the Attach to Process option from the Debug menu. This brings up the dialog box shown in Figure 9.6.
Figure 9.6. Attaching to a process.
In Figure 9.6 notice that the currently attached process is grayed out. This is a visual indicator that you are already attached to the web server running the UI. Beneath this, you see the web service process. To attach the debug session to this process, you simply highlight it and click the Attach button. You are now debugging both processes and can therefore set a breakpoint where the error might be occurring.
Note
If debugging is enabled on both the website and web service applications, Visual Studio still attaches only to the startup project's process. Therefore, if you want to debug code in the web service, you still need to attach to that process (in addition to enabling debugging).
Tip
If you have set a web reference to a web service in your solution and then you enable debugging in that web service, you may get errors from Visual Studio. These errors indicate that the remote server is now rejecting your connection. Unfortunately, the only way around this problem seems to be to delete the web reference and re-add it. If debugging was enabled in your web service prior to setting the web reference, you should be okay.
Setting the Breakpoint
To get the debugger to break into your code when it reaches a specific line, you set a breakpoint on that line. You do so by clicking on the indicator bar for the given line. Alternatively, you can right-click on the line and choose Insert Breakpoint from the Breakpoint context menu. In the example, the error may be coming from the code that gets the customer data from the database. Therefore, you need to navigate to the DataAccessLib project, open the Customer.cs file, and set a breakpoint, as shown in Figure 9.7.
Figure 9.7. Setting a breakpoint.
Continuing the Debugging
After you've navigated off the executing code during a debug session, it can often be hard to find your way back. The line that was executing could be buried in any one of the open code windows. Thankfully, you can use the Show Next Statement button (yellow arrow icon) on the Debug toolbar to take you back. This will return you to the line that was executing when the debugger broke.
In the example, rerun the call to the web service so you can now hit your breakpoint and step through the code. To do so, you must move the current execution point in the code. This can be accomplished by right-clicking the line where you want execution to start (or rerun) and selecting Set Next Statement from the context menu. Figure 9.8 shows this menu option.
Figure 9.8. The Set Next Statement option.
Now that you have backed up the execution point, you are ready to continue the debugging. You can do this by clicking the Run button again. This is essentially indicating you are done with the break and want to continue execution.
Stepping to Find the Error
In the example, the debugger will break execution as soon as it hits the breakpoint in the web service. This will allow you to step through the code. To step line-by-line through the code, you can click the Step Into button on the Debug toolbar or press the F11 function key. This will execute the code one line at a time, allowing you to view both execution flow as well as the state of the application as code executes. Doing so with the example allows you to see the error. It seems that an instance of the DataSet object was not set prior to your trying to fill it.
In most scenarios, you can make this fix during the debug session and continue stepping through or running the code. Unfortunately, in this example you cannot make the change while debugging. You cannot invoke Edit and Continue with an attached process. Figure 9.9 shows the message you get when you try.
Figure 9.9. Edit and Continue error with an attached process.
So instead of using Edit and Continue, you can bookmark the line where you want to make the change using the Text Editor toolbar. You then click the Stop button on the Debug toolbar to stop the debug session. You can now make your change.
To continue through self-checking, you would next restart the debugging process. However, prior to this, you may want to clear the breakpoint you had set. To do so, select the Breakpoints toolbar item. This brings up the Breakpoints window, as shown in Figure 9.10. From this window you can view all breakpoints in the application. Here, you select and clear the breakpoint by clicking the Delete button from the Breakpoint toolbar. Finally, you click the Run button to continue the debug, self-check session.
Figure 9.10. Breakpoints window.
Debugging Basics Summary
You have now set up your scenario and debugged your first error. This example was meant to introduce you to the basics of
doing debugging in Visual Studio 2005. If you are familiar with prior IDE versions, you probably noticed a lot of similarities. Walking through
the scenario showed the many tools inside the debugging environment, including the Debug toolbar and menu, the Breakpoints window, the watch
window, and so on. Now that you have a grasp of the basics, in the next section we intend to explore these debug elements in greater
detail.
The Visual Studio Debugger
The debugger built into Visual Studio 2005 is one of the largest and most complex tools in the IDE. With such a large feature-set area, we cannot cover every scenario you will encounter. However, we hope to expose the most commonly applicable features in this section. We will continue to work with the customer profile scenario for our examples.
The Debug Menu and Toolbar
The Debug menu and related toolbar provide your first-level access to starting debug sessions, stepping into code, managing breakpoints, and accessing the many features of debugging with Visual Studio. There are two states to the debug window: at rest and debug mode. In the at-rest state, the Debug menu allows you to start a debug session, attach code to a running process, or access some of the many debug windows. Figure 9.11 shows the menu in this state.
Figure 9.11. The Debug menu at rest.
When the debugger is engaged and you are working through a debug session, the state of the Debug menu changes. It now provides a number of additional options to those provided by the at-rest state. These options include those designed to move through the code, restart the session, and access even more debug-related windows. Figure 9.12 shows the Debug menu during a debug session.
Figure 9.12. The Debug menu during a debug session.
Let's look at the many options provided by the Debug menu. Table 9.1 presents all the items available from the toolbar, whether an at-rest option or one that is available only during a debug session. When reading through the table, refer to the preceding figures to get context on any given item.
Menu Item |
Description |
---|---|
Windows, Breakpoints |
Opens the Breakpoints window in the IDE. This window provides access to all the breakpoints in the option solution. |
Windows, Output |
Shows the Output window in the IDE. The Output window is a running log of the many messages that are emitted by the IDE, the compiler, and the debugger. This information can be useful during a debug session. |
Windows, Immediate |
Opens the Immediate window in the IDE. This window allows you to execute commands. For example, during design, you can call your methods directly from the Immediate window and have them enter breakpoints and so on. |
Start Debugging, Continue |
Either starts your application in debug mode or continues running the application if already in debug mode. |
Start Without Debugging |
Starts your application. However, it does not connect the debugger to the process. Therefore, this more closely represents what users would see when they run your application. |
Attach to Process |
Allows you to attach the debugger (and your code) to a running process (executable). |
Exceptions |
Opens the Exceptions option dialog box. This dialog box allows you to choose how the debugger breaks on any given exception. |
Step Into |
Starts the application in debug mode when your application is in design mode. For most projects, clicking the Step Into command invokes the debugger on the first executing line of the application. In this way, you can step into the application from the first line. |
If you are in a debug session, Step Into advances the debugger a line. If you choose to "step into" a function, the debugger will do so line-by-line. | |
Step Over |
Functions the same as Step Into with one major difference: If you choose to "step over" a function, the line calling the function will be executed (along with the function), and the debugger will set the next line (after the function call) as the next line to be debugged. |
Toggle Breakpoint |
Toggles the breakpoint on the current line of code. If the breakpoint is set, toggling it will remove it. If it is not set, toggling will set it. |
New Breakpoint, Break at Function |
Brings up the New Breakpoint dialog box. This dialog box allows you to indicate where in a given function the code should break. |
Delete All Breakpoints |
Removes all breakpoints from your solution. |
Disable All Breakpoints |
Disables breakpoints in the solution without deleting them. You can also disable individual breakpoints. This capability is very useful if you want to keep the breakpoints around for later but simply don't want to hit that one at the moment. |
Enable All Breakpoints |
Enables all breakpoints that have been disabled due to a call to Disable All Breakpoints. |
Break All |
Allows you to break the application into the debugger manually (without having to hit a breakpoint) during a debug session. This capability is useful to gain access to the debug information such as watch windows. |
Stop Debugging |
Terminates the debugging session. It also terminates the process you are debugging, provided that process was started by Visual Studio. |
Detach All |
Detaches the debugger from executing process. This allows your application to continue running after the debugger is through with it. |
Terminate All |
Stops debugging and terminates all processes to which you are attached. |
Restart |
Stops the debugging session and restarts it. Similar to clicking both Stop Debugging and Start Debugging in sequence. |
Step Out |
Tells the debugger to execute the current function and then break back into debugging after the function has finished. This capability is useful if you step into a function but then want to have that function just execute and yet return you to debug mode when it is complete. |
QuickWatch |
Brings up the QuickWatch window when the debugger is in break mode. The QuickWatch window shows one variable or expression you are watching and its value. QuickWatch's usefulness has been replaced by the new DataTips (more on this to come). |
Windows, Script Editor |
Opens the script editor. This feature is useful for debugging client- and server-side script. |
Windows, Watch |
Opens one of many possible watch windows in the IDE. Watch windows represent items and expressions you are keeping a close eye on through the debug session. |
Windows, Autos |
Opens the Autos window. This window shows variables (and their value) in the current line of code and the prior line of code. |
Windows, Locals |
Opens the Locals window in the IDE. This window shows variables in the local scope (function). |
Windows, Call Stack |
Shows the list of functions that are on the stack. Also indicates the current stack frame (function). This selected item is what defines the content from the Locals, watch, and Autos windows. |
Windows, Threads |
Shows the Threads window in the IDE. From here, you can view and control the threads in the application you are debugging. |
Windows, Modules |
Shows the Modules window in the IDE. This window lists the DLLs and EXEs used by your application. |
Windows, Processes |
Shows the Processes window in the IDE. This window lists the processes to which the debug session is attached. |
Windows, Memory |
Opens the Memory window for a view at the memory used by your application. This is valid only when address-level debugging is enabled from the Options dialog box. |
Windows, Disassembly |
Opens the Disassembly window. This window shows the assembly code corresponding to the compiler instructions. This is valid only when address-level debugging is enabled from the Options dialog box. |
Windows, Registers |
Opens the Registers window so that you can view register values change as you step through code. This is valid only when address-level debugging is enabled from the Options dialog box. |
The Debug Toolbar
The Debug toolbar provides quick access to some of the key items available on the Debug menu. From here, you can manage your debug session. For example, you can start or continue a debug session, stop an executing session, step through lines of code, and so on.
Figure 9.13 presents the Debug toolbar during an active debug session. In design mode, the Continue button would read Start Debugging, and a number of these items would be disabled. We have added callouts for each item on the toolbar. You can cross-reference these callouts back to Table 9.1 for further information.
Figure 9.13. The Debug toolbar during break mode.
Note
In Figure 9.13 the Breakpoints window icon on the right of the figure with the callout "Debug windows" actually is a drop-down menu. This menu provides access to the many debug windows that are available to developers.
Debug Options
You can control the many debugging options in Visual Studio through the Options dialog box. The Debugging node on the options tree provides access to these debugging switches. Figure 9.14 shows the general debugging settings.
Figure 9.14. The Debug Options dialog box.
The general settings list provides access to turn on and off many debugging options. These options include all of the following:
-
Turn on and off breakpoint filters
-
Enable or disable the warning dialog box associated with clearing all breakpoints
-
Turn on or off the Exception Assistant
-
Enable or disable just-my-code debugging
-
Require source code to exactly match that being debugged (or not)
-
And many more
There are additional debug option dialog boxes, which provide access to more debug settings. For instance, you can control how Edit and Continue works (you can also turn off this feature). There are settings for which type of code (Managed, Native, Script) is enabled for just-in-time debugging. You also have the option for using additional debug symbol files (.pdb and .dbg). These files can be helpful if you do not have the source code associated with a particular library you need to debug, such as Windows source or a third-party component.
These many options help you customize your debug experience. However, for this chapter, we are going to accept the default options and debug accordingly.
Stepping In, Out, and Over Code
Probably the most common debug operation for developers is stepping through their code line-by-line and examining the data emitted by the application and the debugger. Code stepping is just that, examining a line, executing the line, and examining the results (and then repeating the process over and over). Because this is such a dominant activity, becoming efficient with the step operations in Visual Studio is important for maximizing the use of your time during a debug session. Here, we will cover each of the stepping options and provide examples accordingly.
Beginning a Debug Session (Stepping into Code)
The Step Into command is available from both the Debug menu and toolbar (you can also press F11 as a shortcut). There are two behaviors commonly associated with this one command. The first is related to when you invoke the command for an application that is not currently running in debug mode. In this case, the application will be compiled, started, and the first line presented to you in the debug window for stepping purposes. This is, in essence, stepping into your application. Figure 9.15 shows a Windows form application in debug mode as the result of a call to Step Into.
Figure 9.15. Using Step Into to start an application.
Note
For web applications, using Step Into or Step Over does not work the same. Instead, your application simply runs in debug mode in the case of websites. The debugger does not break on the first line of your application. To do this, you must set a breakpoint or choose the Run To Cursor option (see the following section).
A call to the Step Over command (Debug menu, toolbar, or F10) while your application is at rest will result in the same behavior as Step Into. That is, your application (provided it is not a website) will be compiled and started in a debug session on the first line of code.
Run To Cursor
One of the more handy (and overlooked) features of the debug toolset is Run To Cursor. This feature works the way it sounds. You set your cursor position on some code and invoke the command. The application is compiled and run until it hits the line of code where your cursor is placed. At this point, the debugger breaks the application and presents the line of code for you to step through. This capability is especially handy because this is how many developers work. They are looking at a specific line (or lines) of code and want to debug this line. They do not need to start from the first line and often do not want to be bothered with breakpoints. The Run To Cursor feature is therefore an efficient means to get the debugger on the same page as you. Figure 9.16 shows this feature being invoked from the context menu.
Figure 9.16. Invoking the Run To Cursor command.
Run To Cursor works even if the user is required to activate some portion of the code prior to the code's reaching the cursor position. In this way, it really is an invisible, temporary breakpoint. For instance, in the example, users are presented with a default page. From here, they can select to edit their profiles as an option. If you set the Run To Cursor command on a line inside the edit profile screen, the debugger will still execute the application and wait until the users (testers or developers) invoke the given line of code.
Start Debugging
You can also start your debug session by selecting the Start Debugging option (green "play" arrow) from the Debug menu or toolbar (or F5). This starts a debug session but does not break into code unless an exception occurs or a breakpoint is encountered. This is a common operation for developers testing their code without wanting to walk through it or those who use a lot of breakpoints.
Break All
Finally, if your application is running and you want to enter break mode, you can do so at any time by invoking the Break All command from the Debug menu or toolbar (or Ctrl+Alt+Break). The Break All feature is represented by the pause icon. This stops your application wherever it is in execution and allows you to interrogate the debugger for information. The Break All command is especially useful if you need to break into a long-running process or loop.
Walking Through Your Code
During a debug session, you have basically three options for moving through your code. You can step into a line or function, step over a given function, and step out of a function. Let's look at each option.
Step Into
The Step Into command (F11) allows you to progress through your code one line at a time. Invoking this command will execute the current line of code and position your cursor on the next line to be executed. The important distinction between stepping into and other similar commands is how Step Into handles lines of code that contain function calls. If you are positioned on such a line, calling Step Into will take you to the first line inside the function (provided you have the appropriate debug symbols loaded).
As an example, look at Figure 9.17. It shows the sample web service making a call to the data access library's method named Customer.GetById. In this case, both projects are loaded in the solution; thus, you have access to their debug symbols. Therefore, a call to Step Into will result in your stepping into the first line of GetById.
Figure 9.17. Stepping into a line of code.
Figure 9.18 shows stepping into this function. Notice that you are now positioned to step line-by-line through this function. Of course, when you reach the end of this function, the debugger will return you to the next line in the calling function (line 23 in the web service depicted in Figure 9.17).
Figure 9.18. The results of stepping into a function.
Step Over
The Step Over command (F10) allows you to maintain focus on the current procedure without stepping into any methods called by it. That is, calling Step Over will execute line-by-line but will not take you into any function calls, constructors, or property calls.
As an example, consider Figure 9.17. Here, the debugger is positioned on the call to Customer.GetById. If you called the Step Over command, the GetById function would execute without your stepping through it. Instead, the next line to execute in step mode would be the line following the call to GetById. Of course, any exception thrown by the function you step over will result in the debugger breaking into your code as normal.
Step Out
The Step Out command (Shift+F11) is another useful tool. It allows you to tell the debugger to finish executing the current method you are debugging but return to break mode as soon as it is finished. This is a great tool when you get stuck in a long method you wished you had stepped over. In addition, you may step into a given function only to debug a portion of it and then want to step out.
As an example, refer again to Figure 9.18. Recall that you stepped into this method from the code in Figure 9.17. Suppose you start stepping through a couple of lines of code. After you take a look and verify that a connection is made to the database, you simply want to have the function complete and return to debugging back in the calling function (line 23 of Figure 9.17). To do so, you simply invoke Step Out.
Continuing Execution
When you are in a debug session, the Start Debugging (or Run) command changes to Continue. The Continue command is available when you are paused on a given line of code in the debugger. It enables you to let the application continue to run on its own without stepping through each line. For example, suppose you walked through the lines of code you wanted to see, and now you want to continue checking your application from a user's perspective. Using Continue, you tell the application and debugger to keep running until either an exception occurs or a breakpoint is hit.
Ending a Debug Session
You can end your debug session in few ways. One common method is to kill the currently executing application. This might be done by closing the browser window for a web application or clicking the Close (or X) button of a Windows application. Calls in your code that terminate your application will also end a debug session.
There are also a couple of options available to you from the Debug window. The Terminate All command kills all processes that the debugger is attached to and ends the debug session. There is also the Detach All option. Figure 9.19 shows both options in the toolbar. Detach All simply detaches the debugger from all running processes without terminating them. This capability can be useful if you've temporarily attached to a running process, debugged it, and want to leave it running.
Figure 9.19. Detaching from a process.
Indicating When to Break into Code
You control the debugger through breakpoints and tracepoints. With these, you can tell the debugger when you are interested in breaking into code or receiving information about your application. Breakpoints allow you to indicate when the debugger should stop on a specific line in your code. Tracepoints are new to Visual Studio 2005. They are a type of breakpoint that allows you to perform an action when a given line of your code is reached. This typically involves emitting data about your application to the output window. Mastering the use of breakpoints will reduce the time it takes to zero in on and fix issues with your code.
Setting a Breakpoint
The most common method of setting a breakpoint is to first find the line of code on which you want the debugger to stop. You then click in the code editor's indicator margin for the given line of code. Doing so will place a red circle in the indicator margin and highlight the line of code as red. Of course, these are the default colors; you can change the look of breakpoints in the Tools, Options dialog box under the Environment node, Fonts and Colors.
There are a few additional ways to set breakpoints. For instance, you can right-click a given line of code and choose Insert Breakpoint from the Breakpoint context menu. You can also choose New Breakpoint from the Debug menu (or press Ctrl+D, N). This option brings up the New Breakpoint dialog box in which you can set a function breakpoint.
Setting a Function Breakpoint
A function breakpoint is just a breakpoint that is set through the New Breakpoint dialog box. It is called a function breakpoint because it is typically set at the beginning of the function (but does not need to be). From the New Breakpoint dialog box, you can manually set the function on which you want to break, the line of code in the function, and even the character on the line.
If your cursor is on a function or on a call to a function when you invoke this dialog box, the name of the function will automatically be placed in the dialog box. You can also type a function name in the dialog box. Figure 9.20 shows the New Breakpoint dialog box in action. Notice that you can manually set the line and even the character on the line where the breakpoint should be placed.
Figure 9.20. The New Breakpoint dialog box.
Note
If you specify an overloaded function in the New Breakpoint dialog box, you must specify the actual function on which you want to break. You do so by indicating the correct parameter types for the given overload. For example, the current ShowCustomerDetails takes a customer ID as an int. If you had an overload that also looked up a customer by name (as a string), you would indicate this overload in the Function field as ShowCustomerDetails(string).
Recognizing the Many Breakpoints of Visual Studio
Visual Studio 2005 has a number of breakpoint icons. These icons allow you to easily recognize the type of breakpoint associated with a given line of code. For instance, a round, filled circle is a common breakpoint, whereas a round, hollow circle represents a common breakpoint that has been disabled. We've provided Table 9.2 for reference purposes. It shows some of the more common icons associated with breakpoints and presents a description of each.
Icon |
Description |
---|---|
|
This icon indicates a standard, enabled breakpoint. When the debugger encounters this line of code, it will stop the application and break into debug mode. |
|
This icon indicates a standard tracepoint. When the debugger hits this line of code, it will perform the action associated with the tracepoint. |
|
The plus icon inside the breakpoint indicates an advanced breakpoint that contains a condition, hit count, or filter. |
|
The plus icon inside the tracepoint indicates an advanced tracepoint that contains a condition, hit count, or filter. |
|
An empty or hollow breakpoint indicates a disabled breakpoint. The breakpoint is still associated with the line of code. However, the debugger will not recognize the disabled breakpoint until it has been re-enabled. |
Hollow icons are associated with types of breakpoint icons such as tracepoints, advanced items, and even breakpoint errors and warnings. In all conditions, the hollow icon indicates the item is disabled. | |
|
Represents a breakpoint warning indicating that a breakpoint cannot be set due to a temporary condition. This can be the result of debugging not being enabled for a website or debug symbols not being loaded. These icons are set by Visual Studio. |
|
Represents a tracepoint warning (see preceding description). |
Working with the Breakpoints Window
The Breakpoints window in Visual Studio provides a convenient way to organize and manage the many conditions on which you intend to break into the debugger. You access this window from the Debug menu or toolbar (or by pressing Ctrl+D, B). Figure 9.21 shows the Breakpoints menu inside Visual Studio.
Figure 9.21. The Breakpoints window.
The Breakpoints Window Toolbar
The Breakpoints window has its own toolbar that allows you to manage the breakpoints in the window. The commands available from the toolbar are described in detail in Table 9.3.
Item |
Description |
---|---|
|
Brings up the new Breakpoints window, allowing you to set a breakpoint at a function. |
|
Allows you to delete the selected breakpoint in the list. |
|
Deletes all breakpoints in the window. |
|
Toggles all breakpoints as either on or off. If all breakpoints are enabled, clicking this icon allows you to keep the breakpoints but turn them all off as a group. If all breakpoints are disabled, this command will enable them as a group. |
|
Allows you to go to the source code associated with the selected breakpoint. |
|
Allows you to go to the disassembly information associated with the selected breakpoint. |
|
Allows you to choose which columns you want to view in the Breakpoints window. Each column provides information about a given breakpoint. For example, you can see information on the condition associated with each breakpoint, the filename, the function, the filter, the process, and so on. |
Managing Each Individual Breakpoint
The Breakpoints window also gives you access to each individual breakpoint. It serves as a launching point for setting the many options associated with a breakpoint. For example, you can disable a single breakpoint by toggling the check box associated with the breakpoint in the list. In addition, you can set the many properties and conditions associated with a breakpoint. Figure 9.22 shows both a disabled tracepoint and the context menu associated with an individual breakpoint.
Figure 9.22. Managing an individual breakpoint.
Notice that from this context menu, you can delete the breakpoint and navigate to its related source code. More important, however, is the access to setting the conditions and filters associated with the breakpoint. We will cover using each of these in the next section.
Breaking Based on Conditions
Often, setting a simple breakpoint is not sufficient (or efficient). For instance, if you are looking for a particular condition to be true in your codea condition that seems to be causing an exceptionthen you would prefer to break based on that condition. This saves the time of constantly breaking into a function only to examine a few data points and determine that you have not hit your condition. The following sections highlight the conditional options available for breakpoints.
Setting a Breakpoint Condition
A breakpoint condition allows you to break into the debugger or perform an action (tracepoint) when a specific condition is either evaluated as true or has changed. Often, you know that the bug you are working on occurs only based on a very specific condition. Breakpoint conditions are the perfect answer for finding that intermittent bug.
To set a condition, you select the breakpoint on which you want to apply a condition. You then choose the Condition option from the right-click context menu. This will bring up the Breakpoint Condition dialog box, as shown in Figure 9.23. Notice that when setting the condition, you have access to IntelliSense (you can invoke IntelliSense either when you click a dot or press Ctrl+Space).
Figure 9.23. Setting a breakpoint condition.
When you set a condition, you have two options: Is True and Has Changed. The Is True option allows you to set a Boolean condition that, when evaluated to true, results in the debugger's breaking into the given line of code.
For an example, refer to the sample application. Suppose that you are notified of an error that happens only for a specific customer. You might go to the Customer class and set a breakpoint inside the GetCustomerProfile function. You might then add the Is True condition, customerId=1234, to the breakpoint (where customerId is the parameter to the function). This will tell the debugger not to stop on this line of code unless this condition is met. Figure 9.24 shows this condition in the dialog box. It also presents the two options available for conditions.
Figure 9.24. The Breakpoint Condition dialog box.
The Has Changed option tells the debugger to break when the value of an expression changes. The first pass through your code sets the value for the first evaluation. If the value changes after that, the debugger will break on a given line. This capability can be useful when you have fields or properties with initial values and you want to track when those values are being changed. In addition, Has Changed can be useful in looping and if…then scenarios where you are interested in only whether the results of your code changed a particular value.
Tip
Your breakpoint information is persisted between debug sessions. That is, when you close Visual Studio for the day, your breakpoints are still there when you return. This validates the time you might spend setting some sophisticated debugging options. They can remain in your application and turned on and off as required.
Setting a Breakpoint Filter
Breakpoint filters allow you to specify a specific machine, process, or thread on which you want to break. For instance, if your error condition seems to happen only on a certain machine or within a certain process, then you can debug this condition specifically with a filter. Filters are most useful in complex debugging scenarios where your application is highly distributed.
To use this feature, you can specify the machine by name, the process by name or ID, or the thread by name or ID. You can also use specify combinations with & (and), || (or), and ! (not). This allows you to get to a specific thread on a specific process on a certain machine. Figure 9.25 shows the dialog box in which you set breakpoint filters.
Figure 9.25. The Breakpoint Filter dialog box.
Using a Hit Count with a Breakpoint
Using the Hit Count command, you can tell the debugger that you want to break when a given line of code is reached a number of times. This feature is useful only if you cannot set a better condition and know that when you pass over your code a certain number of times something bad happens. However, the Hit Count option might be more useful in tracepoint scenarios where you are emitting data about what is happening in your code.
Figure 9.26 shows the Breakpoint Hit Count dialog box. Notice that this screenshot was taken during an active debug session. You can add any of these conditions to breakpoints during an active debug session. In addition, notice that the current hit count is set to one (1). You have the option to click the Reset button and turn the hit count back to zero and continue debugging from that point.
Figure 9.26. Setting a breakpoint hit count.
This dialog box also provides a few options for setting the actual hit count. In the drop-down list under When the Breakpoint Is Hit, the following options are available:
-
Break Always (the default and does not invoke the hit count option)
-
Break When the Hit Count Is Equal To
-
Break When the Hit Count Is a Multiple Of
-
Break When the Hit Count Is Greater Than or Equal To
Tip
You can combine all the breakpoint conditions we've discussed to create even more specific conditions for your breakpoints.
Working with Tracepoints (When Hit…)
Tracepoints allow you to emit data to the Output window or run a Visual Studio macro when a specific breakpoint is hit. This capability can be very useful if you want to keep a running log of what is happening as your application runs in debug mode. You can then review this log to get valuable information about specific conditions and order of execution when an exception is thrown.
You can set tracepoints explicitly by right-clicking a line of code and choosing Insert Tracepoint from the Breakpoint menu. In addition, selecting the When Hit command from the context menu for a breakpoint (in the Breakpoints window) will bring up a tracepoint dialog box, which is titled When Breakpoint Is Hit, as shown in Figure 9.27.
Figure 9.27. Setting a tracepoint.
The options available for the When Breakpoint Is Hit dialog box include printing a message to the output window, running a macro, and continuing execution. You can choose any combination of these options. The first, printing a message, allows you to output data about your function. There are a number of keywords you can use to output data, such as $FUNCTION for the function name and $CALLER for the name of the calling function. A full list is printed in the dialog box in Figure 9.27. You can also output your specific variable values. You do so by enclosing the variable names in curly braces.
The Continue Execution option allows you to indicate whether this is a true tracepoint or a breakpoint that contains a tracing action. If you choose to continue, you only get the trace action (message and/or macro). If you indicate not to continue, you get the trace action; plus, the debugger stops on this line of code, just like a simple breakpoint. This is essentially applying a When Hit action to a standard breakpoint.
Finally, when you select the Run a Macro option, the dialog box gives you a list of all the macros loaded in your environment for selection.
You can also combine tracepoint actions with conditions. When you do so, the action fires only when the breakpoint condition is met.
As an example, we have set a tracepoint inside the web service GetCustomerProfile (see Figure 9.27). This tracepoint prints a message to the output window when the line of code is hit and simply continues the application executing. The message we intend to print is as follows:
Function: $FUNCTION, Thread: $TID $TNAME, Customer Id {customerId}
This message will print the function name, thread ID and name (if any), and the value of the variable, customerId. Figure 9.28 shows two passes through the tracepoint output in the Output window.
Figure 9.28. The results of a tracepoint.
Viewing Data in the Debugger
After the debugger has thrown you into break mode, the next challenge is to filter all the data your application is emitting. Getting to the right data will help you find problems faster and fix them faster. Visual Studio tries to make the data available where you want it. For example, DataTips show you variable values right in the code editor. There are many similar improvements in the way Visual Studio shows debugging data in the 2005 edition. We will cover these and more throughout the following sections.
Watching Variables
A common activity in a debug session is to view the values associated with the many types in your application. There are a number of windows available to help you here. The two most obvious are the Locals and Autos windows.
Locals Window
The Locals window shows all the variables and their values for the current debug scope. This gives you a view of everything available in the current, executing method. The variables in this window are set automatically by the debugger. They are organized alphabetically in a list by name. In addition, hierarchy is also shown. For example, if a given variable relates to object type, that object's members are listed inside the variable (as a tree).
Figure 9.29 shows an example of the Locals window. In it, you can see the sample application paused inside the GetCustomerProfile method. Notice that the cust variable is expanded to show the various properties and fields associated with this object. As values are set, the results are shown in the Value column.
Figure 9.29. The Locals window.
Tip
You can edit a value in the Locals or Autos window. To do so, right-click the variable and choose Edit Value from the context menu. You can then change the value of the variable similar to using the Immediate window.
The Autos Window
Often, viewing all the locals provides too many options to sort through. This can be true when there is just too much in scope in the given process or function. To hone in on the value of the code you are looking at, you can use the Autos window. This window shows the value of variables and expressions that are in the current executing line of code or in the prior line of code. This allows you to really focus on just the values you are currently debugging.
Figure 9.30 shows the Autos window for the same line of code as was shown in Figure 9.29. Notice the difference in what is shown. Also, notice that Visual Studio has even added specific expressions that differ from the code to the watch list. For example, the call to custDr["city"] is shown as a watch item.
Figure 9.30. The Autos window.
The Watch Windows
The Visual Studio watch windows allow you to set a custom list of variables and expressions that you want to keep an eye on. In this way, you decide the items in which you are interested. The watch windows look and behave just like the Locals and Autos windows. In addition, the items you place in watch windows persist from one debug session to another.
You access each watch window from the Debug menu or toolbar. The four watch windows are named Watch 1 through Watch 4. Having four watch windows allows you to set up four custom lists. This capability can be especially helpful if each custom list applies to a separate scope in your application.
You add a variable or expression to the watch window from either the code editor or the QuickWatch window. If you are in the code editor, you select a variable or highlight an expression, right-click, and choose the Add Watch menu item. This will take the highlighted variable or expression and place it in the watch window. You can also drag and drop the highlighted item into a watch window.
QuickWatch
The QuickWatch window is very similar to the other watch windows. However, it allows you to focus on a single variable or expression. The QuickWatch window is used less often now that DataTips exist. From the QuickWatch window, you can write expressions and add them to the watch window. When writing your expression, you have access to IntelliSense. Figure 9.31 shows the QuickWatch window.
Figure 9.31. The QuickWatch window.
The item you add to QuickWatch will be evaluated when you click the Reevaluate button. Clicking the Add Watch button will send the variable to the watch window.
Getting Data Tips
In prior versions of Visual Studio, you could highlight a section of code and view a ToolTip that indicated the value of the variable or expression. In 2005, this concept is expanded considerably with DataTips. DataTips allow you to highlight a variable or expression in the code editor and get watch information right there in the editor. This feature is more how developers work. For example, if you are looking at a line of code, you might highlight something in that line to evaluate it. Previously, this meant doing a QuickWatch. Now it simply unfolds in a DataTip.
Figure 9.32 provides an example. Here, the cursor is positioned over the cust variable that is of type Customer. Clicking on the plus sign to expand this variable unfolds the many members of the object. You can scroll through this list using the arrow at the bottom of the window. You can also right-click any member in the list and edit its value, copy it, or add it to the watch window. The magnifying glass icon next to the items in the list allows you to select a specific visualizer for a given item (more on these shortly).
Figure 9.32. The DataTips window.
You can still select an expression and have it evaluated as a DataTip. For example, if you select the portion of line 39 in Figure 9.32 that reads (bool)custDr["contact_via_email"], a DataTip will show this variable and its value of true.
Tip
The DataTips window can often get in the way of viewing code. Sometimes, you need to see the DataTips and the code underneath. In this case, holding the Control (Ctrl) key will make the DataTips window transparent for as long as you press it.
Visualizing Data
When you are looking at variable values, what you really want to get to is the data behind the object. Sometimes this data is obscured by the object model itself. For example, suppose you are looking for the data that is contained in a DataSet object. To find it, you have to dig many layers deep in a watch window or a DataTip. You have to traverse the inner workings of the object model just to get at something as basic as the data contained by the object. If you've spent much time doing this in prior versions of Visual Studio, you know how frustrating it can be.
Visual Studio 2005 tries to take away this frustration and provide quick, easy access to the data contained in an object. It does so through a new tool called visualizers. Visualizers are meant to present the object's data in a meaningful way.
A few visualizers ship with Visual Studio by default. They include the following:
-
HTML Shows a browser-like dialog box with the HTML interpreted as a user might see it
-
XML Shows the XML in a structured format
-
Text Shows a string value in an easy-to-read format
-
DataSet Shows the contents of the DataSet, DataView, and DataTable objects
There is also a framework for writing and installing visualizers in Visual Studio. You can write your own and plug them into the debugger. You can also download additional visualizers and install them. The possibilities of visualizers are many: as many ways as there are to structure and view data. A few ideas might be a tree-view visualizer that displays hierarchical data or an image visualizer that shows image data structures.
You invoke a visualizer from one of the many places you view data values. This includes watch windows and DataTips. Visualizers are represented by a magnifying glass icon. Figure 9.33 shows launching a visualizer using this icon.
Figure 9.33. Launching a visualizer from the DataTip.
For a visualizer example, refer to the DataSet problem. Rather than digging through the object hierarchy to get at the data, you can now invoke the DataSet visualizer right from a DataTip. Figure 9.34 shows the visualizer in action for the customer DataSet object in the sample application.
Figure 9.34. The DataSet Visualizer.
Using the Edit and Continue Feature
Edit and Continue allows you to change code as you debug without killing your debug session. You can make a modification to a line of code or even fix a bug and keep working in break mode. Visual Basic developers who worked in versions prior to .NET should recall this powerful tool. Its absence in .NET made it one of the most requested features. The good news is that Edit and Continue was added in 2005 to both Visual Basic and C#.
There is no trick to invoking Edit and Continue. You simply make your code change during a debug session and then keep running through your code with a Step command or Continue.
The feature is turned on by default. If it is turned off, you can re-enable it using the Options dialog box available from the Tools menu.
Not all code changes you make are eligible for Edit and Continue. In fact, it should be used only in minor fixes. Any major additions to your code should not be done in debug mode just as a best practice. If your change is within the body of a method, it has a higher likelihood of passing the Edit and Continue test. Most code changes outside the method body require the debugger to restart. Some common changes that are not eligible for Edit and Continue include
-
Changing code on the current, active statement
-
Changing code on any calls on the stack that lead to the current, active statement
-
Adding new types, methods, fields, events, or properties
-
Changing a method signature
For a more exhaustive list, search MSDN for "Edit and Continue." There are similar lists for both Visual Basic and C#.
Remote Debugging
Remote debugging allows you to connect to a running application on another machine or domain and debug that application in its environment. This is often the only way to experience errors that are occurring on specific hardware. We've all heard the developer's cry, "Works on my machine." Remote debugging helps those developers figure out why their application doesn't work in other environments.
In a number of scenarios, remote debugging makes a lot of sense. They include debugging SQL serverstored procedures, web services, web applications, remote services or processes, and so on.
The hardest part about doing remote debugging is getting it set up properly. The actual debugging is no different from the debugging we've discussed thus far. However, the setup requires you to jump through a lot of hoops in terms of installation and security. These hoops are necessary because you do not, by default, want developers to easily connect debug sessions to applications on your servers.
There is some good news. Visual Studio 2005 tries to minimize and simplify the setup and configuration of remote debugging. Microsoft has introduced the Remote Debugging Monitor (msvsmon.exe) for this purpose. However, developers will still find the setup tasks somewhat arduous (but rewarding when finished). We will not cover the setup in great detail here. We suggest querying MSDN for "Remote Debugging" to get the full walkthrough.
We do offer the following, however, as a set of high-level tasks that you will need to complete to get remote debugging working:
-
Install the remote debugging monitor (msvsmon.exe) on the remote machine being debugged. You install it using the setup application, rdbsetup.exe. You can also run it from a file share.
-
Configure remote debugging permissions. Typically, this means giving your user account administrative access to the machine being debugged.
-
Run the remote debugging monitor on the remote machine. This is a Windows application (with a GUI). You can also set the monitor to run as a Windows service. This capability can be useful for specific server scenarios and ASP .NET remote debugging.
-
If your debug machine is running XP with SP2, you will have to configure the firewall for remote debugging (see MSDN documentation for details).
-
Run Visual Studio on your debug machine as you would to debug any process. Open the project that contains the source for the process you want to debug.
-
Attach to the running process on the remote machine using Attach to Process. You will have to browse to the machine you want to debug and find the process running on that machine.
As you can see, getting remote debugging set up can be a challenge. However, if you have a test environment that you typically
debug, the setup should be a one-time operation. From there, you should be able to debug in a more realistic environment as well as walk
through SQL stored procedures.
Summary
This chapter presented the Visual Studio 2005 debugger. We covered setting breakpoints in code as well as setting conditions for when those breakpoints are hit. We discussed stepping through code after hitting that breakpoint. In addition, we presented tracepoints, which perform an action (such as printing a message to the output window) when a line of code is hit in the debugger. The chapter also examined the many ways you can see the data presented by the debugger, including the watch windows, visualizers, and DataTips.
The many enhancements in the Visual Studio 2005 debugger are sure to speed your overall development effort. This is a key
skill to have. Just like the better you become at coding to anticipate errors and prevent them, honing your skills with the debugging tools is
yet another way to unlock additional productivity.
reference :http://codeidol.com/csharp/visual-studio-2005/Debugging-with-Visual-Studio-
2005/Summary/