Navigating your source code with IntelliTrace debugging
If you’ve looked at previous articles on IntelliTrace you’ll have noticed that IntelliTrace has two main modes that it can run under. In the default mode IntelliTrace will just be collecting debugging data at the predefined IntelliTrace event points. While in the “methods and calls” mode IntelliTrace will be collecting debugging data (including method parameters and return values) at all function enters and exits in all instrumented modules. This methods and calls mode provides a wealth more of information about your program’s recorded execution path but at a substantially higher cost in terms of time overhead and log file size. Aside from the time overhead, this mode also created some issues with how to navigate back and forward to specific points when debugging back in time. Many of the usual debugger UI metaphors didn’t quite fit with browsing our IntelliTrace data so we’ve added in a few new controls to help out with this in Visual Studio 2010 Ultimate.
The first feature that you will probably run into when debugging in methods and calls mode of IntelliTrace is the navigation bar. If you just turn on methods and calls in the options page and run the debugger to a breakpoint you’ll see this control off on the left just to the right of the breakpoint margin.
This navigation control is intended to help with localized stepping when in historical mode as well as the ability to jump back to live debugging mode quickly from any historical context. In the control above there is only one button highlighted as available. This button is the previous call or event button (looks like a VCR rewind pointing up) and if you go ahead and click that your screen should now look like the picture below.
When you stepped back you might have noticed that there was a slight color change in the navigation gutter. This slight darkening is just a little visual clue to let you know that you are back in historical mode and not working with the live debugger. You’ll also notice that we moved back from our breakpoint to the above call to gizmoMgr.StartGame(). It’s important to remember with this that we only collected debugging data at IntelliTrace Events and at method calls and exits. In historical mode the previous call or event and next call or event (this button looks like the inverse of the step back button that we clicked earlier) buttons won’t move you line by line unless you have an instrumented function call on every line. It’s entirely possible that these could step you over large chunk of code so be aware of that when using them.
The top button on the control represents historical step out. Just like step out in live mode this will take you back the callsite of the current function, with context placed after the method exit of that function. Below the step out button is the step back button, which we’ve already covered above. The next button after that is the step in button. As expected, this button mimics the debugger step in functionality and will either move into a function’s body when hit at a valid callsite or will step to the next location with IntelliTrace data inside the current function. Next is the next callsite or event button which mimics the debugger step over and will move you past each IntelliTrace call or event in the current function without drilling down into any instrumented calls. And finally the return to live button is the bottom button with the hourglass and the yellow arrow. This button only appears when you are in historical mode and when clicked will set your context back to the live mode of the debugger, allowing you to continue program execution.
Another handy feature of this gutter is that the tooltips on each button allow you to see the hotkeys for each stepping command. For next call and step in we’ve borrowed the hotkeys for step over and step in from the debugger so (even though the functionality is slightly different) you can browse forward in a similar fashion as with the live debugger.
Another scenario that you might find yourself in when browsing IntelliTrace data is that you have a specific function of interest and that you want to examine your debugger state at the function at some previous point in execution. To help with this scenario we’ve actually built in search functionality to IntelliTrace that operates when you are running in methods and calls mode. This functionality is a little bit hidden so it seemed smart to call it out in a blog post here.
To perform a search just right click on source code on a specific line of interest when running in methods and calls mode of IntelliTrace (or while debugging an iTrace file containing methods and calls information) and select either “Search for this line in IntelliTrace” or “Search for this method in IntelliTrace.”
Note that if you are using search for this line you probably want to be on a line containing a method call as IntelliTrace is only capturing data at function call, enters and exits. If you search on a line that we didn’t capture any data on the search will just return zero results, it won’t try to grab other close by results or anything like that. After triggering the search you will get a search bar to appear on the top of the source window that will gradually fill up with results as the IntelliTrace log is combed through for matching results. On the search results bar (pictured below) you will see the context that you are searching for, arrows that allow you to browse to the first result, the previous result, the next result or the last results and an indicator of your current position and the overall count. In the example below we’ve performed a search for the method runbutton_click, our context is not currently at any results (the “- of X” part would read something like “23 of 401” if we were at the 23rd instance of 401 total results), one results was found and that one result is located at a previous point in program execution as just the previous and first buttons are enabled.
If we were to hit the previous result button we’d get what is seen below. In this example we can now see that context has been set back to the method enter of runbutton_click. The search bar indicates that we are on result one of one and since we are already sitting on the only result the forward and back navigation buttons are disabled.
So the gutter provides local navigation support while the search functionality provides jumping to a specific location in source. We’ve still got a functionality gap here where users want to navigate around their historical data faster than stepping can support but without knowing the specific location that they want to jump to. To fill that gap we’ve created the calls view in the IntelliTrace toolwindow. To see this view just bring up the IntelliTrace toolwindow when running on calls and methods mode (this window will be in IntelliTrace events mode by default) and click the “Switch to calls view” hyperlink just below the toolbar of that toolwindow. Clicking that will present you with the (slightly confusing, yes we are aware of that J) view shown below.
It’s a lot of info to take in at first glance, so I’ll try to walk though the various pieces of this control. The top pane of this control, we refer to it as the context pane, shows a reverse stack of your current location in source code. The current location in runButton_click is located at the bottom line of this pane. By tracing up the stack you can see the current code path leading back up to the ThreadStart function. The bottom pane of this control, we call this the content pane, lists all the calls out from this current instance of runButton_click. In the content pane calls that were made out to uninstrumented code are printed out in gray while the calls out to instrumented code are in black with a blue step into icon on the left side of their row.
If you want to set code context to the callsite of anything in the content window just single click on any entry. This action will leave your current pivot in the calls view at the runButton_click function, but your actual context in code will move to the callsite of the specific function selected (see example below where I’ve selected DrawGameBoard in the content window the debugger is now showing debug context and collected data at the time of the call out to DrawGameBoard). You’ll also see a little arrow icon in the content view to indicate what specific callsite you are set to.
If you want to actually drill deeper into an instrumented child method just double click on it in the content pane. Doing so will move that function call up to be the new pivot at the bottom of the context pane. Doing so will update the contents pane to show all the method calls out from this new pivot function. In the example below I’ve double clicked on the DrawGameBoard function that I had single clicked on before. You can see that the code context has now been set to the method entry of the DrawGameBoard function and the toolwindow has now been updated to show the contents of the DrawGameBoard function.
If you want to navigate back up the tree to other further away locations in the call tree you can double click on any item listed in the context (top) pane. Doing so will set that new function as the pivot and update the code context and content pane accordingly. Also note that IntelliTrace events will show up in the content pane in the proper locations. In the picture below I’ve navigated back up to the ButtonBase.OnClick function in the .NET framework. Since it’s in the framework I don’t have source code for this location, but you can the IntelliTrace click event marked with the lightning bolt over in the content pane.
Navigating around in collected IntelliTrace data can be a tricky proposition. Taking debugger data and opening it up to a whole new dimension of time is something that we’ve worked long and hard on in our UI, but we’ve still got a long ways to go. If you are working with IntelliTrace and run into anything in particular that you think could be added / improved just drop me a line, we’re always open to suggestions.