understand the runtime behavior of your source code and quickly track down logic errors.
call stack
As applications grow in complexity, it is quite common for the execution path to become difficult to
follow. The use of deep inheritance trees and interfaces can often obscure the execution path. This
is where the call stack is useful. Each path of execution must have a finite number of entries on the
stack (unless a cyclic pattern emerges, in which case a stack overflow is inevitable). The stack can be
viewed using the Call Stack window (Debug .
Windows . Call Stack), shown in Figure 39-8.
fiGure 39-8
Using the Call Stack window, it is easy to navigate up the execution path to determine from where
the current executing method is being called. You can do this by clicking any of the rows in the call
stack, known as a stack frame. Other options available from the call stack, using the right-click
context menu, enable viewing the disassembler for a particular stack frame, setting breakpoints,
and varying what information is displayed.
834 .
chaPter 39 uSing The debugging WindoWS
threads
Most applications make use of multiple threads at some point. In particular for Windows applications,
in order for the user interface to always appear responsive, it is important to run time-consuming tasks
on a thread separate from the main application. Of course, concurrent execution of threads makes
debugging more difficult, especially when the threads are accessing the same classes and methods.
Figure 39-9 shows the Threads window (Debug .
Windows . Threads), which lists all the active
threads for a particular application. Notice that in addition to the threads created in the code,
additional background threads have been created by the debugger. For simplicity, the threads used
by this application, including the main user interface thread, have been given names so they can
easily be distinguished.
fiGure 39-9
The Threads window shows a yellow arrow next to the thread that is currently being viewed in the
code window. To navigate to another thread, simply double-click that thread to bring the current
location of that thread into view in the code window and update the call stack to reflect the new
thread.
In Break mode, all threads of an application are paused. However, when you are stepping through
your code with the debugger, the next statement to be executed may or may not be on the same
thread you are interested in. If you are only interested in the execution path of a single thread, and the
execution of other threads can be suspended, right-click the thread in the Threads window and select
Freeze from the context menu. To resume the suspended thread, select Thaw from the same menu.
Debugging multi-threaded applications is explained further in Chapter 43.
Modules
The Modules window (Debug .
Windows . Modules), shown in Figure 39-10, displays a list
of assemblies that are referenced by the running application. Those assemblies that make up the
application will also have debugging symbols loaded, which means that they can be debugged
without dropping into the disassembler. This window is particularly useful if you want to find out
what version of an assembly is currently loaded and where it has been loaded from.
The Memory Windows .
835
fiGure 39-10
In Figure 39-10 the symbols have been loaded for the DebugApp1.exe application. All the other
assemblies have been skipped, because they contain no user code and are optimized. If an
appropriate symbol file is available, it is possible to load it for an assembly via the Load Symbols
option from the right-click context menu.
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 2010 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 in the
Processes window (Debug .
Windows . Processes). Figure 39-11 shows a solution containing two
Windows applications and a web application.
fiGure 39-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.
the MeMory windows
The next three 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
requires a lot of background knowledge and patience to analyze and make use of the information
that is presented. Only in very rare cases while developing managed code would you be required to
perform debugging at such a low level.
836 .
chaPter 39 uSing The debugging WindoWS
Memory windows 1–4
The four Memory windows can be used to view the raw contents of memory at a particular address.
Where the Watch, Autos, and Locals windows provide a way of looking at the content of variables,
which are stored at specific locations in memory, the Memory window shows you the big picture
of what is stored in memory.
Each of the four Memory windows (Debug
.
Windows . Memory 1 to Memory 4)
can examine different memory addresses
to simplify debugging your application.
Figure 39-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.
disassembly
Interesting debates arise periodically over the relative performance of two different code blocks.
Occasionally this discussion devolves 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 involves much less
arguing. Figure 39-13 shows the Disassembly window (Debug .
Windows . Disassembly) for a
LinkLabel click — the run time is about to construct a new Customer object. You can see MSIL
instructions that make up this action.
fiGure 39-12
fiGure 39-13
You can see from Figure 39-13 that a breakpoint has been set on the call to the constructor 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
intelliTrace (Ultimate edition only) .
837
Registers window (Debug . Windows . Registers), shown in Figure 39-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 39-14
intellitrace (ultiMate edition only)
One of the more interesting new features in the Ultimate edition of Visual Studio is IntelliTrace. One
of the limitations of traditional debuggers is that they only show a snapshot of the state of the
application at a single point in time. The IntelliTrace feature of Visual Studio collects information
during the debugging session, thereby allowing you to go back to an earlier point and view the
application state at that time.
You can think of IntelliTrace as your very own black box flight recorder for
debugging.
IntelliTrace has two data collection levels. By default it collects information about diagnostic
events only, such as entering Break mode, stepping through code in the debugger, or when an
exception is thrown. You can also configure IntelliTrace to collect very detailed information,
such as the details of every function call, including the parameters passed to that function and
the values that were returned.
The IntelliTrace Events window (Debug .
Windows . IntelliTrace Events) shown in
Figure 39-15, enables you to navigate to past
diagnostic events. When you click a past
event, the execution point in the code window
changes from a yellow arrow to a red arrow
with a stopwatch icon. The call stack is also
updated to reflect the historical state of the
application.
If you have enabled the detailed data collection
level, you will be able to use the Autos and
Locals windows to inspect the contents of
variables that have been collected.
fiGure 39-15
838 .
chaPter 39 uSing The debugging WindoWS
You can change the data collection level or disable it completely from the IntelliTrace tab in the
options menu (Tools . Options). You can also configure IntelliTrace to exclude certain assemblies
from the data collection.
You can expect a reasonable performance impact if you enable the detailed data
collection level. You must also ensure that you have enough free disk space to
collect this data. The Edit and Continue functionality is also disabled for the
detailed level.
IntelliTrace can also debug logs created by the new Visual Studio software test tools, Test and Lab
Manager. Chapter 55 provides more information on IntelliTrace.
the Parallel debuGGinG windows
Nowadays it is almost impossible to purchase a new computer that has a single processor. The trend
to many - core CPUs, which has been necessary due to physical limitations that have been reached in
CPU architecture, will certainly continue into the future as the primary way for hardware vendors
to release faster computers.
Unfortunately, software that has not been written to explicitly run on multiple CPUs will not run
faster on a many-core machine. This will be a problem for many users who have been conditioned
over the past couple of decades to expect their applications to run faster when they upgrade to
newer hardware.
The solution is to ensure that our applications can execute different code paths concurrently on
multiple CPUs. The traditional approach is to develop software using multiple threads or processes.
Unfortunately, writing and debugging multi-threaded applications is very difficult and error prone,
even for an experienced developer.
Microsoft has recognized this issue, and has introduced a number of new features with Visual Studio
2010 and .NET Framework version 4.0 aimed to simplify the act of writing such software. The Task
Parallel Library (TPL) is a set of extensions to the .NET Framework to provide this functionality. The
TPL includes new language constructs, such as the Parallel.For and Parallel.ForEach loops, and
new collections that are specifi cally designed for concurrent access including ConcurrentDictionary
and ConcurrentQueue.
In the new System.Threading.Tasks namespace are several new classes that greatly simplify the
effort involved in writing multi-threaded and asynchronous code. The Task class is very similar to a
thread; however, it is much more lightweight and therefore performs much better at run time.
Writing parallel applications is only one part of the overall development life cycle — you also need
effective tools for debugging parallel applications. To that end Visual Studio 2010 has introduced
two new debugging windows — the Parallel Stacks window and the Parallel Tasks window.
The Parallel Debugging Windows .
839
Parallel stacks
You will recall from earlier in the chapter, the Call Stacks window can be used to view the execution
path of the current line of code when debugging. One of the limitations of this window is that you
can see only a single call stack at a time. To see the call stack of other threads, you must use the
Threads window or Debug Location toolbar to switch the debugger to a different thread.
The Parallel Stacks window (Debug .
Windows . Parallel Stacks), shown in Figure 39-16, is one
of the more useful windows for debugging multi-threaded and parallelized applications. It provides
not just a way to view multiple call stacks at once, but also provides a graphical visualization of the
code execution including showing how multiple threads are tied together and the execution paths
that they share.
fiGure 39-16
The Parallel Stacks window in Figure 39-16 shows an application that is currently executing seven
threads. The call graph is read from bottom to top. The Main thread appears in one box, and four
others threads are grouped together in another box. The reason these four threads are grouped is
because they share the same call stack (that is, each thread called FuncA, which then called FuncB,
which in turn called FuncC). After these threads executed FuncC, their code paths diverged. One
thread executed FuncD, which then called FuncE. A different thread executed FuncF, FuncG, and
then FuncH. The other two threads executed FuncI, which called FuncJ, and so on. You can see
how visualizing all of the call stacks at once provides a much better understanding on the state
of the application as a whole and what has led to this state, rather than just the history of an
individual thread.
A number of other icons are used on this screen. The execution point of the current thread is shown
with a yellow arrow. In Figure 39-16, this is against FuncE in a box on the left-hand side of the
diagram. Each box that the current thread has progressed through as part of its execution path is
840 .
chaPter 39 uSing The debugging WindoWS
highlighted in blue. The wavy lines (also known as the cloth thread icon) shown against the call to
FuncK in the top-right box indicates that this is the current execution point of a non-current thread.
As shown in Figure 39 - 16, you can hover over the thread count label at the top of each box to see the
Thread ID ’s of the applicable threads. You can also right - click any entry in a call stack to access various