Partager via


#line hidden and 0xFeeFee sequence points

Sometimes you may have functions that you don’t want a debugger to step into (such as 3rd-party library code or even your own helper functions like certain getters or ToString calls).  I blogged here about debugging “Just-my-code” on a per-function basis. You can also mark regions within a given function that you want the debugger to skip over.

In C#, you can use the ‘#line hidden’ directive to mark a region of code as not exposed to the debugger.

Eg:
        int y = 0; // <-- start here, press F10
#line hidden
y++; // This code will be stepped over
y++;
#line default
Console.WriteLine(y); // <-- and you land here

So a debugger would skip over the region between the ‘#line hidden’ and ‘#line default’.

How is this useful?
So the above example is pretty contrived. Real examples show up with generating code for various loop structures. For example, here’s how a basic for loop gets generated in C#:
        for (int i = 0; i < 5; i++)
{
Console.WriteLine(i);
}
Compiles to this code:
.locals init ([0] int32 i,
[1] bool CS$4$0000)

//000009: for (int i = 0; i < 5; i++)
IL_0002: ldc.i4.0
IL_0003: stloc.0
<no source line>
IL_0004: br.s IL_0013

//000010: {
IL_0006: nop
//000011: Console.WriteLine(i);
IL_0007: ldloc.0
IL_0008: call void [mscorlib]System.Console::WriteLine(int32)
IL_000d: nop
//000012: }
IL_000e: nop
//000009: for (int i = 0; i < 5; i++ )
IL_000f: ldloc.0
IL_0010: ldc.i4.1
IL_0011: add
IL_0012: stloc.0

//000009: for (int i = 0; i < 5; i++)
IL_0013: ldloc.0
IL_0014: ldc.i4.5
IL_0015: clt
IL_0017: stloc.1

<no source line>
IL_0018: ldloc.1
IL_0019: brtrue.s IL_0006

//000013: }
IL_001b: nop

So how does it work?
The debugging information in a PDB (program database) maps each region of IL back to its source file.  Thus when you do a source-level step in a debugger (eg, press F10), a debugger can use this map to figure out what range of IL you want to step over. (You can then pass these IL ranges into ICorDebugStepper::Step).
Debuggers + compilers have a common agreement that if a line maps 0xFeeFee, then the debugger will skip over it.  So the ‘#line hidden’ directive tells the C# compiler to map all following lines to a 0xFeeFee line number, instead of the real line number.

Just for kicks, you can verify this. 0xFeeFee = 16707566 in decimal. You could use that instead of the #line hidden directive (since CSC doesn’t stop you).
    int y = 0; // F10 here
#line 16707566 // decimal equivalent of 0xFeeFee
y++; // This statement is skipped
#line default // restores
y++;
You could also verify by using the CorSym API to dump the sequence points (there are various Mdbg extensions to do this).

Random caveats:
The 0xFeeFee sequence points is entirely an protocol between the compiler and debuggers. It has no ICorDebug level support.
Any compiler targeting managed code can take advantage of FeeFee sequence points. C# exposes this via the #line directives, which makes it convenient for code-generators targeting C# instead of raw IL. ILasm exposes this via it’s ‘.line’ directive.

Any debugger can support 0xFeeFee sequence points. V1.1 Cordbg did not support them, but Mdbg, VS, and Cordbg V2.0 (in beta 1) all do. My suggested way to add debugger support for FeeFee is to have the debugger find all FeeFee ranges in the current method and then add those to the step ranges passed into ICorDebugStepper::Step (that’s how we added it to Cordbg).

Another application:
It turns out you can use 0xFeeFee sequence points combined with JMC to do some pretty interesting things. Stay tuned…
[uppdate] One usage is for adding debug support to arbitrary state machines.

Comments

  • Anonymous
    June 19, 2005
    How can you use this in C++? Is there some easy way for the C++ compiler to write 0xFEEFEE into the pdb?
  • Anonymous
    June 20, 2005
    I checked, and apparently this doesn't work with native C++.
    Raj said to search for "Native DE Step Into reg key")
    Andy Pennell has some blogs about it here:
    http://blogs.msdn.com/andypennell/archive/2004/2/6.aspx
    http://blogs.msdn.com/andypennell/archive/2004/02/06/69004.aspx
  • Anonymous
    August 02, 2005
    But what I really want to be able to do... is to insert something like

    #lineNo and #methodName into C# code and have the compiler replace it with the current method name for instrumentation purposes. This would be much better than using reflection.

    Maybe for Orcas?
  • Anonymous
    August 03, 2005
    Josh, I'm not quite sure what you're asking, but it raises some interesting questions. I just wrote another blog entry that I hope answers them: http://blogs.msdn.com/jmstall/archive/2005/08/03/line_directive_caveats.aspx
    Can you clarify your question?
  • Anonymous
    August 20, 2005
    If you copy somebody else's blog entry verbatim, credit the original author and link back to the original...
  • Anonymous
    August 22, 2005
    Here's my sample code for a tool to catch blog plagiarism that I described earlier. In retrospect, it...
  • Anonymous
    August 23, 2005
    Here's my sample code for a tool to catch blog plagiarism that I described earlier. In retrospect, it...
  • Anonymous
    August 24, 2005
    Here's my sample code for a tool to catch blog plagiarism that I described earlier. In retrospect, it...
  • 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;#...