Click here to Skip to main content
15,886,857 members
Articles / Programming Languages / C#

Building .NET Coverage Tool

Rate me:
Please Sign up or sign in to vote.
4.94/5 (34 votes)
25 Aug 2009MIT8 min read 85K   2.3K   109  
This article is a walkthrough for building a .NET coverage tool
//
// MetadataWriter.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// (C) 2005 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

namespace Mono.Cecil.Metadata {

	using System;
	using System.Collections;
	using System.IO;
	using System.Text;

	using Mono.Cecil;
	using Mono.Cecil.Binary;

	internal sealed class MetadataWriter : BaseMetadataVisitor {

		AssemblyDefinition m_assembly;
		MetadataRoot m_root;
		TargetRuntime m_runtime;
		ImageWriter m_imgWriter;
		MetadataTableWriter m_tableWriter;
		MemoryBinaryWriter m_binaryWriter;

		IDictionary m_stringCache;
		MemoryBinaryWriter m_stringWriter;

		IDictionary m_guidCache;
		MemoryBinaryWriter m_guidWriter;

		IDictionary m_usCache;
		MemoryBinaryWriter m_usWriter;

		IDictionary m_blobCache;
		MemoryBinaryWriter m_blobWriter;

		MemoryBinaryWriter m_tWriter;

		MemoryBinaryWriter m_cilWriter;

		MemoryBinaryWriter m_fieldDataWriter;
		MemoryBinaryWriter m_resWriter;

		uint m_mdStart, m_mdSize;
		uint m_resStart, m_resSize;
		uint m_snsStart, m_snsSize;
		uint m_debugHeaderStart;
		uint m_imporTableStart;

		uint m_entryPointToken;

		RVA m_cursor = new RVA (0x2050);

		public MemoryBinaryWriter CilWriter {
			get { return m_cilWriter; }
		}

		public MemoryBinaryWriter StringWriter {
			get { return m_stringWriter; }
		}

		public MemoryBinaryWriter GuidWriter {
			get { return m_guidWriter; }
		}

		public MemoryBinaryWriter UserStringWriter {
			get { return m_usWriter; }
		}

		public MemoryBinaryWriter BlobWriter {
			get { return m_blobWriter; }
		}

		public uint DebugHeaderPosition {
			get { return m_debugHeaderStart; }
		}

		public uint ImportTablePosition {
			get { return m_imporTableStart; }
		}

		public uint EntryPointToken {
			get { return m_entryPointToken; }
			set { m_entryPointToken = value; }
		}

		public TargetRuntime TargetRuntime {
			get { return m_runtime; }
		}

		public MetadataWriter (AssemblyDefinition asm, MetadataRoot root,
			AssemblyKind kind, TargetRuntime rt, BinaryWriter writer)
		{
			m_assembly = asm;
			m_root = root;
			m_runtime = rt;
			m_imgWriter = new ImageWriter (this, kind, writer);
			m_binaryWriter = m_imgWriter.GetTextWriter ();

			m_stringCache = new Hashtable ();
			m_stringWriter = new MemoryBinaryWriter (Encoding.UTF8);
			m_stringWriter.Write ((byte) 0);

			m_guidCache = new Hashtable ();
			m_guidWriter = new MemoryBinaryWriter ();

			m_usCache = new Hashtable ();
			m_usWriter = new MemoryBinaryWriter (Encoding.Unicode);
			m_usWriter.Write ((byte) 0);

			m_blobCache = new Hashtable (ByteArrayEqualityComparer.Instance, ByteArrayEqualityComparer.Instance);
			m_blobWriter = new MemoryBinaryWriter ();
			m_blobWriter.Write ((byte) 0);

			m_tWriter = new MemoryBinaryWriter ();
			m_tableWriter = new MetadataTableWriter (this, m_tWriter);

			m_cilWriter = new MemoryBinaryWriter ();

			m_fieldDataWriter = new MemoryBinaryWriter ();
			m_resWriter = new MemoryBinaryWriter ();
		}

		public MetadataRoot GetMetadataRoot ()
		{
			return m_root;
		}

		public ImageWriter GetImageWriter ()
		{
			return m_imgWriter;
		}

		public MemoryBinaryWriter GetWriter ()
		{
			return m_binaryWriter;
		}

		public MetadataTableWriter GetTableVisitor ()
		{
			return m_tableWriter;
		}

		public void AddData (int length)
		{
			m_cursor += new RVA ((uint) length);
		}

		public RVA GetDataCursor ()
		{
			return m_cursor;
		}

		public uint AddString (string str)
		{
			if (str == null || str.Length == 0)
				return 0;

			if (m_stringCache.Contains (str))
				return (uint) m_stringCache [str];

			uint pointer = (uint) m_stringWriter.BaseStream.Position;
			m_stringCache [str] = pointer;
			m_stringWriter.Write (Encoding.UTF8.GetBytes (str));
			m_stringWriter.Write ('\0');
			return pointer;
		}

		public uint AddBlob (byte [] data)
		{
			if (data == null || data.Length == 0)
				return 0;

			object cached = m_blobCache [data];
			if (cached != null)
				return (uint) cached;

			uint pointer = (uint) m_blobWriter.BaseStream.Position;
			m_blobCache [data] = pointer;
			Utilities.WriteCompressedInteger (m_blobWriter, data.Length);
			m_blobWriter.Write (data);
			return pointer;
		}

		public uint AddGuid (Guid g)
		{
			if (m_guidCache.Contains (g))
				return (uint) m_guidCache [g];

			uint pointer = (uint) m_guidWriter.BaseStream.Position;
			m_guidCache [g] = pointer;
			m_guidWriter.Write (g.ToByteArray ());
			return pointer + 1;
		}

		public uint AddUserString (string str)
		{
			if (str == null)
				return 0;

			if (m_usCache.Contains (str))
				return (uint) m_usCache [str];

			uint pointer = (uint) m_usWriter.BaseStream.Position;
			m_usCache [str] = pointer;
			byte [] us = Encoding.Unicode.GetBytes (str);
			Utilities.WriteCompressedInteger (m_usWriter, us.Length + 1);
			m_usWriter.Write (us);
			m_usWriter.Write ((byte) (RequiresSpecialHandling (us) ? 1 : 0));
			return pointer;
		}

		static bool RequiresSpecialHandling (byte [] chars)
		{
			for (int i = 0; i < chars.Length; i++) {
				byte c = chars [i];
				if ((i % 2) == 1)
					if (c != 0)
						return true;

				if (InRange (0x01, 0x08, c) ||
					InRange (0x0e, 0x1f, c) ||
					c == 0x27 ||
					c == 0x2d ||
					c == 0x7f) {

					return true;
				}
			}

			return false;
		}

		static bool InRange (int left, int right, int value)
		{
			return left <= value && value <= right;
		}

		void CreateStream (string name)
		{
			MetadataStream stream = new MetadataStream ();
			stream.Header.Name = name;
			stream.Heap = MetadataHeap.HeapFactory (stream);
			m_root.Streams.Add (stream);
		}

		void SetHeapSize (MetadataHeap heap, MemoryBinaryWriter data, byte flag)
		{
			if (data.BaseStream.Length > 65536) {
				m_root.Streams.TablesHeap.HeapSizes |= flag;
				heap.IndexSize = 4;
			} else
				heap.IndexSize = 2;
		}

		public uint AddResource (byte [] data)
		{
			uint offset = (uint) m_resWriter.BaseStream.Position;
			m_resWriter.Write (data.Length);
			m_resWriter.Write (data);
			m_resWriter.QuadAlign ();
			return offset;
		}

		public void AddFieldInitData (byte [] data)
		{
			m_fieldDataWriter.Write (data);
			m_fieldDataWriter.QuadAlign ();
		}

		uint GetStrongNameSignatureSize ()
		{
			if (m_assembly.Name.PublicKey != null) {
				// in fx 2.0 the key may be from 384 to 16384 bits
				// so we must calculate the signature size based on
				// the size of the public key (minus the 32 byte header)
				int size = m_assembly.Name.PublicKey.Length;
				if (size > 32)
					return (uint) (size - 32);
				// note: size == 16 for the ECMA "key" which is replaced
				// by the runtime with a 1024 bits key (128 bytes)
			}
			return 128; // default strongname signature size
		}

