Click here to Skip to main content
Click here to Skip to main content

A Release Mode Breakpoint Using Reflection Emit

, 9 Oct 2002
Rate this:
Please Sign up or sign in to vote.
C#'s release mode doesn't allow break points to be set. This function emits an IL break, forcing a breakpoint in the debugger.

Introduction

OK, for some silly reason, I needed to look at the code generated in release mode vs. debug mode. Why? Because I'm writing an article about the differences using the Debug and Trace classes, and I wanted to see what really is going on behind the scenes when I compile code in release mode. Also, I figure that just because C# is "managed", that doesn't mean that I won't get weird behavior when I switch over to a release version, and I may in the future want to set a breakpoint.

As was recently pointed out to me, you can't set a breakpoint in release mode because there's no symbolic information. This is silly, because I should still be able to set a breakpoint based on the instruction address. However, living with the inevitable, the following code emits a "Break" opcode in a module created on the fly and invokes the module's function. Whether in release or debug mode, this results in the application breaking when run under the debugger.

This was also a fun exercise in dynamic code generation and shows some of the nightmarish steps necessary to generate code dynamically. Still, I think it's a really awesome feature of the language!

The Easy Way

As Fredrik Tonn pointed out in a message, adding a breakpoint in release mode can be easily accomplished by inserting System.Diagnostics.Debugger.Break() in your code where you wish the breakpoint to occur.

What else is there to say? So much for the easy way.

The Hard Way

This requires generating some IL on the fly, which invokes the IL opcode Break. The following code documents how to do this.

/*
    *    Credits:
    *        Visual Studio Magazine
    *        Octobor 2002 Issue
    *        "Generate Assemblies With Reflection Emit" by Randy Holloway
    *        http://www.fawcette.com/vsm/2002_10/magazine/columns/blackbelt/ 
*/
// the interface serves as a stub to invoke the generated function
public interface IRMBP
{
    void Break();
}

public class ReleaseMode
{
    static public void Break()
    {
        Assembly asm=new ReleaseMode().GenerateBreak();
        IRMBP bp=null;
        bp=(IRMBP)asm.CreateInstance("RMBP");
        bp.Break();
    }

    private Assembly GenerateBreak()
    {
        // create an instance of the assembly name...
        AssemblyName asmbName=new AssemblyName();
        // ... and name it.
        asmbName.Name="ReleaseModeBreakPoint";

        // get an instance of the assembly builder,
        // indicating that the assembly can be saved and executed
        // If we don't specify "RunAndSave", we get the error
        // "cannot have a persistent module in a transient assembly"
        AssemblyBuilder asmbBuilder=
          AppDomain.CurrentDomain.DefineDynamicAssembly(asmbName, 
          AssemblyBuilderAccess.RunAndSave);

        // create a module builder, giving it the module name,
        // the filename, and whether or not symbolic information is written
        ModuleBuilder mdlBuilder=
          asmbBuilder.DefineDynamicModule("ReleaseModeBreakPoint", 
          "rmbp.dll", false);

        // create a type.  As in "public class RMBP"
        TypeBuilder typBuilder=mdlBuilder.DefineType("RMBP", 
                                      TypeAttributes.Public);

        // add an interface.  As in "public class RMBP : IRMBP"
        typBuilder.AddInterfaceImplementation(typeof(IRMBP));

        // create the "Break" method.  "public virtual void Break();"
        Type[] paramTypes=new Type[0];
        Type returnType=typeof(void);
        MethodBuilder mthdBuilder=typBuilder.DefineMethod("Break", 
           MethodAttributes.Public | MethodAttributes.Virtual, 
           returnType, paramTypes);

        // generate the code:
        ILGenerator ilg=mthdBuilder.GetILGenerator(); // get the generator
        ilg.Emit(OpCodes.Break); // issue a break statement
        ilg.Emit(OpCodes.Ret);    // return afterwards

        // get info about our Break method in the interface class IRMBP
        MethodInfo mthdInfo=typeof(IRMBP).GetMethod("Break");

        // associate our generated method with the interface method
        typBuilder.DefineMethodOverride(mthdBuilder, mthdInfo);

        // create the type
        typBuilder.CreateType();

        return asmbBuilder;
    }
}

Usage

Usage is quite simple. Put the statement ReleaseMode.Break(); where you want the debugger to break. Once the debugger breaks, you can single step through the two returns and then inspect your program's assembly code.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Marc Clifton

United States United States
Marc is the creator of two open source projects, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Comments and Discussions

 
QuestionWhy? PinsussFredrik Tonn9-Oct-02 6:38 
AnswerRe: Why? PinmemberMarc Clifton9-Oct-02 9:13 
GeneralInteresting PinmemberKannan Kalyanaraman9-Oct-02 1:35 
GeneralRe: Interesting PinsussAnonymous9-Oct-02 16:32 
GeneralRe: Interesting PinmemberMarc Clifton10-Oct-02 2:38 
GeneralRe: Interesting PinsussAnonymous11-Nov-02 19:09 
I've had experience with this recently in .NET... I have an application that uses a Mutex to make sure that there is only one instance of it running at once. On subsequent launch attempts, it also notifies the initial instance using Remoting over a TcpChannel...
 
Well in Debug this all worked great, but in the Release build, the subsequent instances were not being blocked by the Mutex that the first created. Turns out that for some reason when the first instance opened the TcpChannel to Marshal itself it would lose the Mutex it had just created... No idea why, but I fixed it by just creating the Mutex again after opening the TcpChannel (hypothetically my code won't work as intended if a second instance launches while the first is opening the channel after thinking it has exclusivity over the Mutex).
 
Well all this blabber was just intented to say that I also don't trust that code works functionally the same in both Debug and Release builds.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 10 Oct 2002
Article Copyright 2002 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid