Channels ▼
RSS

.NET

Debugging Multithreaded Applications in Windows


Debugging applications in which several threads are active can be challenging to the point of great frustration. This article aims to relieve a bit of that frustration by explaining how to utilize Visual Studio to debug multithreaded applications. As part of Visual Studio, Microsoft includes a debugger with multithreaded debug support. We'll start by examining the different multithreaded debug capabilities of Visual Studio, and then demonstrate how to use them.

Threads Window

As part of the debugger, Visual Studio provides a "Threads" window that lists all of the current threads in the system. From this window, you can:

  • Freeze (suspend) or thaw (resume) a thread. This is useful when you want to observe the behavior of your application without a certain thread running.
  • Switch the current active thread. This allows you to manually perform a context switch and make another thread active in the application.
  • Examine thread state. When you double-click an entry in the Threads window, the source window jumps to the source line that the thread is currently executing. This tells you the thread's current program counter. You will be able to examine the state of local variables within the thread.

The Threads window acts as the command center for examining and controlling the different threads in an application.

Tracepoints

Determining the sequence of events that lead to a race condition or deadlock situation is critical in determining the root cause of any multithread-related bug. To facilitate the logging of events, Microsoft has implemented tracepoints as part of the debugger for Visual Studio for many years.

Most developers are familiar with the concept of a breakpoint. A tracepoint is similar to a breakpoint except that instead of stopping program execution when the applications program counter reaches that point, the debugger takes some other action. This action can be printing a message or running a Visual Studio macro.

Enabling tracepoints can be done in one of two ways. To create a new tracepoint, set the cursor to the source line of code and select "Insert Tracepoint." If you want to convert an existing breakpoint to a tracepoint, simply select the breakpoint and pick the "When Hit" option from the Breakpoint submenu. At this point, the tracepoint dialog appears.

When a tracepoint is hit, one of two actions is taken based on the information specified by the user. The simplest action is to print a message. You can customize the message based on a set of predefined keywords. These keywords, along with a synopsis of what gets printed, are shown in Table 1. All values are taken at the time the tracepoint is hit.

KEYWORD

EVALUATES TO

$ADDRESS

The address of the instruction

$CALLER

The name of the function that called this function

$CALLSTACK

The state of the callstack

$FUNCTION

The name of the current function

$PID

The ID of the process

$PNAME

The name of the process

$TID

The ID of the thread

$TNAME

The name of the thread

Table 1: Tracepoint keywords.

In addition to these predefined values, tracepoints also give you the ability to evaluate expressions ins the message. To do this, simply enclose the variable or expression in curly braces. For example, assume your thread has a local variable threadLocalVar that you'd like to have displayed when a tracepoint is hit. The expression you'd use might look something like this:
Thread: $TNAME local variables value is {threadLocalVar}.

Breakpoint Filters

Breakpoint filters enable developers to trigger breakpoints only when certain conditions are triggered. Breakpoints can be filtered by machine name, process, and thread. A list of different breakpoint filters is shown in Table 2.

FILTER

DESCRIPTION

MachineName

Specifies that the breakpoint should only be triggered on certain machines

ProcessId

Limit breakpoint to process with the matching ID

ProcessName

Limit breakpoint to process with matching name

ThreadId

Limit breakpoint to thread with matching ID

ThreadName

Limit breakpoint to thread with matching name

Table 2: Breakpoint filter options.

Breakpoint filters can be combined to form compound statements. Three logic operators are supported: ! (not), & (and), and || (or).

Naming Threads

When debugging a multithreaded application, it is often useful to assign unique names to the threads that are used in the application. Assigning a name to a thread in a managed application is as simple as setting a property on the thread object. In this environment, it is highly recommended that you set the name field when creating the thread because managed code provides no way to identify a thread by its ID.

In native Windows code, a thread ID can be directly matched to an individual thread. Nonetheless, keeping track of different thread IDs makes the job of debugging more difficult, as it is hard to keep track of individual thread IDs. Unfortunately, the standard thread APIs in Win32 lack the ability to associate a name with a thread. As a result, this association must be made by an external debugging tool.

Microsoft has enabled this capability through predefined exceptions built into their debugging tools. Applications that want to see a thread referred to by name need to implement a small function that raises an exception. The exception is caught by the debugger, which then takes the specified name and assigns it to the associated ID. Once the exception handler completes, the debugger will use the user-supplied name from then on.

The implementation of this function can be found on the Microsoft Developer Network (MSDN) website at msdn.microsoft.com by searching for: "setting a thread name (unmanaged)." The function, named SetThreadName(), takes two arguments. The first argument is the thread ID. The recommended way of specifying the thread ID is to send the value -1, indicating that the ID of the calling thread should be used. The second parameter is the name of the thread. The SetThreadName() function calls RaiseException(), passing in a special "thread exception" code and a structure that includes the thread ID and name parameters specified by the programmer.

Once the application has the SetThreadName() function defined, the developer may call the function to name a thread. This is shown in Listing One. The function Thread1 is given the name Producer, indicating that it is producing data for a consumer. Note that the function is called at the start of the thread, and that the thread ID is specified as -1. This indicates to the debugger that it should associate the calling thread with the associated ID.

Listing One: Using SetThreadName to name a thread.

        unsigned   __stdcall Thread1(void *)
        {
            int i, x = 0; // arbitrary local variable   declarations 
            SetThreadName(-1,     "Producer");
            // Thread logic follows
        } 

Naming a thread in this fashion has a few limitations. This technique is a debugger construct; the OS is not in any way aware of the name of the thread. Therefore, the thread name is not available to anyone other than the debugger. You cannot programmatically query a thread for its name using this mechanism. Assigning a name to a thread using this technique requires a debugger that supports exception number 0x406D1388. Both Microsoft's Visual Studio and WinDbg debuggers support this exception. Despite these limitations, it is generally advisable to use this technique where supported as it makes it much easier to use the debugger and track down multithreaded bugs.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video