Created
December 22, 2012 10:45
-
-
Save anonymous/4358405 to your computer and use it in GitHub Desktop.
Replace event add_/remove_ methods with versions that do not use Interlocked.CompareExchange<T>. Instead they use Delegate.Combine/Remove in a way that lacks thread safety. Needed for AOT compilation on Unity3D
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// CAUTION: This can irreversibly change your DLLs. | |
// Reference: Mono.Cecil (DLL version 0.9.4.0, from package Cecil 2.10.9) | |
using System; | |
using Mono.Cecil; | |
using Mono.Cecil.Cil; | |
using System.Linq; | |
using System.IO; | |
using System.Collections.Generic; | |
namespace Rewriter | |
{ | |
class MainClass | |
{ | |
public static int Verbosity = 1; | |
public static void Main(string[] args) | |
{ | |
foreach(var fileName in | |
Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll") | |
.Concat(Directory.GetFiles(Directory.GetCurrentDirectory(), "*.exe"))) | |
{ | |
try | |
{ | |
string outFileName = fileName + "-orig-"; | |
while(File.Exists(outFileName)) | |
{ | |
outFileName += "-"; | |
} | |
ReplaceCE(fileName, outFileName); | |
} | |
catch(Exception ex) | |
{ | |
Console.WriteLine("Exception processing dll " + fileName + ": " + ex); | |
} | |
} | |
} | |
public static void ReplaceCE(string fileName, string outFileName, bool keepOriginal = true, bool swap = true) | |
{ | |
int replaced = 0; | |
int skipped = 0; | |
if(!File.Exists(fileName)) throw new ArgumentException("File not found: " + fileName); | |
if(Verbosity >= 4) Console.WriteLine("Rewriting add/remove methods for " + fileName + " --> " + outFileName); | |
if(Verbosity >= 9) Console.WriteLine(typeof (Delegate).Module.FullyQualifiedName); | |
AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(fileName); | |
ModuleDefinition module = assembly.MainModule; | |
TypeReference delegateTypeDef = module.Import(typeof(Delegate)); | |
MethodReference combineR = delegateTypeDef.Module.Import( | |
typeof(Delegate).GetMethod("Combine", | |
new Type[] { typeof(Delegate), typeof(Delegate) })); | |
MethodReference removeR = delegateTypeDef.Module.Import( | |
typeof(Delegate).GetMethod("Remove", | |
new Type[] { typeof(Delegate), typeof(Delegate) })); | |
foreach(TypeDefinition type in module.Types) | |
{ | |
if(type.IsInterface) continue; | |
foreach(var method in type.Methods) | |
{ | |
try | |
{ | |
string fieldName; | |
MethodReference methodR; | |
#region Get required info or continue | |
if(method.Name.StartsWith("add_")) | |
{ | |
fieldName = method.Name.Substring("add_".Length); | |
methodR = combineR; | |
} | |
else if(method.Name.StartsWith("remove_")) | |
{ | |
fieldName = method.Name.Substring("remove_".Length); | |
methodR = removeR; | |
} | |
else | |
{ | |
continue; | |
} | |
#endregion | |
var newI = new List<Instruction>(); | |
var processor = method.Body.GetILProcessor(); | |
bool foundCompExch = false; | |
foreach(var existingInstruction in processor.Body.Instructions) | |
{ | |
if(existingInstruction.OpCode == OpCodes.Call) | |
{ | |
var meth = existingInstruction.Operand as MethodReference; | |
if(meth!=null) | |
{ | |
if(meth.Name == "CompareExchange") | |
{ | |
if(Verbosity > 7) Console.WriteLine(" Found CompareExchange"); | |
foundCompExch = true; | |
break; | |
} | |
} | |
} | |
} | |
if(!foundCompExch) | |
{ | |
if(Verbosity >= 3) | |
{ | |
Console.WriteLine(" . ignoring body with no CompareExchange: " + type.Name + "." + method.Name); | |
} | |
skipped++; | |
continue; | |
} | |
FieldReference field = type.Fields.Where(f => f.Name == fieldName).FirstOrDefault(); | |
if(field == null) throw new Exception("Could not find field: " + fieldName + " in type " + type.FullName); | |
// IL_0000: ldarg.0 | |
newI.Add(processor.Create(OpCodes.Ldarg_0)); | |
// IL_0001: ldarg.0 | |
newI.Add(processor.Create(OpCodes.Ldarg_0)); | |
// IL_0002: ldfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler | |
newI.Add(processor.Create(OpCodes.Ldfld, field)); | |
// IL_0007: ldarg.1 | |
newI.Add(processor.Create(OpCodes.Ldarg_1)); | |
// IL_0008: call class [mscorlib]System.Delegate class [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) | |
newI.Add(processor.Create(OpCodes.Call, methodR)); | |
// IL_000d: castclass MyNamespace.ActionBool | |
newI.Add(processor.Create(OpCodes.Castclass, field.FieldType)); | |
// IL_0012: stfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler | |
newI.Add(processor.Create(OpCodes.Stfld, field)); | |
// IL_0017: ret | |
newI.Add(processor.Create(OpCodes.Ret)); | |
#region Replace the instructions | |
replaced++; | |
processor.Body.Instructions.Clear(); | |
foreach(var i in newI) | |
{ | |
processor.Body.Instructions.Add(i); | |
} | |
if(Verbosity >= 3) | |
{ | |
Console.WriteLine(" - replaced method: " + type.Name + "." + method.Name); | |
} | |
#endregion | |
} | |
catch(Exception ex) | |
{ | |
Console.WriteLine("Exception for method: " + type.FullName + "." + method.Name + ": " + ex); | |
} | |
} | |
} | |
if(Verbosity >= 1) Console.WriteLine("Replaced " + replaced + " event methods (" + skipped + " skipped) in " + Path.GetFileName(fileName)); | |
if(swap) | |
{ | |
File.Move(fileName, outFileName); | |
assembly.Write(fileName); | |
if(!keepOriginal) File.Delete(outFileName); | |
} | |
else | |
{ | |
assembly.Write(outFileName); | |
if(!keepOriginal) File.Delete(fileName); | |
} | |
} | |
// Used monodis to get the desired add_/remove_ methods from a simpler C# implementation: | |
// // method line 2581 | |
// .method private hidebysig specialname | |
// instance default void add_MyHandlerZYX (class MyNamespace.ActionBool 'value') cil managed | |
// { | |
// // Method begins at RVA 0x20000 | |
// // Code size 24 (0x18) | |
// .maxstack 8 | |
// IL_0000: ldarg.0 | |
// IL_0001: ldarg.0 | |
// IL_0002: ldfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler | |
// IL_0007: ldarg.1 | |
// IL_0008: call class [mscorlib]System.Delegate class [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) | |
// IL_000d: castclass MyNamespace.ActionBool | |
// IL_0012: stfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler | |
// IL_0017: ret | |
// } // end of method EnableableBase::add_MyHandlerZYX | |
// // method line 2846 | |
// .method private hidebysig specialname | |
// instance default void remove_MyHandlerZYX (class MyNamespace.ActionBool 'value') cil managed | |
// { | |
// // Method begins at RVA 0x226d4 | |
// // Code size 24 (0x18) | |
// .maxstack 8 | |
// IL_0000: ldarg.0 | |
// IL_0001: ldarg.0 | |
// IL_0002: ldfld class Namespace.ActionBool MyNamespace.EnableableBase::myHandler | |
// IL_0007: ldarg.1 | |
// IL_0008: call class [mscorlib]System.Delegate class [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) | |
// IL_000d: castclass MyNamespace.ActionBool | |
// IL_0012: stfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler | |
// IL_0017: ret | |
// } // end of method EnableableBase::remove_MyHandlerZYX | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment