// // Author: // Jb Evain (jbevain@gmail.com) // // Copyright (c) 2008 - 2015 Jb Evain // Copyright (c) 2008 - 2011 Novell, Inc. // // Licensed under the MIT/X11 license. // using MonoFN.Cecil.Metadata; using MonoFN.Cecil.PE; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; namespace MonoFN.Cecil.Cil { public sealed class PortablePdbReaderProvider : ISymbolReaderProvider { public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName) { Mixin.CheckModule(module); Mixin.CheckFileName(fileName); FileStream file = File.OpenRead(Mixin.GetPdbFileName(fileName)); return GetSymbolReader(module, Disposable.Owned(file as Stream), file.Name); } public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream) { Mixin.CheckModule(module); Mixin.CheckStream(symbolStream); return GetSymbolReader(module, Disposable.NotOwned(symbolStream), symbolStream.GetFileName()); } private ISymbolReader GetSymbolReader(ModuleDefinition module, Disposable symbolStream, string fileName) { return new PortablePdbReader(ImageReader.ReadPortablePdb(symbolStream, fileName), module); } } public sealed class PortablePdbReader : ISymbolReader { private readonly Image image; private readonly ModuleDefinition module; private readonly MetadataReader reader; private readonly MetadataReader debug_reader; private bool IsEmbedded { get { return reader.image == debug_reader.image; } } internal PortablePdbReader(Image image, ModuleDefinition module) { this.image = image; this.module = module; reader = module.reader; debug_reader = new(image, module, reader); } public ISymbolWriterProvider GetWriterProvider() { return new PortablePdbWriterProvider(); } public bool ProcessDebugHeader(ImageDebugHeader header) { if (image == module.Image) return true; foreach (ImageDebugHeaderEntry entry in header.Entries) { if (!IsMatchingEntry(image.PdbHeap, entry)) continue; ReadModule(); return true; } return false; } private static bool IsMatchingEntry(PdbHeap heap, ImageDebugHeaderEntry entry) { if (entry.Directory.Type != ImageDebugType.CodeView) return false; byte[] data = entry.Data; if (data.Length < 24) return false; int magic = ReadInt32(data, 0); if (magic != 0x53445352) return false; byte[] buffer = new byte [16]; Buffer.BlockCopy(data, 4, buffer, 0, 16); Guid module_guid = new(buffer); Buffer.BlockCopy(heap.Id, 0, buffer, 0, 16); Guid pdb_guid = new(buffer); return module_guid == pdb_guid; } private static int ReadInt32(byte[] bytes, int start) { return bytes[start] | (bytes[start + 1] << 8) | (bytes[start + 2] << 16) | (bytes[start + 3] << 24); } private void ReadModule() { module.custom_infos = debug_reader.GetCustomDebugInformation(module); } public MethodDebugInformation Read(MethodDefinition method) { MethodDebugInformation info = new(method); ReadSequencePoints(info); ReadScope(info); ReadStateMachineKickOffMethod(info); ReadCustomDebugInformations(info); return info; } private void ReadSequencePoints(MethodDebugInformation method_info) { method_info.sequence_points = debug_reader.ReadSequencePoints(method_info.method); } private void ReadScope(MethodDebugInformation method_info) { method_info.scope = debug_reader.ReadScope(method_info.method); } private void ReadStateMachineKickOffMethod(MethodDebugInformation method_info) { method_info.kickoff_method = debug_reader.ReadStateMachineKickoffMethod(method_info.method); } private void ReadCustomDebugInformations(MethodDebugInformation info) { info.method.custom_infos = debug_reader.GetCustomDebugInformation(info.method); } public void Dispose() { if (IsEmbedded) return; image.Dispose(); } } public sealed class EmbeddedPortablePdbReaderProvider : ISymbolReaderProvider { public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName) { Mixin.CheckModule(module); ImageDebugHeader header = module.GetDebugHeader(); ImageDebugHeaderEntry entry = header.GetEmbeddedPortablePdbEntry(); if (entry == null) throw new InvalidOperationException(); return new EmbeddedPortablePdbReader((PortablePdbReader)new PortablePdbReaderProvider().GetSymbolReader(module, GetPortablePdbStream(entry))); } private static Stream GetPortablePdbStream(ImageDebugHeaderEntry entry) { MemoryStream compressed_stream = new(entry.Data); BinaryStreamReader reader = new(compressed_stream); reader.ReadInt32(); // signature int length = reader.ReadInt32(); MemoryStream decompressed_stream = new(length); using (DeflateStream deflate_stream = new(compressed_stream, CompressionMode.Decompress, leaveOpen: true)) { deflate_stream.CopyTo(decompressed_stream); } return decompressed_stream; } public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream) { throw new NotSupportedException(); } } public sealed class EmbeddedPortablePdbReader : ISymbolReader { private readonly PortablePdbReader reader; internal EmbeddedPortablePdbReader(PortablePdbReader reader) { if (reader == null) throw new ArgumentNullException(); this.reader = reader; } public ISymbolWriterProvider GetWriterProvider() { return new EmbeddedPortablePdbWriterProvider(); } public bool ProcessDebugHeader(ImageDebugHeader header) { return reader.ProcessDebugHeader(header); } public MethodDebugInformation Read(MethodDefinition method) { return reader.Read(method); } public void Dispose() { reader.Dispose(); } } public sealed class PortablePdbWriterProvider : ISymbolWriterProvider { public ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName) { Mixin.CheckModule(module); Mixin.CheckFileName(fileName); FileStream file = File.OpenWrite(Mixin.GetPdbFileName(fileName)); return GetSymbolWriter(module, Disposable.Owned(file as Stream)); } public ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream) { Mixin.CheckModule(module); Mixin.CheckStream(symbolStream); return GetSymbolWriter(module, Disposable.NotOwned(symbolStream)); } private ISymbolWriter GetSymbolWriter(ModuleDefinition module, Disposable stream) { MetadataBuilder metadata = new(module, this); ImageWriter writer = ImageWriter.CreateDebugWriter(module, metadata, stream); return new PortablePdbWriter(metadata, module, writer); } } public sealed class PortablePdbWriter : ISymbolWriter { private readonly MetadataBuilder pdb_metadata; private readonly ModuleDefinition module; private readonly ImageWriter writer; private MetadataBuilder module_metadata; private bool IsEmbedded { get { return writer == null; } } internal PortablePdbWriter(MetadataBuilder pdb_metadata, ModuleDefinition module) { this.pdb_metadata = pdb_metadata; this.module = module; module_metadata = module.metadata_builder; if (module_metadata != pdb_metadata) this.pdb_metadata.metadata_builder = module_metadata; pdb_metadata.AddCustomDebugInformations(module); } internal PortablePdbWriter(MetadataBuilder pdb_metadata, ModuleDefinition module, ImageWriter writer) : this(pdb_metadata, module) { this.writer = writer; } public ISymbolReaderProvider GetReaderProvider() { return new PortablePdbReaderProvider(); } public ImageDebugHeader GetDebugHeader() { if (IsEmbedded) return new(); ImageDebugDirectory directory = new() { MajorVersion = 256, MinorVersion = 20557, Type = ImageDebugType.CodeView, TimeDateStamp = (int)module.timestamp }; ByteBuffer buffer = new(); // RSDS buffer.WriteUInt32(0x53445352); // Module ID buffer.WriteBytes(module.Mvid.ToByteArray()); // PDB Age buffer.WriteUInt32(1); // PDB Path string fileName = writer.BaseStream.GetFileName(); if (string.IsNullOrEmpty(fileName)) { fileName = module.Assembly.Name.Name + ".pdb"; } buffer.WriteBytes(System.Text.Encoding.UTF8.GetBytes(fileName)); buffer.WriteByte(0); byte[] data = new byte [buffer.length]; Buffer.BlockCopy(buffer.buffer, 0, data, 0, buffer.length); directory.SizeOfData = data.Length; return new(new ImageDebugHeaderEntry(directory, data)); } public void Write(MethodDebugInformation info) { CheckMethodDebugInformationTable(); pdb_metadata.AddMethodDebugInformation(info); } private void CheckMethodDebugInformationTable() { MethodDebugInformationTable mdi = pdb_metadata.table_heap.GetTable(Table.MethodDebugInformation); if (mdi.length > 0) return; // The MethodDebugInformation table has the same length as the Method table mdi.rows = new Row [module_metadata.method_rid - 1]; mdi.length = mdi.rows.Length; } public void Dispose() { if (IsEmbedded) return; WritePdbFile(); } private void WritePdbFile() { WritePdbHeap(); WriteTableHeap(); writer.BuildMetadataTextMap(); writer.WriteMetadataHeader(); writer.WriteMetadata(); writer.Flush(); writer.stream.Dispose(); } private void WritePdbHeap() { PdbHeapBuffer pdb_heap = pdb_metadata.pdb_heap; pdb_heap.WriteBytes(module.Mvid.ToByteArray()); pdb_heap.WriteUInt32(module_metadata.timestamp); pdb_heap.WriteUInt32(module_metadata.entry_point.ToUInt32()); TableHeapBuffer table_heap = module_metadata.table_heap; MetadataTable[] tables = table_heap.tables; ulong valid = 0; for (int i = 0; i < tables.Length; i++) { if (tables[i] == null || tables[i].Length == 0) continue; valid |= 1UL << i; } pdb_heap.WriteUInt64(valid); for (int i = 0; i < tables.Length; i++) { if (tables[i] == null || tables[i].Length == 0) continue; pdb_heap.WriteUInt32((uint)tables[i].Length); } } private void WriteTableHeap() { pdb_metadata.table_heap.string_offsets = pdb_metadata.string_heap.WriteStrings(); pdb_metadata.table_heap.ComputeTableInformations(); pdb_metadata.table_heap.WriteTableHeap(); } } public sealed class EmbeddedPortablePdbWriterProvider : ISymbolWriterProvider { public ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName) { Mixin.CheckModule(module); Mixin.CheckFileName(fileName); MemoryStream stream = new(); PortablePdbWriter pdb_writer = (PortablePdbWriter)new PortablePdbWriterProvider().GetSymbolWriter(module, stream); return new EmbeddedPortablePdbWriter(stream, pdb_writer); } public ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream) { throw new NotSupportedException(); } } public sealed class EmbeddedPortablePdbWriter : ISymbolWriter { private readonly Stream stream; private readonly PortablePdbWriter writer; internal EmbeddedPortablePdbWriter(Stream stream, PortablePdbWriter writer) { this.stream = stream; this.writer = writer; } public ISymbolReaderProvider GetReaderProvider() { return new EmbeddedPortablePdbReaderProvider(); } public ImageDebugHeader GetDebugHeader() { writer.Dispose(); ImageDebugDirectory directory = new() { Type = ImageDebugType.EmbeddedPortablePdb, MajorVersion = 0x0100, MinorVersion = 0x0100 }; MemoryStream data = new(); BinaryStreamWriter w = new(data); w.WriteByte(0x4d); w.WriteByte(0x50); w.WriteByte(0x44); w.WriteByte(0x42); w.WriteInt32((int)stream.Length); stream.Position = 0; using (DeflateStream compress_stream = new(data, CompressionMode.Compress, leaveOpen: true)) { stream.CopyTo(compress_stream); } directory.SizeOfData = (int)data.Length; return new(new[] { writer.GetDebugHeader().Entries[0], new ImageDebugHeaderEntry(directory, data.ToArray()) }); } public void Write(MethodDebugInformation info) { writer.Write(info); } public void Dispose() { } } internal static class PdbGuidMapping { private static readonly Dictionary guid_language = new(); private static readonly Dictionary language_guid = new(); static PdbGuidMapping() { AddMapping(DocumentLanguage.C, new("63a08714-fc37-11d2-904c-00c04fa302a1")); AddMapping(DocumentLanguage.Cpp, new("3a12d0b7-c26c-11d0-b442-00a0244a1dd2")); AddMapping(DocumentLanguage.CSharp, new("3f5162f8-07c6-11d3-9053-00c04fa302a1")); AddMapping(DocumentLanguage.Basic, new("3a12d0b8-c26c-11d0-b442-00a0244a1dd2")); AddMapping(DocumentLanguage.Java, new("3a12d0b4-c26c-11d0-b442-00a0244a1dd2")); AddMapping(DocumentLanguage.Cobol, new("af046cd1-d0e1-11d2-977c-00a0c9b4d50c")); AddMapping(DocumentLanguage.Pascal, new("af046cd2-d0e1-11d2-977c-00a0c9b4d50c")); AddMapping(DocumentLanguage.Cil, new("af046cd3-d0e1-11d2-977c-00a0c9b4d50c")); AddMapping(DocumentLanguage.JScript, new("3a12d0b6-c26c-11d0-b442-00a0244a1dd2")); AddMapping(DocumentLanguage.Smc, new("0d9b9f7b-6611-11d3-bd2a-0000f80849bd")); AddMapping(DocumentLanguage.MCpp, new("4b35fde8-07c6-11d3-9053-00c04fa302a1")); AddMapping(DocumentLanguage.FSharp, new("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3")); } private static void AddMapping(DocumentLanguage language, Guid guid) { guid_language.Add(guid, language); language_guid.Add(language, guid); } private static readonly Guid type_text = new("5a869d0b-6611-11d3-bd2a-0000f80849bd"); public static DocumentType ToType(this Guid guid) { if (guid == type_text) return DocumentType.Text; return DocumentType.Other; } public static Guid ToGuid(this DocumentType type) { if (type == DocumentType.Text) return type_text; return new(); } private static readonly Guid hash_md5 = new("406ea660-64cf-4c82-b6f0-42d48172a799"); private static readonly Guid hash_sha1 = new("ff1816ec-aa5e-4d10-87f7-6f4963833460"); private static readonly Guid hash_sha256 = new("8829d00f-11b8-4213-878b-770e8597ac16"); public static DocumentHashAlgorithm ToHashAlgorithm(this Guid guid) { if (guid == hash_md5) return DocumentHashAlgorithm.MD5; if (guid == hash_sha1) return DocumentHashAlgorithm.SHA1; if (guid == hash_sha256) return DocumentHashAlgorithm.SHA256; return DocumentHashAlgorithm.None; } public static Guid ToGuid(this DocumentHashAlgorithm hash_algo) { if (hash_algo == DocumentHashAlgorithm.MD5) return hash_md5; if (hash_algo == DocumentHashAlgorithm.SHA1) return hash_sha1; if (hash_algo == DocumentHashAlgorithm.SHA256) return hash_sha256; return new(); } public static DocumentLanguage ToLanguage(this Guid guid) { DocumentLanguage language; if (!guid_language.TryGetValue(guid, out language)) return DocumentLanguage.Other; return language; } public static Guid ToGuid(this DocumentLanguage language) { Guid guid; if (!language_guid.TryGetValue(language, out guid)) return new(); return guid; } private static readonly Guid vendor_ms = new("994b45c4-e6e9-11d2-903f-00c04fa302a1"); public static DocumentLanguageVendor ToVendor(this Guid guid) { if (guid == vendor_ms) return DocumentLanguageVendor.Microsoft; return DocumentLanguageVendor.Other; } public static Guid ToGuid(this DocumentLanguageVendor vendor) { if (vendor == DocumentLanguageVendor.Microsoft) return vendor_ms; return new(); } } }