Last time I wrote about my efforts to create minidumps with PostSharp aspects. I showed some ways of achieving that, but the main conclusion was that no out-of-the-box aspect in PostSharp provides a transformation that would allow inserting minidump generation code in the optimal place: just before throw instructions. Fortunately, it isn’t too hard to create such an aspect, even though the required PostSharp SDK is unsupported and undocumented.
Let’s start by analyzing how PostSharp aspects are architected. Then we’ll move to implementing the actual IL transformation required to temporarily store the exception about be thrown, pass it to the aspect code, and then actually throw it expected.
A look at the FastTrace sample
Although the PostSharp SDK is unsupported and undocumented, Gael Fraiteur (the creator of PostSharp) was kind enough to point me to a sample on the SharpCrafters website that showed me how to solve the most important part of the problem: registering with the PostSharp engine. The sample is FastTrace (Download -> All downloads -> Samples -> PostSharp 2.1 – Sample Code -> FastTrace), which basically achieves the same thing as the standard and supported OnMethodBoundaryAspect, but shows how it can be done manually. The actual PostSharp implementation of this aspect, as far as I can tell, is far more complicated and robust, but for a little experiment like mine this is more than enough.
Registering PostSharp plugins
The FastTrace sample consists of 3 projects:
- FastTrace – defines the aspect attribute, and is shared by both the client project (FastTrace.Test) and the plugin project (FastTrace.Weaver);
- FastTrace.Test – contains test code that has the aspect attribute attached, we expect that aspect code will run along regular code once the project is built and processed by PostSharp;
- FastTrace.Weaver – contains transformation code (the so-called weaver) that runs inside PostSharp, when it is invoked at the end of the build process – the engine detects the aspect attribute and calls into the weaver code passing the elements the attribute was applied to, so that tracing code can be inserted.
The solution has an additional manual dependency (apart from project references) added between FastTrace.Test and FastTrace.Weaver. This ensures that when FastTrace.Test is built, the FastTrace.Weaver binary is available. Without it, PostSharp would report an error about a missing plugin (thanks to the RequirePostSharp attribute on the aspect class). Where do plugins come from? The PostSharp installation directory has Plugins subfolder, but a project property can also override the plugin search path. The FastTrace.Test.csproj file contains the following code:
- <PropertyGroup>
- <PostSharpSearchPath>
- $(PostSharpSearchPath);
- ..\FastTrace.Weaver\bin\$(Configuration)
- </PostSharpSearchPath>
- </PropertyGroup>
You could also specify this property on the msbuild command-line. Speaking of msbuild, the absolutely irreplacable switch is /p:PostSharpAttachDebugger=True
, which will let you debug the aspect transformation code during compile-time. See Debugging Compile-Time Logic for more information. Also, what can save you from losing a lot of hair, is to always stop the PostSharp tray server and do a full rebuild of all projects (attribute, weaver, and target) each time you change the weaver code.
To be recognized as a PostSharp plugin, an assembly must have a corresponding .psplugin file. For FastTrace, it looks as follows:
- <?xmlversion=“1.0“encoding=“utf-8“ ?>
- <PlugInxmlns=“http://schemas.postsharp.org/1.0/configuration“>
- <TaskTypeName=“FastTrace“
- Implementation=“FastTrace.Weaver.FastTracePlugIn, FastTrace.Weaver“>
- <DependencyTaskType=“AspectWeaver“/>
- </TaskType>
- </PlugIn>
The file is copied to the output bin\$(Configuration) directory when FastTrace.Weaver is built. It registers the FastTracePlugIn class as task to be run by PostSharp. In that class you can see how the transformation code is associated with a given aspect attribute:
- protected override void Initialize()
- {
- this.BindAspectWeaver<FastTraceAttribute, FastTraceAspectWeaver>();
- }
The FastTraceAspectWeaver class contains mainly plumbing code, but also one thing that will be of interest to us. See line 6 (line 17 in the original file):
- public FastTraceAspectWeaver()
- : base(
- defaultConfiguration, ReflectionObjectBuilder.Dynamic,
- MulticastTargets.Method | MulticastTargets.InstanceConstructor | MulticastTargets.StaticConstructor | MulticastTargets.Field )
- {
- this.RequiresRuntimeInstance = false;
- this.RequiresRuntimeReflectionObject = false;
- }
This property tells PostSharp whether an instance of the aspect attribute class should be made available to the weaver. In case of FastTrace, this is not necessary, as the attribute it is only used to mark pieces of code for transformation. But later on, when we proceed to implement the minidump aspect, it will be needed to be able to call custom code implemented in the aspect attribute.
Transforming assemblies with aspect weavers
Let’s move to FastTraceTransformation for the really cool part of AOP. This class implements the actual IL rewriting, although it cheats a bit by using the pre-defined MethodBodyWrappingImplementation class. Generally speaking, the starting point here is the MethodBodyTransformation base class, that specifies that rewriting will be performed on the method level.
First, references to other code elements are obtained, in this case to Console.WriteLine, so that the injected code can write out traces to the console:
- public FastTraceTransformation(AspectWeaver aspectWeaver)
- : base(aspectWeaver)
- {
- ModuleDeclaration module = this.AspectInfrastructureTask.Project.Module;
- this.assets = module.Cache.GetItem(() => new Assets(module));
- }
- private sealed class Assets
- {
- public readonly IMethod WriteLineMethod;
- public Assets(ModuleDeclaration module)
- {
- this.WriteLineMethod = module.FindMethod(typeof(Console).GetMethod(“WriteLine”, new[] { typeof(string) }), BindingOptions.Default);
- }
- }
For performance reasons this is cached – remember that an aspect can easily be attached to all elements of a large assembly with multicasting.
Processing starts with a MethodBodyTransformationContext object in Instance.Implement. It provides a reference to a method that was annotated with the FastTrace attribute, and is called for each one of them. Then, the actual rewriting is done in EmitMessage:
- private void EmitMessage(InstructionBlock block, InstructionWriter writer, string message)
- {
- InstructionSequence sequence = block.AddInstructionSequence(null, NodePosition.After, null);
- writer.AttachInstructionSequence(sequence);
- writer.EmitInstructionString(OpCodeNumber.Ldstr, message);
- writer.EmitInstructionMethod(OpCodeNumber.Call,
- ((FastTraceTransformation)this.transformationInstance.Transformation).assets.WriteLineMethod);
- writer.DetachInstructionSequence();
- }
Implementing the OnThrowAspect
Now for the even cooler part, where we implement our own custom transformation. As discussed before, for the richest minidumps, they need to be created right before an exception is thrown. It is is possible to capture that moment with a debugger (see Writing an automatic debugger in 15 minutes). This applies to all exceptions, including those originating from system code. Because running under a debugger has its implications, let’s try actually injecting the minidump code before all throw instructions. Of course with approach we we will only be able to hijack exceptions thrown in our own code, where an aspect attribute can be applied.
All further code will refer to Padre.PostSharp (more on that later), which is a direct result of trial-and-error modifications to FastTrace.
Finding throw instructions
Since the transformation will process method bodies, and not wrap around them, we can safely delete all code run by Instance.Implement. Instead we need to look into the IL instruction sequences and find all throws. Luckily, PostSharp allows that with the visitor pattern:
- namespace PostSharp.Sdk.CodeModel
- {
- public interface IMethodBodyVisitor
- {
- void EnterInstructionBlock(InstructionBlock instructionBlock, InstructionBlockExceptionHandlingKind exceptionHandlingKind);
- void LeaveInstructionBlock(InstructionBlock instructionBlock, InstructionBlockExceptionHandlingKind exceptionHandlingKind);
- void EnterInstructionSequence(InstructionSequence instructionSequence);
- void LeaveInstructionSequence(InstructionSequence instructionSequence);
- void EnterExceptionHandler(ExceptionHandler exceptionHandler);
- void LeaveExceptionHandler(ExceptionHandler exceptionHandler);
- void VisitInstruction(InstructionReader instructionReader);
- }
- }
Before I show the final piece of code and explain what it does, let’s go through all the various helpers that we need. First, we want to be able to implement custom aspects that will be invoked before throwing exceptions, so let’s prepare a base attribute class that supports that:
- [MulticastAttributeUsage(MulticastTargets.Method | MulticastTargets.InstanceConstructor | MulticastTargets.StaticConstructor)]
- [RequirePostSharp(“Padre.PostSharp”, “Padre.PostSharp”)]
- [Serializable]
- public abstract class OnThrowAspectAttribute : MulticastAttribute, IAspect
- {
- public abstract void OnThrow(Exception exception);
- }
The minidump generation code will just implement this aspect. You can of course do anything else, like logging the exception, in the following way:
- [Serializable]
- public class ClrDumpAspectAttribute : OnThrowAspectAttribute
- {
- private bool IsWorthCapturing(Exception exception)
- {
- //…
- }
- public override void OnThrow(Exception exception)
- {
- if (IsWorthCapturing(exception))
- ClrDump.Dump();
- }
- }
To be able to call into the aspect code, we need to set RequiresRuntimeInstance to true, and gather the required assets:
- private sealed class Assets
- {
- public readonly IMethod OnThrow;
- public readonly ITypeSignature Exception;
- public Assets(ModuleDeclaration module)
- {
- OnThrow = module.FindMethod(typeof(OnThrowAspectAttribute).GetMethod(“OnThrow”, new[] { typeof(Exception) }), BindingOptions.Default);
- Exception = module.FindType(typeof(Exception));
- }
- }
In the AspectWeaverInstance we can call our MethodBodyVisitor to scan for throw instructions:
- public override void Implement(MethodBodyTransformationContext context)
- {
- context.InstructionBlock.MethodBody.Visit(new[] { new Visitor(this, context) });
- }
Now, finally, the actual IL transformation for all throw instructions found:
- public void VisitInstruction(InstructionReader reader)
- {
- if (reader.CurrentInstruction.OpCodeNumber == OpCodeNumber.Throw)
- {
- var assets = ((OnThrowTransformation)instance.Transformation).assets;
- context.InstructionBlock.MethodBody.InitLocalVariables = true;
- var variable = reader.CurrentInstructionBlock.DefineLocalVariable(assets.Exception, “”);
- InstructionSequence before, after;
- reader.CurrentInstructionSequence.SplitAroundReaderPosition(reader, out before, out after);
- var sequence = reader.CurrentInstructionBlock.AddInstructionSequence(null, NodePosition.After, before);
- var writer = new InstructionWriter();
- writer.AttachInstructionSequence(sequence);
- writer.EmitInstructionLocalVariable(OpCodeNumber.Stloc, variable);
- instance.AspectWeaverInstance.AspectRuntimeInstanceField.EmitLoadField(writer, context.MethodMapping.CreateWriter());
- writer.EmitInstructionLocalVariable(OpCodeNumber.Ldloc, variable);
- writer.EmitInstructionMethod(OpCodeNumber.Callvirt, assets.OnThrow);
- writer.EmitInstructionLocalVariable(OpCodeNumber.Ldloc, variable);
- writer.DetachInstructionSequence();
- }
- }
Here’s a quick walkthrough of what the code does:
- lines 7-8: create a temporary variable (unnamed) to store the exception, after making sure that the method initializes stack space for local variables – if we are introducing the first one, it may not be the case;
- lines 10-12: split the instruction sequence of the current block, so that we can insert code right before the throw instruction – first sequence will reference everything before the throw, and the second – after (
after
may be null if throw is the last instruction of the block – this will be true most of the time, as any code after throw is unreachable anyway, and will probably be optimized way, if ever actually written), then create a new instruction sequence betweenbefore
andthrow
(attaching directly tobefore
would overwrite existing code); - lines 14-15: create an InstructionWriter, which is a convenient factory of opcodes and their arguments, and attach it to the new instruction sequence;
- line 16: store the exception object, which is currently on the stack ready to be thrown, into the temporary variable;
- line 17: emit code that will read the deserialized aspect attribute instance (luckily PostSharp does this for us) – we will now have the this value ready for calling OnThrowAspectAttribute.OnThrow;
- line 18: push the exception object back to the stack – this will serve as the first argument to OnThrow;
- line 19: execute OnThrow as a virtual function;
- line 20: push the exception object again to the stack – for the original throw instruction that will immediately follow.
It took about 3 hours to get to that 20 line piece of code, but that’s probably still less than it took to write this blog post, and absolutely worth every minute. PostSharp SDK provides a really convenient way of rewriting IL code, and very robust engine that seamlessly integrates with msbuild and VisualStudio. It’s a real shame that SharpCrafters don’t have the resources to market and support it. I imagine a lot of really cool aspects could be developed using it – after all IL manipulation isn’t that hard. And most of the time peverify.exe will tell you exactly what is wrong with the code you produced.
Trying out the OnThrowAspect
The final code is available on GitHub as part of the PADRE repository (as it serves the same purpose as a the PADRE project partly will – to generate information-rich minidumps automatically).
Would anyone like to help and create a NuGet package for it? I suppose it would require writing a custom install script to insert PostSharpSearchPath modifications into .csproj files.
Tim
/ February 2, 2012I was reading about Exception Filters, are they an option?
The code has to be written in VB (or IL), as there is no equivalent in c#.
This blog post shows some example usage towards the bottom:
http://blogs.msdn.com/b/dondu/archive/2010/10/31/writing-minidumps-from-exceptions-in-c.aspx
And this answer shows an example implementation in VB:
http://stackoverflow.com/a/5958354/106623
TripleEmcoder
/ February 4, 2012Very interesting, I didn’t know about this feature of VB/IL. There’s also an explanation of it here: http://blogs.msdn.com/b/toub/archive/2004/03/05/84698.aspx
I don’t know if the PostSharp SDK would support IL manipulation required to insert an exception filter, but it’s definitely an interesting idea.