다음을 통해 공유


How can I debug Just My Code?

Sometimes developers want to debug just the code they wrote and not the 3rd-party code (such as framework and libraries) that’s also inside their app. This becomes particularly useful when user and non-user code call back and forth between each other.  The v2.0 CLR debugging services have a host of new features to support this, which we call “Just-My-Code” (JMC) debugging.

 

In V2.0, ICorDebug:

- lets a debugger mark each function as either user or non-user code. It’s up to the debugger to determine what is and is not user code. Visual Studio will use hints from the project system and also assume if that a given module is non-user code if the symbols are missing. You can also use the System.Diagnostics.DebuggerNonUserCodeAttribute attribute to tell VS to mark a specific method as non-user code.

- allows stepping operations to magically skip all non-user code.

- provides additional exception notifications.

 

A debugger can also do additional things such as filtering non-user code from the callstack.

In this blog, I’ll demo JMC stepping and explain how to use this new functionality from ICorDebug. (I’ll blog about exceptions later)

 

A very simple example of JMC-stepping:

Here’s a trivial example of JMC-stepping. Run this as a console application in VS2005 beta1.

using System;

class Program

{

    static void Main()

    {

        NonUserLibraryCode(); // <-- step in here (F11 in Visual Studio)

    }

    // This attribute tells the debugger to mark this function as non-user code.

    [System.Diagnostics.DebuggerNonUserCode]

    static void NonUserLibraryCode()

    {

        Console.WriteLine("Before");

        UserCode();

        Console.WriteLine("After");

    }

   

    static void UserCode()

    {

        Console.WriteLine("User1"); // <-- step completes here, skipping the non-user code

    }

}

 

VS also can filter out the non-user code from the callstack. So when stopped inside UserCode(), the callstack looks like:

>          ConsoleApplication2.exe!Program.UserCode() Line 21            C#

            [External Code]           

            ConsoleApplication2.exe!Program.Main() Line 7 + 0x6 bytes   C#

            [External Code]           

In VS, JMC can be disabled from Tools | Options | Debugging, “Enable Just My Code” check box (it must be enabled for these examples to work). The callstack filtering can be toggled by right clicking the callstack to toggle “Show External Code”.

All of MDbg’s JMC support is only in extension dlls that we use for testing purposes.

 

A more real example:

The example above is very simple because everything can be determined statically. This example shows Main dynamically invoking callbacks. Some callbacks are user code and others aren’t. This is similar to the winforms case where the message loop is non-user code but some of the handlers (like button1_click) are user code. You can use JMC to step between your handlers without having to put breakpoints in each handler and without having to step through the owning message loop.

 

using System;

using System.Diagnostics;

class Program

{

    delegate void Callback();

    // This will invoke 3 callbacks. A user, non-user, and then a user one.

    // (1) Step-in here. Since Main() is non-user code, we skip straight to the first bit of user

    // code called which is UserCodeHandler1.

    [DebuggerNonUserCode]

    static void Main()

    {

        // Invoke some callbacks.

        Callback[] list = new Callback[] {

            new Callback(UserCodeHandler1), new Callback(NonUserCodeHandler), new Callback(UserCodeHandler2)

        };

        foreach (Callback fp in list)

        {

            fp();

        }

    }

   

    static void UserCodeHandler1()

    {

        Console.WriteLine("Inside my 1st handler!"); // <-- step completes here from (1)

        // (2) Do a step-in here. Normally, a step-in at the end of a method would be a step-out.

        // But since our caller (Main) is non-user code, we don't want to stop there. So we'll run to the next bit of user code.

    }

    [DebuggerNonUserCode]

    static void NonUserCodeHandler()

    {

        Console.WriteLine("Inside a non-user code handler");

    }

    static void UserCodeHandler2()

    {

        // (3) Step-in from (2) lands here.

        Console.WriteLine("Inside my 2nd handler!"); // <-- step completes here, skipping the non-user code

    }

}

 

Why JMC is cool on a technical level.

JMC-stepping has some good technical accomplishments in it. It is …

1) … not limited to static anaylsis (as shown above). JMC works with everything including arbitrarily deep callstacks, multicast delegates, polymorphism / virtual functions, and events + callbacks. There are no smoke and mirrors here - you could construct arbitrarily complicated examples.

2) ... very performant. The step operation may skip large amount of non-user code without a performance penalty.  Constrast this to trying to fake JMC by just using traditional single-step operations to skip through all non-user code (which would be unusably slow).

3) … thread-safe. You can use JMC-stepping in multi-threaded programs without any problems. (Some other solutions would break down here)

4) … very configurable. You can set JMC-status on a per-function level, and you can toggle that status at runtime.

 

JMC-stepping on the ICorDebug level:

ICorDebug implements managed stepping via ICorDebugStepper objects. All the low-level details of how managed stepping actually works are abstracted away from the debugger.

Debuggers create a JMC-stepper by calling ICorDebugStepper2::SetJMC(true). A JMC-stepper will magically skip all non-user code. All non-JMC steppers (which I’ll call “Traditional Steppers”) ignore JMC-status. Thus a debugger must explicitly request JMC else it will get the pre-JMC behavior. This allows JMC functionality to be non-breaking.

 

The CLR defaults to assuming everything is non-user code and defers all JMC decision making to the debugger via the ICorDebug API. Debuggers must mark user code by calling ICorDebug(Function|Class|Module)::SetJMCStatus(). Debuggers can use any heuristics they wish to decide what is and is not user code. So although the DebuggerNonUserCode attribute is defined in mscorlib, the CLR does not pay any attention to it. That attribute is there exclusively to provide a convenient primitive semi-standard protocol for debuggers to mark specific functions as non-user code.  A debugger could use other heuristics such as:

