public byte[] SerializeMethodDebugInfo(EmitContext context, IMethodBody methodBody, MethodDefinitionHandle methodHandle, bool emitEncInfo, bool suppressNewCustomDebugInfo, out bool emitExternNamespaces) { emitExternNamespaces = false; // CONSIDER: this may not be the same "first" method as in Dev10, but // it shouldn't matter since all methods will still forward to a method // containing the appropriate information. if (_methodBodyWithModuleInfo == null) { // This module level information could go on every method (and does in // the edit-and-continue case), but - as an optimization - we'll just // put it on the first method we happen to encounter and then put a // reference to the first method's token in every other method (so they // can find the information). if (context.Module.GetAssemblyReferenceAliases(context).Any()) { _methodWithModuleInfo = methodHandle; _methodBodyWithModuleInfo = methodBody; emitExternNamespaces = true; } } var pooledBuilder = PooledBlobBuilder.GetInstance(); var encoder = new CustomDebugInfoEncoder(pooledBuilder); // NOTE: This is an attempt to match Dev10's apparent behavior. For iterator methods (i.e. the method // that appears in source, not the synthesized ones), Dev10 only emits the StateMachineTypeName // custom debug info (e.g. there will be no information about the usings that were in scope). // NOTE: There seems to be an unusual behavior in ISymUnmanagedWriter where, if all the methods in a type are // iterator methods, no custom debug info is emitted for any method. Adding a single non-iterator // method causes the custom debug info to be produced for all methods (including the iterator methods). // Since we are making the same ISymUnmanagedWriter calls as Dev10, we see the same behavior (i.e. this // is not a regression). if (methodBody.StateMachineTypeName != null) { encoder.AddStateMachineTypeName(methodBody.StateMachineTypeName); } else { SerializeNamespaceScopeMetadata(ref encoder, context, methodBody); encoder.AddStateMachineHoistedLocalScopes(methodBody.StateMachineHoistedLocalScopes); } if (!suppressNewCustomDebugInfo) { SerializeDynamicLocalInfo(ref encoder, methodBody); SerializeTupleElementNames(ref encoder, methodBody); if (emitEncInfo) { var encMethodInfo = MetadataWriter.GetEncMethodDebugInfo(methodBody); SerializeCustomDebugInformation(ref encoder, encMethodInfo); } } byte[] result = encoder.ToArray(); pooledBuilder.Free(); return(result); }
public byte[] SerializeMethodDebugInfo(EmitContext context, IMethodBody methodBody, MethodDefinitionHandle methodHandle, bool emitEncInfo, bool suppressNewCustomDebugInfo, out bool emitExternNamespaces) { emitExternNamespaces = false; // CONSIDER: this may not be the same "first" method as in Dev10, but // it shouldn't matter since all methods will still forward to a method // containing the appropriate information. if (_methodBodyWithModuleInfo == null) { // This module level information could go on every method (and does in // the edit-and-continue case), but - as an optimization - we'll just // put it on the first method we happen to encounter and then put a // reference to the first method's token in every other method (so they // can find the information). if (context.Module.GetAssemblyReferenceAliases(context).Any()) { _methodWithModuleInfo = methodHandle; _methodBodyWithModuleInfo = methodBody; emitExternNamespaces = true; } } var pooledBuilder = PooledBlobBuilder.GetInstance(); var encoder = new CustomDebugInfoEncoder(pooledBuilder); if (methodBody.StateMachineTypeName != null) { encoder.AddStateMachineTypeName(methodBody.StateMachineTypeName); } else { SerializeNamespaceScopeMetadata(ref encoder, context, methodBody); encoder.AddStateMachineHoistedLocalScopes(methodBody.StateMachineHoistedLocalScopes); } if (!suppressNewCustomDebugInfo) { SerializeDynamicLocalInfo(ref encoder, methodBody); SerializeTupleElementNames(ref encoder, methodBody); if (emitEncInfo) { var encMethodInfo = MetadataWriter.GetEncMethodDebugInfo(methodBody); SerializeCustomDebugInformation(ref encoder, encMethodInfo); } } byte[] result = encoder.ToArray(); pooledBuilder.Free(); return(result); }
internal void Convert(PEReader peReader, MetadataReader pdbReader, PdbWriter <TDocumentWriter> pdbWriter, PdbConversionOptions options) { if (!SymReaderHelpers.TryReadPdbId(peReader, out var pePdbId, out int peAge)) { throw new InvalidDataException(ConverterResources.SpecifiedPEFileHasNoAssociatedPdb); } if (!new BlobContentId(pdbReader.DebugMetadataHeader.Id).Equals(pePdbId)) { throw new InvalidDataException(ConverterResources.PdbNotMatchingDebugDirectory); } string vbDefaultNamespace = MetadataUtilities.GetVisualBasicDefaultNamespace(pdbReader); bool vbSemantics = vbDefaultNamespace != null; string vbDefaultNamespaceImportString = string.IsNullOrEmpty(vbDefaultNamespace) ? null : "*" + vbDefaultNamespace; var metadataReader = peReader.GetMetadataReader(); var metadataModel = new MetadataModel(metadataReader, vbSemantics); var documentWriters = new ArrayBuilder <TDocumentWriter>(pdbReader.Documents.Count); var documentNames = new ArrayBuilder <string>(pdbReader.Documents.Count); var symSequencePointBuilder = new SequencePointsBuilder(capacity: 64); var declaredExternAliases = new HashSet <string>(); var importStringsBuilder = new List <string>(); var importGroups = new List <int>(); var cdiBuilder = new BlobBuilder(); var dynamicLocals = new List <(string LocalName, byte[] Flags, int Count, int SlotIndex)>(); var tupleLocals = new List <(string LocalName, int SlotIndex, int ScopeStart, int ScopeEnd, ImmutableArray <string> Names)>(); var openScopeEndOffsets = new Stack <int>(); // state for calculating import string forwarding: var lastImportScopeHandle = default(ImportScopeHandle); var vbLastImportScopeNamespace = default(string); var lastImportScopeMethodDefHandle = default(MethodDefinitionHandle); var importStringsMap = new Dictionary <ImmutableArray <string>, MethodDefinitionHandle>(SequenceComparer <string> .Instance); var aliasedAssemblyRefs = GetAliasedAssemblyRefs(pdbReader); foreach (var documentHandle in pdbReader.Documents) { var document = pdbReader.GetDocument(documentHandle); var languageGuid = pdbReader.GetGuid(document.Language); var name = pdbReader.GetString(document.Name); documentNames.Add(name); documentWriters.Add(pdbWriter.DefineDocument( name: name, language: languageGuid, type: s_documentTypeText, vendor: GetLanguageVendorGuid(languageGuid), algorithmId: pdbReader.GetGuid(document.HashAlgorithm), checksum: pdbReader.GetBlobBytes(document.Hash))); } var localScopeEnumerator = pdbReader.LocalScopes.GetEnumerator(); LocalScope?currentLocalScope = NextLocalScope(); LocalScope?NextLocalScope() => localScopeEnumerator.MoveNext() ? pdbReader.GetLocalScope(localScopeEnumerator.Current) : default(LocalScope?); // Handle of the method that is gonna contain list of AssemblyRef aliases. // Other methods will forward to it. var methodDefHandleWithAssemblyRefAliases = default(MethodDefinitionHandle); foreach (var methodDebugInfoHandle in pdbReader.MethodDebugInformation) { var methodDebugInfo = pdbReader.GetMethodDebugInformation(methodDebugInfoHandle); var methodDefHandle = methodDebugInfoHandle.ToDefinitionHandle(); int methodToken = MetadataTokens.GetToken(methodDefHandle); var methodDef = metadataReader.GetMethodDefinition(methodDefHandle); #if DEBUG var declaringTypeDef = metadataReader.GetTypeDefinition(methodDef.GetDeclaringType()); var typeName = metadataReader.GetString(declaringTypeDef.Name); var methodName = metadataReader.GetString(methodDef.Name); #endif bool methodOpened = false; var methodBodyOpt = (methodDef.RelativeVirtualAddress != 0 && (methodDef.ImplAttributes & MethodImplAttributes.CodeTypeMask) == MethodImplAttributes.Managed) ? peReader.GetMethodBody(methodDef.RelativeVirtualAddress) : null; var vbCurrentMethodNamespace = vbSemantics ? GetMethodNamespace(metadataReader, methodDef) : null; var moveNextHandle = metadataModel.FindStateMachineMoveNextMethod(methodDefHandle, vbSemantics); bool isKickOffMethod = !moveNextHandle.IsNil; var forwardImportScopesToMethodDef = default(MethodDefinitionHandle); Debug.Assert(dynamicLocals.Count == 0); Debug.Assert(tupleLocals.Count == 0); Debug.Assert(openScopeEndOffsets.Count == 0); void LazyOpenMethod() { if (!methodOpened) { #if DEBUG Debug.WriteLine($"Open Method '{typeName}::{methodName}' {methodToken:X8}"); #endif pdbWriter.OpenMethod(methodToken); methodOpened = true; } } void CloseOpenScopes(int currentScopeStartOffset) { // close all open scopes that end before this scope starts: while (openScopeEndOffsets.Count > 0 && currentScopeStartOffset >= openScopeEndOffsets.Peek()) { int scopeEnd = openScopeEndOffsets.Pop(); Debug.WriteLine($"Close Scope [.., {scopeEnd})"); // Note that the root scope end is not end-inclusive in VB: pdbWriter.CloseScope(AdjustEndScopeOffset(scopeEnd, isEndInclusive: vbSemantics && openScopeEndOffsets.Count > 0)); } } bool isFirstMethodScope = true; while (currentLocalScope.HasValue && currentLocalScope.Value.Method == methodDefHandle) { // kickoff methods don't have any scopes emitted to Windows PDBs if (methodBodyOpt == null) { ReportDiagnostic(PdbDiagnosticId.MethodAssociatedWithLocalScopeHasNoBody, MetadataTokens.GetToken(localScopeEnumerator.Current)); } else if (!isKickOffMethod) { LazyOpenMethod(); var localScope = currentLocalScope.Value; CloseOpenScopes(localScope.StartOffset); Debug.WriteLine($"Open Scope [{localScope.StartOffset}, {localScope.EndOffset})"); pdbWriter.OpenScope(localScope.StartOffset); openScopeEndOffsets.Push(localScope.EndOffset); if (isFirstMethodScope) { if (lastImportScopeHandle == localScope.ImportScope && vbLastImportScopeNamespace == vbCurrentMethodNamespace) { // forward to a method that has the same imports: forwardImportScopesToMethodDef = lastImportScopeMethodDefHandle; } else { Debug.Assert(importStringsBuilder.Count == 0); Debug.Assert(declaredExternAliases.Count == 0); Debug.Assert(importGroups.Count == 0); AddImportStrings(importStringsBuilder, importGroups, declaredExternAliases, pdbReader, metadataModel, localScope.ImportScope, aliasedAssemblyRefs, vbDefaultNamespaceImportString, vbCurrentMethodNamespace, vbSemantics); var importStrings = importStringsBuilder.ToImmutableArray(); importStringsBuilder.Clear(); if (importStringsMap.TryGetValue(importStrings, out forwardImportScopesToMethodDef)) { // forward to a method that has the same imports: lastImportScopeMethodDefHandle = forwardImportScopesToMethodDef; } else { // attach import strings to the current method: WriteImports(pdbWriter, importStrings); lastImportScopeMethodDefHandle = methodDefHandle; importStringsMap[importStrings] = methodDefHandle; } lastImportScopeHandle = localScope.ImportScope; vbLastImportScopeNamespace = vbCurrentMethodNamespace; } if (vbSemantics && !forwardImportScopesToMethodDef.IsNil) { pdbWriter.UsingNamespace("@" + MetadataTokens.GetToken(forwardImportScopesToMethodDef)); } // This is the method that's gonna have AssemblyRef aliases attached: if (methodDefHandleWithAssemblyRefAliases.IsNil) { foreach (var(assemblyRefHandle, alias) in aliasedAssemblyRefs) { var assemblyRef = metadataReader.GetAssemblyReference(assemblyRefHandle); pdbWriter.UsingNamespace("Z" + alias + " " + AssemblyDisplayNameBuilder.GetAssemblyDisplayName(metadataReader, assemblyRef)); } } } foreach (var localVariableHandle in localScope.GetLocalVariables()) { var variable = pdbReader.GetLocalVariable(localVariableHandle); string name = pdbReader.GetString(variable.Name); if (name.Length > MaxEntityNameLength) { ReportDiagnostic(PdbDiagnosticId.LocalConstantNameTooLong, MetadataTokens.GetToken(localVariableHandle)); continue; } if (methodBodyOpt.LocalSignature.IsNil) { ReportDiagnostic(PdbDiagnosticId.MethodContainingLocalVariablesHasNoLocalSignature, methodToken); continue; } // TODO: translate hoisted variable scopes to dummy VB hoisted state machine locals (https://github.com/dotnet/roslyn/issues/8473) pdbWriter.DefineLocalVariable(variable.Index, name, variable.Attributes, MetadataTokens.GetToken(methodBodyOpt.LocalSignature)); var dynamicFlags = MetadataUtilities.ReadDynamicCustomDebugInformation(pdbReader, localVariableHandle); if (TryGetDynamicLocal(name, variable.Index, dynamicFlags, out var dynamicLocal)) { dynamicLocals.Add(dynamicLocal); } var tupleElementNames = MetadataUtilities.ReadTupleCustomDebugInformation(pdbReader, localVariableHandle); if (!tupleElementNames.IsDefaultOrEmpty) { tupleLocals.Add((name, SlotIndex: variable.Index, ScopeStart: 0, ScopeEnd: 0, Names: tupleElementNames)); } } foreach (var localConstantHandle in localScope.GetLocalConstants()) { var constant = pdbReader.GetLocalConstant(localConstantHandle); string name = pdbReader.GetString(constant.Name); if (name.Length > MaxEntityNameLength) { ReportDiagnostic(PdbDiagnosticId.LocalConstantNameTooLong, MetadataTokens.GetToken(localConstantHandle)); continue; } var(value, signature) = PortableConstantSignature.GetConstantValueAndSignature(pdbReader, localConstantHandle, metadataReader.GetQualifiedTypeName); if (!metadataModel.TryGetStandaloneSignatureHandle(signature, out var constantSignatureHandle)) { // Signature will be unspecified. At least we store the name and the value. constantSignatureHandle = default(StandaloneSignatureHandle); } pdbWriter.DefineLocalConstant(name, value, MetadataTokens.GetToken(constantSignatureHandle)); var dynamicFlags = MetadataUtilities.ReadDynamicCustomDebugInformation(pdbReader, localConstantHandle); if (TryGetDynamicLocal(name, 0, dynamicFlags, out var dynamicLocal)) { dynamicLocals.Add(dynamicLocal); } var tupleElementNames = MetadataUtilities.ReadTupleCustomDebugInformation(pdbReader, localConstantHandle); if (!tupleElementNames.IsDefaultOrEmpty) { // Note that the end offset of tuple locals is always end-exclusive, regardless of whether the PDB uses VB semantics or not. tupleLocals.Add((name, SlotIndex: -1, ScopeStart: localScope.StartOffset, ScopeEnd: localScope.EndOffset, Names: tupleElementNames)); } } } currentLocalScope = NextLocalScope(); isFirstMethodScope = false; } bool hasAnyScopes = !isFirstMethodScope; CloseOpenScopes(int.MaxValue); if (openScopeEndOffsets.Count > 0) { ReportDiagnostic(PdbDiagnosticId.LocalScopeRangesNestingIsInvalid, methodToken); openScopeEndOffsets.Clear(); } if (!methodDebugInfo.SequencePointsBlob.IsNil) { LazyOpenMethod(); WriteSequencePoints(pdbWriter, documentWriters, symSequencePointBuilder, methodDebugInfo.GetSequencePoints(), methodToken); } // async method data: var asyncData = MetadataUtilities.ReadAsyncMethodData(pdbReader, methodDebugInfoHandle); if (!asyncData.IsNone) { LazyOpenMethod(); pdbWriter.SetAsyncInfo( moveNextMethodToken: methodToken, kickoffMethodToken: MetadataTokens.GetToken(asyncData.KickoffMethod), catchHandlerOffset: asyncData.CatchHandlerOffset, yieldOffsets: asyncData.YieldOffsets.ToArray(), resumeOffsets: asyncData.ResumeOffsets.ToArray()); } // custom debug information: var cdiEncoder = new CustomDebugInfoEncoder(cdiBuilder); if (isKickOffMethod) { cdiEncoder.AddStateMachineTypeName(GetIteratorTypeName(metadataReader, moveNextHandle)); } else { if (!vbSemantics && hasAnyScopes) { if (forwardImportScopesToMethodDef.IsNil) { // record the number of import strings in each scope: cdiEncoder.AddUsingGroups(importGroups); if (!methodDefHandleWithAssemblyRefAliases.IsNil) { // forward assembly ref aliases to the first method: cdiEncoder.AddForwardModuleInfo(methodDefHandleWithAssemblyRefAliases); } } else { // forward all imports to another method: cdiEncoder.AddForwardMethodInfo(forwardImportScopesToMethodDef); } } var hoistedLocalScopes = GetStateMachineHoistedLocalScopes(pdbReader, methodDefHandle); if (!hoistedLocalScopes.IsDefault) { cdiEncoder.AddStateMachineHoistedLocalScopes(hoistedLocalScopes); } if (dynamicLocals.Count > 0) { cdiEncoder.AddDynamicLocals(dynamicLocals); dynamicLocals.Clear(); } if (tupleLocals.Count > 0) { cdiEncoder.AddTupleElementNames(tupleLocals); tupleLocals.Clear(); } } importGroups.Clear(); // the following blobs map 1:1 CopyCustomDebugInfoRecord(ref cdiEncoder, pdbReader, methodDefHandle, PortableCustomDebugInfoKinds.EncLocalSlotMap, CustomDebugInfoKind.EditAndContinueLocalSlotMap); CopyCustomDebugInfoRecord(ref cdiEncoder, pdbReader, methodDefHandle, PortableCustomDebugInfoKinds.EncLambdaAndClosureMap, CustomDebugInfoKind.EditAndContinueLambdaMap); if (cdiEncoder.RecordCount > 0) { LazyOpenMethod(); pdbWriter.DefineCustomMetadata(cdiEncoder.ToArray()); } cdiBuilder.Clear(); if (methodOpened && aliasedAssemblyRefs.Length > 0 && !isKickOffMethod && methodDefHandleWithAssemblyRefAliases.IsNil) { methodDefHandleWithAssemblyRefAliases = methodDefHandle; } if (methodOpened) { Debug.WriteLine($"Close Method {methodToken:X8}"); pdbWriter.CloseMethod(); } } if (!pdbReader.DebugMetadataHeader.EntryPoint.IsNil) { pdbWriter.SetEntryPoint(MetadataTokens.GetToken(pdbReader.DebugMetadataHeader.EntryPoint)); } var sourceLinkHandle = pdbReader.GetCustomDebugInformation(EntityHandle.ModuleDefinition, PortableCustomDebugInfoKinds.SourceLink); if (!sourceLinkHandle.IsNil) { if ((options & PdbConversionOptions.SuppressSourceLinkConversion) == 0) { ConvertSourceServerData(pdbReader.GetStringUTF8(sourceLinkHandle), pdbWriter, documentNames); } else { pdbWriter.SetSourceLinkData(pdbReader.GetBlobBytes(sourceLinkHandle)); } } SymReaderHelpers.GetWindowsPdbSignature(pdbReader.DebugMetadataHeader.Id, out var guid, out var stamp, out var age); pdbWriter.UpdateSignature(guid, stamp, age); }