Change Debugger behavior with Attributes

Certain metadata attributes modify the stepping, breakpoint, and callstack behavior of the VS managed Debugger. This is useful if you are creating an interpreter, language runtime, or a tool that modifies the code in a managed assembly. There are three flavors of these attributes that affect execution control. Each behaves slightly differently, and I always forget which does what, so I'm recording it here for my own reference as well. 

 

The code below shows examples using all three attributes: DebuggerStepThrough, DebuggerHidden, and DebuggerNonUserCode. The example also shows the hidden line directive. I've inlined the explanation as comments in the code so it can travel with the example. This general area of writing tools that interact with the debugger has been well explored on Mike Stall's blog:

 

#line hidden and 0xFeeFee sequence points

How can I debug Just My Code?

Debug support for arbitrary state-machines 

using System;
 
// This example shows the use of the three debugger attributes
// that modify the runtime behavior of the VS debugger.
 
// The motivating example is part of an AOP (Aspect Oriented Programming)
// implemenatation.
// The concepts around AOP are pretty interesting, but for this
// example it is sufficient to know that anything called a 'Thunk'
// is something added by the tool that is of no interest to the user.
 
// The example code is what a weaver that uses C# as a backend could
// send to the compiler to accomplish the hiding of
// JoinPointThunks, and the calls to those thunks.
 
// Try stepping through this with this option on:
// Tools/Options/Debugging/General/Enable Just My Code
// Then try the same with the option off.
 
// You'll see that each attribute creates a slightly different end user
// behavior and slightly different ease of debugging for yourself
// when you need to debug the glue code you are hiding from the user.
 
class DynamicallyAddedUserCode
{
    public static void bar()
    {
        Console.WriteLine("Hello advice\n");
    }
};
class Weaver
{
    [System.Diagnostics.DebuggerStepThrough]
    public static void ExampleJoinPointThunk_withDebuggerStepThrough()
    {
        //_________________________________________________________________
        // Tools/Options/Debugging/Enable Just My Code ON:
        //CALLSTACK view: Frame is viewable
        //BP: Will hit bp.
        //STEPPING: Will not stop inside due to a step.
        // (Even from bp hit! Any step is a step out.)
        // Step in will land inside called methods.
        //_________________________________________________________________
        // Tools/Options/Debugging/Enable Just My Code OFF:
        //CALLSTACK view: [External Code] frame.
        // Real frame is viewable by selecting 'Show External Code'
        // in Callstack window.
        //BP: No bp will set or hit. A warning is provided,
        //STEPPING: Will not stop inside method (step in or step out)
        // A step in will land in called methods.
        //_________________________________________________________________
        DynamicallyAddedUserCode.bar();
        int thisGoesTo = 11;
    }
    [System.Diagnostics.DebuggerHidden]
    public static void ExampleJoinPointThunk_withDebuggerHidden()
    {
        //_________________________________________________________________
        // Tools/Options/Debugging/Enable Just My Code ON:
        //CALLSTACK view: Frame is completely hidden.
        //BP: A Bp will set with no error, but will not hit!
        //stepping: A Step in will land in called methods.
        //_________________________________________________________________
        // Tools/Options/Debugging/Enable Just My Code OFF:
        //CALLSTACK view: Frame is completely hidden. (i.e. No [External Code])
        //BP:Warning provided, no bp will set or hit.
        //STEPPING: Will not stop inside method (step in or step out)
        // A step in will land in called methods.
        //_________________________________________________________________
        DynamicallyAddedUserCode.bar();
        int thisGoesTo = 11;
    }
    [System.Diagnostics.DebuggerNonUserCode]
    public static void ExampleJointPointThunk_withDebuggerNonUserCode()
    {
        //_________________________________________________________________
        // [Same as normal code.]
        // Tools/Options/Debugging/Enable Just My Code ON:
        //CALLSTACK view: Normal view, callstack is viewable.
        //BP: A bp will set and hit.
        //STEPPING: Step as normal.
        //_________________________________________________________________
        // [Same as Debugger StepThrough]
        // Tools/Options/Debugging/Enable Just My Code OFF
        //CALLSTACK view:[ External Code] frame.
        // Real frame is viewable by selecting 'Show External Code'
        // in Callstack window.
        //BP: Warning provided, no bp will set or hit.
        //STEPPING: Will not stop inside method (step in or step out)
        // A step in will land in called methods.
        //_________________________________________________________________
        DynamicallyAddedUserCode.bar();
        int thisGoesTo = 11;
    }
};
class Program
{
    static void foo()
    {
#line hidden //this emits a Sequence Point with line = 0xFeeFEE
        //Imagine these hidden areas were added dynamically
        Weaver.ExampleJoinPointThunk_withDebuggerStepThrough();
#line default
        int theone = 1;
#line hidden
        Weaver.ExampleJoinPointThunk_withDebuggerHidden();
#line default
        int volume = 11;
#line hidden
        Weaver.ExampleJointPointThunk_withDebuggerNonUserCode();
#line default
        int answer = 42;
    }
    static void Main(string[] args)
    {
        foo();
    }
}
 
// The #line hidden directive places 0xFeeFee sequence point in the line
// information. The 0xFeeFee sequence point tells the debugger to continue
// stepping. It is not necessary unless a step can end at the location. 
// If you are writing a compiler and emitting your own debug information,
// then it is often possible to simply omit the line info for code that would
// be marked hidden. If you decompile the above code with ILDasm /Linenum you
// will see an example of that strategy.
// However there are cases where it is important to use the a hidden sequence
// point. Any place where a branch instruction lands must use a hidden sequence
// point if you do not want the debugger to stop there.
//
// The motivating example for this is having all exit points from a Try block
// branch to a single leave instruction. If you produce that type of codegen
// you will need to use 0xFeeFee to create reasonable stepping behavior.