- input from the project system / IDE

- method names (eg, make all ToString() or property getters non-user code).

- other custom attributes. Eg, a custom attribute could take a string parameters and the debugger could use that to create groups of JMC methods.

- presence of symbols. (generally modules without symbols should be considered non-user code).

- source-level information (since the debugger has access to the source).

 

JMC-status can be toggled at runtime. Thus a debugger could build fancy logic to toggle groups of functions at runtime. This also lets a debugger delay setting JMC status until the user first stops in that method.

 

At the ICorDebug level, JMC is orthogonal to breakpoints. Breakpoints will be hit in non-user code, but a debugger may choose to continue the debuggee anyways and not notify the user. (This is what VS does).

Comments

  • Anonymous
    December 31, 2004
    I haven't played with this feature yet but the obvious question is: What are the full differences between the DebuggerStepThroughAttribute and the DebuggerNonUserCode? It appears that one works at the source level while the other goes deeper, the latter should be faster than the former and that the new attribute can be turned on with a simple setting in the IDE whereas the old one has to be manually commented out. Are these statements correct and/or what other differences from a usage perspective are there?

  • Anonymous
    January 01, 2005
    Daniel -
    To be clear: The CLR ignores both attributes. They're defined in mscorlib.dll so that they're available for use by debugger applications (like VS).

    The semantics are very similar in both(annotating "non-user" code). Hopefully we will document the semantics very clear in MSDN when we ship v2.0.

    Thus the real difference between these attributes is entirely what a debugger does with them. In Visual Studios's case:
    - DebuggerStepThroughAttribute is from v1.1. VS uses traditional step operations (pre-JMC_ to skip non-user code there.
    - DebuggerNonUserCode is in v2.0 and VS uses this explicitly for the new JMC functionality above.

    So maybe your question boils down to comparing (1) faking JMC with traditional step operations to (2) real JMC?

    - Real JMC is much faster and can handle arbitrarily complex scenarios.
    - Re: turning on + off: This is entirely a feature within VS. A debugger could just as easily have added a checkbox to ignore the DebuggerStepThroughAttribute . Or it could have just as easily removed the "Enable JMC" checkbox and thus forced you to comment out the DebuggerNonUserCode attribute.

  • Anonymous
    January 02, 2005
    Thank you, that helps. I don't see why DebuggerStepThrough does not go through the obsolescence path then, but that is an academic discussion I guess...

  • Anonymous
    June 19, 2005
    Sometimes you may have functions that you don’t want a debugger to step into (such as 3rd-party library...

  • Anonymous
    July 26, 2005
    I mentioned here&amp;nbsp; that if your language compiles to IL, then you get free debugging support with...

  • Anonymous
    July 27, 2005
    I mentioned here&amp;nbsp; that if your language compiles to IL, then you get free debugging support with...

  • Anonymous
    July 28, 2005
    I mentioned here&amp;nbsp; that if your language compiles to IL, then you get free debugging support with...

  • Anonymous
    August 22, 2005
    Sometimes you may have functions that you don’t want a debugger to step into (such as 3rd-party library...

  • Anonymous
    August 23, 2005
    We added Just-My-Code (JMC) stepping (the ability to step through just your code and skip code that's...

  • Anonymous
    September 15, 2005
    Now I am intrigued... My previous post talked about the 'Just My Code'
    option in Whidbey and how you...

  • Anonymous
    September 15, 2005
    Now I am intrigued... My previous post talked about the 'Just My Code'
    option in Whidbey and how you...

  • Anonymous
    September 15, 2005
    Now I am intrigued... My previous post talked about the 'Just My Code'
    option in Whidbey and how you...

  • Anonymous
    September 15, 2005
    Now I am intrigued... My previous post talked about the 'Just My Code'
    option in Whidbey and how you...

  • Anonymous
    September 16, 2005
    Now I am intrigued... My previous post talked about the 'Just My Code'
    option in Whidbey and how you...

  • Anonymous
    September 19, 2005
    Question from the mail bag:

    I am trying to get some information on what I can do usefully with the...

  • Anonymous
    March 14, 2006
    I've been reading through &quot;Practical .NET2 and C#2&quot; (by Patrick Smacchia). I finished my previous book...

  • Anonymous
    March 28, 2006
    When attempting to step into a web service when a Typed DataSet is passed as a parameter to the web method...

  • Anonymous
    July 12, 2006
    I got this great question from the mailbag:&amp;nbsp;&amp;nbsp;&amp;nbsp; &quot;[W]hat is the relation between the &quot;#...

  • Anonymous
    July 13, 2006
    PingBack from http://blogs.msdn.com/jmstall/archive/2005/01/04/346641.aspx

  • Anonymous
    August 15, 2006
    Now I am intrigued... My previous post talked about the &amp;#39;Just My Code&amp;#39; option in Whidbey and

  • Anonymous
    November 05, 2006
    Below is a screencast that shows how to debug an application using the new 'Go To Reflector' functionality

  • Anonymous
    February 12, 2007
    I previously mentioned that catch / rethrow an exception impedes debuggability because the callstack

  • Anonymous
    May 24, 2007
    The CLR team recently had a compiler dev Lab on campus in building 20, with a strong focus on dynamic

  • Anonymous
    September 03, 2007
    I&rsquo;m starting the switch to using the [DebugNonUserCode] attribute instead of [DebuggerStepThrough]...

  • Anonymous
    December 03, 2007
    PingBack from http://msdnrss.thecoderblogs.com/2007/12/03/debuggability-tops-msbuild-feature-poll/

  • Anonymous
    April 10, 2009
    Debugging IronPython with my Excalibur