다음을 통해 공유


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.

Comments

  • Anonymous
    May 19, 2009
    Thanks so much for this! Seriously, i hate that there are so many undiscovered (to me) trivial features to VS that could of helped save me  countless hours of work.  This being one of them for sure.

  • Anonymous
    May 19, 2009
    Thank you for the positive feedback!  There are definitely a number of cool things buried in the debugger.  You might want to check out my colleague's Channel 9 videos that cover the same materials I used on our Roadshow.  He's got three up there now, but I think there are more coming:  http://channel9.msdn.com/Search/?Term=Brian Hitney

  • Anonymous
    August 24, 2009
    Thanks for the article. I have successfully implemented the way you have descibled using the "When Breakpoint is Hit" dialog. Now, I want to redirect the output to a text file instead of the Output window. Can you please provide some tips how to go about it?

  • Anonymous
    August 25, 2009
    I don't think there's anything built in.  There's an option to redirect output window to immediate window, which doesn't help here, but I'm thinking that if they did that surely they would have considered writing to a file too and yet there is no similar option.  I suspect this might be doable via Visual Studio extensibility, and I've asked a few folks to find out if something like this might already be floating out there.

  • Anonymous
    September 02, 2009
    How do you add a tracepoint programatically? I can add a breakpoint programmatically, but I want to add a breakpoint that triggers a macro.

  • Anonymous
    September 02, 2009
    The comment has been removed