Skip to content

Instantly share code, notes, and snippets.

@MichalStrehovsky
Last active October 31, 2023 21:22
Show Gist options
  • Save MichalStrehovsky/e69551d4ace3c22d51acb4a336e37083 to your computer and use it in GitHub Desktop.
Save MichalStrehovsky/e69551d4ace3c22d51acb4a336e37083 to your computer and use it in GitHub Desktop.
// This is a System.Reflection.Metadata writer to generate managed COFF OBJ files similar to
// what C++/CLI generates with /clr:pure option.
//
// This will generate a c:\\temp\\blah.obj file that contains a single method.
// You can inspect the managed content of the OBJ with ildasm (the GUI doesn't work for obj files, but
// you can run it from the command line and specify /out= to disassemble).
//
// Run "link.exe /debug blah.obj /entry:MyMethod /subsystem:console" to generate an EXE file.
//
// There's also debug information. You can create a fake c:\\temp\\il.il file with a couple
// irrelevant lines in it and step through the fake file in a debugger while debugging the EXE.
//
// You can inspect the debug info with cvdump.exe from https://github.com/Microsoft/microsoft-pdb/tree/master/cvdump.
//
using System;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata.Ecma335;
using System.IO;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var mdBuilder = new MetadataBuilder();
var h = mdBuilder.GetOrAddString("Hello");
mdBuilder.AddTypeDefinition(TypeAttributes.Class, default, mdBuilder.GetOrAddString("<Module>"), default, MetadataTokens.FieldDefinitionHandle(1), MetadataTokens.MethodDefinitionHandle(1));
BlobBuilder sigBuilder = new BlobBuilder();
BlobEncoder sigBlobEncoder = new BlobEncoder(sigBuilder);
var sigEncoder = sigBlobEncoder.MethodSignature();
sigEncoder.Parameters(0, out var rtEnc, out var parEnc);
rtEnc.Type().Int32();
var mdHandle = mdBuilder.AddMethodDefinition(
MethodAttributes.Static | MethodAttributes.Public,
MethodImplAttributes.Managed,
h,
mdBuilder.GetOrAddBlob(sigBuilder),
0,
MetadataTokens.ParameterHandle(1));
mdBuilder.AddModule(0,
mdBuilder.GetOrAddString("blah.dll"),
mdBuilder.GetOrAddGuid(Guid.Empty),
mdBuilder.GetOrAddGuid(Guid.Empty),
mdBuilder.GetOrAddGuid(Guid.Empty));
var coffHeaderBuilder = new CoffHeaderBuilder(Machine.I386, 0);
var symtab = new ManagedCoffSymbolTableBuilder(ManagedCoffBuilder.ClrTextSectionNumber, ObjectFeatures.PureMsil);
var codeviewSymbols = new CodeViewSymbolBuilder(coffHeaderBuilder);
CodeViewFileHandle file1 = codeviewSymbols.GetOrAddFile("c:\\Temp\\il.il");
var instructionStreamBuilder = new BlobBuilder();
var relocationStreamBuilder = new BlobBuilder();
var instructionStreamEncoder = new RelocatableMethodBodyStreamEncoder(instructionStreamBuilder, relocationStreamBuilder, symtab, coffHeaderBuilder, codeviewSymbols);
var encoder = new RelocatableInstructionEncoder(
new BlobBuilder(),
new MethodRelocationBuilder(),
new RelocatableControlFlowBuilder(),
new CodeViewLineNumberBuilder());
var lbl = encoder.DefineLabel();
encoder.MarkLineNumber(file1, 1);
encoder.LoadConstantI4(0);
encoder.Branch(ILOpCode.Brfalse, lbl);
encoder.MarkLineNumber(file1, 2);
encoder.Call(mdHandle);
encoder.OpCode(ILOpCode.Pop);
encoder.MarkLineNumber(file1, 3);
encoder.MarkLabel(lbl);
encoder.LoadConstantI4(0xBABE);
encoder.OpCode(ILOpCode.Ret);
instructionStreamEncoder.AddMethodBody(mdHandle, "MyMethod", encoder);
var root = new MetadataRootBuilder(mdBuilder);
var peB = new ManagedCoffBuilder(coffHeaderBuilder, root, symtab, codeviewSymbols, instructionStreamBuilder, relocationStreamBuilder);
var o = new BlobBuilder();
peB.Serialize(o);
using var fs = File.Create("c:\\temp\\blah.obj");
o.WriteContentTo(fs);
}
}
namespace System.Reflection.PortableExecutable
{
public class CodeViewLineNumberBuilder
{
private readonly List<LineNumberEntry> _entries = new List<LineNumberEntry>();
private struct LineNumberEntry
{
public readonly CodeViewFileHandle File { get; }
public readonly int CodeOffset { get; }
public readonly int LineNumber { get; }
public LineNumberEntry(CodeViewFileHandle file, int codeOffset, int lineNumber)
=> (File, CodeOffset, LineNumber) = (file, codeOffset, lineNumber);
}
public void AddLineNumber(CodeViewFileHandle file, int codeOffset, int lineNumber)
{
_entries.Add(new LineNumberEntry(file, codeOffset, lineNumber));
}
public void Reset()
{
_entries.Clear();
}
public void Serialize(BlobBuilder builder)
{
int fileId = _entries[0].File._index;
builder.WriteInt32(fileId);
builder.WriteInt32(_entries.Count);
builder.WriteInt32(12 + 8 * _entries.Count);
foreach (LineNumberEntry entry in _entries)
{
if (entry.File._index != fileId)
throw new NotSupportedException();
builder.WriteInt32(entry.CodeOffset);
builder.WriteUInt32(0x80000000 | (uint)entry.LineNumber);
}
}
}
public struct CodeViewFileHandle
{
internal readonly int _index;
internal CodeViewFileHandle(int index) => _index = index;
}
public class CodeViewSymbolBuilder
{
private readonly CoffHeaderBuilder _coffHeaderBuilder;
private readonly Dictionary<string, int> _stringTableIndex = new Dictionary<string, int>(StringComparer.Ordinal);
private readonly BlobBuilder _stringTable = new BlobBuilder();
private readonly Dictionary<string, CodeViewFileHandle> _fileIndex = new Dictionary<string, CodeViewFileHandle>(StringComparer.Ordinal);
private readonly BlobBuilder _fileTable = new BlobBuilder();
private readonly BlobBuilder _symbolAndLineNumbersBlob = new BlobBuilder();
private readonly BlobBuilder _relocationsBlob = new BlobBuilder();
public CodeViewSymbolBuilder(CoffHeaderBuilder headerBuilder)
=> _coffHeaderBuilder = headerBuilder;
private int GetOrAddString(string s)
{
if (_stringTableIndex.TryGetValue(s, out int result))
return result;
result = _stringTable.Count;
_stringTable.WriteUTF8(s);
_stringTable.WriteByte(0);
_stringTableIndex.Add(s, result);
return result;
}
public CodeViewFileHandle GetOrAddFile(string name)
{
if (_fileIndex.TryGetValue(name, out CodeViewFileHandle result))
return result;
result = new CodeViewFileHandle(_fileTable.Count);
_fileTable.WriteInt32(GetOrAddString(name));
_fileTable.WriteByte(0);
_fileTable.WriteByte(0);
_fileTable.Align(4);
_fileIndex.Add(name, result);
return result;
}
private void EmitSymbolsAndLineNumbersSectionReloc(CoffSymbolHandle coffSymbol)
{
new CoffRelocationEncoder(_coffHeaderBuilder, _relocationsBlob)
.AddSectionRelocation(_symbolAndLineNumbersBlob.Count + 4 /* Header */, coffSymbol);
}
private void EmitSymbolsAndLineNumbersSectionRelativeReloc(CoffSymbolHandle coffSymbol)
{
new CoffRelocationEncoder(_coffHeaderBuilder, _relocationsBlob)
.AddSectionRelativeRelocation(_symbolAndLineNumbersBlob.Count + 4 /* Header */, coffSymbol);
}
private void EmitSymbolsAndLineNumbersTokenReloc(CoffSymbolHandle coffSymbol)
{
new CoffRelocationEncoder(_coffHeaderBuilder, _relocationsBlob)
.AddTokenRelocation(_symbolAndLineNumbersBlob.Count + 4 /* Header */, coffSymbol);
}
public void AddMethodSymbol(string methodName, CoffSymbolHandle methodCoffSymbol, int methodCoffSymbolDelta, CoffSymbolHandle methodTokenSymbol, int codeSize /*, CodeViewLocalVariableBuilder */)
{
_symbolAndLineNumbersBlob.WriteUInt32(0xF1);
var sizeFixup = _symbolAndLineNumbersBlob.ReserveBytes(4);
var startOffset = _symbolAndLineNumbersBlob.Count;
_symbolAndLineNumbersBlob.WriteUInt16((ushort)(2 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 2 + 2 + 1 + methodName.Length + 1));
_symbolAndLineNumbersBlob.WriteUInt16(0x112a);
_symbolAndLineNumbersBlob.WriteUInt32(0);
_symbolAndLineNumbersBlob.WriteUInt32(0);
_symbolAndLineNumbersBlob.WriteUInt32(0);
_symbolAndLineNumbersBlob.WriteInt32(codeSize);
_symbolAndLineNumbersBlob.WriteUInt32(0);
_symbolAndLineNumbersBlob.WriteUInt32(0);
EmitSymbolsAndLineNumbersTokenReloc(methodTokenSymbol);
_symbolAndLineNumbersBlob.WriteUInt32(0);
EmitSymbolsAndLineNumbersSectionRelativeReloc(methodCoffSymbol);
_symbolAndLineNumbersBlob.WriteInt32(methodCoffSymbolDelta);
EmitSymbolsAndLineNumbersSectionReloc(methodCoffSymbol);
_symbolAndLineNumbersBlob.WriteInt16(0);
_symbolAndLineNumbersBlob.WriteByte(1);
_symbolAndLineNumbersBlob.WriteUInt16(0);
_symbolAndLineNumbersBlob.WriteUTF8(methodName);
_symbolAndLineNumbersBlob.WriteByte(0);
// frameproc
_symbolAndLineNumbersBlob.WriteUInt16(2 + 4 + 4 + 4 + 4 + 4 + 2 + 1 + 1 + 1 + 1);
_symbolAndLineNumbersBlob.WriteUInt16(0x1012);
_symbolAndLineNumbersBlob.WriteInt32(0);
_symbolAndLineNumbersBlob.WriteInt32(0);
_symbolAndLineNumbersBlob.WriteInt32(0);
_symbolAndLineNumbersBlob.WriteInt32(0);
_symbolAndLineNumbersBlob.WriteInt32(0);
_symbolAndLineNumbersBlob.WriteInt16(0);
// MAGIC
_symbolAndLineNumbersBlob.WriteInt32(0x00100200);
// end method
_symbolAndLineNumbersBlob.WriteUInt16(2);
_symbolAndLineNumbersBlob.WriteUInt16(0x114F);
new BlobWriter(sizeFixup).WriteInt32(_symbolAndLineNumbersBlob.Count - startOffset);
_symbolAndLineNumbersBlob.Align(4);
}
public void AddLineNumbers(CoffSymbolHandle methodCoffSymbol, int methodCoffSymbolDelta, int codeSize, CodeViewLineNumberBuilder lineNumbersBlob)
{
_symbolAndLineNumbersBlob.WriteUInt32(0xF2);
var sizeFixup = _symbolAndLineNumbersBlob.ReserveBytes(4);
int startOffset = _symbolAndLineNumbersBlob.Count;
EmitSymbolsAndLineNumbersSectionRelativeReloc(methodCoffSymbol);
_symbolAndLineNumbersBlob.WriteInt32(methodCoffSymbolDelta);
EmitSymbolsAndLineNumbersSectionReloc(methodCoffSymbol);
_symbolAndLineNumbersBlob.WriteInt16(0);
_symbolAndLineNumbersBlob.WriteInt16(0);
_symbolAndLineNumbersBlob.WriteInt32(codeSize);
lineNumbersBlob.Serialize(_symbolAndLineNumbersBlob);
new BlobWriter(sizeFixup).WriteInt32(_symbolAndLineNumbersBlob.Count - startOffset);
_symbolAndLineNumbersBlob.Align(4);
}
internal void Serialize(BlobBuilder builder)
{
builder.WriteUInt32(4); // version
if (_symbolAndLineNumbersBlob.Count > 0)
{
builder.LinkSuffix(_symbolAndLineNumbersBlob);
}
if (_stringTable.Count > 0)
{
builder.WriteUInt32(0xF3); // String table
builder.WriteInt32(_stringTable.Count);
builder.LinkSuffix(_stringTable);
builder.Align(4);
}
if (_fileTable.Count > 0)
{
builder.WriteUInt32(0xF4); // File checksums
builder.WriteInt32(_fileTable.Count);
builder.LinkSuffix(_fileTable);
builder.Align(4);
}
}
internal void SerializeRelocations(BlobBuilder builder)
{
builder.LinkSuffix(_relocationsBlob);
}
}
public readonly struct RelocatableExceptionRegionEncoder
{
private const int TableHeaderSize = 4;
private const int SmallRegionSize =
sizeof(short) + // Flags
sizeof(short) + // TryOffset
sizeof(byte) + // TryLength
sizeof(short) + // HandlerOffset
sizeof(byte) + // HandleLength
sizeof(int); // ClassToken | FilterOffset
private const int FatRegionSize =
sizeof(int) + // Flags
sizeof(int) + // TryOffset
sizeof(int) + // TryLength
sizeof(int) + // HandlerOffset
sizeof(int) + // HandleLength
sizeof(int); // ClassToken | FilterOffset
private const int ThreeBytesMaxValue = 0xffffff;
internal const int MaxSmallExceptionRegions = (byte.MaxValue - TableHeaderSize) / SmallRegionSize;
internal const int MaxExceptionRegions = (ThreeBytesMaxValue - TableHeaderSize) / FatRegionSize;
public BlobBuilder Builder { get; }
public BlobBuilder RelocationBuilder { get; }
public CoffHeaderBuilder HeaderBuilder { get; }
public ManagedCoffSymbolTableBuilder SymbolTableBuilder { get; }
public bool HasSmallFormat { get; }
internal RelocatableExceptionRegionEncoder(BlobBuilder builder, BlobBuilder relocationBuilder, CoffHeaderBuilder headerBuilder, ManagedCoffSymbolTableBuilder symTableBuilder, bool hasSmallFormat)
{
Builder = builder;
RelocationBuilder = relocationBuilder;
HeaderBuilder = headerBuilder;
SymbolTableBuilder = symTableBuilder;
HasSmallFormat = hasSmallFormat;
}
public static bool IsSmallRegionCount(int exceptionRegionCount) =>
unchecked((uint)exceptionRegionCount) <= MaxSmallExceptionRegions;
public static bool IsSmallExceptionRegion(int startOffset, int length) =>
unchecked((uint)startOffset) <= ushort.MaxValue && unchecked((uint)length) <= byte.MaxValue;
internal static bool IsSmallExceptionRegionFromBounds(int startOffset, int endOffset) =>
IsSmallExceptionRegion(startOffset, endOffset - startOffset);
internal static int GetExceptionTableSize(int exceptionRegionCount, bool isSmallFormat) =>
TableHeaderSize + exceptionRegionCount * (isSmallFormat ? SmallRegionSize : FatRegionSize);
internal static bool IsExceptionRegionCountInBounds(int exceptionRegionCount) =>
unchecked((uint)exceptionRegionCount) <= MaxExceptionRegions;
internal static bool IsValidCatchTypeHandle(EntityHandle catchType)
{
return !catchType.IsNil &&
(catchType.Kind == HandleKind.TypeDefinition ||
catchType.Kind == HandleKind.TypeSpecification ||
catchType.Kind == HandleKind.TypeReference);
}
internal static RelocatableExceptionRegionEncoder SerializeTableHeader(BlobBuilder builder, BlobBuilder relocationBuilder, CoffHeaderBuilder headerBuilder, ManagedCoffSymbolTableBuilder symTableBuilder, int exceptionRegionCount, bool hasSmallRegions)
{
Debug.Assert(exceptionRegionCount > 0);
const byte EHTableFlag = 0x01;
const byte FatFormatFlag = 0x40;
bool hasSmallFormat = hasSmallRegions && IsSmallRegionCount(exceptionRegionCount);
int dataSize = GetExceptionTableSize(exceptionRegionCount, hasSmallFormat);
builder.Align(4);
if (hasSmallFormat)
{
builder.WriteByte(EHTableFlag);
builder.WriteByte(unchecked((byte)dataSize));
builder.WriteInt16(0);
}
else
{
Debug.Assert(dataSize <= 0x00ffffff);
builder.WriteByte(EHTableFlag | FatFormatFlag);
builder.WriteByte(unchecked((byte)dataSize));
builder.WriteUInt16(unchecked((ushort)(dataSize >> 8)));
}
return new RelocatableExceptionRegionEncoder(builder, relocationBuilder, headerBuilder, symTableBuilder, hasSmallFormat);
}
public RelocatableExceptionRegionEncoder AddFinally(int tryOffset, int tryLength, int handlerOffset, int handlerLength)
{
return Add(ExceptionRegionKind.Finally, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), 0);
}
public RelocatableExceptionRegionEncoder AddFault(int tryOffset, int tryLength, int handlerOffset, int handlerLength)
{
return Add(ExceptionRegionKind.Fault, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), 0);
}
public RelocatableExceptionRegionEncoder AddCatch(int tryOffset, int tryLength, int handlerOffset, int handlerLength, EntityHandle catchType)
{
return Add(ExceptionRegionKind.Catch, tryOffset, tryLength, handlerOffset, handlerLength, catchType, 0);
}
public RelocatableExceptionRegionEncoder AddFilter(int tryOffset, int tryLength, int handlerOffset, int handlerLength, int filterOffset)
{
return Add(ExceptionRegionKind.Filter, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), filterOffset);
}
public RelocatableExceptionRegionEncoder Add(
ExceptionRegionKind kind,
int tryOffset,
int tryLength,
int handlerOffset,
int handlerLength,
EntityHandle catchType = default(EntityHandle),
int filterOffset = 0)
{
if (Builder == null)
{
throw new InvalidOperationException();
}
if (HasSmallFormat)
{
if (unchecked((ushort)tryOffset) != tryOffset) throw new ArgumentOutOfRangeException(nameof(tryOffset));
if (unchecked((byte)tryLength) != tryLength) throw new ArgumentOutOfRangeException(nameof(tryLength));
if (unchecked((ushort)handlerOffset) != handlerOffset) throw new ArgumentOutOfRangeException(nameof(handlerOffset));
if (unchecked((byte)handlerLength) != handlerLength) throw new ArgumentOutOfRangeException(nameof(handlerLength));
}
else
{
if (tryOffset < 0) throw new ArgumentOutOfRangeException(nameof(tryOffset));
if (tryLength < 0) throw new ArgumentOutOfRangeException(nameof(tryLength));
if (handlerOffset < 0) throw new ArgumentOutOfRangeException(nameof(handlerOffset));
if (handlerLength < 0) throw new ArgumentOutOfRangeException(nameof(handlerLength));
}
int catchTokenOrOffset;
bool isToken;
switch (kind)
{
case ExceptionRegionKind.Catch:
if (!IsValidCatchTypeHandle(catchType))
{
throw new ArgumentException(nameof(catchType));
}
catchTokenOrOffset = MetadataTokens.GetToken(catchType);
isToken = true;
break;
case ExceptionRegionKind.Filter:
if (filterOffset < 0)
{
throw new ArgumentOutOfRangeException(nameof(filterOffset));
}
catchTokenOrOffset = filterOffset;
isToken = false;
break;
case ExceptionRegionKind.Finally:
case ExceptionRegionKind.Fault:
catchTokenOrOffset = 0;
isToken = false;
break;
default:
throw new ArgumentOutOfRangeException(nameof(kind));
}
AddUnchecked(kind, tryOffset, tryLength, handlerOffset, handlerLength, catchTokenOrOffset, isToken);
return this;
}
internal void AddUnchecked(
ExceptionRegionKind kind,
int tryOffset,
int tryLength,
int handlerOffset,
int handlerLength,
int catchTokenOrOffset,
bool isToken)
{
if (HasSmallFormat)
{
Builder.WriteUInt16((ushort)kind);
Builder.WriteUInt16((ushort)tryOffset);
Builder.WriteByte((byte)tryLength);
Builder.WriteUInt16((ushort)handlerOffset);
Builder.WriteByte((byte)handlerLength);
}
else
{
Builder.WriteInt32((int)kind);
Builder.WriteInt32(tryOffset);
Builder.WriteInt32(tryLength);
Builder.WriteInt32(handlerOffset);
Builder.WriteInt32(handlerLength);
}
if (isToken)
{
new ManagedCoffRelocationEncoder(HeaderBuilder, Builder, SymbolTableBuilder)
.AddClrRelocation(Builder.Count, catchTokenOrOffset);
Builder.WriteInt32(0);
}
else
{
Builder.WriteInt32(catchTokenOrOffset);
}
}
}
public sealed class RelocatableControlFlowBuilder
{
private readonly struct BranchInfo
{
internal readonly int ILOffset;
internal readonly LabelHandle Label;
private readonly byte _opCode;
internal ILOpCode OpCode => (ILOpCode)_opCode;
internal BranchInfo(int ilOffset, LabelHandle label, ILOpCode opCode)
{
ILOffset = ilOffset;
Label = label;
_opCode = (byte)opCode;
}
internal int GetBranchDistance(ImmutableArray<int>.Builder labels, ILOpCode branchOpCode, int branchILOffset, bool isShortBranch)
{
int labelTargetOffset = labels[Label.Id - 1];
if (labelTargetOffset < 0)
{
throw new InvalidOperationException(Label.Id.ToString());
}
int branchInstructionSize = 1 + (isShortBranch ? sizeof(sbyte) : sizeof(int));
int distance = labelTargetOffset - (ILOffset + branchInstructionSize);
if (isShortBranch && unchecked((sbyte)distance) != distance)
{
// We could potentially implement algorithm that automatically fixes up branch instructions to accomodate for bigger distances (short vs long),
// however an optimal algorithm would be rather complex (something like: calculate topological ordering of crossing branch instructions
// and then use fixed point to eliminate cycles). If the caller doesn't care about optimal IL size they can use long branches whenever the
// distance is unknown upfront. If they do they probably implement more sophisticated algorithm for IL layout optimization already.
throw new InvalidOperationException();
}
return distance;
}
}
internal readonly struct ExceptionHandlerInfo
{
public readonly ExceptionRegionKind Kind;
public readonly LabelHandle TryStart, TryEnd, HandlerStart, HandlerEnd, FilterStart;
public readonly EntityHandle CatchType;
public ExceptionHandlerInfo(
ExceptionRegionKind kind,
LabelHandle tryStart,
LabelHandle tryEnd,
LabelHandle handlerStart,
LabelHandle handlerEnd,
LabelHandle filterStart,
EntityHandle catchType)
{
Kind = kind;
TryStart = tryStart;
TryEnd = tryEnd;
HandlerStart = handlerStart;
HandlerEnd = handlerEnd;
FilterStart = filterStart;
CatchType = catchType;
}
}
private readonly ImmutableArray<BranchInfo>.Builder _branches;
private readonly ImmutableArray<int>.Builder _labels;
private ImmutableArray<ExceptionHandlerInfo>.Builder _lazyExceptionHandlers;
public RelocatableControlFlowBuilder()
{
_branches = ImmutableArray.CreateBuilder<BranchInfo>();
_labels = ImmutableArray.CreateBuilder<int>();
}
internal void Clear()
{
_branches.Clear();
_labels.Clear();
_lazyExceptionHandlers?.Clear();
}
internal LabelHandle AddLabel()
{
_labels.Add(-1);
unsafe
{
int labelHandle = _labels.Count;
return *(LabelHandle*)&labelHandle;
}
}
internal void AddBranch(int ilOffset, LabelHandle label, ILOpCode opCode)
{
Debug.Assert(ilOffset >= 0);
ValidateLabel(label, nameof(label));
_branches.Add(new BranchInfo(ilOffset, label, opCode));
}
internal void MarkLabel(int ilOffset, LabelHandle label)
{
Debug.Assert(ilOffset >= 0);
ValidateLabel(label, nameof(label));
_labels[label.Id - 1] = ilOffset;
}
private int GetLabelOffsetChecked(LabelHandle label)
{
int offset = _labels[label.Id - 1];
if (offset < 0)
{
throw new InvalidOperationException(label.Id.ToString());
}
return offset;
}
private void ValidateLabel(LabelHandle label, string parameterName)
{
if (label.IsNil)
{
throw new ArgumentNullException(parameterName);
}
if (label.Id > _labels.Count)
{
throw new InvalidOperationException(parameterName);
}
}
public void AddFinallyRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd) =>
AddExceptionRegion(ExceptionRegionKind.Finally, tryStart, tryEnd, handlerStart, handlerEnd);
public void AddFaultRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd) =>
AddExceptionRegion(ExceptionRegionKind.Fault, tryStart, tryEnd, handlerStart, handlerEnd);
public void AddCatchRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, EntityHandle catchType)
{
if (!RelocatableExceptionRegionEncoder.IsValidCatchTypeHandle(catchType))
{
throw new ArgumentException(nameof(catchType));
}
AddExceptionRegion(ExceptionRegionKind.Catch, tryStart, tryEnd, handlerStart, handlerEnd, catchType: catchType);
}
public void AddFilterRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, LabelHandle filterStart)
{
ValidateLabel(filterStart, nameof(filterStart));
AddExceptionRegion(ExceptionRegionKind.Filter, tryStart, tryEnd, handlerStart, handlerEnd, filterStart: filterStart);
}
private void AddExceptionRegion(
ExceptionRegionKind kind,
LabelHandle tryStart,
LabelHandle tryEnd,
LabelHandle handlerStart,
LabelHandle handlerEnd,
LabelHandle filterStart = default(LabelHandle),
EntityHandle catchType = default(EntityHandle))
{
ValidateLabel(tryStart, nameof(tryStart));
ValidateLabel(tryEnd, nameof(tryEnd));
ValidateLabel(handlerStart, nameof(handlerStart));
ValidateLabel(handlerEnd, nameof(handlerEnd));
if (_lazyExceptionHandlers == null)
{
_lazyExceptionHandlers = ImmutableArray.CreateBuilder<ExceptionHandlerInfo>();
}
_lazyExceptionHandlers.Add(new ExceptionHandlerInfo(kind, tryStart, tryEnd, handlerStart, handlerEnd, filterStart, catchType));
}
private IEnumerable<BranchInfo> Branches => _branches;
private IEnumerable<int> Labels => _labels;
internal int BranchCount => _branches.Count;
internal int ExceptionHandlerCount => _lazyExceptionHandlers?.Count ?? 0;
internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBuilder)
{
var branch = _branches[0];
int branchIndex = 0;
// offset within the source builder
int srcOffset = 0;
// current offset within the current source blob
int srcBlobOffset = 0;
foreach (Blob srcBlob in srcBuilder.GetBlobs())
{
byte[] srcBlobBuffer = srcBlob.GetBytes().Array;
Debug.Assert(
srcBlobOffset == 0 ||
srcBlobOffset == 1 && srcBlobBuffer[0] == 0xff ||
srcBlobOffset == 4 && srcBlobBuffer[0] == 0xff && srcBlobBuffer[1] == 0xff && srcBlobBuffer[2] == 0xff && srcBlobBuffer[3] == 0xff);
while (true)
{
// copy bytes preceding the next branch, or till the end of the blob:
int chunkSize = Math.Min(branch.ILOffset - srcOffset, srcBlob.Length - srcBlobOffset);
dstBuilder.WriteBytes(srcBlobBuffer, srcBlobOffset, chunkSize);
srcOffset += chunkSize;
srcBlobOffset += chunkSize;
// there is no branch left in the blob:
if (srcBlobOffset == srcBlob.Length)
{
srcBlobOffset = 0;
break;
}
Debug.Assert(srcBlobBuffer[srcBlobOffset] == (byte)branch.OpCode);
int operandSize = branch.OpCode.GetBranchOperandSize();
bool isShortInstruction = operandSize == 1;
// Note: the 4B operand is contiguous since we wrote it via BlobBuilder.WriteInt32()
Debug.Assert(
srcBlobOffset + 1 == srcBlob.Length ||
(isShortInstruction ?
srcBlobBuffer[srcBlobOffset + 1] == 0xff :
BitConverter.ToUInt32(srcBlobBuffer, srcBlobOffset + 1) == 0xffffffff));
// write branch opcode:
dstBuilder.WriteByte(srcBlobBuffer[srcBlobOffset]);
int branchDistance = branch.GetBranchDistance(_labels, branch.OpCode, srcOffset, isShortInstruction);
// write branch operand:
if (isShortInstruction)
{
dstBuilder.WriteSByte((sbyte)branchDistance);
}
else
{
dstBuilder.WriteInt32(branchDistance);
}
srcOffset += sizeof(byte) + operandSize;
// next branch:
branchIndex++;
if (branchIndex == _branches.Count)
{
branch = new BranchInfo(int.MaxValue, label: default, opCode: default);
}
else
{
branch = _branches[branchIndex];
}
// the branch starts at the very end and its operand is in the next blob:
if (srcBlobOffset == srcBlob.Length - 1)
{
srcBlobOffset = operandSize;
break;
}
// skip fake branch instruction:
srcBlobOffset += sizeof(byte) + operandSize;
}
}
}
internal void SerializeExceptionTable(BlobBuilder builder, BlobBuilder relocationBuilder, CoffHeaderBuilder headerBuilder, ManagedCoffSymbolTableBuilder symTableBuilder)
{
if (_lazyExceptionHandlers == null || _lazyExceptionHandlers.Count == 0)
{
return;
}
var regionEncoder = RelocatableExceptionRegionEncoder.SerializeTableHeader(builder, relocationBuilder, headerBuilder, symTableBuilder, _lazyExceptionHandlers.Count, HasSmallExceptionRegions());
foreach (var handler in _lazyExceptionHandlers)
{
// Note that labels have been validated when added to the handler list,
// they might not have been marked though.
int tryStart = GetLabelOffsetChecked(handler.TryStart);
int tryEnd = GetLabelOffsetChecked(handler.TryEnd);
int handlerStart = GetLabelOffsetChecked(handler.HandlerStart);
int handlerEnd = GetLabelOffsetChecked(handler.HandlerEnd);
if (tryStart > tryEnd)
{
throw new InvalidOperationException();
}
if (handlerStart > handlerEnd)
{
throw new InvalidOperationException();
}
int catchTokenOrOffset = handler.Kind switch
{
ExceptionRegionKind.Catch => MetadataTokens.GetToken(handler.CatchType),
ExceptionRegionKind.Filter => GetLabelOffsetChecked(handler.FilterStart),
_ => 0,
};
regionEncoder.AddUnchecked(
handler.Kind,
tryStart,
tryEnd - tryStart,
handlerStart,
handlerEnd - handlerStart,
catchTokenOrOffset,
handler.Kind == ExceptionRegionKind.Catch);
}
}
private bool HasSmallExceptionRegions()
{
Debug.Assert(_lazyExceptionHandlers != null);
if (!RelocatableExceptionRegionEncoder.IsSmallRegionCount(_lazyExceptionHandlers.Count))
{
return false;
}
foreach (var handler in _lazyExceptionHandlers)
{
if (!RelocatableExceptionRegionEncoder.IsSmallExceptionRegionFromBounds(GetLabelOffsetChecked(handler.TryStart), GetLabelOffsetChecked(handler.TryEnd)) ||
!RelocatableExceptionRegionEncoder.IsSmallExceptionRegionFromBounds(GetLabelOffsetChecked(handler.HandlerStart), GetLabelOffsetChecked(handler.HandlerEnd)))
{
return false;
}
}
return true;
}
}
public class MethodRelocationBuilder
{
private struct Relocation
{
public readonly int Offset;
public readonly int Token;
public Relocation(int offset, int token) => (Offset, Token) = (offset, token);
}
private List<Relocation> _relocations = new List<Relocation>();
public void Reset() => _relocations.Clear();
public void AddTokenRelocation(int offset, int token) => _relocations.Add(new Relocation(offset, token));
internal void Append(BlobBuilder relocationStream, int delta, CoffHeaderBuilder headerBuilder, ManagedCoffSymbolTableBuilder symbolTableBuilder)
{
var encoder = new ManagedCoffRelocationEncoder(headerBuilder, relocationStream, symbolTableBuilder);
foreach (Relocation r in _relocations)
{
encoder.AddClrRelocation(r.Offset + delta, r.Token);
}
}
}
public readonly struct CoffRelocationEncoder
{
public CoffHeaderBuilder HeaderBuilder { get; }
public BlobBuilder Builder { get; }
public CoffRelocationEncoder(CoffHeaderBuilder headerBuilder, BlobBuilder builder)
=> (HeaderBuilder, Builder) = (headerBuilder, builder);
public void AddSectionRelocation(int offset, CoffSymbolHandle coffSymbol)
{
Builder.WriteInt32(offset);
Builder.WriteInt32(coffSymbol._value);
Builder.WriteUInt16(HeaderBuilder.Machine switch
{
Machine.I386 => 0xA,
_ => throw new NotImplementedException(),
});
}
public void AddSectionRelativeRelocation(int offset, CoffSymbolHandle coffSymbol)
{
Builder.WriteInt32(offset);
Builder.WriteInt32(coffSymbol._value);
Builder.WriteUInt16(HeaderBuilder.Machine switch
{
Machine.I386 => 0xB,
_ => throw new NotImplementedException(),
});
}
public void AddTokenRelocation(int offset, CoffSymbolHandle coffSymbol)
{
Builder.WriteInt32(offset);
Builder.WriteInt32(coffSymbol._value);
Builder.WriteUInt16(HeaderBuilder.Machine switch
{
Machine.I386 => 0xC,
_ => throw new NotImplementedException(),
});
}
}
public readonly struct ManagedCoffRelocationEncoder
{
public CoffHeaderBuilder HeaderBuilder { get; }
public BlobBuilder Builder { get; }
public ManagedCoffSymbolTableBuilder SymbolTableBuilder { get; }
public ManagedCoffRelocationEncoder(CoffHeaderBuilder headerBuilder, BlobBuilder builder, ManagedCoffSymbolTableBuilder symbolTableBuilder)
=> (HeaderBuilder, Builder, SymbolTableBuilder) = (headerBuilder, builder, symbolTableBuilder);
public void AddClrRelocation(int offset, int token)
{
string symbolName = token.ToString("X8");
CoffSymbolHandle tokenSymbol = SymbolTableBuilder.GetOrAddCoffSymbol(symbolName, 0, (ushort)SymbolTableBuilder._clrTextSectionNumber, CoffSymbolType.Null, CoffSymbolStorageClass.ClrToken, 0);
new CoffRelocationEncoder(HeaderBuilder, Builder)
.AddTokenRelocation(offset, tokenSymbol);
}
}
public readonly struct RelocatableInstructionEncoder
{
public BlobBuilder CodeBuilder { get; }
public MethodRelocationBuilder RelocationBuilder { get; }
public RelocatableControlFlowBuilder ControlFlowBuilder { get; }
public CodeViewLineNumberBuilder LineNumberBuilder { get; }
public RelocatableInstructionEncoder(BlobBuilder codeBuilder, MethodRelocationBuilder relocationBuilder = null, RelocatableControlFlowBuilder controlFlowBuilder = null, CodeViewLineNumberBuilder lineNumberBuilder = null)
{
if (codeBuilder == null)
{
throw new ArgumentNullException();
}
CodeBuilder = codeBuilder;
RelocationBuilder = relocationBuilder;
ControlFlowBuilder = controlFlowBuilder;
LineNumberBuilder = lineNumberBuilder;
}
public int Offset => CodeBuilder.Count;
public void OpCode(ILOpCode code)
{
if (unchecked((byte)code) == (ushort)code)
{
CodeBuilder.WriteByte((byte)code);
}
else
{
CodeBuilder.WriteUInt16BE((ushort)code);
}
}
public void Token(EntityHandle handle)
{
Token(MetadataTokens.GetToken(handle));
}
public void Token(int token)
{
GetRelocationBuilder().AddTokenRelocation(CodeBuilder.Count, token);
CodeBuilder.WriteInt32(0);
}
public void LoadString(UserStringHandle handle)
{
OpCode(ILOpCode.Ldstr);
Token(MetadataTokens.GetToken(handle));
}
public void Call(EntityHandle methodHandle)
{
if (methodHandle.Kind != HandleKind.MethodDefinition &&
methodHandle.Kind != HandleKind.MethodSpecification &&
methodHandle.Kind != HandleKind.MemberReference)
{
throw new ArgumentException(nameof(methodHandle));
}
OpCode(ILOpCode.Call);
Token(methodHandle);
}
public void Call(MethodDefinitionHandle methodHandle)
{
OpCode(ILOpCode.Call);
Token(methodHandle);
}
public void Call(MethodSpecificationHandle methodHandle)
{
OpCode(ILOpCode.Call);
Token(methodHandle);
}
public void Call(MemberReferenceHandle methodHandle)
{
OpCode(ILOpCode.Call);
Token(methodHandle);
}
public void CallIndirect(StandaloneSignatureHandle signature)
{
OpCode(ILOpCode.Calli);
Token(signature);
}
public void LoadConstantI4(int value)
{
ILOpCode code;
switch (value)
{
case -1: code = ILOpCode.Ldc_i4_m1; break;
case 0: code = ILOpCode.Ldc_i4_0; break;
case 1: code = ILOpCode.Ldc_i4_1; break;
case 2: code = ILOpCode.Ldc_i4_2; break;
case 3: code = ILOpCode.Ldc_i4_3; break;
case 4: code = ILOpCode.Ldc_i4_4; break;
case 5: code = ILOpCode.Ldc_i4_5; break;
case 6: code = ILOpCode.Ldc_i4_6; break;
case 7: code = ILOpCode.Ldc_i4_7; break;
case 8: code = ILOpCode.Ldc_i4_8; break;
default:
if (unchecked((sbyte)value == value))
{
OpCode(ILOpCode.Ldc_i4_s);
CodeBuilder.WriteSByte((sbyte)value);
}
else
{
OpCode(ILOpCode.Ldc_i4);
CodeBuilder.WriteInt32(value);
}
return;
}
OpCode(code);
}
public void LoadConstantI8(long value)
{
OpCode(ILOpCode.Ldc_i8);
CodeBuilder.WriteInt64(value);
}
public void LoadConstantR4(float value)
{
OpCode(ILOpCode.Ldc_r4);
CodeBuilder.WriteSingle(value);
}
public void LoadConstantR8(double value)
{
OpCode(ILOpCode.Ldc_r8);
CodeBuilder.WriteDouble(value);
}
public void LoadLocal(int slotIndex)
{
switch (slotIndex)
{
case 0: OpCode(ILOpCode.Ldloc_0); break;
case 1: OpCode(ILOpCode.Ldloc_1); break;
case 2: OpCode(ILOpCode.Ldloc_2); break;
case 3: OpCode(ILOpCode.Ldloc_3); break;
default:
if (unchecked((uint)slotIndex) <= byte.MaxValue)
{
OpCode(ILOpCode.Ldloc_s);
CodeBuilder.WriteByte((byte)slotIndex);
}
else if (slotIndex > 0)
{
OpCode(ILOpCode.Ldloc);
CodeBuilder.WriteInt32(slotIndex);
}
else
{
throw new ArgumentOutOfRangeException(nameof(slotIndex));
}
break;
}
}
public void StoreLocal(int slotIndex)
{
switch (slotIndex)
{
case 0: OpCode(ILOpCode.Stloc_0); break;
case 1: OpCode(ILOpCode.Stloc_1); break;
case 2: OpCode(ILOpCode.Stloc_2); break;
case 3: OpCode(ILOpCode.Stloc_3); break;
default:
if (unchecked((uint)slotIndex) <= byte.MaxValue)
{
OpCode(ILOpCode.Stloc_s);
CodeBuilder.WriteByte((byte)slotIndex);
}
else if (slotIndex > 0)
{
OpCode(ILOpCode.Stloc);
CodeBuilder.WriteInt32(slotIndex);
}
else
{
throw new ArgumentOutOfRangeException(nameof(slotIndex));
}
break;
}
}
public void LoadLocalAddress(int slotIndex)
{
if (unchecked((uint)slotIndex) <= byte.MaxValue)
{
OpCode(ILOpCode.Ldloca_s);
CodeBuilder.WriteByte((byte)slotIndex);
}
else if (slotIndex > 0)
{
OpCode(ILOpCode.Ldloca);
CodeBuilder.WriteInt32(slotIndex);
}
else
{
throw new ArgumentOutOfRangeException(nameof(slotIndex));
}
}
public void LoadArgument(int argumentIndex)
{
switch (argumentIndex)
{
case 0: OpCode(ILOpCode.Ldarg_0); break;
case 1: OpCode(ILOpCode.Ldarg_1); break;
case 2: OpCode(ILOpCode.Ldarg_2); break;
case 3: OpCode(ILOpCode.Ldarg_3); break;
default:
if (unchecked((uint)argumentIndex) <= byte.MaxValue)
{
OpCode(ILOpCode.Ldarg_s);
CodeBuilder.WriteByte((byte)argumentIndex);
}
else if (argumentIndex > 0)
{
OpCode(ILOpCode.Ldarg);
CodeBuilder.WriteInt32(argumentIndex);
}
else
{
throw new ArgumentOutOfRangeException(nameof(argumentIndex));
}
break;
}
}
public void LoadArgumentAddress(int argumentIndex)
{
if (unchecked((uint)argumentIndex) <= byte.MaxValue)
{
OpCode(ILOpCode.Ldarga_s);
CodeBuilder.WriteByte((byte)argumentIndex);
}
else if (argumentIndex > 0)
{
OpCode(ILOpCode.Ldarga);
CodeBuilder.WriteInt32(argumentIndex);
}
else
{
throw new ArgumentOutOfRangeException(nameof(argumentIndex));
}
}
public void StoreArgument(int argumentIndex)
{
if (unchecked((uint)argumentIndex) <= byte.MaxValue)
{
OpCode(ILOpCode.Starg_s);
CodeBuilder.WriteByte((byte)argumentIndex);
}
else if (argumentIndex > 0)
{
OpCode(ILOpCode.Starg);
CodeBuilder.WriteInt32(argumentIndex);
}
else
{
throw new ArgumentOutOfRangeException(nameof(argumentIndex));
}
}
public LabelHandle DefineLabel()
{
return GetBranchBuilder().AddLabel();
}
public void Branch(ILOpCode code, LabelHandle label)
{
// throws if code is not a branch:
int size = code.GetBranchOperandSize();
GetBranchBuilder().AddBranch(Offset, label, code);
OpCode(code);
// -1 points in the middle of the branch instruction and is thus invalid.
// We want to produce invalid IL so that if the caller doesn't patch the branches
// the branch instructions will be invalid in an obvious way.
if (size == 1)
{
CodeBuilder.WriteSByte(-1);
}
else
{
Debug.Assert(size == 4);
CodeBuilder.WriteInt32(-1);
}
}
public void MarkLabel(LabelHandle label)
{
GetBranchBuilder().MarkLabel(Offset, label);
}
public void MarkLineNumber(CodeViewFileHandle fileId, int lineNumber)
{
GetLineNumberBuilder().AddLineNumber(fileId, CodeBuilder.Count, lineNumber);
}
private RelocatableControlFlowBuilder GetBranchBuilder()
{
if (ControlFlowBuilder == null)
{
throw new InvalidOperationException();
}
return ControlFlowBuilder;
}
private MethodRelocationBuilder GetRelocationBuilder() => RelocationBuilder ?? throw new InvalidOperationException();
private CodeViewLineNumberBuilder GetLineNumberBuilder() => LineNumberBuilder ?? throw new InvalidOperationException();
}
public readonly struct RelocatableMethodBodyStreamEncoder
{
public BlobBuilder Builder { get; }
public BlobBuilder RelocationBuilder { get; }
public ManagedCoffSymbolTableBuilder SymbolTableBuilder { get; }
public CoffHeaderBuilder HeaderBuilder { get; }
public CodeViewSymbolBuilder CodeViewSymbolBuilder { get; }
public RelocatableMethodBodyStreamEncoder(BlobBuilder bodyStreamBuilder, BlobBuilder relocationStreamBuilder, ManagedCoffSymbolTableBuilder symTabBuilder, CoffHeaderBuilder headerBuilder, CodeViewSymbolBuilder codeViewSymbolBuilder)
{
Builder = bodyStreamBuilder;
RelocationBuilder = relocationStreamBuilder;
SymbolTableBuilder = symTabBuilder;
HeaderBuilder = headerBuilder;
CodeViewSymbolBuilder = codeViewSymbolBuilder;
}
public int AddMethodBody(
MethodDefinitionHandle metadataHandle,
string coffSymbolName,
RelocatableInstructionEncoder instructionEncoder,
int maxStack = 8,
StandaloneSignatureHandle localVariablesSignature = default,
MethodBodyAttributes attributes = MethodBodyAttributes.InitLocals,
bool hasDynamicStackAllocation = false)
{
if (unchecked((uint)maxStack) > ushort.MaxValue)
{
throw new ArgumentOutOfRangeException(nameof(maxStack));
}
// The branch fixup code expects the operands of branch instructions in the code builder to be contiguous.
// That's true when we emit thru InstructionEncoder. Taking it as a parameter instead of separate
// code and flow builder parameters ensures they match each other.
var codeBuilder = instructionEncoder.CodeBuilder;
var flowBuilder = instructionEncoder.ControlFlowBuilder;
var relocBuilder = instructionEncoder.RelocationBuilder;
var lineNumberBuilder = instructionEncoder.LineNumberBuilder;
if (codeBuilder == null)
{
throw new ArgumentNullException(nameof(instructionEncoder));
}
int exceptionRegionCount = flowBuilder?.ExceptionHandlerCount ?? 0;
if (!RelocatableExceptionRegionEncoder.IsExceptionRegionCountInBounds(exceptionRegionCount))
{
throw new ArgumentOutOfRangeException(nameof(instructionEncoder));
}
int originalBuilderPosition = Builder.Count;
int bodyOffset = SerializeHeader(codeBuilder.Count, (ushort)maxStack, exceptionRegionCount, attributes, localVariablesSignature, hasDynamicStackAllocation);
CoffSymbolHandle methodSymbol = SymbolTableBuilder.AddClrToken(coffSymbolName, metadataHandle, out CoffSymbolHandle tokenCoffSymbol);
CodeViewSymbolBuilder?.AddMethodSymbol(coffSymbolName, methodSymbol, Builder.Count - originalBuilderPosition, tokenCoffSymbol, codeBuilder.Count);
if (lineNumberBuilder != null)
CodeViewSymbolBuilder?.AddLineNumbers(methodSymbol, Builder.Count - originalBuilderPosition, codeBuilder.Count, lineNumberBuilder);
relocBuilder?.Append(RelocationBuilder, Builder.Count, HeaderBuilder, SymbolTableBuilder);
if (flowBuilder?.BranchCount > 0)
{
flowBuilder.CopyCodeAndFixupBranches(codeBuilder, Builder);
}
else
{
codeBuilder.WriteContentTo(Builder);
}
flowBuilder?.SerializeExceptionTable(Builder, RelocationBuilder, HeaderBuilder, SymbolTableBuilder);
return bodyOffset;
}
private int SerializeHeader(
int codeSize,
ushort maxStack,
int exceptionRegionCount,
MethodBodyAttributes attributes,
StandaloneSignatureHandle localVariablesSignature,
bool hasDynamicStackAllocation)
{
const int TinyFormat = 2;
const int FatFormat = 3;
const int MoreSections = 8;
const byte InitLocals = 0x10;
bool initLocals = (attributes & MethodBodyAttributes.InitLocals) != 0;
bool isTiny = codeSize < 64 &&
maxStack <= 8 &&
localVariablesSignature.IsNil && (!hasDynamicStackAllocation || !initLocals) &&
exceptionRegionCount == 0;
int offset;
if (isTiny)
{
offset = Builder.Count;
Builder.WriteByte((byte)((codeSize << 2) | TinyFormat));
}
else
{
Builder.Align(4);
offset = Builder.Count;
ushort flags = (3 << 12) | FatFormat;
if (exceptionRegionCount > 0)
{
flags |= MoreSections;
}
if (initLocals)
{
flags |= InitLocals;
}
Builder.WriteUInt16((ushort)((int)attributes | flags));
Builder.WriteUInt16(maxStack);
Builder.WriteInt32(codeSize);
new ManagedCoffRelocationEncoder(HeaderBuilder, RelocationBuilder, SymbolTableBuilder)
.AddClrRelocation(Builder.Count, MetadataTokens.GetToken(localVariablesSignature));
Builder.WriteInt32(0);
}
return offset;
}
}
[Flags]
public enum ObjectFeatures : ushort
{
None,
PureMsil = 2,
SafeMsil = 4,
}
public class CoffSymbolTableBuilder
{
private Dictionary<string, CoffSymbolHandle> _coffSymbols = new Dictionary<string, CoffSymbolHandle>(StringComparer.Ordinal);
protected BlobBuilder _coffStringTableBuilder = new BlobBuilder();
protected BlobBuilder _coffSymbolTableBuilder = new BlobBuilder();
private const int SymbolSize = 18;
public int Count => _coffSymbolTableBuilder.Count / SymbolSize;
public CoffSymbolTableBuilder() { }
public CoffSymbolTableBuilder(ObjectFeatures objectFeatures)
{
GetOrAddCoffSymbol("@feat.00", (ushort)objectFeatures, 0xFFFF, CoffSymbolType.Null, CoffSymbolStorageClass.Static, 0);
}
public CoffSymbolHandle GetOrAddCoffSymbol(string name, uint value, ushort sectionNumber, CoffSymbolType type, CoffSymbolStorageClass storageClass, byte numberOfAuxSymbols)
{
if (_coffSymbols.TryGetValue(name, out CoffSymbolHandle result))
{
return result;
}
result = new CoffSymbolHandle(Count);
if (name.Length <= 8)
{
CoffBuilder.WritePaddedName(_coffSymbolTableBuilder, name);
}
else
{
_coffSymbolTableBuilder.WriteUInt32(0);
_coffSymbolTableBuilder.WriteInt32(_coffStringTableBuilder.Count + 4);
_coffStringTableBuilder.WriteUTF8(name);
_coffStringTableBuilder.WriteByte(0);
}
_coffSymbolTableBuilder.WriteUInt32(value);
_coffSymbolTableBuilder.WriteUInt16(sectionNumber);
_coffSymbolTableBuilder.WriteUInt16((ushort)type);
_coffSymbolTableBuilder.WriteByte((byte)storageClass);
_coffSymbolTableBuilder.WriteByte(numberOfAuxSymbols);
_coffSymbols.Add(name, result);
return result;
}
public void Serialize(BlobBuilder builder)
{
builder.LinkSuffix(_coffSymbolTableBuilder);
builder.WriteInt32(_coffStringTableBuilder.Count + 4);
builder.LinkSuffix(_coffStringTableBuilder);
}
}
public struct CoffSymbolHandle
{
internal readonly int _value;
internal CoffSymbolHandle(int value) => _value = value;
}
public class ManagedCoffSymbolTableBuilder : CoffSymbolTableBuilder
{
internal readonly int _clrTextSectionNumber;
public ManagedCoffSymbolTableBuilder(int clrTextSectionNumber, ObjectFeatures objectFeatures)
: base(objectFeatures)
{
_clrTextSectionNumber = clrTextSectionNumber;
}
public CoffSymbolHandle AddClrToken(string name, EntityHandle handle, out CoffSymbolHandle tokenCoffSymbol)
{
int token = MetadataTokens.GetToken(handle);
CoffSymbolHandle index = GetOrAddCoffSymbol(name, 0, (ushort)_clrTextSectionNumber, CoffSymbolType.Function, CoffSymbolStorageClass.External, 0);
tokenCoffSymbol = GetOrAddCoffSymbol(token.ToString("X8"), 0, (ushort)_clrTextSectionNumber, CoffSymbolType.Function, CoffSymbolStorageClass.ClrToken, 1);
_coffSymbolTableBuilder.WriteByte(1);
_coffSymbolTableBuilder.WriteByte(0);
_coffSymbolTableBuilder.WriteInt32(index._value);
_coffSymbolTableBuilder.PadTo(_coffSymbolTableBuilder.Count + 12);
return index;
}
}
public enum CoffSymbolType : short
{
Null = 0x00,
Function = 0x20,
}
public enum CoffSymbolStorageClass : byte
{
External = 2,
Static = 3,
ClrToken = 107,
}
public sealed class CoffHeaderBuilder
{
public Machine Machine { get; }
public Characteristics ImageCharacteristics { get; }
public CoffHeaderBuilder(Machine machine, Characteristics characteristics)
{
Machine = machine;
ImageCharacteristics = characteristics;
}
}
public abstract class CoffBuilder
{
public CoffHeaderBuilder Header { get; }
public CoffSymbolTableBuilder SymbolTableBuilder { get; }
public Func<IEnumerable<Blob>, BlobContentId> IdProvider { get; }
public bool IsDeterministic { get; }
private readonly Lazy<ImmutableArray<Section>> _lazySections;
protected readonly struct Section
{
public readonly string Name;
public readonly SectionCharacteristics Characteristics;
public Section(string name, SectionCharacteristics characteristics)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
Name = name;
Characteristics = characteristics;
}
}
private readonly struct SerializedSection
{
public readonly BlobBuilder Builder;
public readonly BlobBuilder Relocations;
public readonly string Name;
public readonly SectionCharacteristics Characteristics;
public readonly int RelativeVirtualAddress;
public readonly int SizeOfRawData;
public readonly int PointerToRawData;
public SerializedSection(BlobBuilder builder, BlobBuilder relocations, string name, SectionCharacteristics characteristics, int relativeVirtualAddress, int sizeOfRawData, int pointerToRawData)
{
Name = name;
Characteristics = characteristics;
Builder = builder;
Relocations = relocations;
RelativeVirtualAddress = relativeVirtualAddress;
SizeOfRawData = sizeOfRawData;
PointerToRawData = pointerToRawData;
}
public int VirtualSize => Builder.Count;
}
protected CoffBuilder(CoffHeaderBuilder header, CoffSymbolTableBuilder symbolTable, Func<IEnumerable<Blob>, BlobContentId> deterministicIdProvider)
{
if (header == null)
{
throw new ArgumentNullException(nameof(header));
}
IdProvider = deterministicIdProvider ?? BlobContentId.GetTimeBasedProvider();
IsDeterministic = deterministicIdProvider != null;
Header = header;
SymbolTableBuilder = symbolTable;
_lazySections = new Lazy<ImmutableArray<Section>>(CreateSections);
}
protected ImmutableArray<Section> GetSections()
{
var sections = _lazySections.Value;
if (sections.IsDefault)
{
throw new InvalidOperationException();
}
return sections;
}
protected abstract ImmutableArray<Section> CreateSections();
protected abstract BlobBuilder SerializeSection(string name, SectionLocation location);
protected abstract BlobBuilder SerializeRelocations(string name, SectionLocation location);
public BlobContentId Serialize(BlobBuilder builder)
{
// Define and serialize sections in two steps.
// We need to know about all sections before serializing them.
var serializedSections = SerializeSections();
Blob stampFixup;
Blob symTableFixup;
WriteCoffHeader(builder, serializedSections, out stampFixup, (uint)(SymbolTableBuilder?.Count ?? 0), out symTableFixup);
WriteSectionHeaders(builder, serializedSections);
builder.Align(4);
foreach (var section in serializedSections)
{
builder.LinkSuffix(section.Builder);
builder.Align(4);
if (section.Relocations != null)
{
builder.LinkSuffix(section.Relocations);
builder.Align(4);
}
}
var symTableFixupWriter = new BlobWriter(symTableFixup);
symTableFixupWriter.WriteInt32(builder.Count);
SymbolTableBuilder?.Serialize(builder);
var contentId = IdProvider(builder.GetBlobs());
// patch timestamp in COFF header:
var stampWriter = new BlobWriter(stampFixup);
stampWriter.WriteUInt32(contentId.Stamp);
Debug.Assert(stampWriter.RemainingBytes == 0);
return contentId;
}
internal static int Align(int position, int alignment)
{
Debug.Assert(position >= 0 && alignment > 0);
int result = position & ~(alignment - 1);
if (result == position)
{
return result;
}
return result + alignment;
}
private ImmutableArray<SerializedSection> SerializeSections()
{
var sections = GetSections();
var result = ImmutableArray.CreateBuilder<SerializedSection>(sections.Length);
int sizeOfPeHeaders = 20 + (40 * sections.Length);
var nextPointer = Align(sizeOfPeHeaders, 4);
foreach (var section in sections)
{
var builder = SerializeSection(section.Name, new SectionLocation(0, nextPointer));
var relocs = SerializeRelocations(section.Name, new SectionLocation(0, nextPointer));
var serialized = new SerializedSection(
builder,
relocs,
section.Name,
section.Characteristics,
relativeVirtualAddress: 0,
sizeOfRawData: Align(builder.Count, 4),
pointerToRawData: nextPointer);
result.Add(serialized);
nextPointer = serialized.PointerToRawData + serialized.SizeOfRawData;
if (relocs != null)
nextPointer += Align(relocs.Count, 4);
}
return result.MoveToImmutable();
}
private void WriteCoffHeader(BlobBuilder builder, ImmutableArray<SerializedSection> sections, out Blob stampFixup, uint numSymbols, out Blob blobSymTableFixup)
{
// Machine
builder.WriteUInt16((ushort)(Header.Machine == 0 ? Machine.I386 : Header.Machine));
// NumberOfSections
builder.WriteUInt16((ushort)sections.Length);
// TimeDateStamp:
stampFixup = builder.ReserveBytes(sizeof(uint));
// PointerToSymbolTable:
// The file pointer to the COFF symbol table, or zero if no COFF symbol table is present.
// This value should be zero for a PE image.
blobSymTableFixup = builder.ReserveBytes(sizeof(uint));
// NumberOfSymbols:
// The number of entries in the symbol table. This data can be used to locate the string table,
// which immediately follows the symbol table. This value should be zero for a PE image.
builder.WriteUInt32(numSymbols);
// SizeOfOptionalHeader:
// The size of the optional header, which is required for executable files but not for object files.
// This value should be zero for an object file.
builder.WriteUInt16(0);
// Characteristics
builder.WriteUInt16((ushort)Header.ImageCharacteristics);
}
private void WriteSectionHeaders(BlobBuilder builder, ImmutableArray<SerializedSection> serializedSections)
{
foreach (var serializedSection in serializedSections)
{
WriteSectionHeader(builder, serializedSection);
}
}
internal static void WritePaddedName(BlobBuilder builder, string name)
{
for (int j = 0, m = name.Length; j < 8; j++)
{
if (j < m)
{
builder.WriteByte((byte)name[j]);
}
else
{
builder.WriteByte(0);
}
}
}
private static void WriteSectionHeader(BlobBuilder builder, SerializedSection serializedSection)
{
//if (serializedSection.VirtualSize == 0)
//{
// return;
//}
WritePaddedName(builder, serializedSection.Name);
builder.WriteUInt32((uint)serializedSection.VirtualSize);
builder.WriteUInt32((uint)serializedSection.RelativeVirtualAddress);
builder.WriteUInt32((uint)serializedSection.SizeOfRawData);
builder.WriteUInt32((uint)serializedSection.PointerToRawData);
builder.WriteInt32(serializedSection.Relocations != null ? serializedSection.SizeOfRawData + serializedSection.PointerToRawData : 0); // PointerToRelocations
builder.WriteUInt32(0); // PointerToLinenumbers
builder.WriteUInt16(serializedSection.Relocations != null ? checked((ushort)(serializedSection.Relocations.Count / 10)) : (ushort)0); // NumberOfRelocations
builder.WriteUInt16(0); // NumberOfLinenumbers
builder.WriteUInt32((uint)serializedSection.Characteristics);
}
}
public class ManagedCoffBuilder : CoffBuilder
{
private const string CorMetaSectionName = ".cormeta";
private const string TextSectionName = ".text$mn";
private const string CodeViewSymbolsSectionName = ".debug$S";
private readonly CodeViewSymbolBuilder _codeViewSymbols;
private readonly MetadataRootBuilder _metadataRootBuilder;
private readonly BlobBuilder _ilStream;
private readonly BlobBuilder _ilRelocs;
public const int ClrTextSectionNumber = 1;
public ManagedCoffBuilder(
CoffHeaderBuilder header,
MetadataRootBuilder metadataRootBuilder,
ManagedCoffSymbolTableBuilder symbolTable,
CodeViewSymbolBuilder codeViewSymbols,
BlobBuilder ilStream,
BlobBuilder ilRelocs,
Func<IEnumerable<Blob>, BlobContentId> deterministicIdProvider = null)
: base(header, symbolTable, deterministicIdProvider)
{
if (header == null)
{
throw new ArgumentNullException(nameof(header));
}
if (metadataRootBuilder == null)
{
throw new ArgumentNullException(nameof(metadataRootBuilder));
}
if (ilStream == null)
{
throw new ArgumentNullException(nameof(ilStream));
}
if (symbolTable._clrTextSectionNumber != ClrTextSectionNumber)
{
throw new ArgumentException();
}
_codeViewSymbols = codeViewSymbols;
_metadataRootBuilder = metadataRootBuilder;
_ilStream = ilStream;
_ilRelocs = ilRelocs;
}
protected override ImmutableArray<Section> CreateSections()
{
var builder = ImmutableArray.CreateBuilder<Section>();
builder.Add(new Section(TextSectionName, SectionCharacteristics.MemRead | SectionCharacteristics.MemExecute | SectionCharacteristics.ContainsCode | SectionCharacteristics.Align4Bytes));
builder.Add(new Section(CorMetaSectionName, SectionCharacteristics.LinkerInfo | SectionCharacteristics.Align1Bytes));
if (_codeViewSymbols != null)
{
builder.Add(new Section(CodeViewSymbolsSectionName, SectionCharacteristics.ContainsInitializedData | SectionCharacteristics.MemDiscardable | SectionCharacteristics.Align1Bytes | SectionCharacteristics.MemRead));
}
return builder.ToImmutable();
}
protected override BlobBuilder SerializeSection(string name, SectionLocation location) =>
name switch
{
TextSectionName => SerializeTextSection(location),
CorMetaSectionName => SerializeCorMetaSection(location),
CodeViewSymbolsSectionName => SerializeCodeViewSymbols(location),
_ => throw new ArgumentException(),
};
protected override BlobBuilder SerializeRelocations(string name, SectionLocation location)
{
if (name == TextSectionName)
{
return _ilRelocs;
}
else if (name == CodeViewSymbolsSectionName)
{
BlobBuilder builder = new BlobBuilder();
_codeViewSymbols.SerializeRelocations(builder);
return builder;
}
return null;
}
private BlobBuilder SerializeTextSection(SectionLocation location)
{
return _ilStream;
}
private BlobBuilder SerializeCorMetaSection(SectionLocation location)
{
var metadataBuilder = new BlobBuilder();
_metadataRootBuilder.Serialize(metadataBuilder, 0, 0);
return metadataBuilder;
}
private BlobBuilder SerializeCodeViewSymbols(SectionLocation location)
{
var builder = new BlobBuilder();
_codeViewSymbols.Serialize(builder);
return builder;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment