Welcome to MSDN Blogs Sign in | Join | Help

Wanted: Developers passionate about enhancing Visual Studio's Debugger!

The debugger is very likely the most used "feature" of Visual Studio. Millions of users around the world use it every single day.  Why, I've heard through the grapevine that some people find Visual Studio's Debugger the main reason to own or install Visual Studio at all!  Of course, it's silly to think that the debugger is THAT important, but... well... IT IS. (ok, maybe I'm a little biased). 

 

Let me get to the point.  If you're a developer who loves digging into the nitty-gritty internal details of how software works at all or you have big, gooey (er GUI) dreams about features that would make the debugger even more useful AND have the development skills to pull it off, we'd love to talk to you! 

 

The VS debugger team is experiencing a significant expansion and we'd like you to be part of it.  If you think you'd be interested in working on a great team, in a great location, click here to submit your resume:

http://members.microsoft.com/careers/search/details.aspx?JobID=3C87062A-3C66-406F-AE23-5E63FE606995&start=1&interval=10&SortCol=DatePosted

 

I look forward to talking with you!

Posted by JimGries | 0 Comments

Need to write a new Debugging Engine for your favorite platform?

Jackson, recently posted a new sample for how to write a Visual Studio debug engine.  As Joc put it: "in our architecture, the debug engine is the part that does all of the communication with the debugging/diagnostic APIs for a given platform, and communicates those back to the UI through a common set of interfaces.  The current debugger ships debug engines for managed code, native code, script, native devices and t-sql in the box.  The debugger engine extension point gives people writing new platforms the ability to provide an excellent VS debugging experience."

Please note that writing a debug engine is a complex undertaking.  If you have a language that's targetting the .NET runtime and the platforms it supports, then you probably don't need to go this route (a new Expression Evaluator, Language Service and project system are more appropriate targets).  But if you have a new platform (CPU, runtime, etc) that needs debugging support, this sample should provide good guidance.

 

 

 

Posted by JimGries | 0 Comments

Debugger Tips, Tricks and Tools #12

I've had loads of people ask me the question:

"Is there any way to step into a function without having to bother stepping into all the properties that might have to be executed as parameters before getting to what I'm really interested in?"

The standard answer for Visual Studio 2005 and Visual Studio 2008 has been "well, you probably want to get familiar with the DebuggerStepThrough or DebuggerNonUserCode attributes and use them it in your code."  However, if it's not your code that you're debugging through, or you don't have the option or would rather not modify it, then this solution isn't sufficient.

C++ users have had an alternate solution for awhile now.  It's called Step Into Specific.  The basic idea is that while in the editor the user can right click on a function call to choose from a list of additional function calls that will ultimately be made.  The function call chosen becomes the next stepping point.

Why don't we have this for managed code debugging?  The short answer is that this type of solution requires support from the .NET runtime. This support is not in .NET 2.0, nor is it in the runtime that will ship with VS 2008 (which is basically mainly an extension of the core 2.0 runtime).

What's a managed programmer to do then?  Curse and swear.  Or perhaps try to find a workaround.  That's where this article comes in. I've created a macro called StepIntoHere().  It analyzes the code on which the editor caret is currently positioned and then attempts to simulate a "step-into" exactly the code that implements that function, bypassing any other function calls or properties that have to be made along the way.

For example, say you have this code: 

           CoolClass c = new CoolClass();

           c.DoSomething(new UnCoolClass().MyProp);

This macro allows you to place the caret on "DoSomething" and then do a step into the "DoSomething" function directly rather than having to first step into the constructor for "UnCoolClass" and then it's "MyProp" property. 

Sounds great, and it seems to work quite well for trivial applications I've tried it with.  I'm not too sure how well it works for much larger apps, but I'm hoping this blog will help me find out.

(See my Idiot's Guide for creating and using VS Macros for quick and easy steps on how to add this macro to VS.  To make it more like the C++ version, add it to the Editor's context menu. For extra credit, add an image for it by copying and pasting the existing step-into icon and modifying it slightly.  This can be done while customizing your context menu... hint: right click :) )

Sub StepIntoHere()

        If DTE.Debugger Is Nothing Or DTE.Debugger.CurrentMode <> dbgDebugMode.dbgBreakMode Then

            Beep()

        Else

            Dim textSelection As EnvDTE.TextSelection = DTE.ActiveWindow.Selection

            If textSelection.IsEmpty Then

                textSelection.WordLeft(False)

                textSelection.WordRight(True)

            End If

 

            textSelection = DTE.ActiveWindow.Selection

            Dim bp As EnvDTE.Breakpoint

            Dim bps As EnvDTE.Breakpoints

            bps = DTE.Debugger.Breakpoints.Add(textSelection.Text)

 

            'If we have at least one bound breakpoint, go, otherwise just do a step-into.

            If bps.Count > 0 And bps.Item(1).Children.Count > 0 Then

                DTE.Debugger.Go()

            Else

                DTE.Debugger.StepInto()

            End If

 

            ' Unselect what we selected.

            textSelection = Nothing

 

            ' Delete the breakpoints we created.

            For Each bp In bps

                bp.Delete()

            Next

        End If

    End Sub

 

Posted by JimGries | 3 Comments

Why does Visual Studio require debugger symbol files to *exactly* match the binary files that they were built with?

Recently a coworker of mine lost the symbol file for one of his binaries.  Because he needed to debug that binary, getting those symbols back was of utmost importance since debugging without them is near impossible.  He decided to try and use a symbol file from a previous build whose sources exactly matched.  Much to his chagrin he found that Visual Studio refused to load them.  He asked me:

"Why is it that Visual Studio cannot load a symbol file for a binary that was built with the exact same set of source files?" 

Before I answer that, I'll take the question a step further:

"Surely if I build a component twice, consecutively, with no changes to source in-between, I certainly will end up with two identical copies of that component, right?"

Surprisingly, the answer to this question is: "no, that is not necessarily true".  The most obvious reason for this is the fact that an internal time-stamp will be different.  But even disregarding that, the answer is still the same, the actual layout of the code could be different.

The reason is that compiler writers are far more interested in generating correctly functioning code and generating it quickly than ensuring that whatever is generated is laid out identically on your hard drive.  Due to the numerous and varied methods and implementations for optimizing code, it is always possible that one build ended up with a little more time to do something extra or different than another build did.  Thus, the final result could be a different set of bits for what is the same functionality.

 

Here's a very simple example to demonstrate.  Imagine that the component you're building consists of a function and a variable.

Does it matter whether the resulting file contents looks like this?

0000: MyFunc()
0020: gGlobalVariable

or this?

0000: gGlobalVariable
0004: MyFunc()

Functionally, it doesn't matter at all, but for the debugger it's HUGELY important. In fact, getting it wrong can wreak havoc on your debugging session.  In this example, what if you used the symbol file from build #1 to try and determine the value of the global variable when running with the component built by build #2?  The debugger would consult the symbol file and return the value referenced by address 0020.  Unfortunately, the global variable isn't at that address in component build #2.  Rather some some value that makes up the instruction stream for MyFunc() is there.

A debugger depends on knowing what the internal layout of the component is.  So, I can now answer my colleague's original question:

"Because both the Visual Studio and your sanity depends on it."

 

 

PS: In practice, you are not likely to see a difference in compiler/linker output from build to build.  So in theory, it is possible to make use of mismatched symbols.  However, compiler/linker determinism is not guaranteed, our debugger cannot depend on it.  Furthermore, we would much rather not take the support cost of allowing some sort of override.  Therefore, if you find that you absolutely, positively, have to try and use a symbol file that doesn't match you can use WinDbg.  I'll leave getting it to work as an exercise for the reader, since you really, really don't want to do this. :)

 

Posted by JimGries | 1 Comments

Debugger Tips, Tricks and Tools #11

More fun with labelling breakpoints

Back in my first Debugger tips post I provided some macros that allow you to tag a set of breakpoints with a string so that they can easily be enabled or disabled from the command window.  Well, just for kicks, I decided to expand on that notion a bit and made a set of new macros that can not only do that, but also support multiple labels (the new name for tags) for breakpoints and tracepoints.

Following this description you'll find the source for these macros.  Basically, you can use the instructions provided Idiot's Guide to Creating and Using VS Macros to add these macros to your installation.  After you do that, just run the SetupBPLabels macro, and you'll be able to use the following commands in the command window:

  • labelbps - Allows you to add a new label to the current set of enabled breakpoints or tracepoints.  If a label is not provided "label" is used by default.
  • enablebps - Allows you to enable all breakpoints that are associated with the supplied label.  If * is provided as the target label or then all breakpoints are enabled regardless of their label.  If no label is provided then any breakpoint without a label will be enabled.
  • disablebps - Allows you to disable all breakpoints that are associated with the supplied label.  If * is provided as the target label then all breakpoints are disabled regardless of their label.  If no label is provided then any breakpoint without a label will be disabled.
  • clearlabel - Allows you to remove a label from your set of breakpoints.  If * is provided as the target label, then all labels are cleared.  When a label is cleared, it no longer exists, so be careful here.
  • listbps - Lists the breakpoints associated with the supplied label.  If no label is supplied, then all breakpoints are listed along with their labels.

