// 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; namespace dnlib.DotNet.Pdb.Portable { interface IPortablePdbCustomDebugInfoWriterHelper : IWriterError { } readonly struct PortablePdbCustomDebugInfoWriter { readonly IPortablePdbCustomDebugInfoWriterHelper helper; readonly SerializerMethodContext methodContext; readonly Metadata systemMetadata; readonly MemoryStream outStream; readonly DataWriter writer; public static byte[] Write(IPortablePdbCustomDebugInfoWriterHelper helper, SerializerMethodContext methodContext, Metadata systemMetadata, PdbCustomDebugInfo cdi, DataWriterContext context) { var writer = new PortablePdbCustomDebugInfoWriter(helper, methodContext, systemMetadata, context); return writer.Write(cdi); } PortablePdbCustomDebugInfoWriter(IPortablePdbCustomDebugInfoWriterHelper helper, SerializerMethodContext methodContext, Metadata systemMetadata, DataWriterContext context) { this.helper = helper; this.methodContext = methodContext; this.systemMetadata = systemMetadata; outStream = context.OutStream; writer = context.Writer; outStream.SetLength(0); outStream.Position = 0; } byte[] Write(PdbCustomDebugInfo cdi) { switch (cdi.Kind) { case PdbCustomDebugInfoKind.UsingGroups: case PdbCustomDebugInfoKind.ForwardMethodInfo: case PdbCustomDebugInfoKind.ForwardModuleInfo: case PdbCustomDebugInfoKind.StateMachineTypeName: case PdbCustomDebugInfoKind.DynamicLocals: case PdbCustomDebugInfoKind.TupleElementNames: case PdbCustomDebugInfoKind.IteratorMethod: case PdbCustomDebugInfoKind.SourceServer: default: helper.Error("Unreachable code, caller should filter these out"); return null; case PdbCustomDebugInfoKind.StateMachineHoistedLocalScopes: WriteStateMachineHoistedLocalScopes((PdbStateMachineHoistedLocalScopesCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.EditAndContinueLocalSlotMap: WriteEditAndContinueLocalSlotMap((PdbEditAndContinueLocalSlotMapCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.EditAndContinueLambdaMap: WriteEditAndContinueLambdaMap((PdbEditAndContinueLambdaMapCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.Unknown: WriteUnknown((PdbUnknownCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.TupleElementNames_PortablePdb: WriteTupleElementNames((PortablePdbTupleElementNamesCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.DefaultNamespace: WriteDefaultNamespace((PdbDefaultNamespaceCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.DynamicLocalVariables: WriteDynamicLocalVariables((PdbDynamicLocalVariablesCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.EmbeddedSource: WriteEmbeddedSource((PdbEmbeddedSourceCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.SourceLink: WriteSourceLink((PdbSourceLinkCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.AsyncMethod: WriteAsyncMethodSteppingInformation((PdbAsyncMethodCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.CompilationMetadataReferences: WriteCompilationMetadataReferences((PdbCompilationMetadataReferencesCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.CompilationOptions: WriteCompilationOptions((PdbCompilationOptionsCustomDebugInfo)cdi); break; case PdbCustomDebugInfoKind.TypeDefinitionDocuments: WriteTypeDefinitionDocuments((PdbTypeDefinitionDocumentsDebugInfo)cdi); break; case PdbCustomDebugInfoKind.EditAndContinueStateMachineStateMap: WriteEditAndContinueStateMachineStateMap((PdbEditAndContinueStateMachineStateMapDebugInfo)cdi); break; case PdbCustomDebugInfoKind.PrimaryConstructorInformationBlob: WritePrimaryConstructorInformationBlob((PrimaryConstructorInformationBlobDebugInfo)cdi); break; } return outStream.ToArray(); } void WriteUTF8Z(string s) { if (s.IndexOf('\0') >= 0) helper.Error("String must not contain any NUL bytes"); var bytes = Encoding.UTF8.GetBytes(s); writer.WriteBytes(bytes); writer.WriteByte(0); } void WriteStateMachineHoistedLocalScopes(PdbStateMachineHoistedLocalScopesCustomDebugInfo cdi) { if (!methodContext.HasBody) { helper.Error2("Method has no body, can't write custom debug info: {0}.", cdi.Kind); return; } var cdiScopes = cdi.Scopes; int count = cdiScopes.Count; for (int i = 0; i < count; i++) { var scope = cdiScopes[i]; uint startOffset, endOffset; if (scope.IsSynthesizedLocal) { startOffset = 0; endOffset = 0; } else { var startInstr = scope.Start; if (startInstr is null) { helper.Error("Instruction is null"); return; } startOffset = methodContext.GetOffset(startInstr); endOffset = methodContext.GetOffset(scope.End); } if (startOffset > endOffset) { helper.Error("End instruction is before start instruction"); return; } writer.WriteUInt32(startOffset); writer.WriteUInt32(endOffset - startOffset); } } void WriteEditAndContinueLocalSlotMap(PdbEditAndContinueLocalSlotMapCustomDebugInfo cdi) { var d = cdi.Data; if (d is null) { helper.Error("Data blob is null"); return; } writer.WriteBytes(d); } void WriteEditAndContinueLambdaMap(PdbEditAndContinueLambdaMapCustomDebugInfo cdi) { var d = cdi.Data; if (d is null) { helper.Error("Data blob is null"); return; } writer.WriteBytes(d); } void WriteUnknown(PdbUnknownCustomDebugInfo cdi) { var d = cdi.Data; if (d is null) { helper.Error("Data blob is null"); return; } writer.WriteBytes(d); } void WriteTupleElementNames(PortablePdbTupleElementNamesCustomDebugInfo cdi) { var cdiNames = cdi.Names; int count = cdiNames.Count; for (int i = 0; i < count; i++) { var name = cdiNames[i]; if (name is null) { helper.Error("Tuple name is null"); return; } WriteUTF8Z(name); } } void WriteDefaultNamespace(PdbDefaultNamespaceCustomDebugInfo cdi) { var ns = cdi.Namespace; if (ns is null) { helper.Error("Default namespace is null"); return; } var bytes = Encoding.UTF8.GetBytes(ns); writer.WriteBytes(bytes); } void WriteDynamicLocalVariables(PdbDynamicLocalVariablesCustomDebugInfo cdi) { var flags = cdi.Flags; for (int i = 0; i < flags.Length; i += 8) writer.WriteByte(ToByte(flags, i)); } static byte ToByte(bool[] flags, int index) { int res = 0; int bit = 1; for (int i = index; i < flags.Length; i++, bit <<= 1) { if (flags[i]) res |= bit; } return (byte)res; } void WriteEmbeddedSource(PdbEmbeddedSourceCustomDebugInfo cdi) { var d = cdi.SourceCodeBlob; if (d is null) { helper.Error("Source code blob is null"); return; } writer.WriteBytes(d); } void WriteSourceLink(PdbSourceLinkCustomDebugInfo cdi) { var d = cdi.FileBlob; if (d is null) { helper.Error("Source link blob is null"); return; } writer.WriteBytes(d); } void WriteAsyncMethodSteppingInformation(PdbAsyncMethodCustomDebugInfo cdi) { if (!methodContext.HasBody) { helper.Error2("Method has no body, can't write custom debug info: {0}.", cdi.Kind); return; } uint catchHandlerOffset; if (cdi.CatchHandlerInstruction is null) catchHandlerOffset = 0; else catchHandlerOffset = methodContext.GetOffset(cdi.CatchHandlerInstruction) + 1; writer.WriteUInt32(catchHandlerOffset); var cdiStepInfos = cdi.StepInfos; int count = cdiStepInfos.Count; for (int i = 0; i < count; i++) { var info = cdiStepInfos[i]; if (info.YieldInstruction is null) { helper.Error("YieldInstruction is null"); return; } if (info.BreakpointMethod is null) { helper.Error("BreakpointMethod is null"); return; } if (info.BreakpointInstruction is null) { helper.Error("BreakpointInstruction is null"); return; } uint yieldOffset = methodContext.GetOffset(info.YieldInstruction); uint resumeOffset; if (methodContext.IsSameMethod(info.BreakpointMethod)) resumeOffset = methodContext.GetOffset(info.BreakpointInstruction); else resumeOffset = GetOffsetSlow(info.BreakpointMethod, info.BreakpointInstruction); uint resumeMethodRid = systemMetadata.GetRid(info.BreakpointMethod); writer.WriteUInt32(yieldOffset); writer.WriteUInt32(resumeOffset); writer.WriteCompressedUInt32(resumeMethodRid); } } uint GetOffsetSlow(MethodDef method, Instruction instr) { var body = method.Body; if (body is null) { helper.Error("Method has no body"); return uint.MaxValue; } var instrs = body.Instructions; uint offset = 0; for (int i = 0; i < instrs.Count; i++) { var instr2 = instrs[i]; if (instr2 == instr) return offset; offset += (uint)instr2.GetSize(); } helper.Error("Couldn't find an instruction, maybe it was removed. It's still being referenced by some code or by the PDB"); return uint.MaxValue; } void WriteCompilationMetadataReferences(PdbCompilationMetadataReferencesCustomDebugInfo cdi) { foreach (var mdRef in cdi.References) { var name = mdRef.Name; if (name is null) { helper.Error("Metadata reference name is null"); return; } WriteUTF8Z(name); var aliases = mdRef.Aliases; if (aliases is null) { helper.Error("Metadata reference aliases is null"); return; } WriteUTF8Z(aliases); writer.WriteByte((byte)mdRef.Flags); writer.WriteUInt32(mdRef.Timestamp); writer.WriteUInt32(mdRef.SizeOfImage); writer.WriteBytes(mdRef.Mvid.ToByteArray()); } } void WriteCompilationOptions(PdbCompilationOptionsCustomDebugInfo cdi) { foreach (var kv in cdi.Options) { if (kv.Key is null) { helper.Error("Compiler option `key` is null"); return; } if (kv.Value is null) { helper.Error("Compiler option `value` is null"); return; } WriteUTF8Z(kv.Key); 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)); } } void WritePrimaryConstructorInformationBlob(PrimaryConstructorInformationBlobDebugInfo cdi) { var d = cdi.Blob; if (d is null) { helper.Error("Primary constructor information blob is null"); return; } writer.WriteBytes(d); } } }