// // 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.Cil; using MonoFN.Cecil.PE; using MonoFN.Collections.Generic; using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; using MonoFN.Cecil.Metadata; using SR = System.Reflection; namespace MonoFN.Cecil.Cil { [StructLayout(LayoutKind.Sequential)] public struct ImageDebugDirectory { public const int Size = 28; public int Characteristics; public int TimeDateStamp; public short MajorVersion; public short MinorVersion; public ImageDebugType Type; public int SizeOfData; public int AddressOfRawData; public int PointerToRawData; } public enum ImageDebugType { CodeView = 2, Deterministic = 16, EmbeddedPortablePdb = 17 } public sealed class ImageDebugHeader { public bool HasEntries { get { return !Entries.IsNullOrEmpty(); } } public ImageDebugHeaderEntry[] Entries { get; } public ImageDebugHeader(ImageDebugHeaderEntry[] entries) { this.Entries = entries ?? Empty.Array; } public ImageDebugHeader() : this(Empty.Array) { } public ImageDebugHeader(ImageDebugHeaderEntry entry) : this(new[] { entry }) { } } public sealed class ImageDebugHeaderEntry { public ImageDebugDirectory Directory { get; internal set; } public byte[] Data { get; } public ImageDebugHeaderEntry(ImageDebugDirectory directory, byte[] data) { this.Directory = directory; this.Data = data ?? Empty.Array; } } public sealed class ScopeDebugInformation : DebugInformation { internal InstructionOffset start; internal InstructionOffset end; internal ImportDebugInformation import; internal Collection scopes; internal Collection variables; internal Collection constants; public InstructionOffset Start { get { return start; } set { start = value; } } public InstructionOffset End { get { return end; } set { end = value; } } public ImportDebugInformation Import { get { return import; } set { import = value; } } public bool HasScopes { get { return !scopes.IsNullOrEmpty(); } } public Collection Scopes { get { if (scopes == null) Interlocked.CompareExchange(ref scopes, new(), null); return scopes; } } public bool HasVariables { get { return !variables.IsNullOrEmpty(); } } public Collection Variables { get { if (variables == null) Interlocked.CompareExchange(ref variables, new(), null); return variables; } } public bool HasConstants { get { return !constants.IsNullOrEmpty(); } } public Collection Constants { get { if (constants == null) Interlocked.CompareExchange(ref constants, new(), null); return constants; } } internal ScopeDebugInformation() { token = new(TokenType.LocalScope); } public ScopeDebugInformation(Instruction start, Instruction end) : this() { if (start == null) throw new ArgumentNullException("start"); this.start = new(start); if (end != null) this.end = new(end); } public bool TryGetName(VariableDefinition variable, out string name) { name = null; if (variables == null || variables.Count == 0) return false; for (int i = 0; i < variables.Count; i++) { if (variables[i].Index == variable.Index) { name = variables[i].Name; return true; } } return false; } } public struct InstructionOffset { private readonly int? offset; public int Offset { get { if (ResolvedInstruction != null) return ResolvedInstruction.Offset; if (offset.HasValue) return offset.Value; throw new NotSupportedException(); } } public bool IsEndOfMethod { get { return ResolvedInstruction == null && !offset.HasValue; } } internal bool IsResolved => ResolvedInstruction != null || !offset.HasValue; internal Instruction ResolvedInstruction { get; } public InstructionOffset(Instruction instruction) { if (instruction == null) throw new ArgumentNullException("instruction"); this.ResolvedInstruction = instruction; offset = null; } public InstructionOffset(int offset) { ResolvedInstruction = null; this.offset = offset; } } [Flags] public enum VariableAttributes : ushort { None = 0, DebuggerHidden = 1 } public struct VariableIndex { private readonly int? index; public int Index { get { if (ResolvedVariable != null) return ResolvedVariable.Index; if (index.HasValue) return index.Value; throw new NotSupportedException(); } } internal bool IsResolved => ResolvedVariable != null; internal VariableDefinition ResolvedVariable { get; } public VariableIndex(VariableDefinition variable) { if (variable == null) throw new ArgumentNullException("variable"); this.ResolvedVariable = variable; index = null; } public VariableIndex(int index) { ResolvedVariable = null; this.index = index; } } public abstract class DebugInformation : ICustomDebugInformationProvider { internal MetadataToken token; internal Collection custom_infos; public MetadataToken MetadataToken { get { return token; } set { token = value; } } public bool HasCustomDebugInformations { get { return !custom_infos.IsNullOrEmpty(); } } public Collection CustomDebugInformations { get { if (custom_infos == null) Interlocked.CompareExchange(ref custom_infos, new(), null); return custom_infos; } } internal DebugInformation() { } } public sealed class VariableDebugInformation : DebugInformation { private ushort attributes; internal VariableIndex index; public int Index { get { return index.Index; } } public string Name { get; set; } public VariableAttributes Attributes { get { return (VariableAttributes)attributes; } set { attributes = (ushort)value; } } public bool IsDebuggerHidden { get { return attributes.GetAttributes((ushort)VariableAttributes.DebuggerHidden); } set { attributes = attributes.SetAttributes((ushort)VariableAttributes.DebuggerHidden, value); } } internal VariableDebugInformation(int index, string name) { if (name == null) throw new ArgumentNullException("name"); this.index = new(index); this.Name = name; } public VariableDebugInformation(VariableDefinition variable, string name) { if (variable == null) throw new ArgumentNullException("variable"); if (name == null) throw new ArgumentNullException("name"); index = new(variable); this.Name = name; token = new(TokenType.LocalVariable); } } public sealed class ConstantDebugInformation : DebugInformation { public string Name { get; set; } public TypeReference ConstantType { get; set; } public object Value { get; set; } public ConstantDebugInformation(string name, TypeReference constant_type, object value) { if (name == null) throw new ArgumentNullException("name"); this.Name = name; this.ConstantType = constant_type; this.Value = value; token = new(TokenType.LocalConstant); } } public enum ImportTargetKind : byte { ImportNamespace = 1, ImportNamespaceInAssembly = 2, ImportType = 3, ImportXmlNamespaceWithAlias = 4, ImportAlias = 5, DefineAssemblyAlias = 6, DefineNamespaceAlias = 7, DefineNamespaceInAssemblyAlias = 8, DefineTypeAlias = 9 } public sealed class ImportTarget { internal ImportTargetKind kind; internal string @namespace; internal TypeReference type; internal AssemblyNameReference reference; internal string alias; public string Namespace { get { return @namespace; } set { @namespace = value; } } public TypeReference Type { get { return type; } set { type = value; } } public AssemblyNameReference AssemblyReference { get { return reference; } set { reference = value; } } public string Alias { get { return alias; } set { alias = value; } } public ImportTargetKind Kind { get { return kind; } set { kind = value; } } public ImportTarget(ImportTargetKind kind) { this.kind = kind; } } public sealed class ImportDebugInformation : DebugInformation { internal ImportDebugInformation parent; internal Collection targets; public bool HasTargets { get { return !targets.IsNullOrEmpty(); } } public Collection Targets { get { if (targets == null) Interlocked.CompareExchange(ref targets, new(), null); return targets; } } public ImportDebugInformation Parent { get { return parent; } set { parent = value; } } public ImportDebugInformation() { token = new(TokenType.ImportScope); } } public interface ICustomDebugInformationProvider : IMetadataTokenProvider { bool HasCustomDebugInformations { get; } Collection CustomDebugInformations { get; } } public enum CustomDebugInformationKind { Binary, StateMachineScope, DynamicVariable, DefaultNamespace, AsyncMethodBody, EmbeddedSource, SourceLink } public abstract class CustomDebugInformation : DebugInformation { public Guid Identifier { get; } public abstract CustomDebugInformationKind Kind { get; } internal CustomDebugInformation(Guid identifier) { this.Identifier = identifier; token = new(TokenType.CustomDebugInformation); } } public sealed class BinaryCustomDebugInformation : CustomDebugInformation { public byte[] Data { get; set; } public override CustomDebugInformationKind Kind { get { return CustomDebugInformationKind.Binary; } } public BinaryCustomDebugInformation(Guid identifier, byte[] data) : base(identifier) { this.Data = data; } } public sealed class AsyncMethodBodyDebugInformation : CustomDebugInformation { internal InstructionOffset catch_handler; internal Collection yields; internal Collection resumes; internal Collection resume_methods; public InstructionOffset CatchHandler { get { return catch_handler; } set { catch_handler = value; } } public Collection Yields { get { if (yields == null) Interlocked.CompareExchange(ref yields, new(), null); return yields; } } public Collection Resumes { get { if (resumes == null) Interlocked.CompareExchange(ref resumes, new(), null); return resumes; } } public Collection ResumeMethods { get { return resume_methods ?? (resume_methods = new()); } } public override CustomDebugInformationKind Kind { get { return CustomDebugInformationKind.AsyncMethodBody; } } public static Guid KindIdentifier = new("{54FD2AC5-E925-401A-9C2A-F94F171072F8}"); internal AsyncMethodBodyDebugInformation(int catchHandler) : base(KindIdentifier) { catch_handler = new(catchHandler); } public AsyncMethodBodyDebugInformation(Instruction catchHandler) : base(KindIdentifier) { catch_handler = new(catchHandler); } public AsyncMethodBodyDebugInformation() : base(KindIdentifier) { catch_handler = new(-1); } } public sealed class StateMachineScope { internal InstructionOffset start; internal InstructionOffset end; public InstructionOffset Start { get { return start; } set { start = value; } } public InstructionOffset End { get { return end; } set { end = value; } } internal StateMachineScope(int start, int end) { this.start = new(start); this.end = new(end); } public StateMachineScope(Instruction start, Instruction end) { this.start = new(start); this.end = end != null ? new(end) : new InstructionOffset(); } } public sealed class StateMachineScopeDebugInformation : CustomDebugInformation { internal Collection scopes; public Collection Scopes { get { return scopes ?? (scopes = new()); } } public override CustomDebugInformationKind Kind { get { return CustomDebugInformationKind.StateMachineScope; } } public static Guid KindIdentifier = new("{6DA9A61E-F8C7-4874-BE62-68BC5630DF71}"); public StateMachineScopeDebugInformation() : base(KindIdentifier) { } } public sealed class EmbeddedSourceDebugInformation : CustomDebugInformation { internal uint index; internal MetadataReader debug_reader; internal bool resolved; internal byte[] content; internal bool compress; public byte[] Content { get { if (!resolved) Resolve(); return content; } set { content = value; resolved = true; } } public bool Compress { get { if (!resolved) Resolve(); return compress; } set { compress = value; resolved = true; } } public override CustomDebugInformationKind Kind { get { return CustomDebugInformationKind.EmbeddedSource; } } public static Guid KindIdentifier = new("{0E8A571B-6926-466E-B4AD-8AB04611F5FE}"); internal EmbeddedSourceDebugInformation(uint index, MetadataReader debug_reader) : base(KindIdentifier) { this.index = index; this.debug_reader = debug_reader; } public EmbeddedSourceDebugInformation(byte[] content, bool compress) : base(KindIdentifier) { resolved = true; this.content = content; this.compress = compress; } internal byte[] ReadRawEmbeddedSourceDebugInformation() { if (debug_reader == null) throw new InvalidOperationException(); return debug_reader.ReadRawEmbeddedSourceDebugInformation(index); } private void Resolve() { if (resolved) return; if (debug_reader == null) throw new InvalidOperationException(); Row row = debug_reader.ReadEmbeddedSourceDebugInformation(index); content = row.Col1; compress = row.Col2; resolved = true; } } public sealed class SourceLinkDebugInformation : CustomDebugInformation { internal string content; public string Content { get { return content; } set { content = value; } } public override CustomDebugInformationKind Kind { get { return CustomDebugInformationKind.SourceLink; } } public static Guid KindIdentifier = new("{CC110556-A091-4D38-9FEC-25AB9A351A6A}"); public SourceLinkDebugInformation(string content) : base(KindIdentifier) { this.content = content; } } public sealed class MethodDebugInformation : DebugInformation { internal MethodDefinition method; internal Collection sequence_points; internal ScopeDebugInformation scope; internal MethodDefinition kickoff_method; internal int code_size; internal MetadataToken local_var_token; public MethodDefinition Method { get { return method; } } public bool HasSequencePoints { get { return !sequence_points.IsNullOrEmpty(); } } public Collection SequencePoints { get { if (sequence_points == null) Interlocked.CompareExchange(ref sequence_points, new(), null); return sequence_points; } } public ScopeDebugInformation Scope { get { return scope; } set { scope = value; } } public MethodDefinition StateMachineKickOffMethod { get { return kickoff_method; } set { kickoff_method = value; } } internal MethodDebugInformation(MethodDefinition method) { if (method == null) throw new ArgumentNullException("method"); this.method = method; token = new(TokenType.MethodDebugInformation, method.MetadataToken.RID); } public SequencePoint GetSequencePoint(Instruction instruction) { if (!HasSequencePoints) return null; for (int i = 0; i < sequence_points.Count; i++) if (sequence_points[i].Offset == instruction.Offset) return sequence_points[i]; return null; } public IDictionary GetSequencePointMapping() { Dictionary instruction_mapping = new(); if (!HasSequencePoints || !method.HasBody) return instruction_mapping; Dictionary offset_mapping = new(sequence_points.Count); for (int i = 0; i < sequence_points.Count; i++) { if (!offset_mapping.ContainsKey(sequence_points[i].Offset)) offset_mapping.Add(sequence_points[i].Offset, sequence_points[i]); } Collection instructions = method.Body.Instructions; for (int i = 0; i < instructions.Count; i++) { SequencePoint sequence_point; if (offset_mapping.TryGetValue(instructions[i].Offset, out sequence_point)) instruction_mapping.Add(instructions[i], sequence_point); } return instruction_mapping; } public IEnumerable GetScopes() { if (scope == null) return Empty.Array; return GetScopes(new[] { scope }); } private static IEnumerable GetScopes(IList scopes) { for (int i = 0; i < scopes.Count; i++) { ScopeDebugInformation scope = scopes[i]; yield return scope; if (!scope.HasScopes) continue; foreach (ScopeDebugInformation sub_scope in GetScopes(scope.Scopes)) yield return sub_scope; } } public bool TryGetName(VariableDefinition variable, out string name) { name = null; bool has_name = false; string unique_name = ""; foreach (ScopeDebugInformation scope in GetScopes()) { string slot_name; if (!scope.TryGetName(variable, out slot_name)) continue; if (!has_name) { has_name = true; unique_name = slot_name; continue; } if (unique_name != slot_name) return false; } name = unique_name; return has_name; } } public interface ISymbolReader : IDisposable { ISymbolWriterProvider GetWriterProvider(); bool ProcessDebugHeader(ImageDebugHeader header); MethodDebugInformation Read(MethodDefinition method); } public interface ISymbolReaderProvider { ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName); ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream); } #if !NET_CORE [Serializable] #endif public sealed class SymbolsNotFoundException : FileNotFoundException { public SymbolsNotFoundException(string message) : base(message) { } #if !NET_CORE private SymbolsNotFoundException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } #endif } #if !NET_CORE [Serializable] #endif public sealed class SymbolsNotMatchingException : InvalidOperationException { public SymbolsNotMatchingException(string message) : base(message) { } #if !NET_CORE private SymbolsNotMatchingException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } #endif } public class DefaultSymbolReaderProvider : ISymbolReaderProvider { private readonly bool throw_if_no_symbol; public DefaultSymbolReaderProvider() : this(throwIfNoSymbol: true) { } public DefaultSymbolReaderProvider(bool throwIfNoSymbol) { throw_if_no_symbol = throwIfNoSymbol; } public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName) { if (module.Image.HasDebugTables()) return null; if (module.HasDebugHeader) { ImageDebugHeader header = module.GetDebugHeader(); ImageDebugHeaderEntry entry = header.GetEmbeddedPortablePdbEntry(); if (entry != null) return new EmbeddedPortablePdbReaderProvider().GetSymbolReader(module, fileName); } string pdb_file_name = Mixin.GetPdbFileName(fileName); if (File.Exists(pdb_file_name)) { if (Mixin.IsPortablePdb(Mixin.GetPdbFileName(fileName))) return new PortablePdbReaderProvider().GetSymbolReader(module, fileName); try { return SymbolProvider.GetReaderProvider(SymbolKind.NativePdb).GetSymbolReader(module, fileName); } catch (Exception) { // We might not include support for native pdbs. } } string mdb_file_name = Mixin.GetMdbFileName(fileName); if (File.Exists(mdb_file_name)) { try { return SymbolProvider.GetReaderProvider(SymbolKind.Mdb).GetSymbolReader(module, fileName); } catch (Exception) { // We might not include support for mdbs. } } if (throw_if_no_symbol) throw new SymbolsNotFoundException(string.Format("No symbol found for file: {0}", fileName)); return null; } public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream) { if (module.Image.HasDebugTables()) return null; if (module.HasDebugHeader) { ImageDebugHeader header = module.GetDebugHeader(); ImageDebugHeaderEntry entry = header.GetEmbeddedPortablePdbEntry(); if (entry != null) return new EmbeddedPortablePdbReaderProvider().GetSymbolReader(module, ""); } Mixin.CheckStream(symbolStream); Mixin.CheckReadSeek(symbolStream); long position = symbolStream.Position; const int portablePdbHeader = 0x424a5342; BinaryStreamReader reader = new(symbolStream); int intHeader = reader.ReadInt32(); symbolStream.Position = position; if (intHeader == portablePdbHeader) { return new PortablePdbReaderProvider().GetSymbolReader(module, symbolStream); } const string nativePdbHeader = "Microsoft C/C++ MSF 7.00"; byte[] bytesHeader = reader.ReadBytes(nativePdbHeader.Length); symbolStream.Position = position; bool isNativePdb = true; for (int i = 0; i < bytesHeader.Length; i++) { if (bytesHeader[i] != (byte)nativePdbHeader[i]) { isNativePdb = false; break; } } if (isNativePdb) { try { return SymbolProvider.GetReaderProvider(SymbolKind.NativePdb).GetSymbolReader(module, symbolStream); } catch (Exception) { // We might not include support for native pdbs. } } const long mdbHeader = 0x45e82623fd7fa614; long longHeader = reader.ReadInt64(); symbolStream.Position = position; if (longHeader == mdbHeader) { try { return SymbolProvider.GetReaderProvider(SymbolKind.Mdb).GetSymbolReader(module, symbolStream); } catch (Exception) { // We might not include support for mdbs. } } if (throw_if_no_symbol) throw new SymbolsNotFoundException(string.Format("No symbols found in stream")); return null; } } internal enum SymbolKind { NativePdb, PortablePdb, EmbeddedPortablePdb, Mdb } internal static class SymbolProvider { private static SR.AssemblyName GetSymbolAssemblyName(SymbolKind kind) { if (kind == SymbolKind.PortablePdb) throw new ArgumentException(); string suffix = GetSymbolNamespace(kind); SR.AssemblyName cecil_name = typeof(SymbolProvider).Assembly.GetName(); SR.AssemblyName name = new() { Name = cecil_name.Name + "." + suffix, Version = cecil_name.Version, #if NET_CORE CultureName = cecil_name.CultureName, #else CultureInfo = cecil_name.CultureInfo, #endif }; name.SetPublicKeyToken(cecil_name.GetPublicKeyToken()); return name; } private static Type GetSymbolType(SymbolKind kind, string fullname) { Type type = Type.GetType(fullname); if (type != null) return type; SR.AssemblyName assembly_name = GetSymbolAssemblyName(kind); type = Type.GetType(fullname + ", " + assembly_name.FullName); if (type != null) return type; try { SR.Assembly assembly = SR.Assembly.Load(assembly_name); if (assembly != null) return assembly.GetType(fullname); } catch (FileNotFoundException) { } catch (FileLoadException) { } return null; } public static ISymbolReaderProvider GetReaderProvider(SymbolKind kind) { if (kind == SymbolKind.PortablePdb) return new PortablePdbReaderProvider(); if (kind == SymbolKind.EmbeddedPortablePdb) return new EmbeddedPortablePdbReaderProvider(); string provider_name = GetSymbolTypeName(kind, "ReaderProvider"); Type type = GetSymbolType(kind, provider_name); if (type == null) throw new TypeLoadException("Could not find symbol provider type " + provider_name); return (ISymbolReaderProvider)Activator.CreateInstance(type); } private static string GetSymbolTypeName(SymbolKind kind, string name) { return "MonoFN.Cecil" + "." + GetSymbolNamespace(kind) + "." + kind + name; } private static string GetSymbolNamespace(SymbolKind kind) { if (kind == SymbolKind.PortablePdb || kind == SymbolKind.EmbeddedPortablePdb) return "Cil"; if (kind == SymbolKind.NativePdb) return "Pdb"; if (kind == SymbolKind.Mdb) return "Mdb"; throw new ArgumentException(); } } public interface ISymbolWriter : IDisposable { ISymbolReaderProvider GetReaderProvider(); ImageDebugHeader GetDebugHeader(); void Write(MethodDebugInformation info); } public interface ISymbolWriterProvider { ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName); ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream); } public class DefaultSymbolWriterProvider : ISymbolWriterProvider { public ISymbolWriter GetSymbolWriter(ModuleDefinition module, string fileName) { ISymbolReader reader = module.SymbolReader; if (reader == null) throw new InvalidOperationException(); if (module.Image != null && module.Image.HasDebugTables()) return null; return reader.GetWriterProvider().GetSymbolWriter(module, fileName); } public ISymbolWriter GetSymbolWriter(ModuleDefinition module, Stream symbolStream) { throw new NotSupportedException(); } } } namespace MonoFN.Cecil { internal static partial class Mixin { public static ImageDebugHeaderEntry GetCodeViewEntry(this ImageDebugHeader header) { return GetEntry(header, ImageDebugType.CodeView); } public static ImageDebugHeaderEntry GetDeterministicEntry(this ImageDebugHeader header) { return GetEntry(header, ImageDebugType.Deterministic); } public static ImageDebugHeader AddDeterministicEntry(this ImageDebugHeader header) { ImageDebugHeaderEntry entry = new(new() { Type = ImageDebugType.Deterministic }, Empty.Array); if (header == null) return new(entry); ImageDebugHeaderEntry[] entries = new ImageDebugHeaderEntry [header.Entries.Length + 1]; Array.Copy(header.Entries, entries, header.Entries.Length); entries[entries.Length - 1] = entry; return new(entries); } public static ImageDebugHeaderEntry GetEmbeddedPortablePdbEntry(this ImageDebugHeader header) { return GetEntry(header, ImageDebugType.EmbeddedPortablePdb); } private static ImageDebugHeaderEntry GetEntry(this ImageDebugHeader header, ImageDebugType type) { if (!header.HasEntries) return null; for (int i = 0; i < header.Entries.Length; i++) { ImageDebugHeaderEntry entry = header.Entries[i]; if (entry.Directory.Type == type) return entry; } return null; } public static string GetPdbFileName(string assemblyFileName) { return Path.ChangeExtension(assemblyFileName, ".pdb"); } public static string GetMdbFileName(string assemblyFileName) { return assemblyFileName + ".mdb"; } public static bool IsPortablePdb(string fileName) { using (FileStream file = new(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { return IsPortablePdb(file); } } public static bool IsPortablePdb(Stream stream) { const uint ppdb_signature = 0x424a5342; if (stream.Length < 4) return false; long position = stream.Position; try { BinaryReader reader = new(stream); return reader.ReadUInt32() == ppdb_signature; } finally { stream.Position = position; } } } }