		public override void VisitMetadataRoot (MetadataRoot root)
		{
			WriteMemStream (m_cilWriter);
			WriteMemStream (m_fieldDataWriter);
			m_resStart = (uint) m_binaryWriter.BaseStream.Position;
			WriteMemStream (m_resWriter);
			m_resSize = (uint) (m_binaryWriter.BaseStream.Position - m_resStart);

			// for now, we only reserve the place for the strong name signature
			if ((m_assembly.Name.Flags & AssemblyFlags.PublicKey) > 0) {
				m_snsStart = (uint) m_binaryWriter.BaseStream.Position;
				m_snsSize = GetStrongNameSignatureSize ();
				m_binaryWriter.Write (new byte [m_snsSize]);
				m_binaryWriter.QuadAlign ();
			}

			// save place for debug header
			if (m_imgWriter.GetImage ().DebugHeader != null) {
				m_debugHeaderStart = (uint) m_binaryWriter.BaseStream.Position;
				m_binaryWriter.Write (new byte [m_imgWriter.GetImage ().DebugHeader.GetSize ()]);
				m_binaryWriter.QuadAlign ();
			}

			m_mdStart = (uint) m_binaryWriter.BaseStream.Position;

			if (m_stringWriter.BaseStream.Length > 1) {
				CreateStream (MetadataStream.Strings);
				SetHeapSize (root.Streams.StringsHeap, m_stringWriter, 0x01);
				m_stringWriter.QuadAlign ();
			}

			if (m_guidWriter.BaseStream.Length > 0) {
				CreateStream (MetadataStream.GUID);
				SetHeapSize (root.Streams.GuidHeap, m_guidWriter, 0x02);
			}

			if (m_blobWriter.BaseStream.Length > 1) {
				CreateStream (MetadataStream.Blob);
				SetHeapSize (root.Streams.BlobHeap, m_blobWriter, 0x04);
				m_blobWriter.QuadAlign ();
			}

			if (m_usWriter.BaseStream.Length > 2) {
				CreateStream (MetadataStream.UserStrings);
				m_usWriter.QuadAlign ();
			}

			m_root.Header.MajorVersion = 1;
			m_root.Header.MinorVersion = 1;

			switch (m_runtime) {
			case TargetRuntime.NET_1_0 :
				m_root.Header.Version = "v1.0.3705";
				break;
			case TargetRuntime.NET_1_1 :
				m_root.Header.Version = "v1.1.4322";
				break;
			case TargetRuntime.NET_2_0 :
				m_root.Header.Version = "v2.0.50727";
				break;
			case TargetRuntime.NET_4_0 :
				m_root.Header.Version = "v4.0.20506";
				break;
			}

			m_root.Streams.TablesHeap.Tables.Accept (m_tableWriter);

			if (m_tWriter.BaseStream.Length == 0)
				m_root.Streams.Remove (m_root.Streams.TablesHeap.GetStream ());
		}

		public override void VisitMetadataRootHeader (MetadataRoot.MetadataRootHeader header)
		{
			m_binaryWriter.Write (header.Signature);
			m_binaryWriter.Write (header.MajorVersion);
			m_binaryWriter.Write (header.MinorVersion);
			m_binaryWriter.Write (header.Reserved);
			m_binaryWriter.Write (header.Version.Length + 3 & (~3));
			m_binaryWriter.Write (Encoding.ASCII.GetBytes (header.Version));
			m_binaryWriter.QuadAlign ();
			m_binaryWriter.Write (header.Flags);
			m_binaryWriter.Write ((ushort) m_root.Streams.Count);
		}

		public override void VisitMetadataStreamCollection (MetadataStreamCollection streams)
		{
			foreach (MetadataStream stream in streams) {
				MetadataStream.MetadataStreamHeader header = stream.Header;

				header.Offset = (uint) (m_binaryWriter.BaseStream.Position);
				m_binaryWriter.Write (header.Offset);
				MemoryBinaryWriter container;
				string name = header.Name;
				uint size = 0;
				switch (header.Name) {
				case MetadataStream.Tables :
					container = m_tWriter;
					size += 24; // header
					break;
				case MetadataStream.Strings :
					name += "\0\0\0\0";
					container = m_stringWriter;
					break;
				case MetadataStream.GUID :
					container = m_guidWriter;
					break;
				case MetadataStream.Blob :
					container = m_blobWriter;
					break;
				case MetadataStream.UserStrings :
					container = m_usWriter;
					break;
				default :
					throw new MetadataFormatException ("Unknown stream kind");
				}

				size += (uint) (container.BaseStream.Length + 3 & (~3));
				m_binaryWriter.Write (size);
				m_binaryWriter.Write (Encoding.ASCII.GetBytes (name));
				m_binaryWriter.QuadAlign ();
			}
		}