I'm not really sure how useful this set of macros will be for you, but it was fun writing them!  Let me know what you think, or if you think there's some improvements that could be made to make them even more useful!  Please note that I am not much of a VB programmer, so if you have suggestions as to how to improve the code, I'm all ears!

    ' -------------------------------------------------------------------------

    Sub SetupBPLabels()

        DTE.ExecuteCommand("alias enablebps Macros.MyMacros.BPLabels.EnableBPs")

        DTE.ExecuteCommand("alias disablebps Macros.MyMacros.BPLabels.DisableBPs")

        DTE.ExecuteCommand("alias listbps Macros.MyMacros.BPLabels.ListBPs")

        DTE.ExecuteCommand("alias labelbps Macros.MyMacros.BPLabels.LabelEnabledBPs")

        DTE.ExecuteCommand("alias clearlabel Macros.MyMacros.BPLabels.ClearBPLabel")

    End Sub

 

   ' -------------------------------------------------------------------------

   Sub LabelEnabledBPs(Optional ByRef strLabel As String = "label")

        Dim bps As EnvDTE.Breakpoints = DTE.Debugger.Breakpoints

        If (bps.Count > 0) Then

            For Each bp As EnvDTE.Breakpoint In bps

                Dim strCurLabels As String = GetBPLabels(bp)

                If (bp.Enabled = True And Not BPLabeledAs(bp, strLabel)) Then

                    If (strCurLabels.Length > 0) Then

                        strCurLabels += ";"

                    End If

                    strCurLabels = strCurLabels + strLabel

                    SetBPLabels(bp, strCurLabels)

                End If

            Next

        Else

            System.Windows.Forms.MessageBox.Show("Can't find any breakpoints to label")

        End If

    End Sub

 

    ' -------------------------------------------------------------------------

    Sub ClearBPLabel(Optional ByRef strBadLabel As String = "")

        Dim bps As EnvDTE.Breakpoints = DTE.Debugger.Breakpoints

        If (bps.Count > 0) Then

            For Each bp As EnvDTE.Breakpoint In bps

                Dim strCurLabels As String = GetBPLabels(bp)

                SetBPLabels(bp, "")

                If (strBadLabel <> "*") Then

                    Dim labels() As String = strCurLabels.Split(";")

                    Dim strNewLabels As String = ""

                    For Each s As String In labels

                        If (s.ToLower <> strBadLabel.ToLower) Then

                            If (strNewLabels.Length > 0) Then

                                strNewLabels += ";"

                            End If

                            strNewLabels = strNewLabels + s

                        End If

                    Next

                    SetBPLabels(bp, strNewLabels)

                End If

            Next

        Else

            System.Windows.Forms.MessageBox.Show("Can't find any breakpoints to label")

        End If

    End Sub

 

    ' -------------------------------------------------------------------------

    Sub EnableBPs(Optional ByRef strLabel As String = "")

        Dim bps As EnvDTE.Breakpoints = DTE.Debugger.Breakpoints

        If (bps.Count > 0) Then

            For Each bp As EnvDTE.Breakpoint In bps

                If (strLabel = "*" Or BPLabeledAs(bp, strLabel) Or (GetBPLabels(bp).Length = 0 And strLabel.Length = 0)) Then

                    bp.Enabled = True

                End If

            Next

        Else

            System.Windows.Forms.MessageBox.Show("Can't find any breakpoints to enable")

        End If

    End Sub

 

    ' -------------------------------------------------------------------------

    Sub DisableBPs(Optional ByRef strLabel As String = "")

        Dim bps As EnvDTE.Breakpoints = DTE.Debugger.Breakpoints

        If (bps.Count > 0) Then

            For Each bp As EnvDTE.Breakpoint In bps

                If (strLabel = "*" Or BPLabeledAs(bp, strLabel) Or (GetBPLabels(bp).Length = 0 And strLabel.Length = 0)) Then

                    bp.Enabled = False

                End If

            Next

        Else

            System.Windows.Forms.MessageBox.Show("Can't find any breakpoints to enable")

        End If

 

    End Sub

 

    ' -------------------------------------------------------------------------

    Sub ListBPs(Optional ByRef strLabel As String = "")

        Dim bps As EnvDTE.Breakpoints = DTE.Debugger.Breakpoints

        Dim outpane As EnvDTE.CommandWindow = DTE.Windows.Item(EnvDTE.Constants.vsWindowKindCommandWindow).Object

 

        Dim i As Integer = 1

        For Each bp As EnvDTE.Breakpoint In bps

            If (strLabel.Length = 0 Or BPLabeledAs(bp, strLabel)) Then

                Dim strEnabled As String = ""

                If bp.Enabled Then

                    strEnabled = "x"

                End If

 

                outpane.OutputString("BP #" + i.ToString() + " " + strEnabled + vbTab + "Name: " + bp.Name + vbCrLf)

 

                If (strLabel.Length = 0) Then

                    outpane.OutputString(vbTab + vbTab + "Labels: " + GetBPLabels(bp) + vbCrLf)

                End If

            End If

            i = i + 1

        Next

    End Sub

 

    ' -------------------------------------------------------------------------

    Private Function GetBPLabels(ByRef bp As EnvDTE.Breakpoint) As String

        Dim strStartTag As String = "<labels>"

        Dim strEndTag As String = "</labels>"

        Dim ndxStart As Integer = bp.Tag.IndexOf(strStartTag) + strStartTag.Length

        Dim ndxEnd As Integer = bp.Tag.IndexOf(strEndTag)

        If (ndxStart > 0 And ndxEnd > ndxStart) Then

            GetBPLabels = bp.Tag.Substring(ndxStart, ndxEnd - ndxStart)

        Else

            GetBPLabels = ""

        End If

    End Function

 

    ' -------------------------------------------------------------------------

    Private Sub SetBPLabels(ByRef bp As EnvDTE.Breakpoint, ByRef labels As String)

        bp.Tag = "<labels>" + labels + "</labels>"

    End Sub

 

    ' -------------------------------------------------------------------------

    Private Function BPLabeledAs(ByRef bp As EnvDTE.Breakpoint, ByRef strLabel As String) As Boolean

        Dim bLabelFound As Boolean = False

 

        If (strLabel.Length > 0) Then

            Dim strLabels = GetBPLabels(bp)

            Dim labels() As String = strLabels.Split(";")

            For i As Integer = 0 To labels.Length - 1 And bLabelFound = False

                If (labels(i).ToLower = strLabel.ToLower) Then

                    bLabelFound = True

                End If

            Next

        End If

 

        Return bLabelFound

    End Function

 

