diff --git a/src/DotNet/Pdb/CustomDebugInfoGuids.cs b/src/DotNet/Pdb/CustomDebugInfoGuids.cs index 95acf760b..1002f6877 100644 --- a/src/DotNet/Pdb/CustomDebugInfoGuids.cs +++ b/src/DotNet/Pdb/CustomDebugInfoGuids.cs @@ -20,6 +20,8 @@ public static class CustomDebugInfoGuids { public static readonly Guid TupleElementNames = new Guid("ED9FDF71-8879-4747-8ED3-FE5EDE3CE710"); public static readonly Guid CompilationMetadataReferences = new Guid("7E4D4708-096E-4C5C-AEDA-CB10BA6A740D"); public static readonly Guid CompilationOptions = new Guid("B5FEEC05-8CD0-4A83-96DA-466284BB4BD8"); + public static readonly Guid TypeDefinitionDocuments = new Guid("932E74BC-DBA9-4478-8D46-0F32A7BAB3D3"); + public static readonly Guid EncStateMachineStateMap = new Guid("8B78CD68-2EDE-420B-980B-E15884B8AAA3"); #pragma warning restore 1591 // Missing XML comment for publicly visible type or member } } diff --git a/src/DotNet/Pdb/Dss/SymbolDocumentImpl.cs b/src/DotNet/Pdb/Dss/SymbolDocumentImpl.cs index 062f6820d..650797db3 100644 --- a/src/DotNet/Pdb/Dss/SymbolDocumentImpl.cs +++ b/src/DotNet/Pdb/Dss/SymbolDocumentImpl.cs @@ -89,5 +89,7 @@ public override PdbCustomDebugInfo[] CustomDebugInfos { } } PdbCustomDebugInfo[] customDebugInfos; + + public override MDToken? MDToken => null; } } diff --git a/src/DotNet/Pdb/Managed/DbiDocument.cs b/src/DotNet/Pdb/Managed/DbiDocument.cs index b13f70d6f..fde9e87a3 100644 --- a/src/DotNet/Pdb/Managed/DbiDocument.cs +++ b/src/DotNet/Pdb/Managed/DbiDocument.cs @@ -38,6 +38,8 @@ public override PdbCustomDebugInfo[] CustomDebugInfos { } PdbCustomDebugInfo[] customDebugInfos; + public override MDToken? MDToken => null; + public DbiDocument(string url) { this.url = url; documentType = SymDocumentType.Text; diff --git a/src/DotNet/Pdb/PdbCustomDebugInfo.cs b/src/DotNet/Pdb/PdbCustomDebugInfo.cs index 55909f8c6..39f5853cf 100644 --- a/src/DotNet/Pdb/PdbCustomDebugInfo.cs +++ b/src/DotNet/Pdb/PdbCustomDebugInfo.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Threading; using dnlib.DotNet.Emit; namespace dnlib.DotNet.Pdb { @@ -111,6 +112,16 @@ public enum PdbCustomDebugInfoKind { /// /// CompilationOptions, + + /// + /// + /// + TypeDefinitionDocuments, + + /// + /// + /// + EditAndContinueStateMachineStateMap, } /// @@ -665,7 +676,7 @@ public sealed class PortablePdbTupleElementNamesCustomDebugInfo : PdbCustomDebug /// /// Async method stepping info - /// + /// /// It's internal and translated to a /// sealed class PdbAsyncMethodSteppingInformationCustomDebugInfo : PdbCustomDebugInfo { @@ -777,9 +788,9 @@ public sealed class PdbEmbeddedSourceCustomDebugInfo : PdbCustomDebugInfo { /// /// Gets the source code blob. - /// + /// /// It's not decompressed and converted to a string because the encoding isn't specified. - /// + /// /// https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PortablePdb-Metadata.md#embedded-source-c-and-vb-compilers /// public byte[] SourceCodeBlob { get; set; } @@ -1101,4 +1112,161 @@ public sealed class PdbCompilationOptionsCustomDebugInfo : PdbCustomDebugInfo { /// public PdbCompilationOptionsCustomDebugInfo() => Options = new List>(); } + + /// + /// Links a TypeDef with no method IL with a PDB document. + /// + public class PdbTypeDefinitionDocumentsDebugInfo : PdbCustomDebugInfo { + /// + /// Returns + /// + public override PdbCustomDebugInfoKind Kind => PdbCustomDebugInfoKind.TypeDefinitionDocuments; + + /// + /// Gets the custom debug info guid, see + /// + public override Guid Guid => CustomDebugInfoGuids.TypeDefinitionDocuments; + + /// + /// List of documents associated with the type + /// + public IList Documents { + get { + if (documents is null) + InitializeDocuments(); + return documents; + } + } + /// + protected IList documents; + /// Initializes + protected virtual void InitializeDocuments() => + Interlocked.CompareExchange(ref documents, new List(), null); + } + + sealed class PdbTypeDefinitionDocumentsDebugInfoMD : PdbTypeDefinitionDocumentsDebugInfo { + readonly ModuleDef readerModule; + readonly IList documentTokens; + + protected override void InitializeDocuments() { + var list = new List(documentTokens.Count); + if (readerModule.PdbState is not null) { + for (var i = 0; i < documentTokens.Count; i++) { + if (readerModule.PdbState.tokenToDocument.TryGetValue(documentTokens[i], out var document)) + list.Add(document); + } + } + Interlocked.CompareExchange(ref documents, list, null); + } + + public PdbTypeDefinitionDocumentsDebugInfoMD(ModuleDef readerModule, IList documentTokens) { + this.readerModule = readerModule; + this.documentTokens = documentTokens; + } + } + + /// + /// Contains the EnC state machine state mapping + /// + public sealed class PdbEditAndContinueStateMachineStateMapDebugInfo : PdbCustomDebugInfo { + /// + /// Returns + /// + public override PdbCustomDebugInfoKind Kind => PdbCustomDebugInfoKind.EditAndContinueStateMachineStateMap; + + /// + /// Gets the custom debug info guid, see + /// + public override Guid Guid => CustomDebugInfoGuids.EncStateMachineStateMap; + + /// + /// State machine states + /// + public List StateMachineStates { get; } + + /// + /// Constructor + /// + public PdbEditAndContinueStateMachineStateMapDebugInfo() => StateMachineStates = new List(); + } + + /// + /// State machine state information used by debuggers + /// + public struct StateMachineStateInfo { + /// + /// Syntax offset + /// + public readonly int SyntaxOffset; + + /// + /// State machine state + /// + public readonly StateMachineState State; + + /// + /// Constructor + /// + /// Syntax offset + /// State machine state + public StateMachineStateInfo(int syntaxOffset, StateMachineState state) { + SyntaxOffset = syntaxOffset; + State = state; + } + } + + /// + /// State machine state + /// from Roslyn: StateMachineState.cs + /// + public enum StateMachineState { + /// + /// First state of an async iterator state machine that is used to resume the machine after yield return. + /// Initial state is not used to resume state machine that yielded. State numbers decrease as the iterator makes progress. + /// + FirstResumableAsyncIteratorState = InitialAsyncIteratorState - 1, + + /// + /// Initial iterator state of an async iterator. + /// Distinct from so that DisposeAsync can throw in latter case. + /// + InitialAsyncIteratorState = -3, + + /// + /// First state of an iterator state machine. State numbers decrease for subsequent finalize states. + /// + FirstIteratorFinalizeState = -3, + + /// + /// The last state of a state machine. + /// + FinishedState = -2, + + /// + /// State machine not started or is running + /// + NotStartedOrRunningState = -1, + + /// + /// First unused state + /// + FirstUnusedState = 0, + + /// + /// First state in async state machine that is used to resume the machine after await. + /// State numbers increase as the async computation makes progress. + /// + FirstResumableAsyncState = 0, + + /// + /// Initial iterator state of an iterator. + /// + InitialIteratorState = 0, + + /// + /// First state in iterator state machine that is used to resume the machine after yield return. + /// Initial state is not used to resume state machine that yielded. State numbers increase as the iterator makes progress. + /// + FirstResumableIteratorState = InitialIteratorState + 1, + } } diff --git a/src/DotNet/Pdb/PdbDocument.cs b/src/DotNet/Pdb/PdbDocument.cs index b38fe311c..6f25bbe8c 100644 --- a/src/DotNet/Pdb/PdbDocument.cs +++ b/src/DotNet/Pdb/PdbDocument.cs @@ -53,6 +53,11 @@ public sealed class PdbDocument : IHasCustomDebugInformation { public IList CustomDebugInfos => customDebugInfos; IList customDebugInfos; + /// + /// Gets the Metadata token of the document if available. + /// + public MDToken? MDToken { get; internal set; } + /// /// Default constructor /// @@ -86,6 +91,7 @@ internal void Initialize(SymbolDocument symDoc) { customDebugInfos = new List(); foreach (var cdi in symDoc.CustomDebugInfos) customDebugInfos.Add(cdi); + MDToken = symDoc.MDToken; } /// diff --git a/src/DotNet/Pdb/PdbState.cs b/src/DotNet/Pdb/PdbState.cs index 455c24d11..39146563f 100644 --- a/src/DotNet/Pdb/PdbState.cs +++ b/src/DotNet/Pdb/PdbState.cs @@ -15,6 +15,7 @@ namespace dnlib.DotNet.Pdb { public sealed class PdbState { readonly SymbolReader reader; readonly Dictionary docDict = new Dictionary(); + internal readonly Dictionary tokenToDocument = new Dictionary(); MethodDef userEntryPoint; readonly Compiler compiler; readonly PdbFileKind originalPdbFileKind; @@ -64,7 +65,7 @@ public bool HasDocuments { #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } #endif - + } } @@ -123,6 +124,8 @@ PdbDocument Add_NoLock(PdbDocument doc) { if (docDict.TryGetValue(doc, out var orig)) return orig; docDict.Add(doc, doc); + if (doc.MDToken.HasValue) + tokenToDocument.Add(doc.MDToken.Value, doc); return doc; } @@ -133,6 +136,8 @@ PdbDocument Add_NoLock(SymbolDocument symDoc) { // Expensive part, can read source code etc doc.Initialize(symDoc); docDict.Add(doc, doc); + if (symDoc.MDToken.HasValue) + tokenToDocument.Add(symDoc.MDToken.Value, doc); return doc; } @@ -145,6 +150,8 @@ public bool Remove(PdbDocument doc) { #if THREAD_SAFE theLock.EnterWriteLock(); try { #endif + if (doc.MDToken.HasValue) + tokenToDocument.Remove(doc.MDToken.Value); return docDict.Remove(doc); #if THREAD_SAFE } finally { theLock.ExitWriteLock(); } @@ -186,6 +193,7 @@ public List RemoveAllDocuments(bool returnDocs) { theLock.EnterWriteLock(); try { #endif var docs = returnDocs ? new List(docDict.Values) : null; + tokenToDocument.Clear(); docDict.Clear(); return docs; #if THREAD_SAFE diff --git a/src/DotNet/Pdb/Portable/PortablePdbCustomDebugInfoReader.cs b/src/DotNet/Pdb/Portable/PortablePdbCustomDebugInfoReader.cs index 68b61ee80..5a1e1705d 100644 --- a/src/DotNet/Pdb/Portable/PortablePdbCustomDebugInfoReader.cs +++ b/src/DotNet/Pdb/Portable/PortablePdbCustomDebugInfoReader.cs @@ -65,6 +65,10 @@ PdbCustomDebugInfo Read(Guid kind) { return ReadCompilationMetadataReferences(); if (kind == CustomDebugInfoGuids.CompilationOptions) return ReadCompilationOptions(); + if (kind == CustomDebugInfoGuids.TypeDefinitionDocuments) + return ReadTypeDefinitionDocuments(); + if (kind == CustomDebugInfoGuids.EncStateMachineStateMap) + return ReadEncStateMachineStateMap(); Debug.Fail("Unknown custom debug info guid: " + kind.ToString()); return new PdbUnknownCustomDebugInfo(kind, reader.ReadRemainingBytes()); } @@ -228,6 +232,34 @@ PdbCustomDebugInfo ReadCompilationOptions() { return cdi; } + PdbCustomDebugInfo ReadTypeDefinitionDocuments() { + var docList = new List(); + while (reader.BytesLeft > 0) + docList.Add(new MDToken(Table.Document, reader.ReadCompressedUInt32())); + + return new PdbTypeDefinitionDocumentsDebugInfoMD(module, docList); + } + + PdbCustomDebugInfo ReadEncStateMachineStateMap() { + var cdi = new PdbEditAndContinueStateMachineStateMapDebugInfo(); + + var count = reader.ReadCompressedUInt32(); + if (count > 0) { + long syntaxOffsetBaseline = -reader.ReadCompressedUInt32(); + + while (count > 0) { + int stateNumber = reader.ReadCompressedInt32(); + int syntaxOffset = (int)(syntaxOffsetBaseline + reader.ReadCompressedUInt32()); + + cdi.StateMachineStates.Add(new StateMachineStateInfo(syntaxOffset, (StateMachineState)stateNumber)); + + count--; + } + } + + return cdi; + } + Instruction GetInstruction(uint offset) { var instructions = bodyOpt.Instructions; int lo = 0, hi = instructions.Count - 1; diff --git a/src/DotNet/Pdb/Portable/PortablePdbCustomDebugInfoWriter.cs b/src/DotNet/Pdb/Portable/PortablePdbCustomDebugInfoWriter.cs index 0729c602e..789d3ee4a 100644 --- a/src/DotNet/Pdb/Portable/PortablePdbCustomDebugInfoWriter.cs +++ b/src/DotNet/Pdb/Portable/PortablePdbCustomDebugInfoWriter.cs @@ -1,6 +1,8 @@ // dnlib: See LICENSE.txt for more info +using System; using System.IO; +using System.Linq; using System.Text; using dnlib.DotNet.Emit; using dnlib.DotNet.Writer; @@ -92,6 +94,14 @@ byte[] Write(PdbCustomDebugInfo cdi) { case PdbCustomDebugInfoKind.CompilationOptions: WriteCompilationOptions((PdbCompilationOptionsCustomDebugInfo)cdi); break; + + case PdbCustomDebugInfoKind.TypeDefinitionDocuments: + WriteTypeDefinitionDocuments((PdbTypeDefinitionDocumentsDebugInfo)cdi); + break; + + case PdbCustomDebugInfoKind.EditAndContinueStateMachineStateMap: + WriteEditAndContinueStateMachineStateMap((PdbEditAndContinueStateMachineStateMapDebugInfo)cdi); + break; } return outStream.ToArray(); } @@ -317,5 +327,25 @@ void WriteCompilationOptions(PdbCompilationOptionsCustomDebugInfo cdi) { WriteUTF8Z(kv.Value); } } + + void WriteTypeDefinitionDocuments(PdbTypeDefinitionDocumentsDebugInfo cdi) { + foreach (var document in cdi.Documents) + writer.WriteCompressedUInt32(systemMetadata.GetRid(document)); + } + + void WriteEditAndContinueStateMachineStateMap(PdbEditAndContinueStateMachineStateMapDebugInfo cdi) { + writer.WriteCompressedUInt32((uint)cdi.StateMachineStates.Count); + + if (cdi.StateMachineStates.Count <= 0) + return; + + int syntaxOffsetBaseline = Math.Min(cdi.StateMachineStates.Min(state => state.SyntaxOffset), 0); + writer.WriteCompressedUInt32((uint)-syntaxOffsetBaseline); + + foreach (var state in cdi.StateMachineStates) { + writer.WriteCompressedInt32((int)state.State); + writer.WriteCompressedUInt32((uint)(state.SyntaxOffset - syntaxOffsetBaseline)); + } + } } } diff --git a/src/DotNet/Pdb/Portable/PortablePdbReader.cs b/src/DotNet/Pdb/Portable/PortablePdbReader.cs index fc8fdec37..bae2ae621 100644 --- a/src/DotNet/Pdb/Portable/PortablePdbReader.cs +++ b/src/DotNet/Pdb/Portable/PortablePdbReader.cs @@ -61,7 +61,8 @@ SymbolDocument[] ReadDocuments() { var custInfos = ListCache.AllocList(); var gpContext = new GenericParamContext(); for (int i = 0; i < docs.Length; i++) { - bool b = pdbMetadata.TablesStream.TryReadDocumentRow((uint)i + 1, out var row); + uint rid = (uint)i + 1; + bool b = pdbMetadata.TablesStream.TryReadDocumentRow(rid, out var row); Debug.Assert(b); var url = nameReader.ReadDocumentName(row.Name); var language = pdbMetadata.GuidStream.Read(row.Language) ?? Guid.Empty; @@ -70,12 +71,13 @@ SymbolDocument[] ReadDocuments() { var checkSumAlgorithmId = pdbMetadata.GuidStream.Read(row.HashAlgorithm) ?? Guid.Empty; var checkSum = pdbMetadata.BlobStream.ReadNoNull(row.Hash); - var token = new MDToken(Table.Document, i + 1).ToInt32(); + var mdToken = new MDToken(Table.Document, rid); + var token = mdToken.ToInt32(); custInfos.Clear(); GetCustomDebugInfos(token, gpContext, custInfos); var custInfosArray = custInfos.Count == 0 ? Array2.Empty() : custInfos.ToArray(); - docs[i] = new SymbolDocumentImpl(url, language, languageVendor, documentType, checkSumAlgorithmId, checkSum, custInfosArray); + docs[i] = new SymbolDocumentImpl(url, language, languageVendor, documentType, checkSumAlgorithmId, checkSum, custInfosArray, mdToken); } ListCache.Free(ref custInfos); return docs; diff --git a/src/DotNet/Pdb/Portable/SymbolDocumentImpl.cs b/src/DotNet/Pdb/Portable/SymbolDocumentImpl.cs index 7168d3c85..dce64fb4a 100644 --- a/src/DotNet/Pdb/Portable/SymbolDocumentImpl.cs +++ b/src/DotNet/Pdb/Portable/SymbolDocumentImpl.cs @@ -15,6 +15,7 @@ sealed class SymbolDocumentImpl : SymbolDocument { /*readonly*/ Guid checkSumAlgorithmId; readonly byte[] checkSum; readonly PdbCustomDebugInfo[] customDebugInfos; + MDToken mdToken; string GetDebuggerString() { var sb = new StringBuilder(); @@ -45,8 +46,9 @@ string GetDebuggerString() { public override Guid CheckSumAlgorithmId => checkSumAlgorithmId; public override byte[] CheckSum => checkSum; public override PdbCustomDebugInfo[] CustomDebugInfos => customDebugInfos; + public override MDToken? MDToken => mdToken; - public SymbolDocumentImpl(string url, Guid language, Guid languageVendor, Guid documentType, Guid checkSumAlgorithmId, byte[] checkSum, PdbCustomDebugInfo[] customDebugInfos) { + public SymbolDocumentImpl(string url, Guid language, Guid languageVendor, Guid documentType, Guid checkSumAlgorithmId, byte[] checkSum, PdbCustomDebugInfo[] customDebugInfos, MDToken mdToken) { this.url = url; this.language = language; this.languageVendor = languageVendor; @@ -54,6 +56,7 @@ public SymbolDocumentImpl(string url, Guid language, Guid languageVendor, Guid d this.checkSumAlgorithmId = checkSumAlgorithmId; this.checkSum = checkSum; this.customDebugInfos = customDebugInfos; + this.mdToken = mdToken; } } } diff --git a/src/DotNet/Pdb/Symbols/SymbolDocument.cs b/src/DotNet/Pdb/Symbols/SymbolDocument.cs index ec12ded6b..58509969e 100644 --- a/src/DotNet/Pdb/Symbols/SymbolDocument.cs +++ b/src/DotNet/Pdb/Symbols/SymbolDocument.cs @@ -41,5 +41,10 @@ public abstract class SymbolDocument { /// Gets the custom debug infos /// public abstract PdbCustomDebugInfo[] CustomDebugInfos { get; } + + /// + /// Gets the Metadata token of the document if available. + /// + public abstract MDToken? MDToken { get; } } } diff --git a/src/DotNet/Writer/Metadata.cs b/src/DotNet/Writer/Metadata.cs index df93b83e1..37bcc490a 100644 --- a/src/DotNet/Writer/Metadata.cs +++ b/src/DotNet/Writer/Metadata.cs @@ -3384,6 +3384,8 @@ void AddCustomDebugInformation(SerializerMethodContext serializerMethodContext, case PdbCustomDebugInfoKind.SourceLink: case PdbCustomDebugInfoKind.CompilationMetadataReferences: case PdbCustomDebugInfoKind.CompilationOptions: + case PdbCustomDebugInfoKind.TypeDefinitionDocuments: + case PdbCustomDebugInfoKind.EditAndContinueStateMachineStateMap: AddCustomDebugInformationCore(serializerMethodContext, encodedToken, cdi, cdi.Guid); break;