Skip to content

Instantly share code, notes, and snippets.

@GeorgeHahn
Last active December 17, 2015 20:19
Show Gist options
  • Select an option

  • Save GeorgeHahn/5667181 to your computer and use it in GitHub Desktop.

Select an option

Save GeorgeHahn/5667181 to your computer and use it in GitHub Desktop.
Adding dynamic support
using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Rocks;
using Mono.Cecil.Cil;
public class LogForwardingProcessor
{
public MethodDefinition Method;
public FieldReference Field;
public Action FoundUsageInType;
public Action FoundDynamicInType;
bool foundUsageInMethod;
ILProcessor ilProcessor;
public ModuleWeaver ModuleWeaver;
private List<Instruction> instructions_ldsfld = new List<Instruction>();
private List<Instruction> instructions_ldfld = new List<Instruction>();
private List<Instruction> instructions_stsfld = new List<Instruction>();
private List<Instruction> instructions_call = new List<Instruction>();
public void ProcessMethod()
{
try
{
ilProcessor = Method.Body.GetILProcessor();
// Process standard (?) calls
Method.Body.Instructions.Where(x => x.OpCode == OpCodes.Call)
.ToList()
.ForEach(ProcessInstruction);
if (Method.CheckForDynamicUsagesOf("Anotar.NLog.Log"))
{
if (!foundUsageInMethod)
{
Method.Body.InitLocals = true;
Method.Body.SimplifyMacros();
foundUsageInMethod = true;
}
FoundUsageInType(); // TODO: WARNING: This might not belong here (GH)
FoundDynamicInType();
// Perform a simple replace and record other instructions that need to be modified
Method.Body.Instructions.Where(x => x.OpCode == OpCodes.Ldtoken)
.ToList()
.ForEach(ProcessDynamic);
Method.Body.Instructions.Where(x => x.Operand as CallSite != null)
.Where(x => NewInvokeCallSites.ContainsKey((x.Operand as CallSite).Name))
.ToList()
.ForEach(x => ReplaceCallSite(x, NewInvokeCallSites[(x.Operand as CallSite).Name]));
Method.Body.Instructions.Where(x => x.Operand as CallSite != null)
.Where(x => NewCreateCallSites.ContainsKey((x.Operand as CallSite).Name))
.ToList()
.ForEach(x => ReplaceCallSite(x, NewCreateCallSites[(x.Operand as CallSite).Name]));
Method.Body.Instructions.Where(x => x.Operand as FieldReference != null)
.Where(x => NewSiteFields.ContainsKey((x.Operand as FieldReference).Name))
.ToList()
.ForEach(x => ReplaceField(x, NewSiteFields[(x.Operand as FieldReference).Name]));
Method.Body.Instructions.Where(x => x.Operand as FieldReference != null)
.Where(x => NewSiteTargetFields.ContainsKey((x.Operand as FieldReference).Name))
.ToList()
.ForEach(x => ReplaceField(x, NewSiteTargetFields[(x.Operand as FieldReference).Name]));
}
if (foundUsageInMethod)
{
Method.Body.OptimizeMacros();
}
}
catch (Exception exception)
{
throw new Exception(string.Format("Failed to process '{0}'.", Method.FullName), exception);
}
}
private static Dictionary<string, TypeReference> ReplacedTypes = new Dictionary<string, TypeReference>();
private static Dictionary<string, FieldReference> NewSiteFields = new Dictionary<string, FieldReference>();
private static Dictionary<string, FieldReference> NewSiteTargetFields = new Dictionary<string, FieldReference>();
private static Dictionary<string, CallSite> NewInvokeCallSites = new Dictionary<string, CallSite>();
private static Dictionary<string, CallSite> NewCreateCallSites = new Dictionary<string, CallSite>();
void ProcessDynamic(Instruction instruction)
{
var typeReference = instruction.Operand as TypeReference;
if (typeReference == null)
{
return;
}
if (typeReference.FullName != "Anotar.NLog.Log")
{
return;
}
//ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, string, object>> FodyTest.Program/'<Anotar_NLog_NoCast>o__SiteContainer6'::'<>p__Site7'
// |----- This is the type we want -----|
Instruction ldsfld = instruction.Previous;
if (ldsfld.OpCode != OpCodes.Ldsfld)
throw new WeavingException("Expected ldsfield opcode");
var callsitefield = ldsfld.Operand as FieldReference;
if (callsitefield == null)
throw new WeavingException("Unexpected ldsfld operand");
var sitecontainer = callsitefield.DeclaringType;
if (!ReplacedTypes.ContainsKey(sitecontainer.Name) && !NewSiteFields.ContainsKey(callsitefield.Name))
{
// We have not yet replaced this type
var newsitecontainer = new TypeDefinition(sitecontainer.Namespace,
sitecontainer.Name,
TypeAttributes.AnsiClass &
TypeAttributes.AutoClass &
TypeAttributes.Sealed &
TypeAttributes.NestedPrivate &
TypeAttributes.Abstract &
TypeAttributes.BeforeFieldInit);
var msCoreLibDefinition = ModuleWeaver.AssemblyResolver.Resolve("mscorlib");
//class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, string, object>>
var Scope = msCoreLibDefinition.MainModule; // This is probably wrong
var callsiteType_1 = new TypeReference("System.Runtime.CompilerServices", "CallSite`1", msCoreLibDefinition.MainModule, Scope);
var callsiteType = new TypeReference("System.Runtime.CompilerServices", "CallSite",
msCoreLibDefinition.Modules.First(x => x.Name == "System.Core"), Scope);
TypeDefinition NLogTypeDefinition = ModuleWeaver.NLogReference.MainModule.Types.First(x => x.Name == "Logger");
TypeReference NLogType = NLogTypeDefinition.MakeByReferenceType();
var stringType = new TypeReference("", "string", msCoreLibDefinition.MainModule, Scope);
var objectType = new TypeReference("", "object", msCoreLibDefinition.MainModule, Scope);
var actionType = new TypeReference("System", "Action`4", msCoreLibDefinition.MainModule, Scope);
var genericActionType =
actionType.MakeGenericInstanceType(new[] {callsiteType, NLogType, stringType, objectType});
var fieldType = callsiteType_1.MakeGenericInstanceType(genericActionType);
var newSiteField = new FieldDefinition(callsitefield.Name,
FieldAttributes.Public &
FieldAttributes.Static,
fieldType
);
newsitecontainer.Fields.Add(newSiteField);
sitecontainer.Module.Types.Add(newsitecontainer);
ReplacedTypes.Add(newsitecontainer.Name, newsitecontainer);
NewSiteFields.Add(newSiteField.Name, newSiteField);
// TODO:
//NewSiteTargetFields CallSite`1::Target ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [NLog]NLog.Logger, string, object>>::Target
//NewInvokeCallSites Action`4::Invoke callvirt instance void class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [NLog]NLog.Logger, string, object>::Invoke(!0, !1, !2, !3)
//NewCreateCallSites CallSite1:Create call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`4<class [System.Core]System.Runtime.CompilerServices.CallSite, class [NLog]NLog.Logger, string, object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
}
Instruction prev = instruction;
while (prev.OpCode != OpCodes.Call)
{
prev = prev.Previous; // Start with this because we want to capture the first Call opcode
if (prev.OpCode == OpCodes.Ldsfld)
instructions_ldsfld.Add(prev);
else if (prev.OpCode == OpCodes.Ldfld)
instructions_ldfld.Add(prev);
else if (prev.OpCode == OpCodes.Stsfld)
instructions_stsfld.Add(prev);
else if (prev.OpCode == OpCodes.Call)
instructions_call.Add(prev);
}
// Remove typeof(Log)
//IL_006a: ldtoken [System.Core]Anotar.NLog.Log
//IL_006f: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
ilProcessor.Remove(instruction.Next);
ilProcessor.Replace(instruction, Instruction.Create(OpCodes.Ldsfld, Field));
}
void ReplaceCallSite(Instruction instruction, CallSite newCallSite)
{
var callsite = instruction.Operand as CallSite;
if (callsite == null)
throw new WeavingException("Instruction operand is not a CallSite");
var newInstruction = Instruction.Create(instruction.OpCode, newCallSite);
ilProcessor.Replace(instruction, newInstruction);
}
void ReplaceField(Instruction instruction, FieldReference newFieldReference)
{
var fieldReference = instruction.Operand as FieldReference;
if (fieldReference == null)
throw new WeavingException("Instruction operand is not a CallSite");
var newInstruction = Instruction.Create(instruction.OpCode, newFieldReference);
ilProcessor.Replace(instruction, newInstruction);
}
void ProcessInstruction(Instruction instruction)
{
var methodReference = instruction.Operand as MethodReference;
if (methodReference == null)
{
return;
}
if (methodReference.DeclaringType.FullName != "Anotar.NLog.Log")
{
return;
}
if (!foundUsageInMethod)
{
Method.Body.InitLocals = true;
Method.Body.SimplifyMacros();
}
foundUsageInMethod = true;
FoundUsageInType();
var parameters = methodReference.Parameters;
instruction.OpCode = OpCodes.Callvirt;
if (parameters.Count == 0)
{
var fieldAssignment = Instruction.Create(OpCodes.Ldsfld, Field);
ilProcessor.Replace(instruction, fieldAssignment);
ilProcessor.InsertAfter(fieldAssignment,
Instruction.Create(OpCodes.Ldstr, GetMessagePrefix(instruction)),
Instruction.Create(OpCodes.Ldc_I4_0),
Instruction.Create(OpCodes.Newarr, ModuleWeaver.ModuleDefinition.TypeSystem.Object),
Instruction.Create(OpCodes.Callvirt, ModuleWeaver.GetNormalOperand(methodReference)));
}
if (methodReference.IsMatch("String", "Exception"))
{
var messageVar = new VariableDefinition(ModuleWeaver.ModuleDefinition.TypeSystem.String);
var exceptionVar = new VariableDefinition(ModuleWeaver.ExceptionType);
Method.Body.Variables.Add(exceptionVar);
Method.Body.Variables.Add(messageVar);
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, exceptionVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, messageVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldsfld, Field));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldstr, GetMessagePrefix(instruction)));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, messageVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Call, ModuleWeaver.ConcatMethod));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, messageVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, messageVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, exceptionVar));
instruction.Operand = ModuleWeaver.GetExceptionOperand(methodReference);
}
if (methodReference.IsMatch("String", "Object[]"))
{
var formatVar = new VariableDefinition(ModuleWeaver.ModuleDefinition.TypeSystem.String);
var paramsVar = new VariableDefinition(ModuleWeaver.ObjectArray);
Method.Body.Variables.Add(formatVar);
Method.Body.Variables.Add(paramsVar);
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, paramsVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, formatVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldstr, GetMessagePrefix(instruction)));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, formatVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Call, ModuleWeaver.ConcatMethod));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, formatVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldsfld, Field));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, formatVar));
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Ldloc, paramsVar));
instruction.Operand = ModuleWeaver.GetNormalOperand(methodReference);
}
}
string GetMessagePrefix(Instruction instruction)
{
//TODO: should prob wrap calls to this method and not concat an empty string. but this will do for now
if (ModuleWeaver.LogMinimalMessage)
{
return string.Empty;
}
var sequencePoint = instruction.GetPreviousSequencePoint();
if (sequencePoint == null)
{
return string.Format("Method: '{0}'. ", Method.FullName);
}
return string.Format("Method: '{0}'. Line: ~{1}. ", Method.FullName, sequencePoint.StartLine);
}
}
@GeorgeHahn
Copy link
Author

Missed some instructions - call and stsfld. Will probably end up with some combination processing methods because some will be quite similar.

@GeorgeHahn
Copy link
Author

Three references left to replace, then I need to get tests running and write tests so I can refactor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment