/*
* PERWAPI - An API for Reading and Writing PE Files
*
* Copyright (c) Diane Corney, Queensland University of Technology, 2004.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the PERWAPI Copyright as included with this
* distribution in the file PERWAPIcopyright.rtf.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY as is explained in the copyright notice.
*
* The author may be contacted at d.corney@qut.edu.au
*
* Version Date: 26/01/07
*/
using System;
using System.IO;
using System.Text;
using System.Collections;
namespace QUT.PERWAPI
{
/**************************************************************************/
// MetaData generated from user created descriptors
/**************************************************************************/
internal class MetaDataOut : MetaData
{
MetaDataStream strings, us, guid, blob;
MetaDataStream[] streams;
uint numStreams = 5;
uint tildeTide = 0, tildePadding = 0, tildeStart = 0;
uint numTables = 0, resourcesSize = 0;
ArrayList byteCodes = new ArrayList();
uint codeSize = 0, byteCodePadding = 0, metaDataSize = 0;
internal PEWriter output;
private byte heapSizes = 0;
MetaDataElement entryPoint;
long mdStart;
ArrayList resources;
private ArrayList[] tables = new ArrayList[NumMetaDataTables];
// Allow the debug mode to be set.
public bool Debug = false;
internal MetaDataOut()
: base()
{
}
// Out of line generation of metadata for MethodDef code.
// This avoids the ordering errors for method and param indices
// when the code of a method refers to methods/fields of classes
// that have not been entered in the table.
// (kjg March 2010)
internal void BuildCode() {
foreach (object method in tables[(int)MDTable.Method])
(method as MethodDef).TraverseCode(this);
}
Hashtable debugsigs = new Hashtable();
///
/// Get the debug signature for a local.
///
/// The local.
/// The signature.
internal DebugLocalSig GetDebugSig(Local loc)
{
byte[] b = loc.GetSig();
string s = BitConverter.ToString(b);
DebugLocalSig sig = (DebugLocalSig)debugsigs[s];
if (sig != null) return sig;
sig = new DebugLocalSig(b);
debugsigs.Add(s, sig);
return sig;
}
internal void InitMetaDataOut(PEWriter file)
{
// tilde = new MetaDataStream(tildeNameArray,false,0);
this.output = file;
streams = new MetaDataStream[5];
strings = new MetaDataStream(MetaData.stringsNameArray, new UTF8Encoding(), true);
us = new MetaDataStream(MetaData.usNameArray, new UnicodeEncoding(), true);
guid = new MetaDataStream(MetaData.guidNameArray, false);
blob = new MetaDataStream(MetaData.blobNameArray, new UnicodeEncoding(), true);
streams[1] = strings;
streams[2] = us;
streams[3] = guid;
streams[4] = blob;
}
internal uint Size()
{
//Console.WriteLine("metaData size = " + metaDataSize);
return metaDataSize;
}
internal uint AddToUSHeap(string str)
{
if (str == null) return 0;
return us.Add(str, true);
}
internal uint AddToStringsHeap(string str)
{
if ((str == null) || (str == "")) return 0;
return strings.Add(str, false);
}
internal uint AddToGUIDHeap(Guid guidNum)
{
return guid.Add(guidNum);
}
internal uint AddToBlobHeap(byte[] blobBytes)
{
if (blobBytes == null) return 0;
return blob.Add(blobBytes);
}
internal uint AddToBlobHeap(long val, uint numBytes)
{
return blob.Add(val, numBytes);
}
internal uint AddToBlobHeap(ulong val, uint numBytes)
{
return blob.Add(val, numBytes);
}
internal uint AddToBlobHeap(char ch)
{
return blob.Add(ch);
}
/*
internal uint AddToBlobHeap(byte val) {
return blob.Add(val);
}
internal uint AddToBlobHeap(sbyte val) {
return blob.Add(val);
}
internal uint AddToBlobHeap(ushort val) {
return blob.Add(val);
}
internal uint AddToBlobHeap(short val) {
return blob.Add(val);
}
internal uint AddToBlobHeap(uint val) {
return blob.Add(val);
}
internal uint AddToBlobHeap(int val) {
return blob.Add(val);
}
internal uint AddToBlobHeap(ulong val) {
return blob.Add(val);
}
internal uint AddToBlobHeap(long val) {
return blob.Add(val);
}
*/
internal uint AddToBlobHeap(float val)
{
return blob.Add(val);
}
internal uint AddToBlobHeap(double val)
{
return blob.Add(val);
}
internal uint AddToBlobHeap(string val)
{
return blob.Add(val, true);
}
private ArrayList GetTable(MDTable tableIx)
{
int tabIx = (int)tableIx;
if (tables[tabIx] == null)
{
tables[tabIx] = new ArrayList();
valid |= ((ulong)0x1 << tabIx);
// Console.WriteLine("after creating table " + tableIx + "(" + tabIx + ") valid = " + valid);
numTables++;
}
return tables[tabIx];
}
internal void AddToTable(MDTable tableIx, MetaDataElement elem) {
// Console.WriteLine("Adding element to table " + (uint)tableIx);
ArrayList table = GetTable(tableIx);
if (table.Contains(elem)) {
Console.Out.WriteLine("ERROR - element already in table " + tableIx);
return;
}
elem.Row = (uint)table.Count + 1;
table.Add(elem);
}
internal void ConditionalAddTypeSpec(MetaDataElement elem) {
ArrayList table = GetTable(MDTable.TypeSpec);
if (!table.Contains(elem)) {
elem.Row = (uint)table.Count + 1;
table.Add(elem);
}
}
internal uint TableIndex(MDTable tableIx)
{
if (tables[(int)tableIx] == null) return 1;
return (uint)tables[(int)tableIx].Count + 1;
}
internal uint AddCode(CILInstructions byteCode)
{
byteCodes.Add(byteCode);
uint offset = codeSize;
codeSize += byteCode.GetCodeSize();
return offset;
}
internal void SetEntryPoint(MetaDataElement ep)
{
entryPoint = ep;
}
internal uint AddResource(byte[] resBytes)
{
if (resources == null) resources = new ArrayList();
resources.Add(resBytes);
uint offset = resourcesSize;
resourcesSize += (uint)resBytes.Length + 4;
return offset;
}
internal void AddData(DataConstant cVal)
{
output.AddInitData(cVal);
}
internal static void CompressNum(byte[] arr, MemoryStream sig)
{
for (int ix = 0; ix < arr.Length; ix++) sig.WriteByte(arr[ix]);
}
internal uint CodeSize()
{
return codeSize + byteCodePadding;
}
internal uint GetResourcesSize() { return resourcesSize; }
private void SetStreamOffsets()
{
uint sizeOfHeaders = StreamHeaderSize + (uint)tildeNameArray.Length;
for (int i = 1; i < numStreams; i++)
{
sizeOfHeaders += streams[i].headerSize();
}
metaDataSize = MetaDataHeaderSize + sizeOfHeaders;
//Console.WriteLine("Size of meta data headers (tildeStart) = " + Hex.Long(metaDataSize));
tildeStart = metaDataSize;
metaDataSize += tildeTide + tildePadding;
//Console.WriteLine(tildeNameArray + " - size = " + (tildeTide + tildePadding));
for (int i = 1; i < numStreams; i++)
{
//Console.WriteLine("Stream " + i + " " + new String(streams[i].name) + " starts at " + Hex.Long(metaDataSize));
streams[i].Start = metaDataSize;
metaDataSize += streams[i].Size();
streams[i].WriteDetails();
}
if (largeStrings) heapSizes |= 0x01;
if (largeGUID) heapSizes |= 0x02;
if (largeBlob) heapSizes |= 0x04;
}
internal void CalcTildeStreamSize()
{
largeStrings = strings.LargeIx();
largeBlob = blob.LargeIx();
largeGUID = guid.LargeIx();
largeUS = us.LargeIx();
CalcElemSize();
//tilde.SetIndexSizes(strings.LargeIx(),us.LargeIx(),guid.LargeIx(),blob.LargeIx());
tildeTide = TildeHeaderSize;
tildeTide += 4 * numTables;
//Console.WriteLine("Tilde header + sizes = " + tildeTide);
for (int i = 0; i < NumMetaDataTables; i++)
{
if (tables[i] != null)
{
ArrayList table = tables[i];
// Console.WriteLine("Meta data table " + i + " at offset " + tildeTide);
tildeTide += (uint)table.Count * elemSize[i];
// Console.WriteLine("Metadata table " + i + " has size " + table.Count);
// Console.WriteLine("tildeTide = " + tildeTide);
}
}
if ((tildeTide % 4) != 0) tildePadding = 4 - (tildeTide % 4);
//Console.WriteLine("tildePadding = " + tildePadding);
}
internal void WriteTildeStream(PEWriter output)
{
long startTilde = output.Seek(0, SeekOrigin.Current);
//Console.WriteLine("Starting tilde output at offset " + Hex.Long(startTilde));
output.Write((uint)0); // Reserved
output.Write(output.verInfo.tsMajVer); // MajorVersion
output.Write(output.verInfo.tsMinVer); // MinorVersion
output.Write(heapSizes);
output.Write((byte)1); // Reserved
output.Write(valid);
output.Write(sorted);
for (int i = 0; i < NumMetaDataTables; i++)
{
if (tables[i] != null)
{
uint count = (uint)tables[i].Count;
output.Write(count);
}
}
long tabStart = output.Seek(0, SeekOrigin.Current);
//Console.WriteLine("Starting metaData tables at " + tabStart);
for (int i = 0; i < NumMetaDataTables; i++)
{
if (tables[i] != null)
{
//Console.WriteLine("Starting metaData table " + i + " at " + (output.Seek(0,SeekOrigin.Current) - startTilde));
ArrayList table = tables[i];
for (int j = 0; j < table.Count; j++)
{
((MetaDataElement)table[j]).Write(output);
}
}
}
// reset the typespec flags
if (tables[(int)MDTable.TypeSpec] != null)
{
ArrayList typeSpecTable = tables[(int)MDTable.TypeSpec];
for (int i = 0; i < typeSpecTable.Count; i++)
{
((Type)typeSpecTable[i]).typeSpecAdded = false;
}
}
//Console.WriteLine("Writing padding at " + output.Seek(0,SeekOrigin.Current));
for (int i = 0; i < tildePadding; i++) output.Write((byte)0);
}
private void SortTable(ArrayList mTable)
{
//Console.WriteLine("Sorting table");
if (mTable == null) return;
mTable.Sort();
for (int i = 0; i < mTable.Count; i++)
{
((MetaDataElement)mTable[i]).Row = (uint)i + 1;
}
}
internal void BuildMDTables()
{
// Check ordering of specific tables
// Constant, CustomAttribute, FieldMarshal, DeclSecurity, MethodSemantics
// ImplMap, NestedClass, GenericParam
// Need to load GenericParamConstraint AFTER GenericParam table in correct order
// The tables:
// InterfaceImpl, ClassLayout, FieldLayout, MethodImpl, FieldRVA
// will _ALWAYS_ be in the correct order as embedded in BuildMDTables
SortTable(tables[(int)MDTable.Constant]);
SortTable(tables[(int)MDTable.CustomAttribute]);
SortTable(tables[(int)MDTable.FieldMarshal]);
SortTable(tables[(int)MDTable.DeclSecurity]);
SortTable(tables[(int)MDTable.MethodSemantics]);
SortTable(tables[(int)MDTable.ImplMap]);
SortTable(tables[(int)MDTable.NestedClass]);
if (tables[(int)MDTable.GenericParam] != null)
{
SortTable(tables[(int)MDTable.GenericParam]);
// Now add GenericParamConstraints
for (int i = 0; i < tables[(int)MDTable.GenericParam].Count; i++)
{
((GenericParam)tables[(int)MDTable.GenericParam][i]).AddConstraints(this);
}
}
/*
// for bug in Whidbey GenericParam table ordering
int end = tables[(int)MDTable.TypeDef].Count;
int methEnd = 0;
if (tables[(int)MDTable.Method] != null) {
methEnd = tables[(int)MDTable.Method].Count;
}
for (int i=0; i < end; i++) {
((ClassDef)tables[(int)MDTable.TypeDef][i]).AddGenericsToTable(this);
if (methEnd > i)
((MethodDef)tables[(int)MDTable.Method][i]).AddGenericsToTable(this);
}
for (int i=end; i < methEnd; i++) {
((MethodDef)tables[(int)MDTable.Method][i]).AddGenericsToTable(this);
}
// end of bug fix
*/
for (int i = 0; i < tables.Length; i++)
{
if (tables[i] != null)
{
for (int j = 0; j < tables[i].Count; j++)
{
((MetaDataElement)tables[i][j]).BuildSignatures(this);
}
}
}
}
internal void SetIndexSizes()
{
for (int i = 0; i < NumMetaDataTables; i++)
{
if (tables[i] != null)
{
largeIx[i] = (uint)tables[i].Count > maxSmlIxSize;
}
}
for (int i = 0; i < CIxTables.Length; i++)
{
for (int j = 0; j < CIxTables[i].Length; j++)
{
int tabIx = CIxTables[i][j];
if (tables[tabIx] != null)
{
lgeCIx[i] = lgeCIx[i] | tables[tabIx].Count > CIxMaxMap[i];
}
}
}
}
internal void BuildMetaData()
{
SetIndexSizes();
for (int i = 1; i < numStreams; i++)
{
if (streams[i].Size() <= 1)
{
//Console.WriteLine("Stream " + new String(streams[i].name) + " has size 0");
for (int j = i + 1; j < numStreams; j++)
{
streams[i] = streams[j];
}
i--;
numStreams--;
}
else
streams[i].EndStream();
}
//Console.WriteLine("numStreams = " + numStreams);
CalcTildeStreamSize();
SetStreamOffsets();
byteCodePadding = NumToAlign(codeSize, 4);
if (entryPoint != null) output.SetEntryPoint(entryPoint.Token());
}
internal void WriteByteCodes(PEWriter output)
{
for (int i = 0; i < byteCodes.Count; i++)
{
((CILInstructions)byteCodes[i]).Write(output);
}
for (int i = 0; i < byteCodePadding; i++)
{
output.Write((byte)0);
}
}
internal void WriteResources(PEWriter output)
{
if (resources == null) return;
for (int i = 0; i < resources.Count; i++)
{
byte[] resBytes = (byte[])resources[i];
output.Write((uint)resBytes.Length);
output.Write(resBytes);
}
}
internal void WriteMetaData(PEWriter output)
{
this.output = output;
if (Diag.DiagOn)
{
mdStart = output.Seek(0, SeekOrigin.Current);
Console.WriteLine("Writing metaData at " + Hex.Long(mdStart));
}
output.Write(MetaDataSignature);
output.Write(output.verInfo.mdMajVer);
output.Write(output.verInfo.mdMinVer);
output.Write(0); // Reserved
output.Write(output.verInfo.netVerString.Length);
output.Write(output.verInfo.netVerString.ToCharArray()); // version string is already zero padded
output.Write((short)0); // Flags, reserved
output.Write((ushort)numStreams);
// write tilde header
output.Write(tildeStart);
output.Write(tildeTide + tildePadding);
output.Write(tildeNameArray);
for (int i = 1; i < numStreams; i++)
{
if (Diag.DiagOn)
Console.WriteLine("Stream " + new String(streams[i].name) + " should start at " + Hex.Long(streams[i].Start + mdStart));
streams[i].WriteHeader(output);
}
if (Diag.DiagOn)
{
Console.Write("Writing tilde stream at " + Hex.Long(output.Seek(0, SeekOrigin.Current)));
Console.WriteLine(" should be at " + Hex.Long(tildeStart + mdStart));
}
WriteTildeStream(output);
for (int i = 1; i < numStreams; i++)
{
if (Diag.DiagOn)
Console.WriteLine("Writing stream " + new String(streams[i].name) + " at " + Hex.Long(output.Seek(0, SeekOrigin.Current)));
streams[i].Write(output);
}
//Console.WriteLine("Finished Writing metaData at " + output.Seek(0,SeekOrigin.Current));
}
// internal bool LargeStringsIndex() { return strings.LargeIx(); }
// internal bool LargeGUIDIndex() { return guid.LargeIx(); }
// internal bool LargeUSIndex() { return us.LargeIx(); }
// internal bool LargeBlobIndex() { return blob.LargeIx(); }
private uint NumToAlign(uint val, uint alignVal)
{
if ((val % alignVal) == 0) return 0;
return alignVal - (val % alignVal);
}
internal void WriteCodedIndex(CIx code, MetaDataElement elem, PEWriter output)
{
uint ix = 0;
if (elem != null)
{
ix = (elem.Row << CIxShiftMap[(uint)code]) | elem.GetCodedIx(code);
// Console.WriteLine("coded index = " + ix + " row = " + elem.Row);
//} else {
// Console.WriteLine("elem for coded index is null");
}
if (lgeCIx[(uint)code])
output.Write(ix);
else
output.Write((ushort)ix);
}
}
}