T is for… Tracepoint

t

I’ve presented various sessions on Debugging Tips and Tricks as part of the Northeast Roadshow and MSDN Events series, and one of the undiscovered gems in that presentation is that of tracepoints.  Tracepoints have actually been around since Visual Studio 2005, but weren’t all that discoverable until Visual Studio 2008.

What exactly is a tracepoint?  It’s essentially a breakpoint that doesn’t halt the execution of your application in the debugger.  Instead, it provides the opportunity to write information to the output window, with much the same effect that Trace.WriteLine has, but without requiring code to do it.  You can also use tracepoints to run macros for more advanced debugging scenarios.

To get an idea of how to employ tracepoints, here’s a YACE (yet-another-contrived-example) that builds a list of the prime numbers between two input numbers.  There are far better algorithms than this for performing this task, so don’t focus too much on the logic, lack of bounds checking, coding style, performance issues, etc.

 

    1:  List<Int32> primeList = new List<Int32>();
    2:  Boolean isPrime;
    3:   
    4:  if ((startVal <= 2) && (endVal >= 2)) primeList.Add(2);
    5:   
    6:  for (int candidate = Math.Max(3, startVal); candidate <= endVal; 
    7:           candidate++)
    8:  {
    9:      isPrime = true;
   10:      int maxTest = (int) Math.Sqrt(candidate) + 1;
   11:   
   12:      for (int testVal = 2; (testVal < maxTest) && isPrime; testVal++)
   13:          if (candidate % testVal == 0)
   14:              isPrime = false;
   15:   
   16:      if (isPrime) primeList.Add(candidate);
   17:  }

 

If I employ this logic in a simple console application, and set startVal to say 700 and endVal to 800, I’ll get output such as the following:

Prime console output

Now, lets say I’m looking at the output and really wondering why 703 and 713 didn’t make the list.  They “look” prime – at least they don’t pass those handful of quick tests we learned in junior high – so I’m curious as to what factor kicked them out of the list.  It’s the code at lines 13 and 14 where a divisor is detected for a candidate prime number, and that divisor is testVal.

I could put some code in there using System.Diagnostics.Trace to output testVal at that point, but it’s going to require me to introduce more code, as well as some logic, perhaps, to print out only when it’s dealing with the values 703 and 713.  And, maybe it’s not even my code to be tinkering with.

One option here is to introduce a tracepoint on line 14 to capture just the information you want but without causing execution to stop as a breakpoint would.  You can insert a tracepoint by right-clicking on a line of code and selecting the option from the context menu as you see here:

Tracepoint context menu

Alternatively, you can click on the gutter to add a breakpoint, and then use the When hit… menu item to convert it from a breakpoint into a tracepoint (note the diamond shape versus the circle in the gutter):

 

When hit...

 

Both of these mechanisms bring up a dialog with two main options

  • Print a message, or
  • Run a macro

Regardless of which option you choose, you can elect to continue execution or break (as a normal breakpoint would).  By default, when you select either the Print a message or Run a macro option, the Continue execution box will be checked.

 

Print a message

 

When Breakpoint Is Hit... dialog

 

When you choose to Print a message, the verbiage no longer has the disabled look, and you can use the textbox to indicate what you want to display in the output window.  The Function and Thread information are there as a default and demonstrate that you have access to a number of debugging environment values (which are described on the dialog).  You can also include your own variables and expressions enclosed in curly braces.  For our scenario here, I’ve set up the tracepoint as follows:

 

Tracepoint settings

 

When I execute the application now, it runs to completion, but within the Output window, I get the information I was looking for, and I can see that 703 is divisible by 19 and 713 by 23.

 

Output window

 

Keep in mind it’s still a breakpoint too, so the other options on a breakpoint apply to activating the tracepoint as well.  For instance, if I really want the trace information written for only 703 and 713, then I can set up a breakpoint condition such as the following:

 Breakpoint Condition dialog

 

Output windowThe output will show as on the right, and execution will continue.  In such a case, the tracepoint glyph will include a white cross

Tracepoint glyph

indicating that there are advanced options set.  

 

 

Run a macro

The Run a macro option gives you even more power for handling a tracepoint, but presumes you’re willing to work for it by writing a bit of Visual Basic for Applications script to implement a macro.  The dropdown list already provides a number of options; these are macros that come with Visual Studio, and the ones in the Macros.Samples.VSDebugger namespace are the most germane here. 

 Available macros

Now, the ShowCurrentProcess macro here isn’t all that interesting, but when engaged will display the path to the current process in the output window.

 

ShowCurrentProcess output

 

You can examine the implementation of this macro and the other others listed in the dropdown list by opening the Macros IDE (Alt+F11).

Macros IDE

A closer look at the implementation below reveals the use of the EnvDTE namespace and the OutputWindowPane, Process, and DTE objects within that namespace. 

 

 ' This function displays the current debugger mode in the output window.
  Sub ShowCurrentProcess()
      Dim outputWinPane As EnvDTE.OutputWindowPane
      Dim proc As EnvDTE.Process
  
      outputWinPane = Utilities.GetOutputWindowPane("Debugger")
      proc = DTE.Debugger.CurrentProcess
      If (proc Is Nothing) Then
          outputWinPane.OutputString("No process is being debugged")     
      Else
          outputWinPane.OutputString("" + Str(proc.ProcessID) + ": " 
              + proc.Name + vbCrLf)
      End If
  
  End Sub

 

DTE is the top most object in the Visual Studio automation model and provides access to the debugger as well as the IDE including toolbars, commands, the active document, and more.  With programmatic control of these objects, you can create more advanced breakpoints and tracepoints, like, for instance, one that would analyze the stack trace and break only if method bar was called by method foo

Check out the MSDN documentation on Visual Studio Extensibility for more information on building your own macros.