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(); }
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; }
private void InjectLibrary(string name, string version) { XIL.AppendLine(); XIL.AppendLine("// XRay"); XIL.AppendLine(".assembly extern " + name); XIL.AppendLine("{"); XIL.AppendLine(" .ver " + version); XIL.AppendLine("}"); XIL.AppendLine(); LinesAdded += 5; }
private void InjectGui() { // check mscorlib is in assembly // make sure max stack is big enough for init parameters XIL.AppendLine(); AddLine("// XRay"); AddLine("ldc.i4 " + (Build.TrackFlow ? "1" : "0")); AddLine("ldc.i4 " + (Build.TrackInstances ? "1" : "0")); AddLine("call void [XLibrary]XLibrary.XRay::Init(bool,bool)"); XIL.AppendLine(); LinesAdded += 4; }
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 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 + "]"); } } }
private void AddLine(string line) { XIL.AppendLine(CurrentNode.IndentString + line); }