Source

ILDump / src / AssemblyDump.cs

/**
Copyright (c) 2012 Jordan "Earlz/hckr83" Earls  <http://lastyearswishes.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.
   
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

using System;
using System.Linq;
using System.Collections.Generic;
using Mono.Cecil;
using System.Text;
using System.Text.RegularExpressions;
using Earlz.ILDump.Extensions;
using System.Diagnostics;

namespace Earlz.ILDump
{
	public class AssemblyDump
	{
		ModuleDefinition Module;
		public List<TypeDefinition> Types
		{
			get;
			private set;
		}

		/// <summary>
		/// All methods across all types
		/// </summary>
		public List<MethodDefinition> Methods
		{
			get;
			private set;
		}
		static Regex FindLabel=new Regex("IL_[0-9,a-f][0-9,a-f][0-9,a-f][0-9,a-f]",RegexOptions.Compiled);


		public AssemblyDump(string file)
		{
			Types=new List<TypeDefinition>();
			Methods=new List<MethodDefinition>();
			Module=ModuleDefinition.ReadModule(file);
			BuildTypeList(null);
		}
		void BuildTypeList(TypeDefinition parent)
		{
			if(parent==null)
			{
				//we control the recursion
				foreach(var t in Module.Types)
				{
					Types.Add(t);
					VerboseOut("Found Type: "+t.FullName);
					if(t.HasNestedTypes)
					{
						BuildTypeList(t);
					}
					if(t.HasMethods)
					{
						BuildMethodList(t);
					}
				}
			}
			else
			{
				foreach(var t in parent.NestedTypes)
				{
					Types.Add(t);
					VerboseOut("Found Nested Type: "+t.FullName);
					if(t.HasNestedTypes)
					{
						BuildTypeList(t);
					}
					if(t.HasMethods)
					{
						BuildMethodList(t);
					}
				}
			}
		}
		void BuildMethodList(TypeDefinition parent)
		{
			foreach(var m in parent.Methods)
			{
				VerboseOut("Found Method: "+m.FullName);
				Methods.Add(m);
			}
		}
		void VerboseOut(string s)
		{
			if(Config.Verbose)
			{
				Console.WriteLine(s);
			}
		}
		public string DumpAllIL()
		{
			StringBuilder sb=new StringBuilder();
			IEnumerable<MethodDefinition> methods;
			if(Config.NoSort)
			{
				methods=Methods;
			}else{
				methods=Methods.OrderBy(x=>x.FullMemberName());
			}
			foreach(var m in methods)
			{
				if(Config.Constraint!=null)
				{
					if(!m.FullMemberName().Contains(Config.Constraint))
					{
						continue; //just skip over
					}
				}
				if(!m.HasBody)
				{
					VerboseOut("Skipping method with no body: "+m.FullName);
					continue;
				}
				sb.AppendLine("Begin Method "+m.FullName);
				sb.AppendLine(GetMethodHeader(m));

				sb.AppendLine(GetMethodIL(m));
				sb.AppendLine("End Method "+m.FullName);
				sb.AppendLine("");
			}
			return sb.ToString();
		}
		public string GetMethodHeader(MethodDefinition m)
		{
			StringBuilder sb=new StringBuilder();
			sb.AppendLine("  .maxstack "+m.Body.MaxStackSize);
			sb.AppendLine("  //code size: "+m.Body.CodeSize);
			if(m.Body.HasVariables)
			{
				sb.AppendLine("  .local "+(m.Body.InitLocals?"init":"")+" ( ");
				for(int i=0;i<m.Body.Variables.Count;i++)
				{
					var v=m.Body.Variables[i];
					sb.Append("    "+v.VariableType.FullName + " " + v.ToString());
					if(i<m.Body.Variables.Count-1)
					{
						sb.AppendLine(",");
					}else{
						sb.AppendLine("");
					}
				}
				sb.AppendLine("  )");
			}
			return sb.ToString();
		}
		public string GetMethodIL(MethodDefinition method)
		{
			Dictionary<int, int> TrimmedMap=new Dictionary<int, int>();
			List<KeyValuePair<string,int>> lines=new List<KeyValuePair<string, int>>(); //the int value is the offset

			//foreach(var instr in method.Body.Instructions)
			for(int i=0;i<method.Body.Instructions.Count;i++)
			{
				var instr=method.Body.Instructions[i];
				if(Config.Trimming && instr.OpCode.Code==Mono.Cecil.Cil.Code.Nop){
					if(i==method.Body.Instructions.Count-1){
						TrimmedMap.Add(instr.Offset,method.Body.Instructions[i-1].Offset);
					}else{
						TrimmedMap.Add(instr.Offset,method.Body.Instructions[i+1].Offset);
					}
					continue;
				}
				string tmp=instr.ToString();
				if(!Config.ExtraLabels){
					tmp=StripLabel(tmp);
				}
				lines.Add(new KeyValuePair<string, int>(tmp,instr.Offset));
			}
			SortedDictionary<int, int> labels=null;
			if(!Config.ExtraLabels)
			{
				labels=new SortedDictionary<int, int>(ScanForLabels(lines));
			}



			StringBuilder sb=new StringBuilder();
			Dictionary<int, string> labelnames=new Dictionary<int, string>();
			int count=1;
			foreach(var line in lines)
			{
				string key=line.Key;
				if(!Config.ExtraLabels && labels.Count>0)
				{
					//lines and labels are both sorted.
					//So, here, if our line is already past the label
					//then there was some nops removed and screwed up the flow.
					//so add them back now
					while(labels.Count>0 && labels.First().Key<line.Value)
					{
						if(Config.NoRenameLabels)
						{
							string lbl=OffsetToLabel(labels.First().Key);
							labelnames.Add(labels.First().Key,lbl);
						}else{
							string lbl="__Label"+count;
							labelnames.Add(labels.First().Key,lbl);
							//don't increment count here or possibly do? Needs an option
						}
						labels.Remove(labels.First().Key);
					}
					if(labels.ContainsKey(line.Value))
					{
						//need to prefix a label
						if(Config.NoRenameLabels)
						{
							string lbl=OffsetToLabel(line.Value);
							labelnames.Add(line.Value,lbl);
							key=lbl+": "+key;
						}else{
							string lbl="__Label"+count;
							labelnames.Add(line.Value,lbl);
							key=lbl+": "+key;
							count++;
						}
						labels.Remove(line.Value);
					}
				}
				sb.AppendLine("  "+key);
			}
			if(labels!=null && (!Config.ExtraLabels || !Config.Trimming))
			{
				foreach(var l in labels)
				{
					if(labelnames.Keys.Contains(l.Key))
					{
						continue;
					}
					var tmp=labelnames.Keys.FirstOrDefault(x=>x==l.Value);
					Debug.Assert(tmp!=null);
					labelnames.Add(l.Key,labelnames[tmp]);
				}
			}
			if(!Config.NoRenameLabels && !Config.ExtraLabels)
			{
				foreach(var label in labelnames)
				{
					sb=sb.Replace(OffsetToLabel(label.Key),label.Value);
				}

			}
			return sb.ToString();
		}
		/// <summary>
		/// Will scan for labels in the lines of IL that are actually needed
		/// When it finds a used label, it will assure that it exists if Trimming is on. 
		/// If it doesn't exist, then it finds the next "real" offset and maps the fake offset to the real offset for later use
		/// Otherwise, it just adds a 1:1 mapping where the offset key points to the same offset value
		/// </summary>
		Dictionary<int, int> ScanForLabels(List<KeyValuePair<string, int>> lines)
		{
			Dictionary<int, int> labels = new Dictionary<int, int>();
			foreach(var kv in lines)
			{
				var line=kv.Key;
				string instr=line; 
				var matches=FindLabel.Matches(instr);
				foreach(var m in matches.OfType<Match>())
				{
					VerboseOut("Found needed label: "+m.Value);
					//map to "actual" label value in case of trimming
					int off=LabelToOffset(m.Value);
					if(Config.Trimming && !lines.Exists(x=>x.Value==off))
					{
						int fake=off;
						int max=lines[lines.Count-1].Value;
						while(off<max) //while less than last instruction
						{
							off++;
							if(lines.Exists(x=>x.Value==off)) //very ineffecient 
							  							   //TODO make faster. Just needs to expect it to be sorted, look up value, then hop to next value
							{
								break;
							}
						}
						if(!labels.ContainsKey(fake))
						{
							labels.Add(fake,off);
						}
					}else{
						if(!labels.ContainsKey(off)){
							labels.Add(off,off);
						}
					}
				}
			}
			return labels;
		}
		int LabelToOffset(string label)
		{
			label=label.Substring(3,4);
			return Convert.ToInt32(label,16);
		}
		string OffsetToLabel(int offset)
		{
			string num=string.Format("{0:X}",offset).PadLeft(4,'0').ToLower();
			return "IL_"+num;
		}
		/// <summary>
		/// Strips the "IL_xxxx" label from in front of an opcode's ToString output
		/// </summary>
		static string StripLabel(string s)
		{
			return s.Remove(0,9); //not really "safe" per se, but it works. 
		}

		///Ignore below here. Just for ease of debugging
		class foo
		{
			public foo()
			{
				var x=10+10;
			}
			class bar
			{
				public bar()
				{
					var y=10+20;
				}
			}
		}

	}
}