// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using dnlib.DotNet.Emit; using dnlib.DotNet.MD; using dnlib.DotNet.Pdb; using dnlib.DotNet.Pdb.Portable; using dnlib.IO; using dnlib.PE; namespace dnlib.DotNet.Writer { /// /// flags /// [Flags] public enum MetadataFlags : uint { /// /// Preserves all rids in the TypeRef table /// PreserveTypeRefRids = 1, /// /// Preserves all rids in the TypeDef table /// PreserveTypeDefRids = 2, /// /// Preserves all rids in the Field table /// PreserveFieldRids = 4, /// /// Preserves all rids in the Method table /// PreserveMethodRids = 8, /// /// Preserves all rids in the Param table /// PreserveParamRids = 0x10, /// /// Preserves all rids in the MemberRef table /// PreserveMemberRefRids = 0x20, /// /// Preserves all rids in the StandAloneSig table /// PreserveStandAloneSigRids = 0x40, /// /// Preserves all rids in the Event table /// PreserveEventRids = 0x80, /// /// Preserves all rids in the Property table /// PreservePropertyRids = 0x100, /// /// Preserves all rids in the TypeSpec table /// PreserveTypeSpecRids = 0x200, /// /// Preserves all rids in the MethodSpec table /// PreserveMethodSpecRids = 0x400, /// /// Preserves all method rids, i.e., Method, MemberRef and /// MethodSpec rids. /// PreserveAllMethodRids = PreserveMethodRids | PreserveMemberRefRids | PreserveMethodSpecRids, /// /// Preserves all rids in the following tables: TypeRef, TypeDef, /// Field, Method, Param, MemberRef, StandAloneSig, /// Event, Property, TypeSpec, MethodSpec /// PreserveRids = PreserveTypeRefRids | PreserveTypeDefRids | PreserveFieldRids | PreserveMethodRids | PreserveParamRids | PreserveMemberRefRids | PreserveStandAloneSigRids | PreserveEventRids | PreservePropertyRids | PreserveTypeSpecRids | PreserveMethodSpecRids, /// /// Preserves all offsets in the #Strings heap (the original #Strings heap will be saved /// in the new file). Type names, field names, and other non-user strings are stored /// in the #Strings heap. /// PreserveStringsOffsets = 0x800, /// /// Preserves all offsets in the #US heap (the original #US heap will be saved /// in the new file). User strings (referenced by the ldstr instruction) are stored in /// the #US heap. /// PreserveUSOffsets = 0x1000, /// /// Preserves all offsets in the #Blob heap (the original #Blob heap will be saved /// in the new file). Custom attributes, signatures and other blobs are stored in the /// #Blob heap. /// PreserveBlobOffsets = 0x2000, /// /// Preserves the extra data that is present after the original signature in the #Blob /// heap. This extra data shouldn't be present but might be present if an obfuscator /// has added this extra data and is eg. using it to decrypt stuff. /// PreserveExtraSignatureData = 0x4000, /// /// Preserves as much as possible /// PreserveAll = PreserveRids | PreserveStringsOffsets | PreserveUSOffsets | PreserveBlobOffsets | PreserveExtraSignatureData, /// /// The original method body's max stack field should be used and a new one should not /// be calculated. /// KeepOldMaxStack = 0x8000, /// /// Always create the #GUID heap even if it's empty /// AlwaysCreateGuidHeap = 0x10000, /// /// Always create the #Strings heap even if it's empty /// AlwaysCreateStringsHeap = 0x20000, /// /// Always create the #US heap even if it's empty /// AlwaysCreateUSHeap = 0x40000, /// /// Always create the #Blob heap even if it's empty /// AlwaysCreateBlobHeap = 0x80000, /// /// DEPRECATED: /// Sort the InterfaceImpl table the same way Roslyn sorts it. Roslyn doesn't sort it /// according to the ECMA spec, see https://github.com/dotnet/roslyn/issues/3905 /// RoslynSortInterfaceImpl = 0x100000, /// /// Don't write method bodies /// NoMethodBodies = 0x200000, /// /// Don't write .NET resources /// NoDotNetResources = 0x400000, /// /// Don't write field data /// NoFieldData = 0x800000, /// /// Serialized type names stored in custom attributes are optimized if the types /// exist in the core library (eg. mscorlib/System.Private.CoreLib). /// Instead of storing type-name + assembly-name, only type-name is stored. This results in /// slightly smaller assemblies. ///
///
/// If it's a type in the current module, the type name is optimized and no assembly name is stored in the custom attribute. ///
///
/// This is disabled by default. It's safe to enable if the reference core assembly /// is the same as the runtime core assembly (eg. it's mscorlib.dll and .NET Framework, /// but not .NET Core / .NET Standard). ///
OptimizeCustomAttributeSerializedTypeNames = 0x1000000, } /// /// Metadata heaps event args /// public readonly struct MetadataHeapsAddedEventArgs { /// /// Gets the metadata writer /// public Metadata Metadata { get; } /// /// Gets all heaps /// public List Heaps { get; } /// /// Constructor /// /// Metadata writer /// All heaps public MetadataHeapsAddedEventArgs(Metadata metadata, List heaps) { Metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); Heaps = heaps ?? throw new ArgumentNullException(nameof(heaps)); } } /// /// options /// public sealed class MetadataOptions { MetadataHeaderOptions metadataHeaderOptions; MetadataHeaderOptions debugMetadataHeaderOptions; TablesHeapOptions tablesHeapOptions; List customHeaps; /// /// Gets/sets the options. This is never null. /// public MetadataHeaderOptions MetadataHeaderOptions { get => metadataHeaderOptions ??= new MetadataHeaderOptions(); set => metadataHeaderOptions = value; } /// /// Gets/sets the debug (portable PDB) options. This is never null. /// public MetadataHeaderOptions DebugMetadataHeaderOptions { get => debugMetadataHeaderOptions ??= MetadataHeaderOptions.CreatePortablePdbV1_0(); set => debugMetadataHeaderOptions = value; } /// /// Gets/sets the options. This is never null. /// public TablesHeapOptions TablesHeapOptions { get => tablesHeapOptions ??= new TablesHeapOptions(); set => tablesHeapOptions = value; } /// /// Gets/sets the debug (portable PDB) options. This is never null. /// public TablesHeapOptions DebugTablesHeapOptions { get => tablesHeapOptions ??= TablesHeapOptions.CreatePortablePdbV1_0(); set => tablesHeapOptions = value; } /// /// Various options /// public MetadataFlags Flags; /// /// Extra heaps to add to the metadata. Also see and /// public List CustomHeaps => customHeaps ??= new List(); /// /// Raised after all heaps have been added. The caller can sort the list if needed /// public event EventHandler2 MetadataHeapsAdded; internal void RaiseMetadataHeapsAdded(MetadataHeapsAddedEventArgs e) => MetadataHeapsAdded?.Invoke(e.Metadata, e); /// /// Preserves the original order of heaps, and optionally adds all custom heaps to . /// /// Original module with the heaps /// If true, all custom streams are added to public void PreserveHeapOrder(ModuleDef module, bool addCustomHeaps) { if (module is null) throw new ArgumentNullException(nameof(module)); if (module is ModuleDefMD mod) { if (addCustomHeaps) { var otherStreams = mod.Metadata.AllStreams.Where(a => a.GetType() == typeof(CustomDotNetStream)).Select(a => new DataReaderHeap(a)); CustomHeaps.AddRange(otherStreams.OfType()); } var streamToOrder = new Dictionary(mod.Metadata.AllStreams.Count); for (int i = 0, order = 0; i < mod.Metadata.AllStreams.Count; i++) { var stream = mod.Metadata.AllStreams[i]; if (stream.StartOffset == 0) continue; streamToOrder.Add(stream, order++); } var nameToOrder = new Dictionary(mod.Metadata.AllStreams.Count, StringComparer.Ordinal); for (int i = 0, order = 0; i < mod.Metadata.AllStreams.Count; i++) { var stream = mod.Metadata.AllStreams[i]; if (stream.StartOffset == 0) continue; bool isKnownStream = stream is BlobStream || stream is GuidStream || stream is PdbStream || stream is StringsStream || stream is TablesStream || stream is USStream; if (!nameToOrder.ContainsKey(stream.Name) || isKnownStream) nameToOrder[stream.Name] = order; order++; } MetadataHeapsAdded += (s, e) => { e.Heaps.Sort((a, b) => { int oa = GetOrder(streamToOrder, nameToOrder, a); int ob = GetOrder(streamToOrder, nameToOrder, b); int c = oa - ob; if (c != 0) return c; return StringComparer.Ordinal.Compare(a.Name, b.Name); }); }; } } static int GetOrder(Dictionary streamToOrder, Dictionary nameToOrder, IHeap heap) { if (heap is DataReaderHeap drHeap && drHeap.OptionalOriginalStream is DotNetStream dnHeap && streamToOrder.TryGetValue(dnHeap, out int order)) return order; if (nameToOrder.TryGetValue(heap.Name, out order)) return order; return int.MaxValue; } /// /// Default constructor /// public MetadataOptions() { } /// /// Constructor /// /// Flags public MetadataOptions(MetadataFlags flags) => Flags = flags; /// /// Constructor /// /// Meta data header options public MetadataOptions(MetadataHeaderOptions mdhOptions) => metadataHeaderOptions = mdhOptions; /// /// Constructor /// /// Meta data header options /// Flags public MetadataOptions(MetadataHeaderOptions mdhOptions, MetadataFlags flags) { Flags = flags; metadataHeaderOptions = mdhOptions; } } sealed class DataWriterContext { public readonly MemoryStream OutStream; public readonly DataWriter Writer; public DataWriterContext() { OutStream = new MemoryStream(); Writer = new DataWriter(OutStream); } } /// /// Portable PDB metadata kind /// public enum DebugMetadataKind { /// /// No debugging metadata /// None, /// /// Standalone / embedded portable PDB metadata /// Standalone, } /// /// Metadata writer event args /// public readonly struct MetadataWriterEventArgs { /// /// Gets the metadata writer /// public Metadata Metadata { get; } /// /// Gets the event /// public MetadataEvent Event { get; } /// /// Constructor /// /// Writer /// Event public MetadataWriterEventArgs(Metadata metadata, MetadataEvent @event) { Metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); Event = @event; } } /// /// Metadata writer progress event args /// public readonly struct MetadataProgressEventArgs { /// /// Gets the metadata writer /// public Metadata Metadata { get; } /// /// Gets the progress, 0.0 - 1.0 /// public double Progress { get; } /// /// Constructor /// /// Writer /// Progress, 0.0 - 1.0 public MetadataProgressEventArgs(Metadata metadata, double progress) { if (progress < 0 || progress > 1) throw new ArgumentOutOfRangeException(nameof(progress)); Metadata = metadata ?? throw new ArgumentNullException(nameof(metadata)); Progress = progress; } } /// /// .NET meta data /// public abstract class Metadata : IReuseChunk, ISignatureWriterHelper, ITokenProvider, ICustomAttributeWriterHelper, IPortablePdbCustomDebugInfoWriterHelper, IWriterError2 { uint length; FileOffset offset; RVA rva; readonly MetadataOptions options; ILogger logger; readonly MetadataErrorContext errorContext; readonly NormalMetadata debugMetadata; readonly bool isStandaloneDebugMetadata; internal readonly ModuleDef module; internal readonly UniqueChunkList constants; internal readonly MethodBodyChunks methodBodies; internal readonly NetResources netResources; internal readonly MetadataHeader metadataHeader; internal readonly PdbHeap pdbHeap; internal readonly TablesHeap tablesHeap; internal readonly StringsHeap stringsHeap; internal readonly USHeap usHeap; internal readonly GuidHeap guidHeap; internal readonly BlobHeap blobHeap; internal TypeDef[] allTypeDefs; internal readonly Rows moduleDefInfos = new Rows(); internal readonly SortedRows interfaceImplInfos = new SortedRows(); internal readonly SortedRows hasConstantInfos = new SortedRows(); internal readonly SortedRows customAttributeInfos = new SortedRows(); internal readonly SortedRows fieldMarshalInfos = new SortedRows(); internal readonly SortedRows declSecurityInfos = new SortedRows(); internal readonly SortedRows classLayoutInfos = new SortedRows(); internal readonly SortedRows fieldLayoutInfos = new SortedRows(); internal readonly Rows eventMapInfos = new Rows(); internal readonly Rows propertyMapInfos = new Rows(); internal readonly SortedRows methodSemanticsInfos = new SortedRows(); internal readonly SortedRows methodImplInfos = new SortedRows(); internal readonly Rows moduleRefInfos = new Rows(); internal readonly SortedRows implMapInfos = new SortedRows(); internal readonly SortedRows fieldRVAInfos = new SortedRows(); internal readonly Rows assemblyInfos = new Rows(); internal readonly Rows assemblyRefInfos = new Rows(); internal readonly Rows fileDefInfos = new Rows(); internal readonly Rows exportedTypeInfos = new Rows(); internal readonly Rows manifestResourceInfos = new Rows(); internal readonly SortedRows nestedClassInfos = new SortedRows(); internal readonly SortedRows genericParamInfos = new SortedRows(); internal readonly SortedRows genericParamConstraintInfos = new SortedRows(); internal readonly Dictionary methodToBody = new Dictionary(); internal readonly Dictionary methodToNativeBody = new Dictionary(); internal readonly Dictionary embeddedResourceToByteArray = new Dictionary(); readonly Dictionary fieldToInitialValue = new Dictionary(); readonly Rows pdbDocumentInfos = new Rows(); bool methodDebugInformationInfosUsed; readonly SortedRows localScopeInfos = new SortedRows(); readonly Rows localVariableInfos = new Rows(); readonly Rows localConstantInfos = new Rows(); readonly Rows importScopeInfos = new Rows(); readonly SortedRows stateMachineMethodInfos = new SortedRows(); readonly SortedRows customDebugInfos = new SortedRows(); readonly List binaryWriterContexts = new List(); readonly List serializerMethodContexts = new List(); readonly List exportedMethods = new List(); /// /// Raised at various times when writing the metadata /// public event EventHandler2 MetadataEvent; /// /// Raised when the progress is updated /// public event EventHandler2 ProgressUpdated; /// /// Gets/sets the logger /// public ILogger Logger { get => logger; set => logger = value; } /// /// Gets the module /// public ModuleDef Module => module; /// /// Gets the constants /// public UniqueChunkList Constants => constants; /// /// Gets the method body chunks /// public MethodBodyChunks MethodBodyChunks => methodBodies; /// /// Gets the .NET resources /// public NetResources NetResources => netResources; /// /// Gets the MD header /// public MetadataHeader MetadataHeader => metadataHeader; /// /// Gets the tables heap. Access to this heap is not recommended, but is useful if you /// want to add random table entries. /// public TablesHeap TablesHeap => tablesHeap; /// /// Gets the #Strings heap. Access to this heap is not recommended, but is useful if you /// want to add random strings. /// public StringsHeap StringsHeap => stringsHeap; /// /// Gets the #US heap. Access to this heap is not recommended, but is useful if /// you want to add random user strings. /// public USHeap USHeap => usHeap; /// /// Gets the #GUID heap. Access to this heap is not recommended, but is useful if you /// want to add random GUIDs. /// public GuidHeap GuidHeap => guidHeap; /// /// Gets the #Blob heap. Access to this heap is not recommended, but is useful if you /// want to add random blobs. /// public BlobHeap BlobHeap => blobHeap; /// /// Gets the #Pdb heap. It's only used if it's portable PDB metadata /// public PdbHeap PdbHeap => pdbHeap; /// /// Gets all exported methods /// public List ExportedMethods => exportedMethods; /// /// The public key that should be used instead of the one in . /// internal byte[] AssemblyPublicKey { get; set; } internal sealed class SortedRows where T : class where TRow : struct { public List infos = new List(); Dictionary toRid = new Dictionary(); bool isSorted; public struct Info { public readonly T data; public /*readonly*/ TRow row; public Info(T data, ref TRow row) { this.data = data; this.row = row; } } public void Add(T data, TRow row) { if (isSorted) throw new ModuleWriterException($"Adding a row after it's been sorted. Table: {row.GetType()}"); infos.Add(new Info(data, ref row)); toRid[data] = (uint)toRid.Count + 1; } public void Sort(Comparison comparison) { infos.Sort(CreateComparison(comparison)); toRid.Clear(); for (int i = 0; i < infos.Count; i++) toRid[infos[i].data] = (uint)i + 1; isSorted = true; } Comparison CreateComparison(Comparison comparison) => (a, b) => { int c = comparison(a, b); if (c != 0) return c; // Make sure it's a stable sort return toRid[a.data].CompareTo(toRid[b.data]); }; public uint Rid(T data) => toRid[data]; public bool TryGetRid(T data, out uint rid) { if (data is null) { rid = 0; return false; } return toRid.TryGetValue(data, out rid); } } internal sealed class Rows where T : class { Dictionary dict = new Dictionary(); public int Count => dict.Count; public bool TryGetRid(T value, out uint rid) { if (value is null) { rid = 0; return false; } return dict.TryGetValue(value, out rid); } public bool Exists(T value) => dict.ContainsKey(value); public void Add(T value, uint rid) => dict.Add(value, rid); public uint Rid(T value) => dict[value]; public void SetRid(T value, uint rid) => dict[value] = rid; } /// /// Creates a instance /// /// Module /// Constants list /// Method bodies list /// .NET resources list /// Options /// Debug metadata kind /// A new instance public static Metadata Create(ModuleDef module, UniqueChunkList constants, MethodBodyChunks methodBodies, NetResources netResources, MetadataOptions options = null, DebugMetadataKind debugKind = DebugMetadataKind.None) { if (options is null) options = new MetadataOptions(); if ((options.Flags & MetadataFlags.PreserveRids) != 0 && module is ModuleDefMD) return new PreserveTokensMetadata(module, constants, methodBodies, netResources, options, debugKind, false); return new NormalMetadata(module, constants, methodBodies, netResources, options, debugKind, false); } /// public FileOffset FileOffset => offset; /// public RVA RVA => rva; /// /// Gets the bit /// public bool PreserveTypeRefRids => (options.Flags & MetadataFlags.PreserveTypeRefRids) != 0; /// /// Gets the bit /// public bool PreserveTypeDefRids => (options.Flags & MetadataFlags.PreserveTypeDefRids) != 0; /// /// Gets the bit /// public bool PreserveFieldRids => (options.Flags & MetadataFlags.PreserveFieldRids) != 0; /// /// Gets the bit /// public bool PreserveMethodRids => (options.Flags & MetadataFlags.PreserveMethodRids) != 0; /// /// Gets the bit /// public bool PreserveParamRids => (options.Flags & MetadataFlags.PreserveParamRids) != 0; /// /// Gets the bit /// public bool PreserveMemberRefRids => (options.Flags & MetadataFlags.PreserveMemberRefRids) != 0; /// /// Gets the bit /// public bool PreserveStandAloneSigRids => (options.Flags & MetadataFlags.PreserveStandAloneSigRids) != 0; /// /// Gets the bit /// public bool PreserveEventRids => (options.Flags & MetadataFlags.PreserveEventRids) != 0; /// /// Gets the bit /// public bool PreservePropertyRids => (options.Flags & MetadataFlags.PreservePropertyRids) != 0; /// /// Gets the bit /// public bool PreserveTypeSpecRids => (options.Flags & MetadataFlags.PreserveTypeSpecRids) != 0; /// /// Gets the bit /// public bool PreserveMethodSpecRids => (options.Flags & MetadataFlags.PreserveMethodSpecRids) != 0; /// /// Gets/sets the bit /// public bool PreserveStringsOffsets { get => (options.Flags & MetadataFlags.PreserveStringsOffsets) != 0; set { if (value) options.Flags |= MetadataFlags.PreserveStringsOffsets; else options.Flags &= ~MetadataFlags.PreserveStringsOffsets; } } /// /// Gets/sets the bit /// public bool PreserveUSOffsets { get => (options.Flags & MetadataFlags.PreserveUSOffsets) != 0; set { if (value) options.Flags |= MetadataFlags.PreserveUSOffsets; else options.Flags &= ~MetadataFlags.PreserveUSOffsets; } } /// /// Gets/sets the bit /// public bool PreserveBlobOffsets { get => (options.Flags & MetadataFlags.PreserveBlobOffsets) != 0; set { if (value) options.Flags |= MetadataFlags.PreserveBlobOffsets; else options.Flags &= ~MetadataFlags.PreserveBlobOffsets; } } /// /// Gets/sets the bit /// public bool PreserveExtraSignatureData { get => (options.Flags & MetadataFlags.PreserveExtraSignatureData) != 0; set { if (value) options.Flags |= MetadataFlags.PreserveExtraSignatureData; else options.Flags &= ~MetadataFlags.PreserveExtraSignatureData; } } /// /// Gets/sets the bit /// public bool KeepOldMaxStack { get => (options.Flags & MetadataFlags.KeepOldMaxStack) != 0; set { if (value) options.Flags |= MetadataFlags.KeepOldMaxStack; else options.Flags &= ~MetadataFlags.KeepOldMaxStack; } } /// /// Gets/sets the bit /// public bool AlwaysCreateGuidHeap { get => (options.Flags & MetadataFlags.AlwaysCreateGuidHeap) != 0; set { if (value) options.Flags |= MetadataFlags.AlwaysCreateGuidHeap; else options.Flags &= ~MetadataFlags.AlwaysCreateGuidHeap; } } /// /// Gets/sets the bit /// public bool AlwaysCreateStringsHeap { get => (options.Flags & MetadataFlags.AlwaysCreateStringsHeap) != 0; set { if (value) options.Flags |= MetadataFlags.AlwaysCreateStringsHeap; else options.Flags &= ~MetadataFlags.AlwaysCreateStringsHeap; } } /// /// Gets/sets the bit /// public bool AlwaysCreateUSHeap { get => (options.Flags & MetadataFlags.AlwaysCreateUSHeap) != 0; set { if (value) options.Flags |= MetadataFlags.AlwaysCreateUSHeap; else options.Flags &= ~MetadataFlags.AlwaysCreateUSHeap; } } /// /// Gets/sets the bit /// public bool AlwaysCreateBlobHeap { get => (options.Flags & MetadataFlags.AlwaysCreateBlobHeap) != 0; set { if (value) options.Flags |= MetadataFlags.AlwaysCreateBlobHeap; else options.Flags &= ~MetadataFlags.AlwaysCreateBlobHeap; } } /// /// Gets/sets the bit /// public bool RoslynSortInterfaceImpl { get => (options.Flags & MetadataFlags.RoslynSortInterfaceImpl) != 0; set { if (value) options.Flags |= MetadataFlags.RoslynSortInterfaceImpl; else options.Flags &= ~MetadataFlags.RoslynSortInterfaceImpl; } } /// /// Gets/sets the bit /// public bool NoMethodBodies { get => (options.Flags & MetadataFlags.NoMethodBodies) != 0; set { if (value) options.Flags |= MetadataFlags.NoMethodBodies; else options.Flags &= ~MetadataFlags.NoMethodBodies; } } /// /// Gets/sets the bit /// public bool NoDotNetResources { get => (options.Flags & MetadataFlags.NoDotNetResources) != 0; set { if (value) options.Flags |= MetadataFlags.NoDotNetResources; else options.Flags &= ~MetadataFlags.NoDotNetResources; } } /// /// Gets/sets the bit /// public bool NoFieldData { get => (options.Flags & MetadataFlags.NoFieldData) != 0; set { if (value) options.Flags |= MetadataFlags.NoFieldData; else options.Flags &= ~MetadataFlags.NoFieldData; } } /// /// Gets/sets the bit /// public bool OptimizeCustomAttributeSerializedTypeNames { get => (options.Flags & MetadataFlags.OptimizeCustomAttributeSerializedTypeNames) != 0; set { if (value) options.Flags |= MetadataFlags.OptimizeCustomAttributeSerializedTypeNames; else options.Flags &= ~MetadataFlags.OptimizeCustomAttributeSerializedTypeNames; } } /// /// If true, use the original Field RVAs. If it has no RVA, assume it's a new /// field value and create a new Field RVA. /// internal bool KeepFieldRVA { get; set; } /// /// Gets the number of methods that will be written. /// protected abstract int NumberOfMethods { get; } internal Metadata(ModuleDef module, UniqueChunkList constants, MethodBodyChunks methodBodies, NetResources netResources, MetadataOptions options, DebugMetadataKind debugKind, bool isStandaloneDebugMetadata) { this.module = module; this.constants = constants; this.methodBodies = methodBodies; this.netResources = netResources; this.options = options ?? new MetadataOptions(); metadataHeader = new MetadataHeader(isStandaloneDebugMetadata ? this.options.DebugMetadataHeaderOptions : this.options.MetadataHeaderOptions); tablesHeap = new TablesHeap(this, isStandaloneDebugMetadata ? this.options.DebugTablesHeapOptions : this.options.TablesHeapOptions); stringsHeap = new StringsHeap(); usHeap = new USHeap(); guidHeap = new GuidHeap(); blobHeap = new BlobHeap(); pdbHeap = new PdbHeap(); errorContext = new MetadataErrorContext(); this.isStandaloneDebugMetadata = isStandaloneDebugMetadata; switch (debugKind) { case DebugMetadataKind.None: break; case DebugMetadataKind.Standalone: Debug.Assert(!isStandaloneDebugMetadata); //TODO: Refactor this into a smaller class debugMetadata = new NormalMetadata(module, constants, methodBodies, netResources, options, DebugMetadataKind.None, true); break; default: throw new ArgumentOutOfRangeException(nameof(debugKind)); } } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(ModuleDef module) { moduleDefInfos.TryGetRid(module, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(TypeRef tr); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(TypeDef td); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(FieldDef fd); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(MethodDef md); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(ParamDef pd); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(InterfaceImpl ii) { interfaceImplInfos.TryGetRid(ii, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(MemberRef mr); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetConstantRid(IHasConstant hc) { hasConstantInfos.TryGetRid(hc, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetCustomAttributeRid(CustomAttribute ca) { customAttributeInfos.TryGetRid(ca, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetFieldMarshalRid(IHasFieldMarshal hfm) { fieldMarshalInfos.TryGetRid(hfm, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(DeclSecurity ds) { declSecurityInfos.TryGetRid(ds, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetClassLayoutRid(TypeDef td) { classLayoutInfos.TryGetRid(td, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetFieldLayoutRid(FieldDef fd) { fieldLayoutInfos.TryGetRid(fd, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(StandAloneSig sas); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetEventMapRid(TypeDef td) { eventMapInfos.TryGetRid(td, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(EventDef ed); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetPropertyMapRid(TypeDef td) { propertyMapInfos.TryGetRid(td, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(PropertyDef pd); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetMethodSemanticsRid(MethodDef md) { methodSemanticsInfos.TryGetRid(md, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(ModuleRef mr) { moduleRefInfos.TryGetRid(mr, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(TypeSpec ts); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetImplMapRid(IMemberForwarded mf) { implMapInfos.TryGetRid(mf, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetFieldRVARid(FieldDef fd) { fieldRVAInfos.TryGetRid(fd, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(AssemblyDef asm) { assemblyInfos.TryGetRid(asm, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(AssemblyRef asmRef) { assemblyRefInfos.TryGetRid(asmRef, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(FileDef fd) { fileDefInfos.TryGetRid(fd, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(ExportedType et) { exportedTypeInfos.TryGetRid(et, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetManifestResourceRid(Resource resource) { manifestResourceInfos.TryGetRid(resource, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetNestedClassRid(TypeDef td) { nestedClassInfos.TryGetRid(td, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(GenericParam gp) { genericParamInfos.TryGetRid(gp, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public abstract uint GetRid(MethodSpec ms); /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(GenericParamConstraint gpc) { genericParamConstraintInfos.TryGetRid(gpc, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(PdbDocument doc) { if (debugMetadata is null) return 0; debugMetadata.pdbDocumentInfos.TryGetRid(doc, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(PdbScope scope) { if (debugMetadata is null) return 0; debugMetadata.localScopeInfos.TryGetRid(scope, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(PdbLocal local) { if (debugMetadata is null) return 0; debugMetadata.localVariableInfos.TryGetRid(local, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(PdbConstant constant) { if (debugMetadata is null) return 0; debugMetadata.localConstantInfos.TryGetRid(constant, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetRid(PdbImportScope importScope) { if (debugMetadata is null) return 0; debugMetadata.importScopeInfos.TryGetRid(importScope, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetStateMachineMethodRid(PdbAsyncMethodCustomDebugInfo asyncMethod) { if (debugMetadata is null) return 0; debugMetadata.stateMachineMethodInfos.TryGetRid(asyncMethod, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetStateMachineMethodRid(PdbIteratorMethodCustomDebugInfo iteratorMethod) { if (debugMetadata is null) return 0; debugMetadata.stateMachineMethodInfos.TryGetRid(iteratorMethod, out uint rid); return rid; } /// /// Gets the new rid /// /// Value /// Its new rid or 0 public uint GetCustomDebugInfoRid(PdbCustomDebugInfo cdi) { if (debugMetadata is null) return 0; debugMetadata.customDebugInfos.TryGetRid(cdi, out uint rid); return rid; } /// /// Gets the /// /// Method /// The or null if is /// null or not a method defined in this module. public MethodBody GetMethodBody(MethodDef md) { if (md is null) return null; methodToBody.TryGetValue(md, out var mb); return mb; } /// /// Gets a method's local variable signature token /// /// Method /// Locals sig token or 0 public uint GetLocalVarSigToken(MethodDef md) => GetMethodBody(md)?.LocalVarSigTok ?? 0; /// /// Gets the where the resource data will be stored /// /// Embedded resource /// A instance or null if /// is invalid public DataReaderChunk GetChunk(EmbeddedResource er) { if (er is null) return null; embeddedResourceToByteArray.TryGetValue(er, out var chunk); return chunk; } /// /// Gets the where the initial value is stored /// /// Field /// A instance or null if /// is invalid public ByteArrayChunk GetInitialValueChunk(FieldDef fd) { if (fd is null) return null; fieldToInitialValue.TryGetValue(fd, out var chunk); return chunk; } ILogger GetLogger() => logger ?? DummyLogger.ThrowModuleWriterExceptionOnErrorInstance; /// /// Called when an error is detected /// /// Error message /// Optional message arguments protected void Error(string message, params object[] args) { errorContext.Append("Error", ref message, ref args); GetLogger().Log(this, LoggerEvent.Error, message, args); } /// /// Called to warn of something /// /// Warning message /// Optional message arguments protected void Warning(string message, params object[] args) { errorContext.Append("Warning", ref message, ref args); GetLogger().Log(this, LoggerEvent.Warning, message, args); } /// /// Raises /// /// Event protected void OnMetadataEvent(MetadataEvent evt) { errorContext.Event = evt; RaiseProgress(evt, 0); MetadataEvent?.Invoke(this, new MetadataWriterEventArgs(this, evt)); } static readonly double[] eventToProgress = new double[(int)Writer.MetadataEvent.EndCreateTables - (int)Writer.MetadataEvent.BeginCreateTables + 1 + 1] { 0, // BeginCreateTables 0.00134240009466231,// AllocateTypeDefRids 0.00257484711254305,// AllocateMemberDefRids 0.0762721800615359, // MemberDefRidsAllocated 0.196633787905108, // MemberDefsInitialized 0.207788892253819, // BeforeSortTables 0.270543867900699, // MostTablesSorted 0.451478814851716, // MemberDefCustomAttributesWritten 0.451478949929206, // BeginAddResources 0.454664752528583, // EndAddResources 0.454664887606073, // BeginWriteMethodBodies 0.992591810143725, // EndWriteMethodBodies 0.999984331011171, // OnAllTablesSorted 1, // EndCreateTables 1,// An extra one so we can get the next base progress without checking the index }; /// /// Raises the progress event /// /// Base event /// Sub progress protected void RaiseProgress(MetadataEvent evt, double subProgress) { subProgress = Math.Min(1, Math.Max(0, subProgress)); var baseProgress = eventToProgress[(int)evt]; var nextProgress = eventToProgress[(int)evt + 1]; var progress = baseProgress + (nextProgress - baseProgress) * subProgress; progress = Math.Min(1, Math.Max(0, progress)); ProgressUpdated?.Invoke(this, new MetadataProgressEventArgs(this, progress)); } /// /// Creates the .NET metadata tables /// public void CreateTables() { OnMetadataEvent(Writer.MetadataEvent.BeginCreateTables); if (module.Types.Count == 0 || module.Types[0] is null) throw new ModuleWriterException("Missing global type"); if (module is ModuleDefMD moduleDefMD) { if (PreserveStringsOffsets) stringsHeap.Populate(moduleDefMD.StringsStream); if (PreserveUSOffsets) usHeap.Populate(moduleDefMD.USStream); if (PreserveBlobOffsets) blobHeap.Populate(moduleDefMD.BlobStream); } Create(); } /// /// Updates each Method row's RVA column if it has any code /// void UpdateMethodRvas() { foreach (var kv in methodToBody) { var method = kv.Key; var body = kv.Value; uint rid = GetRid(method); var row = tablesHeap.MethodTable[rid]; row = new RawMethodRow((uint)body.RVA, row.ImplFlags, row.Flags, row.Name, row.Signature, row.ParamList); tablesHeap.MethodTable[rid] = row; } foreach (var kv in methodToNativeBody) { var method = kv.Key; var body = kv.Value; uint rid = GetRid(method); var row = tablesHeap.MethodTable[rid]; row = new RawMethodRow((uint)body.RVA, row.ImplFlags, row.Flags, row.Name, row.Signature, row.ParamList); tablesHeap.MethodTable[rid] = row; } } /// /// Updates the FieldRVA rows /// void UpdateFieldRvas() { foreach (var kv in fieldToInitialValue) { var field = kv.Key; var iv = kv.Value; uint rid = fieldRVAInfos.Rid(field); var row = tablesHeap.FieldRVATable[rid]; row = new RawFieldRVARow((uint)iv.RVA, row.Field); tablesHeap.FieldRVATable[rid] = row; } } void Create() { Debug.Assert(!isStandaloneDebugMetadata); Initialize(); allTypeDefs = GetAllTypeDefs(); OnMetadataEvent(Writer.MetadataEvent.AllocateTypeDefRids); AllocateTypeDefRids(); OnMetadataEvent(Writer.MetadataEvent.AllocateMemberDefRids); AllocateMemberDefRids(); OnMetadataEvent(Writer.MetadataEvent.MemberDefRidsAllocated); AddModule(module); AddPdbDocuments(); InitializeMethodDebugInformation(); InitializeTypeDefsAndMemberDefs(); OnMetadataEvent(Writer.MetadataEvent.MemberDefsInitialized); InitializeVTableFixups(); AddExportedTypes(); InitializeEntryPoint(); if (module.Assembly is not null) AddAssembly(module.Assembly, AssemblyPublicKey); OnMetadataEvent(Writer.MetadataEvent.BeforeSortTables); SortTables(); InitializeGenericParamConstraintTable(); OnMetadataEvent(Writer.MetadataEvent.MostTablesSorted); WriteTypeDefAndMemberDefCustomAttributesAndCustomDebugInfos(); OnMetadataEvent(Writer.MetadataEvent.MemberDefCustomAttributesWritten); OnMetadataEvent(Writer.MetadataEvent.BeginAddResources); AddResources(module.Resources); OnMetadataEvent(Writer.MetadataEvent.EndAddResources); OnMetadataEvent(Writer.MetadataEvent.BeginWriteMethodBodies); WriteMethodBodies(); OnMetadataEvent(Writer.MetadataEvent.EndWriteMethodBodies); BeforeSortingCustomAttributes(); InitializeCustomAttributeAndCustomDebugInfoTables(); OnMetadataEvent(Writer.MetadataEvent.OnAllTablesSorted); EverythingInitialized(); OnMetadataEvent(Writer.MetadataEvent.EndCreateTables); } /// /// Initializes all TypeDef, Field, Method, Event, /// Property and Param rows. Other tables that are related to these six /// tables are also updated. No custom attributes are written yet, though. Method bodies /// aren't written either. /// void InitializeTypeDefsAndMemberDefs() { int count; int numTypes = allTypeDefs.Length; int typeNum = 0; int notifyNum = 0; const int numNotifyEvents = 5; int notifyAfter = numTypes / numNotifyEvents; foreach (var type in allTypeDefs) { using var _ = errorContext.SetSource(type); if (typeNum++ == notifyAfter && notifyNum < numNotifyEvents) { RaiseProgress(Writer.MetadataEvent.MemberDefRidsAllocated, (double)typeNum / numTypes); notifyNum++; notifyAfter = (int)((double)numTypes / numNotifyEvents * (notifyNum + 1)); } if (type is null) { Error("TypeDef is null"); continue; } uint typeRid = GetRid(type); var typeRow = tablesHeap.TypeDefTable[typeRid]; typeRow = new RawTypeDefRow((uint)type.Attributes, stringsHeap.Add(type.Name), stringsHeap.Add(type.Namespace), type.BaseType is null ? 0 : AddTypeDefOrRef(type.BaseType), typeRow.FieldList, typeRow.MethodList); tablesHeap.TypeDefTable[typeRid] = typeRow; AddGenericParams(new MDToken(Table.TypeDef, typeRid), type.GenericParameters); AddDeclSecurities(new MDToken(Table.TypeDef, typeRid), type.DeclSecurities); AddInterfaceImpls(typeRid, type.Interfaces); AddClassLayout(type); AddNestedType(type, type.DeclaringType); var fields = type.Fields; count = fields.Count; for (int i = 0; i < count; i++) { var field = fields[i]; if (field is null) { Error("Field is null"); continue; } using var __ = errorContext.SetSource(field); uint rid = GetRid(field); var row = new RawFieldRow((ushort)field.Attributes, stringsHeap.Add(field.Name), GetSignature(field.Signature)); tablesHeap.FieldTable[rid] = row; AddFieldLayout(field); AddFieldMarshal(new MDToken(Table.Field, rid), field); AddFieldRVA(field); AddImplMap(new MDToken(Table.Field, rid), field); AddConstant(new MDToken(Table.Field, rid), field); } var methods = type.Methods; count = methods.Count; for (int i = 0; i < count; i++) { var method = methods[i]; if (method is null) { Error("Method is null"); continue; } using var __ = errorContext.SetSource(method); if (method.ExportInfo is not null) ExportedMethods.Add(method); uint rid = GetRid(method); var row = tablesHeap.MethodTable[rid]; row = new RawMethodRow(row.RVA, (ushort)method.ImplAttributes, (ushort)method.Attributes, stringsHeap.Add(method.Name), GetSignature(method.Signature), row.ParamList); tablesHeap.MethodTable[rid] = row; AddGenericParams(new MDToken(Table.Method, rid), method.GenericParameters); AddDeclSecurities(new MDToken(Table.Method, rid), method.DeclSecurities); AddImplMap(new MDToken(Table.Method, rid), method); AddMethodImpls(method, method.Overrides); var paramDefs = method.ParamDefs; int count2 = paramDefs.Count; for (int j = 0; j < count2; j++) { var pd = paramDefs[j]; if (pd is null) { Error("Param is null"); continue; } uint pdRid = GetRid(pd); var pdRow = new RawParamRow((ushort)pd.Attributes, pd.Sequence, stringsHeap.Add(pd.Name)); tablesHeap.ParamTable[pdRid] = pdRow; AddConstant(new MDToken(Table.Param, pdRid), pd); AddFieldMarshal(new MDToken(Table.Param, pdRid), pd); } } var events = type.Events; count = events.Count; for (int i = 0; i < count; i++) { var evt = events[i]; if (evt is null) { Error("Event is null"); continue; } using var __ = errorContext.SetSource(evt); uint rid = GetRid(evt); var row = new RawEventRow((ushort)evt.Attributes, stringsHeap.Add(evt.Name), AddTypeDefOrRef(evt.EventType)); tablesHeap.EventTable[rid] = row; AddMethodSemantics(evt); } var properties = type.Properties; count = properties.Count; for (int i = 0; i < count; i++) { var prop = properties[i]; if (prop is null) { Error("Property is null"); continue; } using var __ = errorContext.SetSource(prop); uint rid = GetRid(prop); var row = new RawPropertyRow((ushort)prop.Attributes, stringsHeap.Add(prop.Name), GetSignature(prop.Type)); tablesHeap.PropertyTable[rid] = row; AddConstant(new MDToken(Table.Property, rid), prop); AddMethodSemantics(prop); } } } /// /// Writes TypeDef, Field, Method, Event, /// Property and Param custom attributes and custom debug infos. /// void WriteTypeDefAndMemberDefCustomAttributesAndCustomDebugInfos() { int count; int numTypes = allTypeDefs.Length; int typeNum = 0; int notifyNum = 0; const int numNotifyEvents = 5; int notifyAfter = numTypes / numNotifyEvents; uint rid; foreach (var type in allTypeDefs) { using var _ = errorContext.SetSource(type); if (typeNum++ == notifyAfter && notifyNum < numNotifyEvents) { RaiseProgress(Writer.MetadataEvent.MostTablesSorted, (double)typeNum / numTypes); notifyNum++; notifyAfter = (int)((double)numTypes / numNotifyEvents * (notifyNum + 1)); } if (type is null) continue; if (type.HasCustomAttributes || type.HasCustomDebugInfos) { rid = GetRid(type); AddCustomAttributes(Table.TypeDef, rid, type); AddCustomDebugInformationList(Table.TypeDef, rid, type); } var fields = type.Fields; count = fields.Count; for (int i = 0; i < count; i++) { var field = fields[i]; if (field is null) continue; if (field.HasCustomAttributes || field.HasCustomDebugInfos) { rid = GetRid(field); AddCustomAttributes(Table.Field, rid, field); AddCustomDebugInformationList(Table.Field, rid, field); } } var methods = type.Methods; count = methods.Count; for (int i = 0; i < count; i++) { var method = methods[i]; if (method is null) continue; using var __ = errorContext.SetSource(method); if (method.HasCustomAttributes) { rid = GetRid(method); AddCustomAttributes(Table.Method, rid, method); // Method custom debug info is added later when writing method bodies } var paramDefs = method.ParamDefs; int count2 = paramDefs.Count; for (int j = 0; j < count2; j++) { var pd = paramDefs[j]; if (pd is null) continue; if (pd.HasCustomAttributes || pd.HasCustomDebugInfos) { rid = GetRid(pd); AddCustomAttributes(Table.Param, rid, pd); AddCustomDebugInformationList(Table.Param, rid, pd); } } } var events = type.Events; count = events.Count; for (int i = 0; i < count; i++) { var evt = events[i]; if (evt is null) continue; if (evt.HasCustomAttributes || evt.HasCustomDebugInfos) { rid = GetRid(evt); AddCustomAttributes(Table.Event, rid, evt); AddCustomDebugInformationList(Table.Event, rid, evt); } } var properties = type.Properties; count = properties.Count; for (int i = 0; i < count; i++) { var prop = properties[i]; if (prop is null) continue; if (prop.HasCustomAttributes || prop.HasCustomDebugInfos) { rid = GetRid(prop); AddCustomAttributes(Table.Property, rid, prop); AddCustomDebugInformationList(Table.Property, rid, prop); } } } } /// /// Adds the tokens of all methods in all vtables, if any /// void InitializeVTableFixups() { var fixups = module.VTableFixups; if (fixups is null || fixups.VTables.Count == 0) return; using var _ = errorContext.SetSource("vtable fixups"); foreach (var vtable in fixups) { if (vtable is null) { Error("VTable is null"); continue; } foreach (var method in vtable) { if (method is null) continue; AddMDTokenProvider(method); } } } void AddExportedTypes() { using var _ = errorContext.SetSource("exported types"); var exportedTypes = module.ExportedTypes; int count = exportedTypes.Count; for (int i = 0; i < count; i++) AddExportedType(exportedTypes[i]); } /// /// Adds the entry point. It's only needed if it's a since if it's /// a , it will have already been added. /// void InitializeEntryPoint() { using var _ = errorContext.SetSource("entry point"); if (module.ManagedEntryPoint is FileDef epFile) AddFile(epFile); } /// /// Sorts all unsorted tables except GenericParamConstraint and CustomAttribute /// void SortTables() { classLayoutInfos.Sort((a, b) => a.row.Parent.CompareTo(b.row.Parent)); hasConstantInfos.Sort((a, b) => a.row.Parent.CompareTo(b.row.Parent)); declSecurityInfos.Sort((a, b) => a.row.Parent.CompareTo(b.row.Parent)); fieldLayoutInfos.Sort((a, b) => a.row.Field.CompareTo(b.row.Field)); fieldMarshalInfos.Sort((a, b) => a.row.Parent.CompareTo(b.row.Parent)); fieldRVAInfos.Sort((a, b) => a.row.Field.CompareTo(b.row.Field)); implMapInfos.Sort((a, b) => a.row.MemberForwarded.CompareTo(b.row.MemberForwarded)); methodImplInfos.Sort((a, b) => a.row.Class.CompareTo(b.row.Class)); methodSemanticsInfos.Sort((a, b)=> a.row.Association.CompareTo(b.row.Association)); nestedClassInfos.Sort((a, b) => a.row.NestedClass.CompareTo(b.row.NestedClass)); genericParamInfos.Sort((a, b) => { if (a.row.Owner != b.row.Owner) return a.row.Owner.CompareTo(b.row.Owner); return a.row.Number.CompareTo(b.row.Number); }); interfaceImplInfos.Sort((a, b) => a.row.Class.CompareTo(b.row.Class)); tablesHeap.ClassLayoutTable.IsSorted = true; tablesHeap.ConstantTable.IsSorted = true; tablesHeap.DeclSecurityTable.IsSorted = true; tablesHeap.FieldLayoutTable.IsSorted = true; tablesHeap.FieldMarshalTable.IsSorted = true; tablesHeap.FieldRVATable.IsSorted = true; tablesHeap.GenericParamTable.IsSorted = true; tablesHeap.ImplMapTable.IsSorted = true; tablesHeap.InterfaceImplTable.IsSorted = true; tablesHeap.MethodImplTable.IsSorted = true; tablesHeap.MethodSemanticsTable.IsSorted = true; tablesHeap.NestedClassTable.IsSorted = true; // These two are also sorted tablesHeap.EventMapTable.IsSorted = true; tablesHeap.PropertyMapTable.IsSorted = true; foreach (var info in classLayoutInfos.infos) tablesHeap.ClassLayoutTable.Create(info.row); foreach (var info in hasConstantInfos.infos) tablesHeap.ConstantTable.Create(info.row); foreach (var info in declSecurityInfos.infos) tablesHeap.DeclSecurityTable.Create(info.row); foreach (var info in fieldLayoutInfos.infos) tablesHeap.FieldLayoutTable.Create(info.row); foreach (var info in fieldMarshalInfos.infos) tablesHeap.FieldMarshalTable.Create(info.row); foreach (var info in fieldRVAInfos.infos) tablesHeap.FieldRVATable.Create(info.row); foreach (var info in genericParamInfos.infos) tablesHeap.GenericParamTable.Create(info.row); foreach (var info in implMapInfos.infos) tablesHeap.ImplMapTable.Create(info.row); foreach (var info in interfaceImplInfos.infos) tablesHeap.InterfaceImplTable.Create(info.row); foreach (var info in methodImplInfos.infos) tablesHeap.MethodImplTable.Create(info.row); foreach (var info in methodSemanticsInfos.infos) tablesHeap.MethodSemanticsTable.Create(info.row); foreach (var info in nestedClassInfos.infos) tablesHeap.NestedClassTable.Create(info.row); foreach (var info in interfaceImplInfos.infos) { if (info.data.HasCustomAttributes || info.data.HasCustomDebugInfos) { uint rid = interfaceImplInfos.Rid(info.data); AddCustomAttributes(Table.InterfaceImpl, rid, info.data); AddCustomDebugInformationList(Table.InterfaceImpl, rid, info.data); } } foreach (var info in declSecurityInfos.infos) { if (info.data.HasCustomAttributes || info.data.HasCustomDebugInfos) { uint rid = declSecurityInfos.Rid(info.data); AddCustomAttributes(Table.DeclSecurity, rid, info.data); AddCustomDebugInformationList(Table.DeclSecurity, rid, info.data); } } foreach (var info in genericParamInfos.infos) { if (info.data.HasCustomAttributes || info.data.HasCustomDebugInfos) { uint rid = genericParamInfos.Rid(info.data); AddCustomAttributes(Table.GenericParam, rid, info.data); AddCustomDebugInformationList(Table.GenericParam, rid, info.data); } } } /// /// Initializes the GenericParamConstraint table /// void InitializeGenericParamConstraintTable() { foreach (var type in allTypeDefs) { if (type is null) continue; using var _ = errorContext.SetSource(type); AddGenericParamConstraints(type.GenericParameters); var methods = type.Methods; int count = methods.Count; for (int i = 0; i < count; i++) { var method = methods[i]; if (method is null) continue; using var __ = errorContext.SetSource(method); AddGenericParamConstraints(method.GenericParameters); } } genericParamConstraintInfos.Sort((a, b) => a.row.Owner.CompareTo(b.row.Owner)); tablesHeap.GenericParamConstraintTable.IsSorted = true; foreach (var info in genericParamConstraintInfos.infos) tablesHeap.GenericParamConstraintTable.Create(info.row); foreach (var info in genericParamConstraintInfos.infos) { if (info.data.HasCustomAttributes || info.data.HasCustomDebugInfos) { uint rid = genericParamConstraintInfos.Rid(info.data); AddCustomAttributes(Table.GenericParamConstraint, rid, info.data); AddCustomDebugInformationList(Table.GenericParamConstraint, rid, info.data); } } } /// /// Inserts all custom attribute / custom debug info rows in the tables and sort them /// void InitializeCustomAttributeAndCustomDebugInfoTables() { customAttributeInfos.Sort((a, b) => a.row.Parent.CompareTo(b.row.Parent)); tablesHeap.CustomAttributeTable.IsSorted = true; foreach (var info in customAttributeInfos.infos) tablesHeap.CustomAttributeTable.Create(info.row); if (debugMetadata is not null) { debugMetadata.stateMachineMethodInfos.Sort((a, b) => a.row.MoveNextMethod.CompareTo(b.row.MoveNextMethod)); debugMetadata.tablesHeap.StateMachineMethodTable.IsSorted = true; foreach (var info in debugMetadata.stateMachineMethodInfos.infos) debugMetadata.tablesHeap.StateMachineMethodTable.Create(info.row); debugMetadata.customDebugInfos.Sort((a, b) => a.row.Parent.CompareTo(b.row.Parent)); debugMetadata.tablesHeap.CustomDebugInformationTable.IsSorted = true; foreach (var info in debugMetadata.customDebugInfos.infos) debugMetadata.tablesHeap.CustomDebugInformationTable.Create(info.row); } } struct MethodScopeDebugInfo { public uint MethodRid; public PdbScope Scope; public uint ScopeStart; public uint ScopeLength; } /// /// Writes all method bodies /// void WriteMethodBodies() { Debug.Assert(!isStandaloneDebugMetadata); if (NoMethodBodies) return; int numMethods = NumberOfMethods; int methodNum = 0; int notifyNum = 0; // Writing method bodies is the most expensive part and takes the longest const int numNotifyEvents = 40; int notifyAfter = numMethods / numNotifyEvents; var debugMetadata = this.debugMetadata; var methodBodies = this.methodBodies; var methodToBody = this.methodToBody; List methodScopeDebugInfos; List scopeStack; SerializerMethodContext serializerMethodContext; if (debugMetadata is null) { methodScopeDebugInfos = null; scopeStack = null; serializerMethodContext = null; } else { methodScopeDebugInfos = new List(); scopeStack = new List(); serializerMethodContext = AllocSerializerMethodContext(); } bool keepMaxStack = KeepOldMaxStack; var writer = new MethodBodyWriter(this); foreach (var type in allTypeDefs) { if (type is null) continue; using var _ = errorContext.SetSource(type); var methods = type.Methods; for (int i = 0; i < methods.Count; i++) { var method = methods[i]; if (method is null) continue; using var __ = errorContext.SetSource(method); if (methodNum++ == notifyAfter && notifyNum < numNotifyEvents) { RaiseProgress(Writer.MetadataEvent.BeginWriteMethodBodies, (double)methodNum / numMethods); notifyNum++; notifyAfter = (int)((double)numMethods / numNotifyEvents * (notifyNum + 1)); } uint localVarSigTok = 0; var cilBody = method.Body; if (cilBody is not null) { if (!(cilBody.Instructions.Count == 0 && cilBody.Variables.Count == 0)) { writer.Reset(cilBody, keepMaxStack || cilBody.KeepOldMaxStack); writer.Write(); var origRva = method.RVA; uint origSize = cilBody.MetadataBodySize; var mb = methodBodies.Add(new MethodBody(writer.Code, writer.ExtraSections, writer.LocalVarSigTok), origRva, origSize); methodToBody[method] = mb; localVarSigTok = writer.LocalVarSigTok; } } else { var nativeBody = method.NativeBody; if (nativeBody is not null) methodToNativeBody[method] = nativeBody; else if (method.MethodBody is not null) Error("Unsupported method body"); } if (debugMetadata is not null) { uint rid = GetRid(method); if (cilBody is not null) { var pdbMethod = cilBody.PdbMethod; if (pdbMethod is not null) { // We don't need to write empty scopes if (!IsEmptyRootScope(cilBody, pdbMethod.Scope)) { serializerMethodContext.SetBody(method); scopeStack.Add(pdbMethod.Scope); while (scopeStack.Count > 0) { var scope = scopeStack[scopeStack.Count - 1]; scopeStack.RemoveAt(scopeStack.Count - 1); scopeStack.AddRange(scope.Scopes); uint scopeStart = serializerMethodContext.GetOffset(scope.Start); uint scopeEnd = serializerMethodContext.GetOffset(scope.End); methodScopeDebugInfos.Add(new MethodScopeDebugInfo() { MethodRid = rid, Scope = scope, ScopeStart = scopeStart, ScopeLength = scopeEnd - scopeStart, }); } } } } // Always add CDIs even if it has no managed method body AddCustomDebugInformationList(method, rid, localVarSigTok); } } } if (debugMetadata is not null) { methodScopeDebugInfos.Sort((a, b) => { int c = a.MethodRid.CompareTo(b.MethodRid); if (c != 0) return c; c = a.ScopeStart.CompareTo(b.ScopeStart); if (c != 0) return c; return b.ScopeLength.CompareTo(a.ScopeLength); }); foreach (var info in methodScopeDebugInfos) { uint localScopeRid = (uint)debugMetadata.localScopeInfos.infos.Count + 1; var row = new RawLocalScopeRow(info.MethodRid, AddImportScope(info.Scope.ImportScope), (uint)debugMetadata.tablesHeap.LocalVariableTable.Rows + 1, (uint)debugMetadata.tablesHeap.LocalConstantTable.Rows + 1, info.ScopeStart, info.ScopeLength); debugMetadata.localScopeInfos.Add(info.Scope, row); var variables = info.Scope.Variables; int count = variables.Count; for (int i = 0; i < count; i++) { var local = variables[i]; AddLocalVariable(local); } var constants = info.Scope.Constants; count = constants.Count; for (int i = 0; i < count; i++) { var constant = constants[i]; AddLocalConstant(constant); } AddCustomDebugInformationList(Table.LocalScope, localScopeRid, info.Scope.CustomDebugInfos); } debugMetadata.tablesHeap.LocalScopeTable.IsSorted = true; foreach (var info in debugMetadata.localScopeInfos.infos) debugMetadata.tablesHeap.LocalScopeTable.Create(info.row); } if (serializerMethodContext is not null) Free(ref serializerMethodContext); } static bool IsEmptyRootScope(CilBody cilBody, PdbScope scope) { if (scope.Variables.Count != 0) return false; if (scope.Constants.Count != 0) return false; if (scope.Namespaces.Count != 0) return false; if (scope.ImportScope is not null) return false; if (scope.Scopes.Count != 0) return false; if (scope.CustomDebugInfos.Count != 0) return false; if (scope.End is not null) return false; if (cilBody.Instructions.Count != 0 && cilBody.Instructions[0] != scope.Start) return false; return true; } /// /// Checks whether a list is empty or whether it contains only nulls /// /// Any type /// The list /// true if the list is empty or if it contains only nulls, false otherwise protected static bool IsEmpty(IList list) where T : class { if (list is null) return true; int count = list.Count; for (int i = 0; i < count; i++) { if (list[i] is not null) return false; } return true; } /// public MDToken GetToken(object o) { if (o is IMDTokenProvider tp) return new MDToken(tp.MDToken.Table, AddMDTokenProvider(tp)); if (o is string s) return new MDToken((Table)0x70, usHeap.Add(s)); if (o is MethodSig methodSig) return new MDToken(Table.StandAloneSig, AddStandAloneSig(methodSig, methodSig.OriginalToken)); if (o is FieldSig fieldSig) return new MDToken(Table.StandAloneSig, AddStandAloneSig(fieldSig, 0)); if (o is null) Error("Instruction operand is null"); else Error("Invalid instruction operand"); return new MDToken((Table)0xFF, 0x00FFFFFF); } /// public virtual MDToken GetToken(IList locals, uint origToken) { if (locals is null || locals.Count == 0) return new MDToken((Table)0, 0); var row = new RawStandAloneSigRow(GetSignature(new LocalSig(locals, false))); uint rid = tablesHeap.StandAloneSigTable.Add(row); //TODO: Add custom attributes //TODO: Add custom debug infos return new MDToken(Table.StandAloneSig, rid); } /// /// Adds a /// /// Method signature /// Original StandAloneSig token or 0 if none /// Its new rid protected virtual uint AddStandAloneSig(MethodSig methodSig, uint origToken) { if (methodSig is null) { Error("StandAloneSig: MethodSig is null"); return 0; } var row = new RawStandAloneSigRow(GetSignature(methodSig)); uint rid = tablesHeap.StandAloneSigTable.Add(row); //TODO: Add custom attributes //TODO: Add custom debug infos return rid; } /// /// Adds a /// /// FIeld signature /// Original StandAloneSig token or 0 if none /// Its new rid protected virtual uint AddStandAloneSig(FieldSig fieldSig, uint origToken) { if (fieldSig is null) { Error("StandAloneSig: FieldSig is null"); return 0; } var row = new RawStandAloneSigRow(GetSignature(fieldSig)); uint rid = tablesHeap.StandAloneSigTable.Add(row); //TODO: Add custom attributes //TODO: Add custom debug infos return rid; } uint AddMDTokenProvider(IMDTokenProvider tp) { if (tp is not null) { switch (tp.MDToken.Table) { case Table.Module: return AddModule((ModuleDef)tp); case Table.TypeRef: return AddTypeRef((TypeRef)tp); case Table.TypeDef: return GetRid((TypeDef)tp); case Table.Field: return GetRid((FieldDef)tp); case Table.Method: return GetRid((MethodDef)tp); case Table.Param: return GetRid((ParamDef)tp); case Table.MemberRef: return AddMemberRef((MemberRef)tp); case Table.StandAloneSig: return AddStandAloneSig((StandAloneSig)tp); case Table.Event: return GetRid((EventDef)tp); case Table.Property: return GetRid((PropertyDef)tp); case Table.ModuleRef: return AddModuleRef((ModuleRef)tp); case Table.TypeSpec: return AddTypeSpec((TypeSpec)tp); case Table.Assembly: return AddAssembly((AssemblyDef)tp, null); case Table.AssemblyRef: return AddAssemblyRef((AssemblyRef)tp); case Table.File: return AddFile((FileDef)tp); case Table.ExportedType: return AddExportedType((ExportedType)tp); case Table.MethodSpec: return AddMethodSpec((MethodSpec)tp); case Table.FieldPtr: case Table.MethodPtr: case Table.ParamPtr: case Table.InterfaceImpl: case Table.Constant: case Table.CustomAttribute: case Table.FieldMarshal: case Table.DeclSecurity: case Table.ClassLayout: case Table.FieldLayout: case Table.EventMap: case Table.EventPtr: case Table.PropertyMap: case Table.PropertyPtr: case Table.MethodSemantics: case Table.MethodImpl: case Table.ImplMap: case Table.FieldRVA: case Table.ENCLog: case Table.ENCMap: case Table.AssemblyProcessor: case Table.AssemblyOS: case Table.AssemblyRefProcessor: case Table.AssemblyRefOS: case Table.ManifestResource: case Table.NestedClass: case Table.GenericParam: case Table.GenericParamConstraint: case Table.Document: case Table.MethodDebugInformation: case Table.LocalScope: case Table.LocalVariable: case Table.LocalConstant: case Table.ImportScope: case Table.StateMachineMethod: case Table.CustomDebugInformation: default: break; } } if (tp is null) Error("IMDTokenProvider is null"); else Error("Invalid IMDTokenProvider"); return 0; } /// /// Adds a /// /// Value /// Its encoded token protected uint AddTypeDefOrRef(ITypeDefOrRef tdr) { if (tdr is null) { Error("TypeDefOrRef is null"); return 0; } var token = new MDToken(tdr.MDToken.Table, AddMDTokenProvider(tdr)); if (!CodedToken.TypeDefOrRef.Encode(token, out uint encodedToken)) { Error("Can't encode TypeDefOrRef token 0x{0:X8}.", token.Raw); encodedToken = 0; } return encodedToken; } /// /// Adds a /// /// Value /// Its encoded token protected uint AddResolutionScope(IResolutionScope rs) { if (rs is null) { return 0; } var token = new MDToken(rs.MDToken.Table, AddMDTokenProvider(rs)); if (!CodedToken.ResolutionScope.Encode(token, out uint encodedToken)) { Error("Can't encode ResolutionScope token 0x{0:X8}.", token.Raw); encodedToken = 0; } return encodedToken; } /// /// Adds a /// /// Value /// Its encoded token protected uint AddMethodDefOrRef(IMethodDefOrRef mdr) { if (mdr is null) { Error("MethodDefOrRef is null"); return 0; } var token = new MDToken(mdr.MDToken.Table, AddMDTokenProvider(mdr)); if (!CodedToken.MethodDefOrRef.Encode(token, out uint encodedToken)) { Error("Can't encode MethodDefOrRef token 0x{0:X8}.", token.Raw); encodedToken = 0; } return encodedToken; } /// /// Adds a /// /// Value /// Its encoded token protected uint AddMemberRefParent(IMemberRefParent parent) { if (parent is null) { Error("MemberRefParent is null"); return 0; } var token = new MDToken(parent.MDToken.Table, AddMDTokenProvider(parent)); if (!CodedToken.MemberRefParent.Encode(token, out uint encodedToken)) { Error("Can't encode MemberRefParent token 0x{0:X8}.", token.Raw); encodedToken = 0; } return encodedToken; } /// /// Adds a /// /// Value /// Its encoded token protected uint AddImplementation(IImplementation impl) { if (impl is null) { Error("Implementation is null"); return 0; } var token = new MDToken(impl.MDToken.Table, AddMDTokenProvider(impl)); if (!CodedToken.Implementation.Encode(token, out uint encodedToken)) { Error("Can't encode Implementation token 0x{0:X8}.", token.Raw); encodedToken = 0; } return encodedToken; } /// /// Adds a /// /// Value /// Its encoded token protected uint AddCustomAttributeType(ICustomAttributeType cat) { if (cat is null) { Error("CustomAttributeType is null"); return 0; } var token = new MDToken(cat.MDToken.Table, AddMDTokenProvider(cat)); if (!CodedToken.CustomAttributeType.Encode(token, out uint encodedToken)) { Error("Can't encode CustomAttributeType token 0x{0:X8}.", token.Raw); encodedToken = 0; } return encodedToken; } /// /// Adds a NestedType row /// /// Nested type /// Declaring type protected void AddNestedType(TypeDef nestedType, TypeDef declaringType) { if (nestedType is null || declaringType is null) return; uint nestedRid = GetRid(nestedType); uint dtRid = GetRid(declaringType); if (nestedRid == 0 || dtRid == 0) return; var row = new RawNestedClassRow(nestedRid, dtRid); nestedClassInfos.Add(declaringType, row); } /// /// Adds a Module row /// /// Module /// Its new rid protected uint AddModule(ModuleDef module) { if (module is null) { Error("Module is null"); return 0; } if (this.module != module) Error("Module '{0}' must be referenced with a ModuleRef, not a ModuleDef.", module); if (moduleDefInfos.TryGetRid(module, out uint rid)) return rid; var row = new RawModuleRow(module.Generation, stringsHeap.Add(module.Name), guidHeap.Add(module.Mvid), guidHeap.Add(module.EncId), guidHeap.Add(module.EncBaseId)); rid = tablesHeap.ModuleTable.Add(row); moduleDefInfos.Add(module, rid); AddCustomAttributes(Table.Module, rid, module); AddCustomDebugInformationList(Table.Module, rid, module); return rid; } /// /// Adds a ModuleRef row /// /// Module ref /// Its new rid protected uint AddModuleRef(ModuleRef modRef) { if (modRef is null) { Error("ModuleRef is null"); return 0; } if (moduleRefInfos.TryGetRid(modRef, out uint rid)) return rid; var row = new RawModuleRefRow(stringsHeap.Add(modRef.Name)); rid = tablesHeap.ModuleRefTable.Add(row); moduleRefInfos.Add(modRef, rid); AddCustomAttributes(Table.ModuleRef, rid, modRef); AddCustomDebugInformationList(Table.ModuleRef, rid, modRef); return rid; } /// /// Adds an AssemblyRef row /// /// Assembly ref /// Its new rid protected uint AddAssemblyRef(AssemblyRef asmRef) { if (asmRef is null) { Error("AssemblyRef is null"); return 0; } if (assemblyRefInfos.TryGetRid(asmRef, out uint rid)) return rid; var version = Utils.CreateVersionWithNoUndefinedValues(asmRef.Version); var row = new RawAssemblyRefRow((ushort)version.Major, (ushort)version.Minor, (ushort)version.Build, (ushort)version.Revision, (uint)asmRef.Attributes, blobHeap.Add(PublicKeyBase.GetRawData(asmRef.PublicKeyOrToken)), stringsHeap.Add(asmRef.Name), stringsHeap.Add(asmRef.Culture), blobHeap.Add(asmRef.Hash)); rid = tablesHeap.AssemblyRefTable.Add(row); assemblyRefInfos.Add(asmRef, rid); AddCustomAttributes(Table.AssemblyRef, rid, asmRef); AddCustomDebugInformationList(Table.AssemblyRef, rid, asmRef); return rid; } /// /// Adds an Assembly row /// /// Assembly /// The public key that should be used /// Its new rid protected uint AddAssembly(AssemblyDef asm, byte[] publicKey) { if (asm is null) { Error("Assembly is null"); return 0; } if (assemblyInfos.TryGetRid(asm, out uint rid)) return rid; var asmAttrs = asm.Attributes; if (publicKey is not null) asmAttrs |= AssemblyAttributes.PublicKey; else publicKey = PublicKeyBase.GetRawData(asm.PublicKeyOrToken); var version = Utils.CreateVersionWithNoUndefinedValues(asm.Version); var row = new RawAssemblyRow((uint)asm.HashAlgorithm, (ushort)version.Major, (ushort)version.Minor, (ushort)version.Build, (ushort)version.Revision, (uint)asmAttrs, blobHeap.Add(publicKey), stringsHeap.Add(asm.Name), stringsHeap.Add(asm.Culture)); rid = tablesHeap.AssemblyTable.Add(row); assemblyInfos.Add(asm, rid); AddDeclSecurities(new MDToken(Table.Assembly, rid), asm.DeclSecurities); AddCustomAttributes(Table.Assembly, rid, asm); AddCustomDebugInformationList(Table.Assembly, rid, asm); return rid; } /// /// Adds generic parameters /// /// New token of owner /// All generic params protected void AddGenericParams(MDToken token, IList gps) { if (gps is null) return; int count = gps.Count; for (int i = 0; i < count; i++) AddGenericParam(token, gps[i]); } /// /// Adds a generic param /// /// New token of owner /// Generic paramater protected void AddGenericParam(MDToken owner, GenericParam gp) { if (gp is null) { Error("GenericParam is null"); return; } if (!CodedToken.TypeOrMethodDef.Encode(owner, out uint encodedOwner)) { Error("Can't encode TypeOrMethodDef token 0x{0:X8}.", owner.Raw); encodedOwner = 0; } var row = new RawGenericParamRow(gp.Number, (ushort)gp.Flags, encodedOwner, stringsHeap.Add(gp.Name), gp.Kind is null ? 0 : AddTypeDefOrRef(gp.Kind)); genericParamInfos.Add(gp, row); } void AddGenericParamConstraints(IList gps) { if (gps is null) return; int count = gps.Count; for (int i = 0; i < count; i++) { var gp = gps[i]; if (gp is null) continue; uint rid = genericParamInfos.Rid(gp); AddGenericParamConstraints(rid, gp.GenericParamConstraints); } } /// /// Adds generic parameter constraints /// /// New rid of owner generic param /// Its constraints protected void AddGenericParamConstraints(uint gpRid, IList constraints) { if (constraints is null) return; int count = constraints.Count; for (int i = 0; i < count; i++) AddGenericParamConstraint(gpRid, constraints[i]); } /// /// Adds a generic parameter constraint /// /// New rid of owner generic param /// Generic parameter constraint protected void AddGenericParamConstraint(uint gpRid, GenericParamConstraint gpc) { if (gpc is null) { Error("GenericParamConstraint is null"); return; } var row = new RawGenericParamConstraintRow(gpRid, AddTypeDefOrRef(gpc.Constraint)); genericParamConstraintInfos.Add(gpc, row); } /// /// Adds a InterfaceImpl row /// /// New rid of owner /// All interfaces protected void AddInterfaceImpls(uint typeDefRid, IList ifaces) { int count = ifaces.Count; for (int i = 0; i < count; i++) { var iface = ifaces[i]; if (iface is null) continue; var row = new RawInterfaceImplRow(typeDefRid, AddTypeDefOrRef(iface.Interface)); interfaceImplInfos.Add(iface, row); } } /// /// Adds a FieldLayout row /// /// Owner field protected void AddFieldLayout(FieldDef field) { if (field is null || field.FieldOffset is null) return; var rid = GetRid(field); var row = new RawFieldLayoutRow(field.FieldOffset.Value, rid); fieldLayoutInfos.Add(field, row); } /// /// Adds a FieldMarshal row /// /// New owner token /// Owner protected void AddFieldMarshal(MDToken parent, IHasFieldMarshal hfm) { if (hfm is null || hfm.MarshalType is null) return; var fieldMarshal = hfm.MarshalType; if (!CodedToken.HasFieldMarshal.Encode(parent, out uint encodedParent)) { Error("Can't encode HasFieldMarshal token 0x{0:X8}.", parent.Raw); encodedParent = 0; } var row = new RawFieldMarshalRow(encodedParent, blobHeap.Add(MarshalBlobWriter.Write(module, fieldMarshal, this, OptimizeCustomAttributeSerializedTypeNames))); fieldMarshalInfos.Add(hfm, row); } /// /// Adds a FieldRVA row /// /// The field protected void AddFieldRVA(FieldDef field) { Debug.Assert(!isStandaloneDebugMetadata); if (NoFieldData) return; if (field.RVA != 0 && KeepFieldRVA) { uint rid = GetRid(field); var row = new RawFieldRVARow((uint)field.RVA, rid); fieldRVAInfos.Add(field, row); } else { if (field is null || field.InitialValue is null) return; var ivBytes = field.InitialValue; if (!VerifyFieldSize(field, ivBytes.Length)) Error("Field '{0}' (0x{1:X8}) initial value size != size of field type.", field, field.MDToken.Raw); uint rid = GetRid(field); uint alignment = ModuleWriterBase.DEFAULT_CONSTANTS_ALIGNMENT; if (field.FieldType is TypeDefOrRefSig tdrSig && tdrSig.TypeDef?.ClassLayout is {} classLayout) alignment = Math.Max(alignment, Utils.RoundToNextPowerOfTwo(classLayout.PackingSize)); var iv = constants.Add(new ByteArrayChunk(ivBytes, alignment), alignment); fieldToInitialValue[field] = iv; var row = new RawFieldRVARow(0, rid); fieldRVAInfos.Add(field, row); } } static bool VerifyFieldSize(FieldDef field, int size) { if (field is null) return false; var sig = field.FieldSig; if (sig is null) return false; return field.GetFieldSize() == size; } /// /// Adds a ImplMap row /// /// New owner token /// Owner protected void AddImplMap(MDToken parent, IMemberForwarded mf) { if (mf is null || mf.ImplMap is null) return; var implMap = mf.ImplMap; if (!CodedToken.MemberForwarded.Encode(parent, out uint encodedParent)) { Error("Can't encode MemberForwarded token 0x{0:X8}.", parent.Raw); encodedParent = 0; } var row = new RawImplMapRow((ushort)implMap.Attributes, encodedParent, stringsHeap.Add(implMap.Name), AddModuleRef(implMap.Module)); implMapInfos.Add(mf, row); } /// /// Adds a Constant row /// /// New owner token /// Owner protected void AddConstant(MDToken parent, IHasConstant hc) { if (hc is null || hc.Constant is null) return; var constant = hc.Constant; if (!CodedToken.HasConstant.Encode(parent, out uint encodedParent)) { Error("Can't encode HasConstant token 0x{0:X8}.", parent.Raw); encodedParent = 0; } var row = new RawConstantRow((byte)constant.Type, 0, encodedParent, blobHeap.Add(GetConstantValueAsByteArray(constant.Type, constant.Value))); hasConstantInfos.Add(hc, row); } static readonly byte[] constantClassByteArray = new byte[4]; static readonly byte[] constantDefaultByteArray = new byte[8]; byte[] GetConstantValueAsByteArray(ElementType etype, object o) { if (o is null) { if (etype == ElementType.Class) return constantClassByteArray; Error("Constant is null"); return constantDefaultByteArray; } var typeCode = Type.GetTypeCode(o.GetType()); switch (typeCode) { case TypeCode.Boolean: VerifyConstantType(etype, ElementType.Boolean); return BitConverter.GetBytes((bool)o); case TypeCode.Char: VerifyConstantType(etype, ElementType.Char); return BitConverter.GetBytes((char)o); case TypeCode.SByte: VerifyConstantType(etype, ElementType.I1); return new byte[1] { (byte)(sbyte)o }; case TypeCode.Byte: VerifyConstantType(etype, ElementType.U1); return new byte[1] { (byte)o }; case TypeCode.Int16: VerifyConstantType(etype, ElementType.I2); return BitConverter.GetBytes((short)o); case TypeCode.UInt16: VerifyConstantType(etype, ElementType.U2); return BitConverter.GetBytes((ushort)o); case TypeCode.Int32: VerifyConstantType(etype, ElementType.I4); return BitConverter.GetBytes((int)o); case TypeCode.UInt32: VerifyConstantType(etype, ElementType.U4); return BitConverter.GetBytes((uint)o); case TypeCode.Int64: VerifyConstantType(etype, ElementType.I8); return BitConverter.GetBytes((long)o); case TypeCode.UInt64: VerifyConstantType(etype, ElementType.U8); return BitConverter.GetBytes((ulong)o); case TypeCode.Single: VerifyConstantType(etype, ElementType.R4); return BitConverter.GetBytes((float)o); case TypeCode.Double: VerifyConstantType(etype, ElementType.R8); return BitConverter.GetBytes((double)o); case TypeCode.String: VerifyConstantType(etype, ElementType.String); return Encoding.Unicode.GetBytes((string)o); default: Error("Invalid constant type: {0}", typeCode); return constantDefaultByteArray; } } void VerifyConstantType(ElementType realType, ElementType expectedType) { if (realType != expectedType) Error("Constant value's type is the wrong type: {0} != {1}", realType, expectedType); } /// /// Adds a DeclSecurity row /// /// New owner token /// All DeclSecurity rows protected void AddDeclSecurities(MDToken parent, IList declSecurities) { if (declSecurities is null) return; if (!CodedToken.HasDeclSecurity.Encode(parent, out uint encodedParent)) { Error("Can't encode HasDeclSecurity token 0x{0:X8}.", parent.Raw); encodedParent = 0; } var bwctx = AllocBinaryWriterContext(); int count = declSecurities.Count; for (int i = 0; i < count; i++) { var decl = declSecurities[i]; if (decl is null) continue; var row = new RawDeclSecurityRow((short)decl.Action, encodedParent, blobHeap.Add(DeclSecurityWriter.Write(module, decl.SecurityAttributes, this, OptimizeCustomAttributeSerializedTypeNames, bwctx))); declSecurityInfos.Add(decl, row); } Free(ref bwctx); } /// /// Adds MethodSemantics rows /// /// Event protected void AddMethodSemantics(EventDef evt) { if (evt is null) { Error("Event is null"); return; } uint rid = GetRid(evt); if (rid == 0) return; var token = new MDToken(Table.Event, rid); AddMethodSemantics(token, evt.AddMethod, MethodSemanticsAttributes.AddOn); AddMethodSemantics(token, evt.RemoveMethod, MethodSemanticsAttributes.RemoveOn); AddMethodSemantics(token, evt.InvokeMethod, MethodSemanticsAttributes.Fire); AddMethodSemantics(token, evt.OtherMethods, MethodSemanticsAttributes.Other); } /// /// Adds MethodSemantics rows /// /// Property protected void AddMethodSemantics(PropertyDef prop) { if (prop is null) { Error("Property is null"); return; } uint rid = GetRid(prop); if (rid == 0) return; var token = new MDToken(Table.Property, rid); AddMethodSemantics(token, prop.GetMethods, MethodSemanticsAttributes.Getter); AddMethodSemantics(token, prop.SetMethods, MethodSemanticsAttributes.Setter); AddMethodSemantics(token, prop.OtherMethods, MethodSemanticsAttributes.Other); } void AddMethodSemantics(MDToken owner, IList methods, MethodSemanticsAttributes attrs) { if (methods is null) return; int count = methods.Count; for (int i = 0; i < count; i++) AddMethodSemantics(owner, methods[i], attrs); } void AddMethodSemantics(MDToken owner, MethodDef method, MethodSemanticsAttributes flags) { if (method is null) return; uint methodRid = GetRid(method); if (methodRid == 0) return; if (!CodedToken.HasSemantic.Encode(owner, out uint encodedOwner)) { Error("Can't encode HasSemantic token 0x{0:X8}.", owner.Raw); encodedOwner = 0; } var row = new RawMethodSemanticsRow((ushort)flags, methodRid, encodedOwner); methodSemanticsInfos.Add(method, row); } void AddMethodImpls(MethodDef method, IList overrides) { if (overrides is null) return; if (method.DeclaringType is null) { Error("Method declaring type is null"); return; } if (overrides.Count != 0) { uint rid = GetRid(method.DeclaringType); int count = overrides.Count; for (int i = 0; i < count; i++) { var ovr = overrides[i]; var row = new RawMethodImplRow(rid, AddMethodDefOrRef(ovr.MethodBody), AddMethodDefOrRef(ovr.MethodDeclaration)); methodImplInfos.Add(method, row); } } } /// /// Adds a ClassLayout row /// /// Type protected void AddClassLayout(TypeDef type) { if (type is null || type.ClassLayout is null) return; var rid = GetRid(type); var classLayout = type.ClassLayout; var row = new RawClassLayoutRow(classLayout.PackingSize, classLayout.ClassSize, rid); classLayoutInfos.Add(type, row); } void AddResources(IList resources) { if (NoDotNetResources) return; if (resources is null) return; int count = resources.Count; for (int i = 0; i < count; i++) AddResource(resources[i]); } void AddResource(Resource resource) { Debug.Assert(!NoDotNetResources); if (resource is EmbeddedResource er) { AddEmbeddedResource(er); return; } if (resource is AssemblyLinkedResource alr) { AddAssemblyLinkedResource(alr); return; } if (resource is LinkedResource lr) { AddLinkedResource(lr); return; } if (resource is null) Error("Resource is null"); else Error("Invalid resource type: '{0}'.", resource.GetType()); } uint AddEmbeddedResource(EmbeddedResource er) { Debug.Assert(!isStandaloneDebugMetadata); Debug.Assert(!NoDotNetResources); if (er is null) { Error("EmbeddedResource is null"); return 0; } if (manifestResourceInfos.TryGetRid(er, out uint rid)) return rid; var row = new RawManifestResourceRow(netResources.NextOffset, (uint)er.Attributes, stringsHeap.Add(er.Name), 0); rid = tablesHeap.ManifestResourceTable.Add(row); manifestResourceInfos.Add(er, rid); embeddedResourceToByteArray[er] = netResources.Add(er.CreateReader()); AddCustomAttributes(Table.ManifestResource, rid, er); AddCustomDebugInformationList(Table.ManifestResource, rid, er); return rid; } uint AddAssemblyLinkedResource(AssemblyLinkedResource alr) { Debug.Assert(!NoDotNetResources); if (alr is null) { Error("AssemblyLinkedResource is null"); return 0; } if (manifestResourceInfos.TryGetRid(alr, out uint rid)) return rid; var row = new RawManifestResourceRow(0, (uint)alr.Attributes, stringsHeap.Add(alr.Name), AddImplementation(alr.Assembly)); rid = tablesHeap.ManifestResourceTable.Add(row); manifestResourceInfos.Add(alr, rid); AddCustomAttributes(Table.ManifestResource, rid, alr); AddCustomDebugInformationList(Table.ManifestResource, rid, alr); return rid; } uint AddLinkedResource(LinkedResource lr) { Debug.Assert(!NoDotNetResources); if (lr is null) { Error("LinkedResource is null"); return 0; } if (manifestResourceInfos.TryGetRid(lr, out uint rid)) return rid; var row = new RawManifestResourceRow(0, (uint)lr.Attributes, stringsHeap.Add(lr.Name), AddImplementation(lr.File)); rid = tablesHeap.ManifestResourceTable.Add(row); manifestResourceInfos.Add(lr, rid); AddCustomAttributes(Table.ManifestResource, rid, lr); AddCustomDebugInformationList(Table.ManifestResource, rid, lr); return rid; } /// /// Adds a File row /// /// File /// Its new rid protected uint AddFile(FileDef file) { if (file is null) { Error("FileDef is null"); return 0; } if (fileDefInfos.TryGetRid(file, out uint rid)) return rid; var row = new RawFileRow((uint)file.Flags, stringsHeap.Add(file.Name), blobHeap.Add(file.HashValue)); //TODO: Re-calculate the hash value if possible rid = tablesHeap.FileTable.Add(row); fileDefInfos.Add(file, rid); AddCustomAttributes(Table.File, rid, file); AddCustomDebugInformationList(Table.File, rid, file); return rid; } /// /// Adds a ExportedType row /// /// Exported type /// Its new rid protected uint AddExportedType(ExportedType et) { if (et is null) { Error("ExportedType is null"); return 0; } if (exportedTypeInfos.TryGetRid(et, out uint rid)) return rid; exportedTypeInfos.Add(et, 0); // Prevent inf recursion var row = new RawExportedTypeRow((uint)et.Attributes, et.TypeDefId, //TODO: Should be updated with the new rid stringsHeap.Add(et.TypeName), stringsHeap.Add(et.TypeNamespace), AddImplementation(et.Implementation)); rid = tablesHeap.ExportedTypeTable.Add(row); exportedTypeInfos.SetRid(et, rid); AddCustomAttributes(Table.ExportedType, rid, et); AddCustomDebugInformationList(Table.ExportedType, rid, et); return rid; } /// /// Gets a #Blob offset of a type signature /// /// Type sig /// Extra data to append the signature if /// is true. /// #Blob offset protected uint GetSignature(TypeSig ts, byte[] extraData) { byte[] blob; if (ts is null) { Error("TypeSig is null"); blob = null; } else { var bwctx = AllocBinaryWriterContext(); blob = SignatureWriter.Write(this, ts, bwctx); Free(ref bwctx); } AppendExtraData(ref blob, extraData); return blobHeap.Add(blob); } /// /// Gets a #Blob offset of a calling convention signature /// /// Signature /// #Blob offset protected uint GetSignature(CallingConventionSig sig) { if (sig is null) { Error("CallingConventionSig is null"); return 0; } var bwctx = AllocBinaryWriterContext(); var blob = SignatureWriter.Write(this, sig, bwctx); Free(ref bwctx); AppendExtraData(ref blob, sig.ExtraData); return blobHeap.Add(blob); } void AppendExtraData(ref byte[] blob, byte[] extraData) { if (PreserveExtraSignatureData && extraData is not null && extraData.Length > 0) { int blen = blob is null ? 0 : blob.Length; Array.Resize(ref blob, blen + extraData.Length); Array.Copy(extraData, 0, blob, blen, extraData.Length); } } /// /// Adds a CustomAttribute row /// /// Owner table /// New owner rid /// Onwer protected void AddCustomAttributes(Table table, uint rid, IHasCustomAttribute hca) => AddCustomAttributes(table, rid, hca.CustomAttributes); void AddCustomAttributes(Table table, uint rid, CustomAttributeCollection caList) { var token = new MDToken(table, rid); int count = caList.Count; for (int i = 0; i < count; i++) AddCustomAttribute(token, caList[i]); } void AddCustomAttribute(MDToken token, CustomAttribute ca) { if (ca is null) { Error("Custom attribute is null"); return; } if (!CodedToken.HasCustomAttribute.Encode(token, out uint encodedToken)) { Error("Can't encode HasCustomAttribute token 0x{0:X8}.", token.Raw); encodedToken = 0; } var bwctx = AllocBinaryWriterContext(); var caBlob = CustomAttributeWriter.Write(this, ca, bwctx); Free(ref bwctx); var row = new RawCustomAttributeRow(encodedToken, AddCustomAttributeType(ca.Constructor), blobHeap.Add(caBlob)); customAttributeInfos.Add(ca, row); } void AddCustomDebugInformationList(MethodDef method, uint rid, uint localVarSigToken) { Debug.Assert(debugMetadata is not null); if (debugMetadata is null) return; var serializerMethodContext = AllocSerializerMethodContext(); serializerMethodContext.SetBody(method); if (method.CustomDebugInfos.Count != 0) AddCustomDebugInformationCore(serializerMethodContext, Table.Method, rid, method.CustomDebugInfos); AddMethodDebugInformation(method, rid, localVarSigToken); Free(ref serializerMethodContext); } void AddMethodDebugInformation(MethodDef method, uint rid, uint localVarSigToken) { Debug.Assert(debugMetadata is not null); var body = method.Body; if (body is null) return; GetSingleDocument(body, out var singleDoc, out var firstDoc, out bool hasNoSeqPoints); if (hasNoSeqPoints) return; var bwctx = AllocBinaryWriterContext(); var outStream = bwctx.OutStream; var writer = bwctx.Writer; outStream.SetLength(0); outStream.Position = 0; writer.WriteCompressedUInt32(localVarSigToken); if (singleDoc is null) writer.WriteCompressedUInt32(VerifyGetRid(firstDoc)); var instrs = body.Instructions; var currentDoc = firstDoc; uint ilOffset = uint.MaxValue; int line = -1, column = 0; uint instrOffset = 0; Instruction instr = null; for (int i = 0; i < instrs.Count; i++, instrOffset += (uint)instr.GetSize()) { instr = instrs[i]; var seqPoint = instr.SequencePoint; if (seqPoint is null) continue; if (seqPoint.Document is null) { Error("PDB document is null"); return; } if (currentDoc != seqPoint.Document) { // document-record currentDoc = seqPoint.Document; writer.WriteCompressedUInt32(0); writer.WriteCompressedUInt32(VerifyGetRid(currentDoc)); } // SequencePointRecord if (ilOffset == uint.MaxValue) writer.WriteCompressedUInt32(instrOffset); else writer.WriteCompressedUInt32(instrOffset - ilOffset); ilOffset = instrOffset; if (seqPoint.StartLine == SequencePointConstants.HIDDEN_LINE && seqPoint.EndLine == SequencePointConstants.HIDDEN_LINE) { // hidden-sequence-point-record writer.WriteCompressedUInt32(0); writer.WriteCompressedUInt32(0); } else { // sequence-point-record uint dlines = (uint)(seqPoint.EndLine - seqPoint.StartLine); int dcolumns = seqPoint.EndColumn - seqPoint.StartColumn; writer.WriteCompressedUInt32(dlines); if (dlines == 0) writer.WriteCompressedUInt32((uint)dcolumns); else writer.WriteCompressedInt32(dcolumns); if (line < 0) { writer.WriteCompressedUInt32((uint)seqPoint.StartLine); writer.WriteCompressedUInt32((uint)seqPoint.StartColumn); } else { writer.WriteCompressedInt32(seqPoint.StartLine - line); writer.WriteCompressedInt32(seqPoint.StartColumn - column); } line = seqPoint.StartLine; column = seqPoint.StartColumn; } } var seqPointsBlob = outStream.ToArray(); var row = new RawMethodDebugInformationRow(singleDoc is null ? 0 : AddPdbDocument(singleDoc), debugMetadata.blobHeap.Add(seqPointsBlob)); debugMetadata.tablesHeap.MethodDebugInformationTable[rid] = row; debugMetadata.methodDebugInformationInfosUsed = true; Free(ref bwctx); } uint VerifyGetRid(PdbDocument doc) { Debug.Assert(debugMetadata is not null); if (!debugMetadata.pdbDocumentInfos.TryGetRid(doc, out uint rid)) { Error("PDB document has been removed"); return 0; } return rid; } static void GetSingleDocument(CilBody body, out PdbDocument singleDoc, out PdbDocument firstDoc, out bool hasNoSeqPoints) { var instrs = body.Instructions; int docCount = 0; singleDoc = null; firstDoc = null; for (int i = 0; i < instrs.Count; i++) { var seqPt = instrs[i].SequencePoint; if (seqPt is null) continue; var doc = seqPt.Document; if (doc is null) continue; if (firstDoc is null) firstDoc = doc; if (singleDoc != doc) { singleDoc = doc; docCount++; if (docCount > 1) break; } } hasNoSeqPoints = docCount == 0; if (docCount != 1) singleDoc = null; } /// /// Adds a CustomDebugInformation row /// /// Owner table /// New owner rid /// Onwer protected void AddCustomDebugInformationList(Table table, uint rid, IHasCustomDebugInformation hcdi) { Debug.Assert(table != Table.Method); if (debugMetadata is null) return; if (hcdi.CustomDebugInfos.Count == 0) return; var serializerMethodContext = AllocSerializerMethodContext(); serializerMethodContext.SetBody(null); AddCustomDebugInformationCore(serializerMethodContext, table, rid, hcdi.CustomDebugInfos); Free(ref serializerMethodContext); } void AddCustomDebugInformationList(Table table, uint rid, IList cdis) { Debug.Assert(table != Table.Method); if (debugMetadata is null) return; if (cdis.Count == 0) return; var serializerMethodContext = AllocSerializerMethodContext(); serializerMethodContext.SetBody(null); AddCustomDebugInformationCore(serializerMethodContext, table, rid, cdis); Free(ref serializerMethodContext); } void AddCustomDebugInformationCore(SerializerMethodContext serializerMethodContext, Table table, uint rid, IList cdis) { Debug.Assert(debugMetadata is not null); Debug.Assert(cdis.Count != 0); var token = new MDToken(table, rid); if (!CodedToken.HasCustomDebugInformation.Encode(token, out uint encodedToken)) { Error("Couldn't encode HasCustomDebugInformation token 0x{0:X8}.", token.Raw); return; } for (int i = 0; i < cdis.Count; i++) { var cdi = cdis[i]; if (cdi is null) { Error("Custom debug info is null"); continue; } AddCustomDebugInformation(serializerMethodContext, token.Raw, encodedToken, cdi); } } void AddCustomDebugInformation(SerializerMethodContext serializerMethodContext, uint token, uint encodedToken, PdbCustomDebugInfo cdi) { Debug.Assert(debugMetadata is not null); switch (cdi.Kind) { case PdbCustomDebugInfoKind.UsingGroups: case PdbCustomDebugInfoKind.ForwardMethodInfo: case PdbCustomDebugInfoKind.ForwardModuleInfo: case PdbCustomDebugInfoKind.StateMachineTypeName: case PdbCustomDebugInfoKind.DynamicLocals: case PdbCustomDebugInfoKind.TupleElementNames: case PdbCustomDebugInfoKind.SourceServer: // These are Windows PDB CDIs Error("Unsupported custom debug info {0}", cdi.Kind); break; case PdbCustomDebugInfoKind.StateMachineHoistedLocalScopes: case PdbCustomDebugInfoKind.EditAndContinueLocalSlotMap: case PdbCustomDebugInfoKind.EditAndContinueLambdaMap: case PdbCustomDebugInfoKind.Unknown: case PdbCustomDebugInfoKind.TupleElementNames_PortablePdb: case PdbCustomDebugInfoKind.DefaultNamespace: case PdbCustomDebugInfoKind.DynamicLocalVariables: case PdbCustomDebugInfoKind.EmbeddedSource: case PdbCustomDebugInfoKind.SourceLink: case PdbCustomDebugInfoKind.CompilationMetadataReferences: case PdbCustomDebugInfoKind.CompilationOptions: case PdbCustomDebugInfoKind.TypeDefinitionDocuments: case PdbCustomDebugInfoKind.EditAndContinueStateMachineStateMap: case PdbCustomDebugInfoKind.PrimaryConstructorInformationBlob: AddCustomDebugInformationCore(serializerMethodContext, encodedToken, cdi, cdi.Guid); break; case PdbCustomDebugInfoKind.AsyncMethod: // This is a portable PDB pseudo CDI AddCustomDebugInformationCore(serializerMethodContext, encodedToken, cdi, CustomDebugInfoGuids.AsyncMethodSteppingInformationBlob); AddStateMachineMethod(cdi, token, ((PdbAsyncMethodCustomDebugInfo)cdi).KickoffMethod); break; case PdbCustomDebugInfoKind.IteratorMethod: // This is a portable PDB pseudo CDI AddStateMachineMethod(cdi, token, ((PdbIteratorMethodCustomDebugInfo)cdi).KickoffMethod); break; default: Error("Unknown custom debug info {0}.", cdi.Kind); break; } } void AddStateMachineMethod(PdbCustomDebugInfo cdi, uint moveNextMethodToken, MethodDef kickoffMethod) { Debug.Assert(new MDToken(moveNextMethodToken).Table == Table.Method); Debug.Assert(debugMetadata is not null); if (kickoffMethod is null) { Error("KickoffMethod is null"); return; } var row = new RawStateMachineMethodRow(new MDToken(moveNextMethodToken).Rid, GetRid(kickoffMethod)); debugMetadata.stateMachineMethodInfos.Add(cdi, row); } void AddCustomDebugInformationCore(SerializerMethodContext serializerMethodContext, uint encodedToken, PdbCustomDebugInfo cdi, Guid cdiGuid) { Debug.Assert(debugMetadata is not null); var bwctx = AllocBinaryWriterContext(); var cdiBlob = PortablePdbCustomDebugInfoWriter.Write(this, serializerMethodContext, this, cdi, bwctx); Debug.Assert(cdiGuid != Guid.Empty); Free(ref bwctx); var row = new RawCustomDebugInformationRow(encodedToken, debugMetadata.guidHeap.Add(cdiGuid), debugMetadata.blobHeap.Add(cdiBlob)); debugMetadata.customDebugInfos.Add(cdi, row); } void InitializeMethodDebugInformation() { if (debugMetadata is null) return; int numMethods = NumberOfMethods; for (int i = 0; i < numMethods; i++) debugMetadata.tablesHeap.MethodDebugInformationTable.Create(new RawMethodDebugInformationRow()); } void AddPdbDocuments() { if (debugMetadata is null) return; foreach (var doc in module.PdbState.Documents) AddPdbDocument(doc); } uint AddPdbDocument(PdbDocument doc) { Debug.Assert(debugMetadata is not null); if (doc is null) { Error("PdbDocument is null"); return 0; } if (debugMetadata.pdbDocumentInfos.TryGetRid(doc, out uint rid)) return rid; var row = new RawDocumentRow(GetDocumentNameBlobOffset(doc.Url), debugMetadata.guidHeap.Add(doc.CheckSumAlgorithmId), debugMetadata.blobHeap.Add(doc.CheckSum), debugMetadata.guidHeap.Add(doc.Language)); rid = debugMetadata.tablesHeap.DocumentTable.Add(row); debugMetadata.pdbDocumentInfos.Add(doc, rid); AddCustomDebugInformationList(Table.Document, rid, doc.CustomDebugInfos); return rid; } uint GetDocumentNameBlobOffset(string name) { Debug.Assert(debugMetadata is not null); if (name is null) { Error("Document name is null"); name = string.Empty; } var bwctx = AllocBinaryWriterContext(); var outStream = bwctx.OutStream; var writer = bwctx.Writer; outStream.SetLength(0); outStream.Position = 0; var parts = name.Split(directorySeparatorCharArray); if (parts.Length == 1) writer.WriteByte(0); else writer.WriteBytes(directorySeparatorCharUtf8); for (int i = 0; i < parts.Length; i++) { var part = parts[i]; uint partOffset = debugMetadata.blobHeap.Add(Encoding.UTF8.GetBytes(part)); writer.WriteCompressedUInt32(partOffset); } var res = debugMetadata.blobHeap.Add(outStream.ToArray()); Free(ref bwctx); return res; } static readonly byte[] directorySeparatorCharUtf8 = Encoding.UTF8.GetBytes(Path.DirectorySeparatorChar.ToString()); static readonly char[] directorySeparatorCharArray = new char[] { Path.DirectorySeparatorChar }; uint AddImportScope(PdbImportScope scope) { Debug.Assert(debugMetadata is not null); if (scope is null) return 0; if (debugMetadata.importScopeInfos.TryGetRid(scope, out uint rid)) { if (rid == 0) Error("PdbImportScope has an infinite Parent loop"); return rid; } debugMetadata.importScopeInfos.Add(scope, 0); // Prevent inf recursion var bwctx = AllocBinaryWriterContext(); var outStream = bwctx.OutStream; var writer = bwctx.Writer; outStream.SetLength(0); outStream.Position = 0; ImportScopeBlobWriter.Write(this, this, writer, debugMetadata.blobHeap, scope.Imports); var importsData = outStream.ToArray(); Free(ref bwctx); var row = new RawImportScopeRow(AddImportScope(scope.Parent), debugMetadata.blobHeap.Add(importsData)); rid = debugMetadata.tablesHeap.ImportScopeTable.Add(row); debugMetadata.importScopeInfos.SetRid(scope, rid); AddCustomDebugInformationList(Table.ImportScope, rid, scope.CustomDebugInfos); return rid; } void AddLocalVariable(PdbLocal local) { Debug.Assert(debugMetadata is not null); if (local is null) { Error("PDB local is null"); return; } var row = new RawLocalVariableRow((ushort)local.Attributes, (ushort)local.Index, debugMetadata.stringsHeap.Add(local.Name)); uint rid = debugMetadata.tablesHeap.LocalVariableTable.Create(row); debugMetadata.localVariableInfos.Add(local, rid); AddCustomDebugInformationList(Table.LocalVariable, rid, local.CustomDebugInfos); } void AddLocalConstant(PdbConstant constant) { Debug.Assert(debugMetadata is not null); if (constant is null) { Error("PDB constant is null"); return; } var bwctx = AllocBinaryWriterContext(); var outStream = bwctx.OutStream; var writer = bwctx.Writer; outStream.SetLength(0); outStream.Position = 0; LocalConstantSigBlobWriter.Write(this, this, writer, constant.Type, constant.Value); var signature = outStream.ToArray(); Free(ref bwctx); var row = new RawLocalConstantRow(debugMetadata.stringsHeap.Add(constant.Name), debugMetadata.blobHeap.Add(signature)); uint rid = debugMetadata.tablesHeap.LocalConstantTable.Create(row); debugMetadata.localConstantInfos.Add(constant, rid); AddCustomDebugInformationList(Table.LocalConstant, rid, constant.CustomDebugInfos); } /// /// Writes the portable PDB to . /// /// Output stream /// Entry point token /// Updated with the offset of the 20-byte PDB ID. The caller is responsible for initializing it with the PDB ID internal void WritePortablePdb(Stream output, uint entryPointToken, out long pdbIdOffset) { if (debugMetadata is null) throw new InvalidOperationException(); var pdbHeap = debugMetadata.PdbHeap; pdbHeap.EntryPoint = entryPointToken; tablesHeap.GetSystemTableRows(out ulong systemTablesMask, pdbHeap.TypeSystemTableRows); debugMetadata.tablesHeap.SetSystemTableRows(pdbHeap.TypeSystemTableRows); if (!debugMetadata.methodDebugInformationInfosUsed) debugMetadata.tablesHeap.MethodDebugInformationTable.Reset(); pdbHeap.ReferencedTypeSystemTables = systemTablesMask; var writer = new DataWriter(output); debugMetadata.OnBeforeSetOffset(); debugMetadata.SetOffset(0, 0); debugMetadata.GetFileLength(); debugMetadata.VerifyWriteTo(writer); pdbIdOffset = (long)pdbHeap.PdbIdOffset; } /// uint ISignatureWriterHelper.ToEncodedToken(ITypeDefOrRef typeDefOrRef) => AddTypeDefOrRef(typeDefOrRef); /// void IWriterError.Error(string message) => Error(message); /// void IWriterError2.Error(string message, params object[] args) => Error(message, args); /// bool IFullNameFactoryHelper.MustUseAssemblyName(IType type) => FullNameFactory.MustUseAssemblyName(module, type, OptimizeCustomAttributeSerializedTypeNames); /// /// Called before any other methods /// protected virtual void Initialize() { } /// /// Gets all s that should be saved in the meta data /// protected abstract TypeDef[] GetAllTypeDefs(); /// /// Initializes TypeDef rids and creates raw rows, but does not initialize /// any columns. /// protected abstract void AllocateTypeDefRids(); /// /// Allocates Field, Method, Property, Event, Param: /// rid and raw row, but doesn't initialize the raw row. /// Initializes TypeDef columns: FieldList, MethodList. /// Initializes Method column: ParamList. /// Initializes and . /// protected abstract void AllocateMemberDefRids(); /// /// Adds a . Its custom attributes are also added. /// /// Type reference /// Its new rid protected abstract uint AddTypeRef(TypeRef tr); /// /// Adds a . Its custom attributes are also added. /// /// Type spec /// Its new rid protected abstract uint AddTypeSpec(TypeSpec ts); /// /// Adds a . Its custom attributes are also added. /// /// Member ref /// Its new rid protected abstract uint AddMemberRef(MemberRef mr); /// /// Adds a . Its custom attributes are also added. /// /// Stand alone sig /// Its new rid protected abstract uint AddStandAloneSig(StandAloneSig sas); /// /// Adds a . Its custom attributes are also added. /// /// Method spec /// Its new rid protected abstract uint AddMethodSpec(MethodSpec ms); /// /// Called before sorting the CustomAttribute table. This is the last time anything /// can be inserted into this table. /// protected virtual void BeforeSortingCustomAttributes() { } /// /// Called after everything has been initialized. The sub class can initialize more /// rows if necessary or do nothing. After this method has been called, nothing else /// can be added. /// protected virtual void EverythingInitialized() { } const uint HEAP_ALIGNMENT = 4; bool IReuseChunk.CanReuse(RVA origRva, uint origSize) { // The caller should've called SetOffset() so we know our final size Debug.Assert(length != 0); if (length == 0) throw new InvalidOperationException(); return length <= origSize; } /// /// Should be called before all chunks get an RVA /// internal void OnBeforeSetOffset() => stringsHeap.AddOptimizedStringsAndSetReadOnly(); /// public void SetOffset(FileOffset offset, RVA rva) { // This method can be called twice by NativeModuleWriter. It needs to know the size // of the final metadata. If it fits in the old location, the new MD will be written // there (smaller file size). If the new MD doesn't fit in the old location, this // method gets called a second time with the updated offset + rva. bool initAll = this.offset == 0; this.offset = offset; this.rva = rva; if (initAll) { // #Strings heap is initialized in OnBeforeSetOffset() blobHeap.SetReadOnly(); guidHeap.SetReadOnly(); tablesHeap.SetReadOnly(); pdbHeap.SetReadOnly(); tablesHeap.BigStrings = stringsHeap.IsBig; tablesHeap.BigBlob = blobHeap.IsBig; tablesHeap.BigGuid = guidHeap.IsBig; metadataHeader.Heaps = GetHeaps(); } metadataHeader.SetOffset(offset, rva); uint len = metadataHeader.GetFileLength(); offset += len; rva += len; foreach (var heap in metadataHeader.Heaps) { offset = offset.AlignUp(HEAP_ALIGNMENT); rva = rva.AlignUp(HEAP_ALIGNMENT); heap.SetOffset(offset, rva); len = heap.GetFileLength(); offset += len; rva += len; } Debug.Assert(initAll || length == rva - this.rva); if (!(initAll || length == rva - this.rva)) throw new InvalidOperationException(); length = rva - this.rva; if (!isStandaloneDebugMetadata && initAll) UpdateMethodAndFieldRvas(); } internal void UpdateMethodAndFieldRvas() { UpdateMethodRvas(); UpdateFieldRvas(); } IList GetHeaps() { var heaps = new List(); if (isStandaloneDebugMetadata) { heaps.Add(pdbHeap); heaps.Add(tablesHeap); if (!stringsHeap.IsEmpty) heaps.Add(stringsHeap); if (!usHeap.IsEmpty) heaps.Add(usHeap); if (!guidHeap.IsEmpty) heaps.Add(guidHeap); if (!blobHeap.IsEmpty) heaps.Add(blobHeap); } else { heaps.Add(tablesHeap); if (!stringsHeap.IsEmpty || AlwaysCreateStringsHeap) heaps.Add(stringsHeap); if (!usHeap.IsEmpty || AlwaysCreateUSHeap) heaps.Add(usHeap); if (!guidHeap.IsEmpty || AlwaysCreateGuidHeap) heaps.Add(guidHeap); if (!blobHeap.IsEmpty || AlwaysCreateBlobHeap) heaps.Add(blobHeap); heaps.AddRange(options.CustomHeaps); options.RaiseMetadataHeapsAdded(new MetadataHeapsAddedEventArgs(this, heaps)); } return heaps; } /// public uint GetFileLength() => length; /// public uint GetVirtualSize() => GetFileLength(); /// public uint CalculateAlignment() => 0; /// public void WriteTo(DataWriter writer) { var rva2 = rva; metadataHeader.VerifyWriteTo(writer); rva2 += metadataHeader.GetFileLength(); foreach (var heap in metadataHeader.Heaps) { writer.WriteZeroes((int)(rva2.AlignUp(HEAP_ALIGNMENT) - rva2)); rva2 = rva2.AlignUp(HEAP_ALIGNMENT); heap.VerifyWriteTo(writer); rva2 += heap.GetFileLength(); } } /// /// Sorts the s /// /// All s /// A sorted list protected static List Sort(IEnumerable pds) { var sorted = new List(pds); sorted.Sort((a, b) => { if (a is null) return -1; if (b is null) return 1; return a.Sequence.CompareTo(b.Sequence); }); return sorted; } DataWriterContext AllocBinaryWriterContext() { if (binaryWriterContexts.Count == 0) return new DataWriterContext(); var res = binaryWriterContexts[binaryWriterContexts.Count - 1]; binaryWriterContexts.RemoveAt(binaryWriterContexts.Count - 1); return res; } void Free(ref DataWriterContext ctx) { binaryWriterContexts.Add(ctx); ctx = null; } SerializerMethodContext AllocSerializerMethodContext() { if (serializerMethodContexts.Count == 0) return new SerializerMethodContext(this); var res = serializerMethodContexts[serializerMethodContexts.Count - 1]; serializerMethodContexts.RemoveAt(serializerMethodContexts.Count - 1); return res; } void Free(ref SerializerMethodContext ctx) { serializerMethodContexts.Add(ctx); ctx = null; } } }