Posted by JimGries | 1 Comments

Debugger Tips, Tricks and Tools #10

Create a one-step operation to attach to a process

If you find your debugging session typically involves attaching to a specific process you can save yourself some time in VS 2005 by creating a macro to do it for you.

  1. Choose Tools->Macros->Record Temporary Macro
  2. Choose Tools->Attach to Process and choose the process you want to attach to.  You can choose any combination of process, transport, qualifier and code-type.
  3. Click the Stop Recording macro toolbar button, or press Ctrl-Shift-R.
  4. Choose Tools->Macros->Save Temporary Macro.  The caret will be moved to the Macro Explorer and you'll be prompted to change the name of TemporaryMacro to the name of your choosing. 

Here's an example macro that was generated when I attached to calc.exe with the native engine:

Sub AttachToCalc()
           Try

                  Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger

                  Dim trans As EnvDTE80.Transport = dbg2.Transports.Item("Default")

                  Dim dbgeng(1) As EnvDTE80.Engine

                  dbgeng(0) = trans.Engines.Item("Native")

                  Dim proc2 As EnvDTE80.Process2 = dbg2.GetProcesses(trans, "JIMSMACHINE").Item("calc.exe")

                  proc2.Attach2(dbgeng)

 

           Catch ex As System.Exception

                  MsgBox(ex.Message)

           End Try

 

End Sub

 

Now, invoking this macro through the Macro Explorer will cause the exact same attach to whatever program you originally chose!  If you want to make your life even easier you could add this macro to a toolbar by:

  1. Choose Tools->Customize
  2. Activate the Commands Tab.
  3. Scroll down to "Macros" in the Categories list.
  4. Drag Macros.MyMacros.RecordingModule.AttachToCalc to a toolbar

You could also create an alias for the macro that you can invoke from the command window as follows:

 

   alias attcalc Macros.MyMacros.RecordingModule.AttachToCalc

 

Now to attach to calc, all you have to do is type "attcalc" in the command window.

 

Of course, the macro that is created for you is a very simple skeleton.  If you'd like you could modify it to do all sorts of other things, like attach to additional processes, or set a breakpoint or whatever! 

 

You might also want to peruse my Idiot's Guide to Creating and Using VS Macros for more helpful hints.

Posted by JimGries | 1 Comments

Debugger Tips, Tricks and Tools #9

Making native array viewing easier with Enhanced DataTips

I'm back from vacation, and have a new found respect for people (mostly US wives/women, I presume) that slave all day over preparing a Thanksgiving meal and then cleaning it all up.  Yep, I did the whole turkey, potatoes, yams, corn, etc. thing this year for me and my kids.  By the end of the day I was completely exhausted.  But it all turned out great!

Anyway onto a new debugging trick.  This one is geared directly at users of native C/C++ debugger in Visual Studio 2005.  It revolves around a little registry tweak you can do to make viewing arrays of objects a little easier using Enhanced DataTips if all you have is pointer to one of the items.  This is not a supported behavior and cannot be adjusted outside of doing this registry change.  As is per usual with any registry tweaking, please be careful -- if you mess up (significantly) you can destroy your entire OS installation.

First, shut down Visual Studio.  Since VS writes a new set of option values to the registry everytime you shut it down, any changes you make to the registry prior to doing so will be lost. 

Now open the registry and navigate to:

HKCU\Software\Microsoft\VisualStudio\8.0\Debugger