		void WriteMemStream (MemoryBinaryWriter writer)
		{
			m_binaryWriter.Write (writer);
			m_binaryWriter.QuadAlign ();
		}

		void PatchStreamHeaderOffset (MetadataHeap heap)
		{
			long pos = m_binaryWriter.BaseStream.Position;
			m_binaryWriter.BaseStream.Position = heap.GetStream ().Header.Offset;
			m_binaryWriter.Write ((uint) (pos - m_mdStart));
			m_binaryWriter.BaseStream.Position = pos;
		}

		public override void VisitGuidHeap (GuidHeap heap)
		{
			PatchStreamHeaderOffset (heap);
			WriteMemStream (m_guidWriter);
		}

		public override void VisitStringsHeap (StringsHeap heap)
		{
			PatchStreamHeaderOffset (heap);
			WriteMemStream (m_stringWriter);
		}

		public override void VisitTablesHeap (TablesHeap heap)
		{
			PatchStreamHeaderOffset (heap);
			m_binaryWriter.Write (heap.Reserved);
			switch (m_runtime) {
			case TargetRuntime.NET_1_0 :
			case TargetRuntime.NET_1_1 :
				heap.MajorVersion = 1;
				heap.MinorVersion = 0;
				break;
			case TargetRuntime.NET_2_0 :
			case TargetRuntime.NET_4_0 :
				heap.MajorVersion = 2;
				heap.MinorVersion = 0;
				break;
			}
			m_binaryWriter.Write (heap.MajorVersion);
			m_binaryWriter.Write (heap.MinorVersion);
			m_binaryWriter.Write (heap.HeapSizes);
			m_binaryWriter.Write (heap.Reserved2);
			m_binaryWriter.Write (heap.Valid);
			m_binaryWriter.Write (heap.Sorted);
			WriteMemStream (m_tWriter);
		}

		public override void VisitBlobHeap (BlobHeap heap)
		{
			PatchStreamHeaderOffset (heap);
			WriteMemStream (m_blobWriter);
		}

		public override void VisitUserStringsHeap (UserStringsHeap heap)
		{
			PatchStreamHeaderOffset (heap);
			WriteMemStream (m_usWriter);
		}

		void PatchHeader ()
		{
			Image img = m_imgWriter.GetImage ();

			img.CLIHeader.EntryPointToken = m_entryPointToken;

			if ((m_assembly.Name.Flags & AssemblyFlags.PublicKey) == 0)
				img.CLIHeader.Flags &= ~RuntimeImage.StrongNameSigned;

			if (m_mdSize > 0)
				img.CLIHeader.Metadata = new DataDirectory (
					img.TextSection.VirtualAddress + m_mdStart, m_imporTableStart - m_mdStart);

			if (m_resSize > 0)
				img.CLIHeader.Resources = new DataDirectory (
					img.TextSection.VirtualAddress + m_resStart, m_resSize);

			if (m_snsStart > 0)
				img.CLIHeader.StrongNameSignature = new DataDirectory (
					img.TextSection.VirtualAddress + m_snsStart, m_snsSize);

			if (m_debugHeaderStart > 0)
				img.PEOptionalHeader.DataDirectories.Debug = new DataDirectory (
					img.TextSection.VirtualAddress + m_debugHeaderStart, 0x1c);
		}

		public override void TerminateMetadataRoot (MetadataRoot root)
		{
			m_mdSize = (uint) (m_binaryWriter.BaseStream.Position - m_mdStart);
			m_imporTableStart = (uint) m_binaryWriter.BaseStream.Position;
			m_binaryWriter.Write (new byte [0x60]); // imports
			m_imgWriter.Initialize ();
			PatchHeader ();
			root.GetImage ().Accept (m_imgWriter);
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions