Debugging with Visual Studio 2005/2008, Part 1: Debugging Conceptsby Patrick Mancier
Whenever you write C++ programs, inevitably you're going to have to debug the program. This series of articles will discuss how to debug using Visual Studio, but first, we'll need to start out with an overview of important concepts used by debuggers.
Local debugging involves running the program you are debugging runs on the same machine as the debugger. For most situations, this is what people will do. Debugging locally has the least amount of latency involved. In other words your program will respond to the debugging environment best if the debugger is running on the same system. However, a disadvantage is if for some reason the process traps and causes the system to crash to a blue screen or the debugger hangs up, you have to wait for things to restart to see what may have happened. Another disadvantage is that it may be difficult to debug, watch variables and see the trace output while you're running a program on the same system. If your application is a GUI and it takes up the entire screen, it's a little difficult to switch back and forth as your debugging. The best situation for debugging code locally is to have two screens hooked up to your system. This way, you can run the program on one screen and debug it on the other.
Remote debugging means that the program your debugging is running on a separate machine and the debugging takes place on an isolated box. Remote debugging usually takes place over a network connection. Visual studio has a special program that is used to set up a remote debugging session. Other than the fact that the process is running on a separate machine, debugging remotely is really no different than running locally from the perspective of the IDE. However, there is some latency issues to deal with, but if your target machine and the debugging machine are communicating on the same network switch these issues really aren't that bad. Using a remote debugging setup is similar to having two screens hooked to one system.
Breakpoints are simply spots in the code that are marked to tell the debugger to stop the programs execution at that point when the program counter is set for that particular line in code. Breakpoints have various uses and to set one is completely dependent on the situation. Many people will set a breakpoint just to make sure a specific function is being called for example. Another example is a breakpoint is set to check the value of a variables or memory after various sets of instructions.
Breakpoints can also be set up to execute on a specified condition. For example, if you're running code in a loop and you want the breakpoint to trigger on the third iteration, you can set up a 'conditional breakpoint'. Basically set up the breakpoint to check for this condition and when the condition is met the program execution will stop.
Whatever the case may be, breakpoints are the cornerstone of effective debugging. The use of breakpoints in Visual Studio will be detailed more in the section of the setting up the IDE.
Visual Studio has what is called an 'Output' window. This window serves a variety of purposes. It gives a status of how the compilation of a program is going; it shows the various responses to linking. It also shows the output of a 'Trace'. A trace is simply a macro in Visual Studio that is used to print out a log message onto the output window. This is an effective tool for watching the status of how a program is running in a quasi-real time fashion. It is difficult to use sometimes depending on how your program runs. Generally you would want to use this window for user type events, for example when the user selects an item on a list control you might want to put up in the trace window the output of the selected index.
Dump files are simply files that are used to save the program run state. To create a dump file, supporting code in the program must be written. Generally dump files are generated on some type of exception. The dump file contains the symbolic debug information, the current line the program is on, all sorts of other program information. The general idea is to take the dump file of a program that has trapped, pull this dump file into the debugger, associate it with some source code and effectively see what state the program was in as if you were debugging it there as it was running. The dump file is a very useful tool for released code that is running in the field. It is a tool that a software engineer can use to check for problems as code runs over time. It is not always easy to reproduce conditions that cause a bug in the lab; sometimes it may take more effort to reproduce and the dump file gives the software engineer a view into what is happening when the exception occurs.
Log files are simply text-based output of a program that is running. The log file mechanism itself can range from a simple appending of a text file to something more complex like a separate thread or process that accepts incoming queued log entries and writes them out asynchronously.
A properly set up log file framework will track various levels of information. For example, with a default setting maybe just warnings, errors and basic information are tracked. With another setting, perhaps a specific module, feature or thread is tracked. With another setting, all function entries and exits are checked. It is advisable to make this trace level configurable, putting it in a registry entry or an INI file.
Log files are an essential part of debugging because they act as a flight recorder. When a program traps, the log file can give the software developer clues as to what was occurring before the trap that an IDE debugger may not necessarily be able to provide. Stepping through code sometimes produces different results than just allowing the program to run and the log file allows tracking of information while minimizing the latency associated with full IDE debugging.