You'll see a boatload of debugger options, most of which are addressable under the Tools->Options functionality of VS.  However there is one, OfferArrayExpansion, that is not exposed.  Change it from 0 to 1 and reopen VS.

Now when you activate a DataTip over any pointer to any type and you expand it once, you'll see a message allowing you to expand it as an array.  If you do so, you'll see the first 100 items as if the pointer was really pointing to an array of 100.

 

Posted by JimGries | 0 Comments

Debugger Tips, Tricks and Tools (On vacation)

Spending the week on vacation.  I'll return with more tips next week!  Happy Thanksgiving!

Posted by JimGries | 0 Comments

Debugger Tips, Tricks and Tools #8

Noisy breakpoints!

Ok this feature has been around for a long time, but it's really almost impossible to discover on your own.  Scott Nonnenberg asked me to remind him out to do this yesterday and I thought HEY good idea for a tip!  Here's what you do:

  1. Open the Windows Control Panel
  2. If you are in Category View choose "Sounds, Speech, and Audio Devices", then "Change the sound scheme"
  3. If you are in Classic View choose "Sounds and Audio Devices", then choose the "Sounds" tab.
  4. In the "Program events" listbox scroll down to "Microsoft Development Environment"
  5. Change the "Breakpoint Hit" event to the sound of your choice.

 

Posted by JimGries | 1 Comments

Debugger Tips, Tricks and Tools #7

Moving tracepoints and breakpoints around

I have found the addition of tracepoints in Visual Studio 2005 extremely useful, allowing me to debug issues that have normally been pretty difficult to deal with.  For example, UI debugging often involves breaking into code that was invoked due to the receipt of a WM_PAINT message.  In the pre-tracepoint world, I would often end up resorting to printf-style debugging.  Either that, or I'd need to make sure that my debugger is on a separate monitor, or in no way covering up my window, otherwise I end up in a difficult to manage breakpoint-hit cycle.  With tracepoints I can do printf-style debugging without needing to muck with my code.

However, I often find myself wanting to move a tracepoint that I carefully setup to different place in my code.  Breakpoints didn't suffer from that need nearly as much since not much effort usually goes into creating them.

Well, fortunately tracepoints (and breakpoints) can be moved.  It's a little more difficult than it should be, but usually not as much effort as removing then redoing it in a different place.

Here's how to do it.

  • Determine the line to which you want to move the tracepoint.
  • Right click on the tracepoint and choose Location...
  • Change the line number to the line you'd like the tracepoint to fire.
  • Click OK

Pretty straightforward and easy.  I fully intend on making sure future versions of Visual Studio will allow you to drag the glyph, but for now this will do.  :)

 

 

Posted by JimGries | 2 Comments

Debugger Tips, Tricks and Tools #6

Create an Object ID to keep track of an object while debugging

In yesterday's tip I hinted at another new feature of the debugger specially designed for C# and J# programmers.  This is the ability to create an Object ID  for any particular object during your debugging session, no matter what your current context is.  In other words, it's quite similar to being able to get the address of an object in C or C++ so that you can refer to that particular object at any time during debugging. 

For example, given the following short program:

using System;

using System.Collections.Generic;

using System.Text;

 

namespace ConsoleApplication1

{

    class Program

    {

        static void Main(string[] args)

        {

            List<string> stringList = new List<string>();

 

            stringList.Add("Hello");

            stringList.Add("World");

 

            foreach (string str in stringList)

            {

                Console.WriteLine(ReverseString(str));

            }

        }

 

        static string ReverseString(string str)

        {

            if (str.Length > 1)

            {

                return ReverseString(str.Substring(1)) + str.Substring(0, 1);

            }

 

            return str;

        }

    }

}

 

Put a breakpoint on the first line of ReverseString and run the program under the debugger.  When the breakpoint is hit, hover your mouse over the 'str' variable, and on the DataTip that appears right click to bring up the context menu.