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.
Trace.WriteLine
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:
startVal
endVal
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.
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.
System.Diagnostics.Trace
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:
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):
Both of these mechanisms bring up a dialog with two main options
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.
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:
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.
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:
The output will show as on the right, and execution will continue. In such a case, the tracepoint glyph will include a white cross
indicating that there are advanced options set.
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.
Macros.Samples.VSDebugger
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
You can examine the implementation of this macro and the other others listed in the dropdown list by opening the Macros IDE (Alt+F11).
A closer look at the implementation below reveals the use of the EnvDTE namespace and the OutputWindowPane, Process, and DTE objects within that namespace.
EnvDTE
OutputWindowPane
Process
DTE
' 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.
bar
foo
Check out the MSDN documentation on Visual Studio Extensibility for more information on building your own macros.
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.
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
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?
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.
How do you add a tracepoint programatically?
I can add a breakpoint programmatically, but I want to add a breakpoint that triggers a macro.
I noticed the EnvDTE90a.Breakpoint3 interface has what you need. I hacked this together and it seems to work (it doesn't look like a tracepoint - with the diamond - until you run the debugger)
Public Module Module1
Public Sub addTracepoint()
DTE.Debugger.Breakpoints.Add(Function:="Main", Line:=1)
Dim bp As EnvDTE90a.Breakpoint3
bp = DTE.Debugger.Breakpoints.Item(1)
bp.Macro = "Macros.MyMacros.Module1.foo"
bp.BreakWhenHit = False
Public Sub foo()
MsgBox("Got here!")
End Module