public static XNodeOut SignatureToClass(string signature, XNodeOut fileNode) { // create syntax tree for signature XDef def = XDef.ParseAndCheck(signature); // iterate syntax tree and add to our node map XNodeOut currentNode = fileNode; while (def != null) { if (def.DefType == XDef.XDefType.Namespace) { currentNode = currentNode.AddNode(def.Name, XObjType.Namespace); } else if (def.DefType == XDef.XDefType.Class) { currentNode = currentNode.AddNode(def.GetShortName(), XObjType.Class); /* Cant map generic params because the fileNode is not right * if (def.Generics != null) * foreach (var genericSig in def.Generics) * SignatureToClass(genericSig.GetFullName(), fileNode);*/ } def = def.SubDef; } Debug.Assert(currentNode.ObjType == XObjType.Class); return(currentNode); }
public long SaveDat(string path, Dictionary <string, string> settings, XNodeOut root, Dictionary <int, FunctionCall> callMap, Dictionary <int, FunctionCall> initMap) { long trackedObjects = 0; root.ComputeSums(); byte[] temp = new byte[4096]; using (FileStream stream = new FileStream(path, FileMode.Create)) { // save settings foreach (var setting in settings) { WriteSetting(stream, setting.Key, setting.Value); } // save nodes trackedObjects += root.WriteNode(stream); // save call map SaveCallMap(stream, XPacketType.CallMap, callMap); SaveCallMap(stream, XPacketType.InitMap, initMap); } return(trackedObjects); }
private void InjectMethodEnter(XNodeOut node) { Debug.Assert(!node.Exclude); XIL.AppendLine(); AddLine("ldc.i4 " + node.ID.ToString() + " // XRay - Method enter"); AddLine("call void [XLibrary]XLibrary.XRay::MethodEnter(int32)"); LinesAdded += 2; if (Build.TrackInstances) { if (node.Name == ".ctor") { AddLine("ldc.i4 " + node.Parent.ID.ToString()); AddLine("call void [XLibrary]XLibrary.XRay::Constructed(int32)"); LinesAdded += 2; } else if (node.Name == "Finalize") { AddLine("ldc.i4 " + node.Parent.ID.ToString()); AddLine("call void [XLibrary]XLibrary.XRay::Deconstructed(int32)"); LinesAdded += 2; } } XIL.AppendLine(); }
public XDecompile(XNodeOut intRoot, XNodeOut extRoot, XRayedFile item, BuildModel build) { ExtRoot = extRoot; OriginalPath = item.FilePath; item.RecompiledPath = null; // reset XFile = item; Build = build; }
void MarkNodeAsAnon(XNodeOut node) { node.IsAnon = true; foreach (XNodeOut subnode in node.Nodes) { MarkNodeAsAnon(subnode); } }
public XNodeOut AddNode(string name, XObjType objType) { // used for namespaces XNodeOut existing = Nodes.Where(n => n.Name == name && n.ObjType == objType).FirstOrDefault() as XNodeOut; if (existing != null) return existing; XNodeOut node = new XNodeOut(this, name, objType); Nodes.Add(node); return node; }
internal XNodeOut GetNotAnonParent() { XNodeOut next = Parent as XNodeOut; while (next != null && next.IsAnon) { next = next.Parent as XNodeOut; } return(next); }
private void InjectMethodCatch(XNodeOut node) { Debug.Assert(!node.Exclude); Debug.Assert(Build.TrackFlow); XIL.AppendLine(); AddLine("ldc.i4 " + node.ID.ToString() + " // XRay - Method Catch"); AddLine("call void [XLibrary]XLibrary.XRay::MethodCatch(int32)"); XIL.AppendLine(); LinesAdded += 2; }
XNodeOut GetClassRef(TypeReference declaringType) { //if(!declaringType.IsGenericParameter && !declaringType.IsFunctionPointer) // XDef.ParseAndCheck(declaringType.ToString()); if (declaringType.IsGenericParameter) { Debug.WriteLine("GetClassRef for Generic Param - " + declaringType.ToString()); } if (declaringType.IsFunctionPointer) { Debug.WriteLine("GetClassRef for Function Pointer - " + declaringType.ToString()); } var scope = declaringType.Scope; if (scope.MetadataScopeType == MetadataScopeType.ModuleReference) { // is this scope type internal or external, should it be tracked externally? Debug.WriteLine("Skipped GetClassRef for - " + declaringType.ToString()); Debug.Assert(false); return(null); } //string namespaces = (declaringType.DeclaringType != null) ? declaringType.DeclaringType.Namespace : declaringType.Namespace; //string className = declaringType.Name; XNodeOut fileNode = null; // if xrayed internal if (scope.MetadataScopeType == MetadataScopeType.ModuleDefinition) { fileNode = XFile.FileNode; } // xrayed, but in diff module else if (Build.Files.Any(f => f.AssemblyName == scope.Name)) { fileNode = Build.Files.First(f => f.AssemblyName == scope.Name).FileNode; } // if not xrayed - map to external root else { string moduleName = scope.Name; fileNode = ExtRoot.AddNode(moduleName, XObjType.File); } return(SignatureToClass(declaringType.ToString(), fileNode)); }
public XNodeOut AddMethod(MethodReference method) { // used for namespaces XNodeOut existing = Nodes.Cast<XNodeOut>().Where(n => n.MethodRef != null && n.MethodRef.FullName == method.FullName).FirstOrDefault(); if (existing != null) return existing; XNodeOut node = new XNodeOut(this, method.Name, XObjType.Method); node.MethodRef = method; Nodes.Add(node); return node; }
public XNodeOut AddNode(string name, XObjType objType) { // used for namespaces XNodeOut existing = Nodes.Where(n => n.Name == name && n.ObjType == objType).FirstOrDefault() as XNodeOut; if (existing != null) { return(existing); } XNodeOut node = new XNodeOut(this, name, objType); Nodes.Add(node); return(node); }
public XNodeOut(XNodeOut parent, string name, XObjType objType) { Parent = parent; Name = name; ObjType = objType; if (objType == XObjType.External) External = true; else if (parent != null) // else important cause external objs parent is not tagged as external External = parent.External; ID = NextID++; //Debug.WriteLine(string.Format("Added {0}: {1}", objType, FullName())); }
public XNodeOut AddMethod(MethodReference method) { // used for namespaces XNodeOut existing = Nodes.Cast <XNodeOut>().Where(n => n.MethodRef != null && n.MethodRef.FullName == method.FullName).FirstOrDefault(); if (existing != null) { return(existing); } XNodeOut node = new XNodeOut(this, method.Name, XObjType.Method); node.MethodRef = method; Nodes.Add(node); return(node); }
private void InjectMethodExit(XNodeOut node) { Debug.Assert(!node.Exclude); Debug.Assert(Build.TrackFlow); if (AddsDone >= AllowedAdds) { return; } XIL.AppendLine(); AddLine("ldc.i4 " + node.ID.ToString() + " // XRay - Method Exit"); AddLine("call void [XLibrary]XLibrary.XRay::MethodExit(int32)"); XIL.AppendLine(); AddsDone++; LinesAdded += 2; }
internal double ComputeFieldValues(XNodeOut node) { // give fields a value that makes them take up a total of %15 of the value of the class double total = node.Lines; double fieldCount = 0; // compute sum of all dependents foreach (XNodeOut subnode in node.Nodes) { if (subnode.ObjType == XObjType.Field) { fieldCount++; } else { total += ComputeFieldValues(subnode); } } if (fieldCount > 0) { // inflate total 15% and fit fields in there double subTotal = total; total = total * 100.0 / 85.0; double fieldTotal = total - subTotal; int fieldValue = (int)(fieldTotal / fieldCount); if (fieldValue < 1) { fieldValue = 1; } foreach (XNodeOut field in node.Nodes.Where(n => n.ObjType == XObjType.Field)) { field.Lines = fieldValue; } } return(total); }
public XNodeOut(XNodeOut parent, string name, XObjType objType) { Parent = parent; Name = name; ObjType = objType; if (objType == XObjType.External) { External = true; } else if (parent != null) // else important cause external objs parent is not tagged as external { External = parent.External; } ID = NextID++; //Debug.WriteLine(string.Format("Added {0}: {1}", objType, FullName())); }
XNodeOut SetClassDependency(XNodeOut dependentClass, TypeReference declaringType) { if (declaringType == null) { return(null); } var target = GetClassRef(declaringType); target = target.GetParentClass(true) as XNodeOut; dependentClass = dependentClass.GetParentClass(true) as XNodeOut; if (dependentClass.ClassDependencies == null) { dependentClass.ClassDependencies = new HashSet <int>(); } dependentClass.ClassDependencies.Add(target.ID); return(target); }
private void AddInstanceTracking(TypeDefinition classDef, XNodeOut classNode) { bool hasCtor = false; // add tracking to constructor foreach (var ctorMethod in classDef.Methods.Where(m => (m.Name == ".ctor" || m.Name == ".cctor") && m.Body != null)) { ctorMethod.Body.SimplifyMacros(); var processor = ctorMethod.Body.GetILProcessor(); // to prevent warnings in verify, our code should be put after the base constructor call AddInstruction(ctorMethod, 0, processor.Create(OpCodes.Ldc_I4, classNode.ID)); if (ctorMethod.Name == ".ctor") { hasCtor = true; AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(ctorMethod, 2, processor.Create(OpCodes.Call, ClassConstructedRef)); } // else static constructor else { // ldtoken XTestLib.SmallStatic // ldtoken XTestLib.StaticTemplateClass`1<!T> (for generic static classes) // call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) if (classDef.HasGenericParameters) { // for some reason the GenericInstanceType does not carry over the parameters var genericDef = new GenericInstanceType(classDef); foreach (var p in classDef.GenericParameters) genericDef.GenericArguments.Add(p); AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldtoken, genericDef)); } else AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldtoken, classDef)); AddInstruction(ctorMethod, 2, processor.Create(OpCodes.Call, GetTypeFromHandleRef)); AddInstruction(ctorMethod, 3, processor.Create(OpCodes.Call, ClassConstructedRef)); } ctorMethod.Body.OptimizeMacros(); } // add tracking to desconstructor (only add if ctor tracking added) if (hasCtor) { var finMethod = classDef.Methods.FirstOrDefault(m => m.Name == "Finalize"); bool callObjectFinalize = false; if (finMethod == null) { finMethod = new MethodDefinition("Finalize", Mono.Cecil.MethodAttributes.Family | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Virtual, VoidRef); callObjectFinalize = true; classDef.Methods.Add(finMethod); } finMethod.Body.SimplifyMacros(); var processor = finMethod.Body.GetILProcessor(); AddInstruction(finMethod, 0, processor.Create(OpCodes.Ldc_I4, classNode.ID)); AddInstruction(finMethod, 1, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(finMethod, 2, processor.Create(OpCodes.Call, ClassDeconstructedRef)); if (callObjectFinalize) { AddInstruction(finMethod, 3, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(finMethod, 4, processor.Create(OpCodes.Call, ObjectFinalizeRef)); AddInstruction(finMethod, 5, processor.Create(OpCodes.Ret)); } finMethod.Body.OptimizeMacros(); } }
private void AddStaticCall(XNodeOut source, XNodeOut dest) { if (!Build.StaticAnalysis) return; int hash = XRay.PairHash(source.ID, dest.ID); CallMap[hash] = new FunctionCall() { ID = hash, Source = source.ID, Destination = dest.ID }; }
private void RecompileMethod(XNodeOut classNode, TypeDefinition classDef, MethodDefinition method) { XNodeOut methodNode = classNode.AddMethod(method); // return if (method.ReturnType != null) { var returnNode = SetClassDependency(classNode, method.ReturnType); if (returnNode != null) methodNode.ReturnID = returnNode.ID; } // params for (int i = 0; i < method.Parameters.Count; i++) { var p = method.Parameters[i]; if (methodNode.ParamIDs == null) { methodNode.ParamIDs = new int[method.Parameters.Count]; methodNode.ParamNames = new string[method.Parameters.Count]; } var paramNode = SetClassDependency(classNode, p.ParameterType); methodNode.ParamIDs[i] = paramNode.ID; methodNode.ParamNames[i] = p.Name; } if (method.Body == null) return; // local vars foreach (var local in method.Body.Variables) SetClassDependency(classNode, local.VariableType); // expands branches/jumps to support adddresses > 255 // possibly needed if injecting code into large functions // OptimizeMacros at end of the function re-optimizes method.Body.SimplifyMacros(); methodNode.Lines = method.Body.Instructions.Count; var processor = method.Body.GetILProcessor(); for (int i = 0; i < method.Body.Instructions.Count; i++) { var instruction = method.Body.Instructions[i]; // record method exited if (Build.TrackFlow && instruction.OpCode == OpCodes.Ret) { instruction.OpCode = OpCodes.Nop; // any 'goto return' will go to here so method exit gets logged first i = TrackMethodExit(method, method, methodNode, processor, i); AddInstruction(method, ++i, processor.Create(OpCodes.Ret)); } // if we're tracking calls to non-xrayed assemblies else if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Calli || instruction.OpCode == OpCodes.Callvirt) { var call = instruction.Operand as MethodReference; if (call == null) { Debug.WriteLine("Unable to track not xrayed: " + instruction.Operand.ToString()); continue; } SetClassDependency(classNode, call.ReturnType); SetClassDependency(classNode, call.DeclaringType); foreach (var p in call.Parameters) SetClassDependency(classNode, p.ParameterType); var calledRef = GetClassRef(call.DeclaringType); var calledNode = calledRef.AddMethod(call); /*if( TrackExternal && !(method.Name == "Finalize" && method.DeclaringType.Namespace == "System") && (instruction.Operand as MethodReference).DeclaringType.Namespace != EnterMethodRef.DeclaringType.Namespace )*/ if (Build.TrackExternal && calledNode.External && calledRef.Name != "XRay") // !(method.Name == "Finalize" && method.DeclaringType.Namespace == "System")) { if (method.Name == ".cctor" && call.Name == "GetTypeFromHandle") continue; // call added to cctor by xray calledNode.Lines = 1; bool isConstrained = false; // in function is prefixed by .constrained, wrap enter/exit around those 2 lines int offset = 0; if (i > 0 && method.Body.Instructions[i - 1].OpCode == OpCodes.Constrained) { i -= 1; offset = 1; isConstrained = true; } // put stuff below in here so instructions dont get messed up var oldPos = method.Body.Instructions[i]; int pos = i; // if this is an external call and we want to track the parameters, then we have to wrap the function if (Build.TrackParameters && call.HasParameters) { // wrap call in a new function with the same parameters and return type as the original call, we do this // because it's maybe impossible to build the object[] of parameters from the current stack because it's unknown // in a wrapped function we can access the arguments easily to build the object[] and pass to method enter var wrappedName = string.Format("{0}_{1}_XRay{2}", call.DeclaringType.Name, call.Name, UniqueEnterSig++); var resolvedCall = ResolveGenericMethod(call); var wrapFunc = new MethodDefinition(wrappedName, new Mono.Cecil.MethodAttributes(), resolvedCall.ReturnType); // the wrapper can be static because we pass the called function's declaring type into the wrapper wrapFunc.IsPrivate = true; wrapFunc.IsStatic = true; wrapFunc.HasThis = false; wrapFunc.IsHideBySig = true; if (call.HasThis) { // calling functions against a value type is done by calling against its address eg DateTime.AddMinutes() if (call.DeclaringType.IsValueType) wrapFunc.Parameters.Add(new ParameterDefinition(new ByReferenceType(call.DeclaringType))); else wrapFunc.Parameters.Add(new ParameterDefinition(call.DeclaringType)); } foreach (var p in resolvedCall.Parameters) wrapFunc.Parameters.Add(p); // write body of method var wrapProcessor = wrapFunc.Body.GetILProcessor(); TrackMethodEnterParams(wrapFunc, calledNode.ID, wrapProcessor, call.HasThis); // load 'this' and arguemnts for(int x = 0; x < wrapFunc.Parameters.Count; x++) wrapFunc.Body.Instructions.Add(wrapProcessor.Create(OpCodes.Ldarg, x)); if (isConstrained) { // test with oldfashionedfun with 'no parameter' filter above turned off var inst = method.Body.Instructions[pos]; wrapFunc.Body.Instructions.Add(wrapProcessor.Create(inst.OpCode, inst.Operand as TypeReference)); } // call original function wrapFunc.Body.Instructions.Add(wrapProcessor.Create(instruction.OpCode, call)); // return wrapFunc.Body.Instructions.Add(wrapProcessor.Create(OpCodes.Ret)); classDef.Methods.Add(wrapFunc); // replace current call instruction with call to copy method var wrapRef = new MethodReference(wrapFunc.Name, wrapFunc.ReturnType); foreach (var parameter in wrapFunc.Parameters) wrapRef.Parameters.Add(parameter); if (classDef.HasGenericParameters) { // have to add arguments to declaring type manually for some reason var genericClassDef = new GenericInstanceType(classDef); foreach (var parameter in classDef.GenericParameters) genericClassDef.GenericArguments.Add(parameter); wrapRef.DeclaringType = genericClassDef; } else wrapRef.DeclaringType = classDef; method.Body.Instructions[pos++].OpCode = OpCodes.Nop; if(isConstrained) method.Body.Instructions[pos++].OpCode = OpCodes.Nop; // sets the actual call to nop AddInstruction(method, pos, processor.Create(OpCodes.Call, wrapRef)); // not incrementing pos because enter function takes un-inc'd pos as a param // really need to go back through and standardize pos setting } else { // wrap the call with enter and exit, because enter to an external method may cause // an xrayed method to be called, we want to track the flow of that process AddInstruction(method, pos++, processor.Create(OpCodes.Ldc_I4, calledNode.ID)); AddInstruction(method, pos++, processor.Create(OpCodes.Call, MethodEnterRef)); // method pos += offset; } pos = TrackMethodExit(method, call, calledNode, processor, pos); var newPos = method.Body.Instructions[i]; // get new instruction at original position, inserting stuff changed it UpdateExceptionHandlerPositions(method, oldPos, newPos); i = pos; // loop end will add 1 putting us right after last added function } } else if (Build.TrackFields && (instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld || instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldflda || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldsflda)) { var fieldDef = instruction.Operand as FieldReference; var classRef = GetClassRef(fieldDef.DeclaringType); var fieldRef = classRef.AddField(fieldDef); // some times volitile prefixes set/get field int offset = 0; if (i > 0 && method.Body.Instructions[i - 1].OpCode == OpCodes.Volatile) { i--; offset = 1; } AddInstruction(method, i, processor.Create(OpCodes.Ldc_I4, fieldRef.ID)); if (instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) AddInstruction(method, i + 1, processor.Create(OpCodes.Call, SetFieldRef)); else AddInstruction(method, i + 1, processor.Create(OpCodes.Call, LoadFieldRef)); i = i + 2 + offset; // skip instuction added, and field itself, end of loop will iterate this again // Debug.WriteLine("{0} in Module: {1}, Namespace: {2}, Class: {3}, Name: {4}, Type: {5}", instruction.OpCode, fieldDef.DeclaringType.Scope.Name, namespaces, className, fieldName, fieldType); } else if (instruction.OpCode == OpCodes.Newobj) { var newObj = instruction.Operand as MethodReference; SetClassDependency(classNode, newObj.DeclaringType); } /* Still not really working - goal - to get side by side wpf apps to work * else if (instruction.OpCode == OpCodes.Ldstr && !Build.ReplaceOriginal) { // rename Pack URIs in WPF so resources can be found with an XRay.. namespace // ex: "/MyApp;component/views/aboutview.xaml" -> "/XRay.MyApp;component/views/aboutview.xaml" var packUri = instruction.Operand as String; foreach (var file in XRayedFiles) packUri = packUri.Replace("/" + file.AssemblyName + ";", "/XRay." + file.AssemblyName + ";"); //packUri = packUri.Replace(file.AssemblyName, "XRay." + file.AssemblyName); instruction.Operand = packUri; }*/ } // end iterating through instructions // record function was entered if (Build.TrackFunctions) { if (Build.TrackParameters && method.HasParameters) TrackMethodEnterParams(method, methodNode.ID, processor, false); else { AddInstruction(method, 0, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, 1, processor.Create(OpCodes.Call, MethodEnterRef)); } } // record catches if (Build.TrackFlow) foreach (var handler in method.Body.ExceptionHandlers) { if (handler.HandlerType != ExceptionHandlerType.Catch) continue; int i = method.Body.Instructions.IndexOf(handler.HandlerStart); AddInstruction(method, i, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, i + 1, processor.Create(OpCodes.Call, MethodCatchRef)); var oldPos = handler.HandlerStart; var newPos = method.Body.Instructions[i]; UpdateExceptionHandlerPositions(method, oldPos, newPos); } method.Body.OptimizeMacros(); }
private void AddInstanceTracking(TypeDefinition classDef, XNodeOut classNode) { bool hasCtor = false; // add tracking to constructor foreach (var ctorMethod in classDef.Methods.Where(m => (m.Name == ".ctor" || m.Name == ".cctor") && m.Body != null)) { ctorMethod.Body.SimplifyMacros(); var processor = ctorMethod.Body.GetILProcessor(); // to prevent warnings in verify, our code should be put after the base constructor call AddInstruction(ctorMethod, 0, processor.Create(OpCodes.Ldc_I4, classNode.ID)); if (ctorMethod.Name == ".ctor") { hasCtor = true; AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(ctorMethod, 2, processor.Create(OpCodes.Call, ClassConstructedRef)); } // else static constructor else { // ldtoken XTestLib.SmallStatic // ldtoken XTestLib.StaticTemplateClass`1<!T> (for generic static classes) // call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) if (classDef.HasGenericParameters) { // for some reason the GenericInstanceType does not carry over the parameters var genericDef = new GenericInstanceType(classDef); foreach (var p in classDef.GenericParameters) { genericDef.GenericArguments.Add(p); } AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldtoken, genericDef)); } else { AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldtoken, classDef)); } AddInstruction(ctorMethod, 2, processor.Create(OpCodes.Call, GetTypeFromHandleRef)); AddInstruction(ctorMethod, 3, processor.Create(OpCodes.Call, ClassConstructedRef)); } ctorMethod.Body.OptimizeMacros(); } // add tracking to desconstructor (only add if ctor tracking added) if (hasCtor) { var finMethod = classDef.Methods.FirstOrDefault(m => m.Name == "Finalize"); bool callObjectFinalize = false; if (finMethod == null) { finMethod = new MethodDefinition("Finalize", Mono.Cecil.MethodAttributes.Family | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Virtual, VoidRef); callObjectFinalize = true; classDef.Methods.Add(finMethod); } finMethod.Body.SimplifyMacros(); var processor = finMethod.Body.GetILProcessor(); AddInstruction(finMethod, 0, processor.Create(OpCodes.Ldc_I4, classNode.ID)); AddInstruction(finMethod, 1, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(finMethod, 2, processor.Create(OpCodes.Call, ClassDeconstructedRef)); if (callObjectFinalize) { AddInstruction(finMethod, 3, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(finMethod, 4, processor.Create(OpCodes.Call, ObjectFinalizeRef)); AddInstruction(finMethod, 5, processor.Create(OpCodes.Ret)); } finMethod.Body.OptimizeMacros(); } }
private void RecompileMethod(XNodeOut classNode, MethodDefinition method) { XNodeOut methodNode = classNode.AddMethod(method); // return if (method.ReturnType != null) { var returnNode = SetClassDependency(classNode, method.ReturnType); if (returnNode != null) methodNode.ReturnID = returnNode.ID; } // params for (int i = 0; i < method.Parameters.Count; i++) { var p = method.Parameters[i]; if (methodNode.ParamIDs == null) { methodNode.ParamIDs = new int[method.Parameters.Count]; methodNode.ParamNames = new string[method.Parameters.Count]; } var paramNode = SetClassDependency(classNode, p.ParameterType); methodNode.ParamIDs[i] = paramNode.ID; methodNode.ParamNames[i] = p.Name; } if (method.Body == null) return; // local vars foreach (var local in method.Body.Variables) SetClassDependency(classNode, local.VariableType); // expands branches/jumps to support adddresses > 255 // possibly needed if injecting code into large functions // OptimizeMacros at end of the function re-optimizes method.Body.SimplifyMacros(); methodNode.Lines = method.Body.Instructions.Count; var processor = method.Body.GetILProcessor(); for (int i = 0; i < method.Body.Instructions.Count; i++) { var instruction = method.Body.Instructions[i]; // record method exited if (Build.TrackFlow && instruction.OpCode == OpCodes.Ret) { instruction.OpCode = OpCodes.Nop; // any 'goto return' will go to here so method exit gets logged first AddInstruction(method, i + 1, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, i + 2, processor.Create(OpCodes.Call, ExitMethodRef)); AddInstruction(method, i + 3, processor.Create(OpCodes.Ret)); i += 3; } // if we're tracking calls to non-xrayed assemblies else if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Calli || instruction.OpCode == OpCodes.Callvirt) { var call = instruction.Operand as MethodReference; if (call == null) { Debug.WriteLine("Unable to track not xrayed: " + instruction.Operand.ToString()); continue; } SetClassDependency(classNode, call.ReturnType); SetClassDependency(classNode, call.DeclaringType); foreach (var p in call.Parameters) SetClassDependency(classNode, p.ParameterType); var calledRef = GetClassRef(call.DeclaringType); var calledNode = calledRef.AddMethod(call); /*if( TrackExternal && !(method.Name == "Finalize" && method.DeclaringType.Namespace == "System") && (instruction.Operand as MethodReference).DeclaringType.Namespace != EnterMethodRef.DeclaringType.Namespace )*/ if (Build.TrackExternal && calledNode.External && calledRef.Name != "XRay") // !(method.Name == "Finalize" && method.DeclaringType.Namespace == "System")) { if (method.Name == ".cctor" && call.Name == "GetTypeFromHandle") continue; // call added to cctor by xray calledNode.Lines = 1; // in function is prefixed by .constrained, wrap enter/exit around those 2 lines int offset = 0; if (i > 0 && method.Body.Instructions[i - 1].OpCode == OpCodes.Constrained) { i -= 1; offset = 1; } var oldPos = method.Body.Instructions[i]; // wrap the call with enter and exit, because enter to an external method may cause // an xrayed method to be called, we want to track the flow of that process int pos = i; AddInstruction(method, pos++, processor.Create(OpCodes.Ldc_I4, calledNode.ID)); AddInstruction(method, pos++, processor.Create(OpCodes.Call, EnterMethodRef)); // method pos += 1 + offset; AddInstruction(method, pos++, processor.Create(OpCodes.Ldc_I4, calledNode.ID)); AddInstruction(method, pos, processor.Create(OpCodes.Call, ExitMethodRef)); var newPos = method.Body.Instructions[i]; UpdateExceptionHandlerPositions(method, oldPos, newPos); i = pos; // loop end will add 1 putting us right after last added function } } else if (Build.TrackFields && (instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld || instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldflda || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldsflda)) { var fieldDef = instruction.Operand as FieldReference; var classRef = GetClassRef(fieldDef.DeclaringType); var fieldRef = classRef.AddField(fieldDef); // some times volitile prefixes set/get field int offset = 0; if (i > 0 && method.Body.Instructions[i - 1].OpCode == OpCodes.Volatile) { i--; offset = 1; } AddInstruction(method, i, processor.Create(OpCodes.Ldc_I4, fieldRef.ID)); if (instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) AddInstruction(method, i + 1, processor.Create(OpCodes.Call, SetFieldRef)); else AddInstruction(method, i + 1, processor.Create(OpCodes.Call, LoadFieldRef)); i = i + 2 + offset; // skip instuction added, and field itself, end of loop will iterate this again // Debug.WriteLine("{0} in Module: {1}, Namespace: {2}, Class: {3}, Name: {4}, Type: {5}", instruction.OpCode, fieldDef.DeclaringType.Scope.Name, namespaces, className, fieldName, fieldType); } else if (instruction.OpCode == OpCodes.Newobj) { var newObj = instruction.Operand as MethodReference; SetClassDependency(classNode, newObj.DeclaringType); } /* Still not really working - goal - to get side by side wpf apps to work * else if (instruction.OpCode == OpCodes.Ldstr && !Build.ReplaceOriginal) { // rename Pack URIs in WPF so resources can be found with an XRay.. namespace // ex: "/MyApp;component/views/aboutview.xaml" -> "/XRay.MyApp;component/views/aboutview.xaml" var packUri = instruction.Operand as String; foreach (var file in XRayedFiles) packUri = packUri.Replace("/" + file.AssemblyName + ";", "/XRay." + file.AssemblyName + ";"); //packUri = packUri.Replace(file.AssemblyName, "XRay." + file.AssemblyName); instruction.Operand = packUri; }*/ } // end iterating through instructions // record function was entered if (Build.TrackFunctions) { AddInstruction(method, 0, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, 1, processor.Create(OpCodes.Call, EnterMethodRef)); } // record catches if (Build.TrackFlow) foreach (var handler in method.Body.ExceptionHandlers) { if (handler.HandlerType != ExceptionHandlerType.Catch) continue; int i = method.Body.Instructions.IndexOf(handler.HandlerStart); AddInstruction(method, i, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, i + 1, processor.Create(OpCodes.Call, CatchMethodRef)); var oldPos = handler.HandlerStart; var newPos = method.Body.Instructions[i]; UpdateExceptionHandlerPositions(method, oldPos, newPos); } method.Body.OptimizeMacros(); }
public void Recompile(bool test) { if (Files.Count == 0) return; BuildStatus = ""; BuildError = ""; BuildSuccess = false; BuildThread = new Thread(() => { string stepname = ""; long linesAdded = 0; long trackedObjects = 0; try { BuildStatus = "Checking"; if (XDecompile.CheckIfAlreadyXRayed(Files) && !TryRestoreBackups()) { return; } BuildStatus = "Preparing"; // copy XLibrary to final destination CopyLocalToOutputDir("XLibrary.dll", OutputDir); if (EnableLocalViewer) { CopyLocalToOutputDir("OpenTK.dll", OutputDir); CopyLocalToOutputDir("OpenTK.GLControl.dll", OutputDir); CopyLocalToOutputDir("QuickFont.dll", OutputDir); } string errorLog = ""; XNodeOut.NextID = 0; XNodeOut root = new XNodeOut(null, "root", XObjType.Root); XNodeOut extRoot = root.AddNode("Not XRayed", XObjType.External); XNodeOut intRoot = root.AddNode("XRayed", XObjType.Internal); // init root file nodes so links can be made for processed fields before they are directly xrayed foreach (var item in Files) item.FileNode = intRoot.AddNode(Path.GetFileName(item.FilePath), XObjType.File); var callMap = new Dictionary<int, FunctionCall>(); var initMap = new Dictionary<int, FunctionCall>(); foreach (var item in Files) { var decompile = new XDecompile(this, intRoot, extRoot, item, callMap, initMap); try { if (CompileWithMS) { BuildStatus = "Decompiling " + item.FileName; decompile.MsDecompile(); // used for debugging tricky ilasm errors //for (int i = 0; i < int.MaxValue; i++) // { decompile.AllowedAdds = int.MaxValue; // i; decompile.AddsDone = 0; BuildStatus = "Scanning " + item.FileName; decompile.ScanLines(test); BuildStatus = "Recompiling " + item.FileName; item.RecompiledPath = decompile.Compile(); // } } else { BuildStatus = "Recompiling " + item.FileName; decompile.MonoRecompile(); } // remove signature // files that arent signed are coming up as signed meaning this would probably corrupt a file // also not sure if checked anymore - http://msdn.microsoft.com/en-us/library/cc713694.aspx //var meta = new MetaInfo(item.RecompiledPath); //if (meta.Load()) // meta.RemoveSignature(); // decompile to check if (DecompileAgain) { string filename = Path.GetFileName(item.FilePath); string compiler = CompileWithMS ? "MS" : "Mono"; // create directories var dir = Path.Combine(Application.StartupPath, "recompile", filename, compiler + "_original"); var originalPath = decompile.BackupPath != null ? decompile.BackupPath : item.FilePath; decompile.Decompile(originalPath, dir); dir = Path.Combine(Application.StartupPath, "recompile", filename, compiler + "_new"); decompile.Decompile(item.RecompiledPath, dir); } } catch (CompileError ex) { errorLog += item.FileName + "\r\n" + ex.Summary + "\r\n--------------------------------------------------------\r\n"; } catch (Exception ex) { BuildError = "Error recompiling: " + ex.Message + "\r\n" + ex.StackTrace; BuildStatus = "Error on " + item.FileName; return; } linesAdded += decompile.LinesAdded; } // save node map before verifying because we dont want bogus verify // errors from preventing the dat file form being made BuildStatus = "Saving Map"; var settings = new Dictionary<string, string>(); settings["Version"] = XRay.BuilderVersion; if (Pro.Verified) settings["Pro"] = Pro.SignedFile; if (EnableLocalViewer) { settings["EnableLocalViewer"] = EnableLocalViewer.ToString(); settings["EnableIpcServer"] = EnableIpcServer.ToString(); settings["ShowViewerOnStart"] = ShowViewerOnStart.ToString(); } if (EnableTcpServer) { settings["EnableTcpServer"] = EnableTcpServer.ToString(); settings["TcpListenPort"] = TcpListenPort.ToString(); settings["EncryptionKey"] = EncryptionKey; } settings["FunctionCount"] = XNodeOut.NextID.ToString(); var writePath = Path.Combine(OutputDir, "XRay.dat"); trackedObjects = SaveDat(writePath, settings, root, callMap, initMap); if (errorLog.Length > 0) { string logPath = Path.Combine(Application.StartupPath, "errorlog.txt"); File.WriteAllText(logPath, errorLog); Process.Start(logPath); } } catch (Exception ex) { BuildError = "Error during " + stepname + ": " + ex.Message; } BuildStatus = String.Format("Success! {0:#,#} instructions added, {1:#,#} objects tracked", linesAdded, trackedObjects); BuildSuccess = true; }); BuildThread.Start(); }
private void InjectMethodCatch(XNodeOut node) { Debug.Assert(!node.Exclude); Debug.Assert(TrackFlow); XIL.AppendLine(); AddLine("ldc.i4 " + node.ID.ToString() + " // XRay - Method Catch"); AddLine("call void [XLibrary]XLibrary.XRay::MethodCatch(int32)"); XIL.AppendLine(); LinesAdded += 2; }
XNodeOut SetClassDependency(XNodeOut dependentClass, TypeReference declaringType) { if (declaringType == null) return null; var target = GetClassRef(declaringType); target = target.GetParentClass(true) as XNodeOut; dependentClass = dependentClass.GetParentClass(true) as XNodeOut; if (dependentClass.ClassDependencies == null) dependentClass.ClassDependencies = new HashSet<int>(); dependentClass.ClassDependencies.Add(target.ID); return target; }
internal void ProcessClassNames(XNodeOut node) { // if class move anonymous objects into their non-anon counterparts if (node.ObjType == XObjType.Class) { // class under a namespace could be anon if (node.Name.StartsWith("<>")) { MarkNodeAsAnon(node); } // put anonymous classes in the functions that they were generated from // ex -. Nested Class: <>c__DisplayClass15, has method: <FilterTextBox_TextChanged>b__11 var anonClasses = node.Nodes.Where(n => n.ObjType == XObjType.Class && n.Name.StartsWith("<>")).ToArray(); foreach (XNodeOut anonClass in anonClasses) { // mark as anon MarkNodeAsAnon(anonClass); // if parent is not anon if (!anonClass.Parent.IsAnon) { // iterate anon methods to find association var anonMethod = anonClass.Nodes.FirstOrDefault(n => !n.Name.StartsWith("<>") && n.Name.StartsWith("<") && n.Name.Contains(">")); if (anonMethod == null) { continue; } string anonMethodName = anonMethod.Name.Substring(1, anonMethod.Name.IndexOf(">") - 1); // iterate parent class methods to find match var parentMethod = node.Nodes.FirstOrDefault(n => n.Name == anonMethodName) as XNodeOut; if (parentMethod == null) { continue; } // move node under that method node.Nodes.Remove(anonClass); parentMethod.Nodes.Add(anonClass); anonClass.Parent = parentMethod; // will be renamed when class is iterated below } } // iterate anon methods, move into parent methods var anonMethods = node.Nodes.Where(n => n.ObjType == XObjType.Method && n.Name.StartsWith("<")).ToArray(); foreach (XNodeOut anonMethod in anonMethods) { MarkNodeAsAnon(anonMethod); string parentName = anonMethod.Name.Substring(1, anonMethod.Name.IndexOf(">") - 1); var parentMethod = node.Nodes.FirstOrDefault(n => n.Name == parentName) as XNodeOut; if (parentMethod == null) { continue; } // move node under that method node.Nodes.Remove(anonMethod); parentMethod.Nodes.Add(anonMethod); anonMethod.Parent = parentMethod; } // if current class is anon, rename class parentNodeName.classX if (node.IsAnon) { XNodeOut parent = node.GetNotAnonParent(); node.Name = parent.Name + ".class" + parent.AnonClasses.ToString(); parent.AnonClasses++; } } // if method if (node.ObjType == XObjType.Method) { XNodeOut parent = node.GetNotAnonParent(); // if method is ctor/cctor rename if (node.Name == ".ctor" || node.Name == ".cctor") { node.Name = parent.Name + ((node.Name == ".ctor") ? ".ctor" : ".static_ctor"); if (parent.InitCount > 1) { node.Name += parent.InitCount.ToString(); } parent.InitCount++; } else if (node.IsAnon || // simple anon method node.Parent.IsAnon) // method of an anon class { if (node.Name.StartsWith("<")) { node.Name = parent.Name + ".func" + parent.AnonFuncs.ToString(); parent.AnonFuncs++; } } } // iterate sub elements foreach (XNodeOut subnode in node.Nodes) { ProcessClassNames(subnode); } }
public void Recompile(bool test) { if (Files.Count == 0) { return; } BuildStatus = ""; BuildError = ""; BuildSuccess = false; BuildThread = new Thread(() => { string stepname = ""; long linesAdded = 0; long trackedObjects = 0; try { BuildStatus = "Checking"; if (XDecompile.CheckIfAlreadyXRayed(Files) && !TryRestoreBackups()) { return; } BuildStatus = "Preparing"; // copy XLibrary to final destination CopyLocalToOutputDir("XLibrary.dll", OutputDir); if (EnableLocalViewer) { CopyLocalToOutputDir("OpenTK.dll", OutputDir); CopyLocalToOutputDir("OpenTK.GLControl.dll", OutputDir); CopyLocalToOutputDir("QuickFont.dll", OutputDir); } string errorLog = ""; XNodeOut.NextID = 0; XNodeOut root = new XNodeOut(null, "root", XObjType.Root); XNodeOut extRoot = root.AddNode("Not XRayed", XObjType.External); XNodeOut intRoot = root.AddNode("XRayed", XObjType.Internal); // init root file nodes so links can be made for processed fields before they are directly xrayed foreach (var item in Files) { item.FileNode = intRoot.AddNode(Path.GetFileName(item.FilePath), XObjType.File); } foreach (var item in Files) { var decompile = new XDecompile(intRoot, extRoot, item, this); try { if (CompileWithMS) { BuildStatus = "Decompiling " + item.FileName; decompile.MsDecompile(); // used for debugging tricky ilasm errors //for (int i = 0; i < int.MaxValue; i++) // { decompile.AllowedAdds = int.MaxValue; // i; decompile.AddsDone = 0; BuildStatus = "Scanning " + item.FileName; decompile.ScanLines(test); BuildStatus = "Recompiling " + item.FileName; item.RecompiledPath = decompile.Compile(); // } } else { BuildStatus = "Recompiling " + item.FileName; decompile.MonoRecompile(); } // remove signature // files that arent signed are coming up as signed meaning this would probably corrupt a file // also not sure if checked anymore - http://msdn.microsoft.com/en-us/library/cc713694.aspx //var meta = new MetaInfo(item.RecompiledPath); //if (meta.Load()) // meta.RemoveSignature(); // decompile to check if (DecompileAgain) { string filename = Path.GetFileName(item.FilePath); string compiler = CompileWithMS ? "MS" : "Mono"; // create directories var dir = Path.Combine(Application.StartupPath, "recompile", filename, compiler + "_original"); var originalPath = decompile.BackupPath != null ? decompile.BackupPath : item.FilePath; decompile.Decompile(originalPath, dir); dir = Path.Combine(Application.StartupPath, "recompile", filename, compiler + "_new"); decompile.Decompile(item.RecompiledPath, dir); } } catch (CompileError ex) { errorLog += item.FileName + "\r\n" + ex.Summary + "\r\n--------------------------------------------------------\r\n"; } catch (Exception ex) { BuildError = "Error recompiling: " + ex.Message + "\r\n" + ex.StackTrace; BuildStatus = "Error on " + item.FileName; return; } linesAdded += decompile.LinesAdded; } // save node map before verifying because we dont want bogus verify // errors from preventing the dat file form being made BuildStatus = "Saving Map"; var settings = new Dictionary <string, string>(); settings["Version"] = XRay.BuilderVersion; if (Pro.Verified) { settings["Pro"] = Pro.SignedFile; } if (EnableLocalViewer) { settings["EnableLocalViewer"] = EnableLocalViewer.ToString(); settings["EnableIpcServer"] = EnableIpcServer.ToString(); settings["ShowViewerOnStart"] = ShowViewerOnStart.ToString(); } if (EnableTcpServer) { settings["EnableTcpServer"] = EnableTcpServer.ToString(); settings["TcpListenPort"] = TcpListenPort.ToString(); settings["EncryptionKey"] = EncryptionKey; } var writePath = Path.Combine(OutputDir, "XRay.dat"); trackedObjects = root.SaveTree(writePath, settings); if (errorLog.Length > 0) { string logPath = Path.Combine(Application.StartupPath, "errorlog.txt"); File.WriteAllText(logPath, errorLog); Process.Start(logPath); } } catch (Exception ex) { BuildError = "Error during " + stepname + ": " + ex.Message; } BuildStatus = String.Format("Success! {0:#,#} instructions added, {1:#,#} objects tracked", linesAdded, trackedObjects); BuildSuccess = true; }); BuildThread.Start(); }
private void RecompileMethods(TypeDefinition classDef, XNodeOut classNode) { ILProcessor processor = null; // add fields if (TrackFields && classDef.HasFields) foreach (var fieldDef in classDef.Fields) { var fieldNode = classNode.AddField(fieldDef); SetClassDependency(classNode, fieldDef.DeclaringType); if (fieldDef.FieldType.IsGenericParameter) Debug.WriteLine("Generic parameter ignored - " + fieldDef.FieldType.ToString()); else fieldNode.ReturnID = GetClassRef(fieldDef.FieldType).ID; } if(TrackCode) foreach (var method in classDef.Methods) { XNodeOut methodNode = classNode.AddMethod(method); if(DecompileCSharp) methodNode.CSharp = DecompileMethod(method); if (method.Body == null) continue; // record MSIL if (SaveMsil) { methodNode.Msil = new List<XInstruction>(); foreach (var inst in method.Body.Instructions) { var xInst = new XInstruction(); xInst.Offset = inst.Offset; xInst.OpCode = inst.OpCode.Name; xInst.Line = (inst.Operand != null) ? inst.Operand.ToString() : ""; if (inst.OpCode == OpCodes.Call || inst.OpCode == OpCodes.Calli || inst.OpCode == OpCodes.Callvirt || inst.OpCode == OpCodes.Newobj || inst.OpCode == OpCodes.Ldftn) // pushes a function pointer to the stack { var call = inst.Operand as MethodReference; if (call != null) { var classRef = GetClassRef(call.DeclaringType); var methodRef = classRef.AddMethod(call); xInst.RefId = methodRef.ID; } else Debug.WriteLine("Unable to track: " + inst.Operand.ToString()); } else if (inst.OpCode == OpCodes.Stfld || inst.OpCode == OpCodes.Stsfld || inst.OpCode == OpCodes.Ldfld || inst.OpCode == OpCodes.Ldflda || inst.OpCode == OpCodes.Ldsfld || inst.OpCode == OpCodes.Ldsflda) { var fieldDef = inst.Operand as FieldReference; var classRef = GetClassRef(fieldDef.DeclaringType); var fieldRef = classRef.AddField(fieldDef); xInst.RefId = fieldRef.ID; } else if (inst.OpCode.FlowControl == FlowControl.Branch || inst.OpCode.FlowControl == FlowControl.Cond_Branch) { var op = inst.Operand as Instruction; if (op != null) { int offset = op.Offset; xInst.Line = "goto " + offset.ToString("X"); xInst.RefId = offset; } } methodNode.Msil.Add(xInst); } } } if (TrackInstances && !classDef.IsValueType) { bool hasCtor = false; // add tracking to constructor foreach(var ctorMethod in classDef.Methods.Where(m => (m.Name == ".ctor" || m.Name == ".cctor") && m.Body != null)) { ctorMethod.Body.SimplifyMacros(); processor = ctorMethod.Body.GetILProcessor(); // to prevent warnings in verify, our code should be put after the base constructor call AddInstruction(ctorMethod, 0, processor.Create(OpCodes.Ldc_I4, classNode.ID)); if (ctorMethod.Name == ".ctor") { hasCtor = true; AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(ctorMethod, 2, processor.Create(OpCodes.Call, ClassConstructedRef)); } // else static constructor else { // ldtoken XTestLib.SmallStatic // ldtoken XTestLib.StaticTemplateClass`1<!T> (for generic static classes) // call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) if (classDef.HasGenericParameters) { // for some reason the GenericInstanceType does not carry over the parameters var genericDef = new GenericInstanceType(classDef); foreach (var p in classDef.GenericParameters) genericDef.GenericArguments.Add(p); AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldtoken, genericDef)); } else AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldtoken, classDef)); AddInstruction(ctorMethod, 2, processor.Create(OpCodes.Call, GetTypeFromHandleRef)); AddInstruction(ctorMethod, 3, processor.Create(OpCodes.Call, ClassConstructedRef)); } ctorMethod.Body.OptimizeMacros(); } // add tracking to desconstructor (only add if ctor tracking added) if (hasCtor) { var finMethod = classDef.Methods.FirstOrDefault(m => m.Name == "Finalize"); bool callObjectFinalize = false; if (finMethod == null) { finMethod = new MethodDefinition("Finalize", Mono.Cecil.MethodAttributes.Family | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Virtual, VoidRef); callObjectFinalize = true; classDef.Methods.Add(finMethod); } finMethod.Body.SimplifyMacros(); processor = finMethod.Body.GetILProcessor(); AddInstruction(finMethod, 0, processor.Create(OpCodes.Ldc_I4, classNode.ID)); AddInstruction(finMethod, 1, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(finMethod, 2, processor.Create(OpCodes.Call, ClassDeconstructedRef)); if (callObjectFinalize) { AddInstruction(finMethod, 3, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(finMethod, 4, processor.Create(OpCodes.Call, ObjectFinalizeRef)); AddInstruction(finMethod, 5, processor.Create(OpCodes.Ret)); } finMethod.Body.OptimizeMacros(); } } // iterate method nodes foreach (var method in classDef.Methods) { XNodeOut methodNode = classNode.AddMethod(method); // return if(method.ReturnType != null) { var returnNode = SetClassDependency(classNode, method.ReturnType); if(returnNode != null) methodNode.ReturnID = returnNode.ID; } // params for(int i = 0; i < method.Parameters.Count; i++) { var p = method.Parameters[i]; if (methodNode.ParamIDs == null) { methodNode.ParamIDs = new int[method.Parameters.Count]; methodNode.ParamNames = new string[method.Parameters.Count]; } var paramNode = SetClassDependency(classNode, p.ParameterType); methodNode.ParamIDs[i] = paramNode.ID; methodNode.ParamNames[i] = p.Name; } if (method.Body == null) continue; // local vars foreach (var local in method.Body.Variables) SetClassDependency(classNode, local.VariableType); // expands branches/jumps to support adddresses > 255 // possibly needed if injecting code into large functions // OptimizeMacros at end of the function re-optimizes method.Body.SimplifyMacros(); methodNode.Lines = method.Body.Instructions.Count; processor = method.Body.GetILProcessor(); for (int i = 0; i < method.Body.Instructions.Count; i++) { var instruction = method.Body.Instructions[i]; // record method exited if (TrackFlow && instruction.OpCode == OpCodes.Ret) { instruction.OpCode = OpCodes.Nop; // any 'goto return' will go to here so method exit gets logged first AddInstruction(method, i + 1, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, i + 2, processor.Create(OpCodes.Call, ExitMethodRef)); AddInstruction(method, i + 3, processor.Create(OpCodes.Ret)); i += 3; } // if we're tracking calls to non-xrayed assemblies else if ( instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Calli || instruction.OpCode == OpCodes.Callvirt) { var call = instruction.Operand as MethodReference; if (call == null) { Debug.WriteLine("Unable to track not xrayed: " + instruction.Operand.ToString()); continue; } SetClassDependency(classNode, call.ReturnType); SetClassDependency(classNode, call.DeclaringType); foreach(var p in call.Parameters) SetClassDependency(classNode, p.ParameterType); var calledRef = GetClassRef(call.DeclaringType); var calledNode = calledRef.AddMethod(call); /*if( TrackExternal && !(method.Name == "Finalize" && method.DeclaringType.Namespace == "System") && (instruction.Operand as MethodReference).DeclaringType.Namespace != EnterMethodRef.DeclaringType.Namespace )*/ if (TrackExternal && calledNode.External && calledRef.Name != "XRay") // !(method.Name == "Finalize" && method.DeclaringType.Namespace == "System")) { if (method.Name == ".cctor" && call.Name == "GetTypeFromHandle") continue; // call added to cctor by xray calledNode.Lines = 1; // in function is prefixed by .constrained, wrap enter/exit around those 2 lines int offset = 0; if (i > 0 && method.Body.Instructions[i - 1].OpCode == OpCodes.Constrained) { i -= 1; offset = 1; } var oldPos = method.Body.Instructions[i]; // wrap the call with enter and exit, because enter to an external method may cause // an xrayed method to be called, we want to track the flow of that process int pos = i; AddInstruction(method, pos++, processor.Create(OpCodes.Ldc_I4, calledNode.ID)); AddInstruction(method, pos++, processor.Create(OpCodes.Call, EnterMethodRef)); // method pos += 1 + offset; AddInstruction(method, pos++, processor.Create(OpCodes.Ldc_I4, calledNode.ID)); AddInstruction(method, pos, processor.Create(OpCodes.Call, ExitMethodRef)); var newPos = method.Body.Instructions[i]; UpdateExceptionHandlerPositions(method, oldPos, newPos); i = pos; // loop end will add 1 putting us right after last added function } } else if (TrackFields && (instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld || instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldflda || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldsflda)) { var fieldDef = instruction.Operand as FieldReference; var classRef = GetClassRef(fieldDef.DeclaringType); var fieldRef = classRef.AddField(fieldDef); // some times volitile prefixes set/get field int offset = 0; if (i > 0 && method.Body.Instructions[i - 1].OpCode == OpCodes.Volatile) { i--; offset = 1; } AddInstruction(method, i, processor.Create(OpCodes.Ldc_I4, fieldRef.ID)); if (instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) AddInstruction(method, i + 1, processor.Create(OpCodes.Call, SetFieldRef)); else AddInstruction(method, i + 1, processor.Create(OpCodes.Call, LoadFieldRef)); i = i + 2 + offset; // skip instuction added, and field itself, end of loop will iterate this again // Debug.WriteLine("{0} in Module: {1}, Namespace: {2}, Class: {3}, Name: {4}, Type: {5}", instruction.OpCode, fieldDef.DeclaringType.Scope.Name, namespaces, className, fieldName, fieldType); } else if (instruction.OpCode == OpCodes.Newobj) { var newObj = instruction.Operand as MethodReference; SetClassDependency(classNode, newObj.DeclaringType); } /* Still not really working - goal - to get side by side wpf apps to work * else if (instruction.OpCode == OpCodes.Ldstr && SideBySide) { // rename Pack URIs in WPF so resources can be found with an XRay.. namespace // ex: "/MyApp;component/views/aboutview.xaml" -> "/XRay.MyApp;component/views/aboutview.xaml" var packUri = instruction.Operand as String; foreach (var file in XRayedFiles) packUri = packUri.Replace("/" + file.AssemblyName + ";", "/XRay." + file.AssemblyName + ";"); //packUri = packUri.Replace(file.AssemblyName, "XRay." + file.AssemblyName); instruction.Operand = packUri; }*/ } // end iterating through instructions // record function was entered AddInstruction(method, 0, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, 1, processor.Create(OpCodes.Call, EnterMethodRef)); // record catches if (TrackFlow) foreach (var handler in method.Body.ExceptionHandlers) { if (handler.HandlerType != ExceptionHandlerType.Catch) continue; int i = method.Body.Instructions.IndexOf(handler.HandlerStart); AddInstruction(method, i, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, i + 1, processor.Create(OpCodes.Call, CatchMethodRef)); var oldPos = handler.HandlerStart; var newPos = method.Body.Instructions[i]; UpdateExceptionHandlerPositions(method, oldPos, newPos); } method.Body.OptimizeMacros(); } }
private void ReCompile(bool test) { XRayedFile[] files = FileList.Items.Cast<XRayedFile>().ToArray(); if (files.Length == 0) return; OptionsPanel.Enabled = false; // have to extract out because thread cant access gui elements bool trackFlow = TrackFlowCheckBox.Checked; bool trackExternal = TrackExternalCheckBox.Checked; bool trackAnon = TrackAnonCheckBox.Checked; bool trackFields = TrackFieldsCheckBox.Checked; bool trackInstances = TrackInstancesCheckBox.Checked; bool replaceOriginal = ReplaceOriginalCheckBox.Checked; bool doVerify = RunVerifyCheckbox.Checked; bool compileWithMS = MsToolsCheckbox.Checked; bool decompileAgain = DecompileAgainCheckbox.Checked; bool showUiOnStart = ShowOnStartCheckBox.Checked; bool saveMsil = SaveMsilCheckBox.Checked; bool decompileCSharp = DecompileCSharpCheckBox.Checked; new Thread(() => { string stepname = ""; var status = new Action<string, string>((step, name) => { stepname = step; RunInGui(() => StatusLabel.Text = step + " " + name); }); long linesAdded = 0; long trackedObjects = 0; try { status("checking", ""); if (XDecompile.CheckIfAlreadyXRayed(files) && !TryRestoreBackups(files)) { RunInGui(() => OptionsPanel.Enabled = true); return; } status("Preparing", ""); XDecompile.PrepareOutputDir(SourceDir, OutputDir); string errorLog = ""; XNodeOut.NextID = 0; XNodeOut root = new XNodeOut(null, "root", XObjType.Root); XNodeOut extRoot = root.AddNode("Not XRayed", XObjType.External); XNodeOut intRoot = root.AddNode("XRayed", XObjType.Internal); // init root file nodes so links can be made for processed fields before they are directly xrayed foreach (XRayedFile item in files) item.FileNode = intRoot.AddNode(Path.GetFileName(item.FilePath), XObjType.File); foreach (XRayedFile item in files) { XDecompile decompile = new XDecompile(intRoot, extRoot, item, OutputDir, DatPath, files, !replaceOriginal) { TrackFlow = trackFlow, TrackExternal = trackExternal, TrackAnon = trackAnon, TrackFields = trackFields, TrackInstances = trackInstances, ShowUIonStart = showUiOnStart, SaveMsil = saveMsil, DecompileCSharp = decompileCSharp }; try { if (compileWithMS) { status("Decompiling", item.FileName); decompile.MsDecompile(); // used for debugging tricky ilasm errors //for (int i = 0; i < int.MaxValue; i++) // { decompile.AllowedAdds = int.MaxValue; // i; decompile.AddsDone = 0; status("Scanning", item.FileName); decompile.ScanLines(test); status("Recompiling", item.FileName); item.RecompiledPath = decompile.Compile(); // } } else { status("Recompiling", item.FileName); decompile.MonoRecompile(); } // remove signature // files that arent signed are coming up as signed meaning this would probably corrupt a file // also not sure if checked anymore - http://msdn.microsoft.com/en-us/library/cc713694.aspx //var meta = new MetaInfo(item.RecompiledPath); //if (meta.Load()) // meta.RemoveSignature(); // decompile to check if (decompileAgain) { string filename = Path.GetFileName(item.FilePath); string compiler = compileWithMS ? "MS" : "Mono"; // create directories var dir = Path.Combine(Application.StartupPath, "recompile", filename, compiler + "_original"); var originalPath = decompile.BackupPath != null ? decompile.BackupPath : item.FilePath; decompile.Decompile(originalPath, dir); dir = Path.Combine(Application.StartupPath, "recompile", filename, compiler + "_new"); decompile.Decompile(item.RecompiledPath, dir); } } catch (CompileError ex) { errorLog += item.FileName + "\r\n" + ex.Summary + "\r\n--------------------------------------------------------\r\n"; } catch (Exception ex) { RunInGui(() => { MessageBox.Show("Error recompiling: " + ex.Message + "\r\n" + ex.StackTrace); StatusLabel.Text = "Error on " + item.FileName; OptionsPanel.Enabled = true; }); return; } linesAdded += decompile.LinesAdded; } // save node map before verifying because we dont want bogus verify // errors from preventing the dat file form being made status("Saving Map", ""); var settings = new Dictionary<string, string>(); settings["Version"] = XRay.BuilderVersion; if (Pro.Verified) settings["Pro"] = Pro.SignedFile; trackedObjects = root.SaveTree(DatPath, settings); // verify last and aggregate errors' if (doVerify) foreach (XRayedFile item in files) { try { status("Verifying", item.FileName); XDecompile.Verify(item.RecompiledPath); } catch (CompileError ex) { errorLog += item.FileName + "\r\n" + ex.Summary + "\r\n--------------------------------------------------------\r\n"; } } if (errorLog.Length > 0) throw new CompileError(errorLog); } catch (CompileError ex) { string summary = ex.Summary; summary = summary.Replace("Unexpected type on the stack.", "Unexpected type on the stack. (Ignore)"); summary = summary.Replace("Unmanaged pointers are not a verifiable type.", "Unmanaged pointers are not a verifiable type. (Ignore)"); if (!replaceOriginal) summary = summary.Replace("Unable to resolve token.", "Unable to resolve token. (Try turning off side by side)"); //todo token error - turn off side by side string logPath = Path.Combine(Application.StartupPath, "errorlog.txt"); File.WriteAllText(logPath, summary); Process.Start(logPath); } catch (Exception ex) { RunInGui(() => MessageBox.Show("Error during " + stepname + ": " + ex.Message)); } status(String.Format("Ready to Launch - {0:#,#} instructions added, {1:#,#} objects tracked", linesAdded, trackedObjects), ""); RunInGui(() => { OptionsPanel.Enabled = true; }); }).Start(); }
private void InjectMethodExit(XNodeOut node) { Debug.Assert(!node.Exclude); Debug.Assert(TrackFlow); if (AddsDone >= AllowedAdds) return; XIL.AppendLine(); AddLine("ldc.i4 " + node.ID.ToString() + " // XRay - Method Exit"); AddLine("call void [XLibrary]XLibrary.XRay::MethodExit(int32)"); XIL.AppendLine(); AddsDone++; LinesAdded += 2; }
private void InjectMethodEnter(XNodeOut node) { Debug.Assert(!node.Exclude); XIL.AppendLine(); AddLine("ldc.i4 " + node.ID.ToString() + " // XRay - Method enter"); AddLine("call void [XLibrary]XLibrary.XRay::MethodEnter(int32)"); LinesAdded += 2; if (TrackInstances) if (node.Name == ".ctor") { AddLine("ldc.i4 " + node.Parent.ID.ToString()); AddLine("call void [XLibrary]XLibrary.XRay::Constructed(int32)"); LinesAdded += 2; } else if (node.Name == "Finalize") { AddLine("ldc.i4 " + node.Parent.ID.ToString()); AddLine("call void [XLibrary]XLibrary.XRay::Deconstructed(int32)"); LinesAdded += 2; } XIL.AppendLine(); }
public static XNodeOut SignatureToClass(string signature, XNodeOut fileNode) { // create syntax tree for signature XDef def = XDef.ParseAndCheck(signature); // iterate syntax tree and add to our node map XNodeOut currentNode = fileNode; while (def != null) { if (def.DefType == XDef.XDefType.Namespace) currentNode = currentNode.AddNode(def.Name, XObjType.Namespace); else if (def.DefType == XDef.XDefType.Class) { currentNode = currentNode.AddNode(def.GetShortName(), XObjType.Class); /* Cant map generic params because the fileNode is not right if (def.Generics != null) foreach (var genericSig in def.Generics) SignatureToClass(genericSig.GetFullName(), fileNode);*/ } def = def.SubDef; } Debug.Assert(currentNode.ObjType == XObjType.Class); return currentNode; }
private XNodeOut RecompileClass(TypeDefinition classDef, XNodeOut fileNode) { if (!Build.TrackAnon && classDef.Name.StartsWith("<>")) { return(null); } var classNode = SignatureToClass(classDef.ToString(), fileNode); if (classDef.BaseType != null) { SetClassDependency(classNode, classDef.BaseType); } // add fields if (Build.TrackFields && classDef.HasFields) { foreach (var fieldDef in classDef.Fields) { var fieldNode = classNode.AddField(fieldDef); SetClassDependency(classNode, fieldDef.DeclaringType); if (fieldDef.FieldType.IsGenericParameter) { Debug.WriteLine("Generic parameter ignored - " + fieldDef.FieldType.ToString()); } else { fieldNode.ReturnID = GetClassRef(fieldDef.FieldType).ID; } } } // save source code for methods foreach (var method in classDef.Methods) { SaveCode(classNode, method); } // track method creation/destruction if (Build.TrackInstances && !classDef.IsValueType) { AddInstanceTracking(classDef, classNode); } // add enter/exit info to method and call tracking foreach (var method in classDef.Methods) { RecompileMethod(classNode, method); } // recompile sub classes foreach (var nestedDef in classDef.NestedTypes) { if (nestedDef.MetadataType == MetadataType.Class || nestedDef.MetadataType == MetadataType.ValueType) { RecompileClass(nestedDef, fileNode); } else { Debug.WriteLine("Ignored nested type - " + nestedDef.ToString()); } } return(classNode); }
public long SaveDat(string path, Dictionary<string, string> settings, XNodeOut root, Dictionary<int, FunctionCall> callMap, Dictionary<int, FunctionCall> initMap) { long trackedObjects = 0; root.ComputeSums(); byte[] temp = new byte[4096]; using (FileStream stream = new FileStream(path, FileMode.Create)) { // save settings foreach (var setting in settings) WriteSetting(stream, setting.Key, setting.Value); // save nodes trackedObjects += root.WriteNode(stream); // save call map SaveCallMap(stream, XPacketType.CallMap, callMap); SaveCallMap(stream, XPacketType.InitMap, initMap); } return trackedObjects; }
private XNodeOut RecompileClass(TypeDefinition classDef, XNodeOut fileNode) { if ( !TrackAnon && classDef.Name.StartsWith("<>") ) return null; var classNode = SignatureToClass(classDef.ToString(), fileNode); if(classDef.BaseType != null) SetClassDependency(classNode, classDef.BaseType); RecompileMethods(classDef, classNode); foreach (var nestedDef in classDef.NestedTypes) if (nestedDef.MetadataType == MetadataType.Class || nestedDef.MetadataType == MetadataType.ValueType) RecompileClass(nestedDef, fileNode); else Debug.WriteLine("Ignored nested type - " + nestedDef.ToString()); return classNode; }
internal void ScanLines(bool test) { XIL.Length = 0; CurrentNode = XFile.FileNode; if (!test) { InjectLibrary("XLibrary", "1:0:0:0"); } bool stripSig = false; using (StreamReader reader = new StreamReader(ILPathOriginal)) { while (!reader.EndOfStream) { string[] line = reader.SplitNextLine(XIL); if (test || line.Length == 0) { continue; } else if (line[0] == ".assembly") { // get last element, if in assemblies, replace with xray version stripSig = false; string assembly = line.Last(); // assemblies are referenced externally by xray. prefix, internally namespace names are the same if (Build.Files.Any(f => f.AssemblyName == assembly)) { if (!Build.ReplaceOriginal) { line[line.Length - 1] = "XRay." + line.Last(); XIL.RemoveLine(); XIL.AppendLine(String.Join(" ", line)); } stripSig = true; } } // the result dll is changed so a strong sig links need to be removed else if (line[0] == ".publickeytoken") { if (stripSig) { XIL.RemoveLine(); } } else if (line[0] == ".file") { // embedded files have a .hash that we won't be messing with, they require a hash stripSig = false; } else if (line[0] == ".hash" && stripSig) { XIL.RemoveLine(); if (line[1] != "algorithm") { string nextLine = string.Join(" ", line).FilterComment(); while (!nextLine.Contains(")")) { nextLine = reader.ReadLine().FilterComment(); } } } // remove assembly's public key else if (line[0] == ".publickey") { XIL.RemoveLine(); string nextLine = string.Join(" ", line).FilterComment();; while (!nextLine.Contains(")")) { nextLine = reader.ReadLine().FilterComment(); } } else if (line[0] == ".class") { // read the whole class before the { while (!line.Contains("{")) { line = line.Concat(reader.SplitNextLine(XIL)).ToArray(); } // right before the class is extended that is the7 name string name = line.TakeWhile(s => s != "extends" && s != "implements" && s != "{").LastOrDefault(); // add namespaces, and class, set current app obj to string[] namespaces = name.Split('.'); if (!line.Contains("nested")) { CurrentNode = XFile.FileNode; for (int i = 0; i < namespaces.Length - 1; i++) { CurrentNode = CurrentNode.AddNode(namespaces[i], XObjType.Namespace); } } string className = namespaces.Last(); int pos = className.LastIndexOf('`'); if (pos != -1) { className = className.Substring(0, pos); } CurrentNode = CurrentNode.AddNode(className, XObjType.Class); // exclude if we dont track anon classes if (!Build.TrackAnon && className.StartsWith("'")) { CurrentNode.Exclude = true; } } else if (line[0] == ".method") { // read the whole method before the { while (!line.Contains("{")) { line = line.Concat(reader.SplitNextLine(XIL)).ToArray(); } // method is the name with the ( in it string name = line.Where(s => s.Contains('(')).LastOrDefault(); //pinvokes can have afdaf( before method name name = name.Substring(0, name.IndexOf('(')); CurrentNode = CurrentNode.AddNode(name, XObjType.Method); // dont inject tracking code under these conditions if (line.Contains("abstract") || line.Where(s => s.StartsWith("pinvokeimpl")).FirstOrDefault() != null || (line.Contains("runtime") && line.Contains("managed")) || // 'runtime managed' / 'managed internalcall' at end of function indicates the body should be empty (line.Contains("managed") && line.Contains("internalcall"))) { CurrentNode.Exclude = true; continue; } // exclude if we dont track anony methods, but dont continue cause entry point could still be inside if (!Build.TrackAnon && name.StartsWith("'")) { CurrentNode.Exclude = true; } // scan for entry, break on .maxstack or } // read the whole method before the { // order method, entry, custom, maxstack, IL_ bool entry = false; while (!line.Contains(".maxstack")) { line = reader.SplitNextLine(XIL); Debug.Assert(!line.Contains("}")); // inject gui after .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 ) // if not then thread state will not be set for app's gui if (line.Contains(".entrypoint")) { entry = true; } } if (line.Length > 1 && line[0] == ".maxstack") { XIL.RemoveLine(); int maxstack = int.Parse(line[1]); if (maxstack == 0) { maxstack = 1; // for method enter function } // if we need to increase stack further for other added functions, // this is how to do it, checksIndexOfAny above, and check above this if (entry && maxstack < 2) { maxstack = 2; } // needs to push 1 more item on stack above whats needed for return // elements so that MethodExit can run if (Build.TrackFlow) { maxstack++; } XIL.AppendLine(".maxstack " + maxstack); // increase stack enough for hit function - need room for thread, hit, constructor if (entry) { InjectGui(); entry = false; } } if (!CurrentNode.Exclude) { InjectMethodEnter(CurrentNode); } } else if (line[0] == ".property") // ignore for now { while (!line.Contains("}")) { line = reader.SplitNextLine(XIL); } } else if (line[0] == ".field") { //todo - add option to track fields //string name = line.LastOrDefault(); //XNodeOut fieldNode = CurrentNode.AddNode(name, XObjType.Field); //fieldNode.Lines = 1; } else if (CurrentNode.ObjType == XObjType.Method) { bool inCatch = false; if (line[0] == "catch") { line = reader.SplitNextLine(XIL); inCatch = true; // inject after indent set } else if (Build.TrackFlow && !CurrentNode.Exclude && line.Length > 1 && line[1] == "ret") { Debug.Assert(line.Length == 2); XIL.RemoveLine(); // remove ret call XIL.AppendLine(); AddLine(line[0] + " nop // XRay - Redirected return address"); // anything jumping to return, will jump here instead and allow us to log exit InjectMethodExit(CurrentNode); AddLine("ret"); // put ret call back in } else if (line.Length > 1 && (Build.TrackFlow || Build.TrackExternal) && line[1].EndsWith(".s")) { // when we insert code we add more instructions, br is the branch instruction // and br.s is the short version allowing a max jump of 255 places which may // not be valid after our injection. Strip the .s, and run ilasm with /optimize // to add them back in Debug.Assert(line.Length == 3); XIL.RemoveLine(); line[1] = line[1].Replace(".s", ""); AddLine(string.Join(" ", line) + " // XRay - removed .s"); } // external method call tracking if (Build.TrackExternal && Build.TrackFlow && !CurrentNode.Exclude && line.Length > 1 && (line[1].StartsWith("constrained.") || line[1].StartsWith("call") || line[1].StartsWith("callvirt") || line[1].StartsWith("calli"))) { Debug.Assert(!line[1].StartsWith("calli")); // whats the format of calli? // any line starting with a constrained prefix is immediately followed by a call virt string[] constrainedLine = null; if (line[1].StartsWith("constrained.")) { XIL.RemoveLine(); // save constrained line a inject after external method tracking constrainedLine = line; line = reader.SplitNextLine(XIL); // read in callvirt } string parse = string.Join(" ", line); // get function name if (!parse.Contains("::")) { continue; // if no :: then caller is accessing a global internal function that is already tracked } int pos = parse.LastIndexOf('('); Debug.Assert(pos != -1); int pos2 = parse.LastIndexOf("::") + 2; Debug.Assert(pos2 != 1 && pos2 < pos); string functionName = parse.Substring(pos2, pos - pos2); parse = parse.Substring(0, pos2 - 2); // cut out what we just parsed // strip template info, read forward mark if in template string withoutTemplate = ""; int inTemplate = 0; for (int i = 0; i < parse.Length; i++) { if (parse[i] == '<') { inTemplate++; } else if (parse[i] == '>') { inTemplate--; } else if (inTemplate == 0) { withoutTemplate += parse[i]; } } parse = withoutTemplate; // now should just be namespace and extern dec to space pos = parse.LastIndexOf(' '); parse = parse.Substring(pos + 1); // we only care if this function is external if (!parse.StartsWith("[")) { continue; } pos = parse.IndexOf("]"); string external = parse.Substring(1, pos - 1); string namespaces = parse.Substring(pos + 1); pos = namespaces.LastIndexOf('`'); if (pos != -1) { namespaces = namespaces.Substring(0, pos); } // if already tracked, skip if (Build.Files.Any(f => f.AssemblyName == external)) { continue; } // add external file to root XNodeOut node = ExtRoot.Nodes.FirstOrDefault(n => n.Name == external) as XNodeOut; if (node == null) { node = ExtRoot.AddNode(external, XObjType.File); } // traverse or add namespace to root string[] names = namespaces.Split('.'); for (int i = 0; i < names.Length; i++) { string name = names[i]; XNodeOut next = node.Nodes.FirstOrDefault(n => n.Name == name) as XNodeOut; XObjType objType = (i == names.Length - 1) ? XObjType.Class : XObjType.Namespace; node = next ?? node.AddNode(name, objType); } node = node.AddNode(functionName, XObjType.Method); node.Lines = 1; // add wrapping for external tracking // remove function XIL.RemoveLine(); // inject method enter - re-route IL address to function to ensure it is logged XIL.AppendLine(); string address = (constrainedLine != null) ? constrainedLine[0] : line[0]; AddLine(address + " nop // XRay - Redirect external method begin"); XIL.AppendLine(); InjectMethodEnter(node); // add function line - strip IL address if (constrainedLine != null) { XIL.AppendLine(string.Join(" ", constrainedLine.Skip(1).ToArray())); } string nextLine = string.Join(" ", line.Skip(1).ToArray()); XIL.AppendLine(nextLine); // read over rest of function until ')' while (!nextLine.Contains(")")) { nextLine = reader.ReadLine(); XIL.AppendLine(nextLine); } // inject exit InjectMethodExit(node); } if (line[0] == "{") // try, catch, finallys, inside one another { CurrentNode.Indent++; CurrentNode.IndentString += " "; if (inCatch && Build.TrackFlow && !CurrentNode.Exclude) { InjectMethodCatch(CurrentNode); } } else if (line[0] == "}") // try, catch, finallys, inside one another { if (CurrentNode.Indent == 0) { CurrentNode = CurrentNode.Parent as XNodeOut; } else { CurrentNode.Indent--; CurrentNode.IndentString = CurrentNode.IndentString.Substring(2); } } CurrentNode.Lines++; } else if (CurrentNode.ObjType == XObjType.Class) { if (line[0] == "}") // try, catch, finallys, inside one another { CurrentNode = CurrentNode.Parent as XNodeOut; } } } } // change method call refs from old assembly to new if (!Build.ReplaceOriginal) { foreach (string assembly in Build.Files.Select(f => f.AssemblyName)) { XIL.Replace("[" + assembly + "]", "[XRay." + assembly + "]"); } } }
public XDecompile(BuildModel build, XNodeOut intRoot, XNodeOut extRoot, XRayedFile item, Dictionary<int, FunctionCall> callMap, Dictionary<int, FunctionCall> initMap) { Build = build; ExtRoot = extRoot; OriginalPath = item.FilePath; item.RecompiledPath = null; // reset XFile = item; CallMap = callMap; InitMap = initMap; }
private void RecompileMethod(XNodeOut classNode, MethodDefinition method) { XNodeOut methodNode = classNode.AddMethod(method); // return if (method.ReturnType != null) { var returnNode = SetClassDependency(classNode, method.ReturnType); if (returnNode != null) { methodNode.ReturnID = returnNode.ID; } } // params for (int i = 0; i < method.Parameters.Count; i++) { var p = method.Parameters[i]; if (methodNode.ParamIDs == null) { methodNode.ParamIDs = new int[method.Parameters.Count]; methodNode.ParamNames = new string[method.Parameters.Count]; } var paramNode = SetClassDependency(classNode, p.ParameterType); methodNode.ParamIDs[i] = paramNode.ID; methodNode.ParamNames[i] = p.Name; } if (method.Body == null) { return; } // local vars foreach (var local in method.Body.Variables) { SetClassDependency(classNode, local.VariableType); } // expands branches/jumps to support adddresses > 255 // possibly needed if injecting code into large functions // OptimizeMacros at end of the function re-optimizes method.Body.SimplifyMacros(); methodNode.Lines = method.Body.Instructions.Count; var processor = method.Body.GetILProcessor(); for (int i = 0; i < method.Body.Instructions.Count; i++) { var instruction = method.Body.Instructions[i]; // record method exited if (Build.TrackFlow && instruction.OpCode == OpCodes.Ret) { instruction.OpCode = OpCodes.Nop; // any 'goto return' will go to here so method exit gets logged first AddInstruction(method, i + 1, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, i + 2, processor.Create(OpCodes.Call, ExitMethodRef)); AddInstruction(method, i + 3, processor.Create(OpCodes.Ret)); i += 3; } // if we're tracking calls to non-xrayed assemblies else if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Calli || instruction.OpCode == OpCodes.Callvirt) { var call = instruction.Operand as MethodReference; if (call == null) { Debug.WriteLine("Unable to track not xrayed: " + instruction.Operand.ToString()); continue; } SetClassDependency(classNode, call.ReturnType); SetClassDependency(classNode, call.DeclaringType); foreach (var p in call.Parameters) { SetClassDependency(classNode, p.ParameterType); } var calledRef = GetClassRef(call.DeclaringType); var calledNode = calledRef.AddMethod(call); /*if( TrackExternal && * !(method.Name == "Finalize" && method.DeclaringType.Namespace == "System") && * (instruction.Operand as MethodReference).DeclaringType.Namespace != EnterMethodRef.DeclaringType.Namespace )*/ if (Build.TrackExternal && calledNode.External && calledRef.Name != "XRay") // !(method.Name == "Finalize" && method.DeclaringType.Namespace == "System")) { if (method.Name == ".cctor" && call.Name == "GetTypeFromHandle") { continue; // call added to cctor by xray } calledNode.Lines = 1; // in function is prefixed by .constrained, wrap enter/exit around those 2 lines int offset = 0; if (i > 0 && method.Body.Instructions[i - 1].OpCode == OpCodes.Constrained) { i -= 1; offset = 1; } var oldPos = method.Body.Instructions[i]; // wrap the call with enter and exit, because enter to an external method may cause // an xrayed method to be called, we want to track the flow of that process int pos = i; AddInstruction(method, pos++, processor.Create(OpCodes.Ldc_I4, calledNode.ID)); AddInstruction(method, pos++, processor.Create(OpCodes.Call, EnterMethodRef)); // method pos += 1 + offset; AddInstruction(method, pos++, processor.Create(OpCodes.Ldc_I4, calledNode.ID)); AddInstruction(method, pos, processor.Create(OpCodes.Call, ExitMethodRef)); var newPos = method.Body.Instructions[i]; UpdateExceptionHandlerPositions(method, oldPos, newPos); i = pos; // loop end will add 1 putting us right after last added function } } else if (Build.TrackFields && (instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld || instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldflda || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldsflda)) { var fieldDef = instruction.Operand as FieldReference; var classRef = GetClassRef(fieldDef.DeclaringType); var fieldRef = classRef.AddField(fieldDef); // some times volitile prefixes set/get field int offset = 0; if (i > 0 && method.Body.Instructions[i - 1].OpCode == OpCodes.Volatile) { i--; offset = 1; } AddInstruction(method, i, processor.Create(OpCodes.Ldc_I4, fieldRef.ID)); if (instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld) { AddInstruction(method, i + 1, processor.Create(OpCodes.Call, SetFieldRef)); } else { AddInstruction(method, i + 1, processor.Create(OpCodes.Call, LoadFieldRef)); } i = i + 2 + offset; // skip instuction added, and field itself, end of loop will iterate this again // Debug.WriteLine("{0} in Module: {1}, Namespace: {2}, Class: {3}, Name: {4}, Type: {5}", instruction.OpCode, fieldDef.DeclaringType.Scope.Name, namespaces, className, fieldName, fieldType); } else if (instruction.OpCode == OpCodes.Newobj) { var newObj = instruction.Operand as MethodReference; SetClassDependency(classNode, newObj.DeclaringType); } /* Still not really working - goal - to get side by side wpf apps to work * else if (instruction.OpCode == OpCodes.Ldstr && !Build.ReplaceOriginal) * { * // rename Pack URIs in WPF so resources can be found with an XRay.. namespace * // ex: "/MyApp;component/views/aboutview.xaml" -> "/XRay.MyApp;component/views/aboutview.xaml" * var packUri = instruction.Operand as String; * * foreach (var file in XRayedFiles) * packUri = packUri.Replace("/" + file.AssemblyName + ";", "/XRay." + file.AssemblyName + ";"); * //packUri = packUri.Replace(file.AssemblyName, "XRay." + file.AssemblyName); * * instruction.Operand = packUri; * }*/ } // end iterating through instructions // record function was entered if (Build.TrackFunctions) { AddInstruction(method, 0, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, 1, processor.Create(OpCodes.Call, EnterMethodRef)); } // record catches if (Build.TrackFlow) { foreach (var handler in method.Body.ExceptionHandlers) { if (handler.HandlerType != ExceptionHandlerType.Catch) { continue; } int i = method.Body.Instructions.IndexOf(handler.HandlerStart); AddInstruction(method, i, processor.Create(OpCodes.Ldc_I4, methodNode.ID)); AddInstruction(method, i + 1, processor.Create(OpCodes.Call, CatchMethodRef)); var oldPos = handler.HandlerStart; var newPos = method.Body.Instructions[i]; UpdateExceptionHandlerPositions(method, oldPos, newPos); } } method.Body.OptimizeMacros(); }
internal void ProcessClassNames(XNodeOut node) { // if class move anonymous objects into their non-anon counterparts if(node.ObjType == XObjType.Class) { // class under a namespace could be anon if(node.Name.StartsWith("<>")) MarkNodeAsAnon(node); // put anonymous classes in the functions that they were generated from // ex -. Nested Class: <>c__DisplayClass15, has method: <FilterTextBox_TextChanged>b__11 var anonClasses = node.Nodes.Where(n => n.ObjType == XObjType.Class && n.Name.StartsWith("<>")).ToArray(); foreach(XNodeOut anonClass in anonClasses) { // mark as anon MarkNodeAsAnon(anonClass); // if parent is not anon if( !anonClass.Parent.IsAnon ) { // iterate anon methods to find association var anonMethod = anonClass.Nodes.FirstOrDefault(n => !n.Name.StartsWith("<>") && n.Name.StartsWith("<") && n.Name.Contains(">")); if (anonMethod == null) continue; string anonMethodName = anonMethod.Name.Substring(1, anonMethod.Name.IndexOf(">") - 1); // iterate parent class methods to find match var parentMethod = node.Nodes.FirstOrDefault(n => n.Name == anonMethodName) as XNodeOut; if (parentMethod == null) continue; // move node under that method node.Nodes.Remove(anonClass); parentMethod.Nodes.Add(anonClass); anonClass.Parent = parentMethod; // will be renamed when class is iterated below } } // iterate anon methods, move into parent methods var anonMethods = node.Nodes.Where(n => n.ObjType == XObjType.Method && n.Name.StartsWith("<")).ToArray(); foreach(XNodeOut anonMethod in anonMethods) { MarkNodeAsAnon(anonMethod); string parentName = anonMethod.Name.Substring(1, anonMethod.Name.IndexOf(">") - 1); var parentMethod = node.Nodes.FirstOrDefault(n => n.Name == parentName) as XNodeOut; if (parentMethod == null) continue; // move node under that method node.Nodes.Remove(anonMethod); parentMethod.Nodes.Add(anonMethod); anonMethod.Parent = parentMethod; } // if current class is anon, rename class parentNodeName.classX if (node.IsAnon) { XNodeOut parent = node.GetNotAnonParent(); node.Name = parent.Name + ".class" + parent.AnonClasses.ToString(); parent.AnonClasses++; } } // if method if(node.ObjType == XObjType.Method) { XNodeOut parent = node.GetNotAnonParent(); // if method is ctor/cctor rename if (node.Name == ".ctor" || node.Name == ".cctor") { node.Name = parent.Name + ((node.Name == ".ctor") ? ".ctor" : ".static_ctor"); if (parent.InitCount > 1) node.Name += parent.InitCount.ToString(); parent.InitCount++; } else if (node.IsAnon || // simple anon method node.Parent.IsAnon) // method of an anon class { if (node.Name.StartsWith("<")) { node.Name = parent.Name + ".func" + parent.AnonFuncs.ToString(); parent.AnonFuncs++; } } } // iterate sub elements foreach (XNodeOut subnode in node.Nodes) ProcessClassNames(subnode); }
private void SaveCode(XNodeOut classNode, MethodDefinition method) { XNodeOut methodNode = classNode.AddMethod(method); if (Build.DecompileCSharp) { methodNode.CSharp = DecompileMethod(method); } if (method.Body == null) { return; } // record MSIL if (Build.SaveMsil) { methodNode.Msil = new List <XInstruction>(); foreach (var inst in method.Body.Instructions) { var xInst = new XInstruction(); xInst.Offset = inst.Offset; xInst.OpCode = inst.OpCode.Name; xInst.Line = (inst.Operand != null) ? inst.Operand.ToString() : ""; if (inst.OpCode == OpCodes.Call || inst.OpCode == OpCodes.Calli || inst.OpCode == OpCodes.Callvirt || inst.OpCode == OpCodes.Newobj || inst.OpCode == OpCodes.Ldftn) // pushes a function pointer to the stack { var call = inst.Operand as MethodReference; if (call != null) { var classRef = GetClassRef(call.DeclaringType); var methodRef = classRef.AddMethod(call); xInst.RefId = methodRef.ID; } else { Debug.WriteLine("Unable to track: " + inst.Operand.ToString()); } } else if (inst.OpCode == OpCodes.Stfld || inst.OpCode == OpCodes.Stsfld || inst.OpCode == OpCodes.Ldfld || inst.OpCode == OpCodes.Ldflda || inst.OpCode == OpCodes.Ldsfld || inst.OpCode == OpCodes.Ldsflda) { var fieldDef = inst.Operand as FieldReference; var classRef = GetClassRef(fieldDef.DeclaringType); var fieldRef = classRef.AddField(fieldDef); xInst.RefId = fieldRef.ID; } else if (inst.OpCode.FlowControl == FlowControl.Branch || inst.OpCode.FlowControl == FlowControl.Cond_Branch) { var op = inst.Operand as Instruction; if (op != null) { int offset = op.Offset; xInst.Line = "goto " + offset.ToString("X"); xInst.RefId = offset; } } methodNode.Msil.Add(xInst); } } }
private void AddInstanceTracking(TypeDefinition classDef, XNodeOut classNode) { bool hasCtor = false; // add tracking to constructor foreach (var ctorMethod in classDef.Methods.Where(m => (m.Name == ".ctor" || m.Name == ".cctor") && m.Body != null)) { ctorMethod.Body.SimplifyMacros(); var processor = ctorMethod.Body.GetILProcessor(); if (ctorMethod.Name == ".ctor") { // enter Constructed logging after System.Object::.ctor() is called, because passing the 'this' parameter before // that causes PEVerify to complain about using an uninitialized variable for (int i = 0; i < processor.Body.Instructions.Count; i++) { var instruction = processor.Body.Instructions[i]; if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference) { var callRef = instruction.Operand as MethodReference; if (callRef.DeclaringType != null && classDef.BaseType.FullName == callRef.DeclaringType.FullName && callRef.Name == ".ctor") { hasCtor = true; AddInstruction(ctorMethod, ++i, processor.Create(OpCodes.Ldc_I4, classNode.ID)); AddInstruction(ctorMethod, ++i, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(ctorMethod, ++i, processor.Create(OpCodes.Call, ClassConstructedRef)); break; } } } } // else static constructor else { // ldtoken XTestLib.SmallStatic // ldtoken XTestLib.StaticTemplateClass`1<!T> (for generic static classes) // call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) AddInstruction(ctorMethod, 0, processor.Create(OpCodes.Ldc_I4, classNode.ID)); if (classDef.HasGenericParameters) { // for some reason the GenericInstanceType does not carry over the parameters var genericDef = new GenericInstanceType(classDef); foreach (var p in classDef.GenericParameters) genericDef.GenericArguments.Add(p); AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldtoken, genericDef)); } else AddInstruction(ctorMethod, 1, processor.Create(OpCodes.Ldtoken, classDef)); AddInstruction(ctorMethod, 2, processor.Create(OpCodes.Call, GetTypeFromHandleRef)); AddInstruction(ctorMethod, 3, processor.Create(OpCodes.Call, ClassConstructedRef)); } ctorMethod.Body.OptimizeMacros(); } // add tracking to desconstructor (only add if ctor tracking added) if (hasCtor) { var finMethod = classDef.Methods.FirstOrDefault(m => m.Name == "Finalize"); bool callObjectFinalize = false; if (finMethod == null) { finMethod = new MethodDefinition("Finalize", Mono.Cecil.MethodAttributes.Family | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Virtual, VoidRef); callObjectFinalize = true; classDef.Methods.Add(finMethod); } finMethod.Body.SimplifyMacros(); var processor = finMethod.Body.GetILProcessor(); AddInstruction(finMethod, 0, processor.Create(OpCodes.Ldc_I4, classNode.ID)); AddInstruction(finMethod, 1, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(finMethod, 2, processor.Create(OpCodes.Call, ClassDeconstructedRef)); if (callObjectFinalize) { AddInstruction(finMethod, 3, processor.Create(OpCodes.Ldarg, 0)); AddInstruction(finMethod, 4, processor.Create(OpCodes.Call, ObjectFinalizeRef)); AddInstruction(finMethod, 5, processor.Create(OpCodes.Ret)); } finMethod.Body.OptimizeMacros(); } }
void MarkNodeAsAnon(XNodeOut node) { node.IsAnon = true; foreach (XNodeOut subnode in node.Nodes) MarkNodeAsAnon(subnode); }
private XNodeOut RecompileClass(TypeDefinition classDef, XNodeOut fileNode) { if (!Build.TrackAnon && classDef.Name.StartsWith("<>")) return null; var classNode = SignatureToClass(classDef.ToString(), fileNode); if(classDef.BaseType != null) SetClassDependency(classNode, classDef.BaseType); // add fields if (Build.TrackFields && classDef.HasFields) foreach (var fieldDef in classDef.Fields) { var fieldNode = classNode.AddField(fieldDef); SetClassDependency(classNode, fieldDef.DeclaringType); if (fieldDef.FieldType.IsGenericParameter) Debug.WriteLine("Generic parameter ignored - " + fieldDef.FieldType.ToString()); else fieldNode.ReturnID = GetClassRef(fieldDef.FieldType).ID; } // save source code for methods foreach (var method in classDef.Methods) SaveCode(classNode, method); // track method creation/destruction if (Build.TrackInstances && !classDef.IsValueType) AddInstanceTracking(classDef, classNode); // add enter/exit info to method and call tracking List<MethodDefinition> addMethods = new List<MethodDefinition>(); foreach (var method in classDef.Methods.ToArray()) // toarray because recompile method may add new methods RecompileMethod(classNode, classDef, method); // recompile sub classes foreach (var nestedDef in classDef.NestedTypes) if (nestedDef.MetadataType == MetadataType.Class || nestedDef.MetadataType == MetadataType.ValueType) RecompileClass(nestedDef, fileNode); else Debug.WriteLine("Ignored nested type - " + nestedDef.ToString()); return classNode; }
private int TrackMethodExit(MethodDefinition method, MethodReference target, XNodeOut node, ILProcessor processor, int pos) { // really annoying, we need to box value type that are returned by the target method to pass to xray exit method // if the return type is generic msil returns something like !0, but we can't box !0, we have to resolve it from the function declaration // hence all the code below var resolved = ResolveGenericMethod(target); var returnType = resolved.ReturnType; if (Build.TrackReturnValue && returnType.FullName != VoidRef.FullName) { // this is key, duplicate the head of the stack, and we'll pass this to the xray method exit function to be recorded AddInstruction(method, ++pos, processor.Create(OpCodes.Dup)); // if it's a value type or generic param then box because method exit takes an object as a param if (returnType.IsValueType || returnType.IsGenericParameter) AddInstruction(method, ++pos, processor.Create(OpCodes.Box, returnType)); AddInstruction(method, ++pos, processor.Create(OpCodes.Ldc_I4, node.ID)); // push id AddInstruction(method, ++pos, processor.Create(OpCodes.Call, MethodExitWithValueRef)); // call exit with object and id } // else not tracking return value else { AddInstruction(method, ++pos, processor.Create(OpCodes.Ldc_I4, node.ID)); AddInstruction(method, ++pos, processor.Create(OpCodes.Call, MethodExitRef)); } return pos; }
public XDecompile(XNodeOut intRoot, XNodeOut extRoot, XRayedFile item, string outDir, string datPath, XRayedFile[] files, bool sxs) { ExtRoot = extRoot; OriginalPath = item.FilePath; OutputDir = outDir; DatPath = datPath; SideBySide = sxs; item.RecompiledPath = null; // reset XFile = item; XRayedFiles = files; }
private void SaveCode(XNodeOut classNode, MethodDefinition method) { XNodeOut methodNode = classNode.AddMethod(method); if (Build.DecompileCSharp) methodNode.CSharp = DecompileMethod(method); if (method.Body == null) return; // record MSIL if (Build.SaveMsil) { methodNode.Msil = new List<XInstruction>(); foreach (var inst in method.Body.Instructions) { var xInst = new XInstruction(); xInst.Offset = inst.Offset; xInst.OpCode = inst.OpCode.Name; xInst.Line = (inst.Operand != null) ? inst.Operand.ToString() : ""; if (inst.OpCode == OpCodes.Call || inst.OpCode == OpCodes.Calli || inst.OpCode == OpCodes.Callvirt || inst.OpCode == OpCodes.Newobj || inst.OpCode == OpCodes.Ldftn) // pushes a function pointer to the stack { var call = inst.Operand as MethodReference; if (call != null) { var classRef = GetClassRef(call.DeclaringType); var methodRef = classRef.AddMethod(call); xInst.RefId = methodRef.ID; } else Debug.WriteLine("Unable to track: " + inst.Operand.ToString()); } else if (inst.OpCode == OpCodes.Stfld || inst.OpCode == OpCodes.Stsfld || inst.OpCode == OpCodes.Ldfld || inst.OpCode == OpCodes.Ldflda || inst.OpCode == OpCodes.Ldsfld || inst.OpCode == OpCodes.Ldsflda) { var fieldDef = inst.Operand as FieldReference; var classRef = GetClassRef(fieldDef.DeclaringType); var fieldRef = classRef.AddField(fieldDef); xInst.RefId = fieldRef.ID; } else if (inst.OpCode.FlowControl == FlowControl.Branch || inst.OpCode.FlowControl == FlowControl.Cond_Branch) { var op = inst.Operand as Instruction; if (op != null) { int offset = op.Offset; xInst.Line = "goto " + offset.ToString("X"); xInst.RefId = offset; } } methodNode.Msil.Add(xInst); } } }
internal double ComputeFieldValues(XNodeOut node) { // give fields a value that makes them take up a total of %15 of the value of the class double total = node.Lines; double fieldCount = 0; // compute sum of all dependents foreach (XNodeOut subnode in node.Nodes) if(subnode.ObjType == XObjType.Field) fieldCount++; else total += ComputeFieldValues(subnode); if(fieldCount > 0) { // inflate total 15% and fit fields in there double subTotal = total; total = total * 100.0 / 85.0; double fieldTotal = total - subTotal; int fieldValue = (int) (fieldTotal / fieldCount); if (fieldValue < 1) fieldValue = 1; foreach (XNodeOut field in node.Nodes.Where(n => n.ObjType == XObjType.Field)) field.Lines = fieldValue; } return total; }