365 lines
11 KiB
C#
365 lines
11 KiB
C#
// 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);
|
|
}
|
|
}
|
|
}
|