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.
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()
{
AssemblyName asmbName=new AssemblyName();
asmbName.Name="ReleaseModeBreakPoint";
AssemblyBuilder asmbBuilder=
AppDomain.CurrentDomain.DefineDynamicAssembly(asmbName,
AssemblyBuilderAccess.RunAndSave);
ModuleBuilder mdlBuilder=
asmbBuilder.DefineDynamicModule("ReleaseModeBreakPoint",
"rmbp.dll", false);
TypeBuilder typBuilder=mdlBuilder.DefineType("RMBP",
TypeAttributes.Public);
typBuilder.AddInterfaceImplementation(typeof(IRMBP));
Type[] paramTypes=new Type[0];
Type returnType=typeof(void);
MethodBuilder mthdBuilder=typBuilder.DefineMethod("Break",
MethodAttributes.Public | MethodAttributes.Virtual,
returnType, paramTypes);
ILGenerator ilg=mthdBuilder.GetILGenerator();
ilg.Emit(OpCodes.Break);
ilg.Emit(OpCodes.Ret);
MethodInfo mthdInfo=typeof(IRMBP).GetMethod("Break");
typBuilder.DefineMethodOverride(mthdBuilder, mthdInfo);
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 return
s and then inspect your program's assembly code.