public void EncCdiAlignment() { var slots = ImmutableArray.Create( new LocalSlotDebugInfo(SynthesizedLocalKind.UserDefined, new LocalDebugId(-1, 10)), new LocalSlotDebugInfo(SynthesizedLocalKind.TryAwaitPendingCaughtException, new LocalDebugId(-20000, 10))); var closures = ImmutableArray.Create( new ClosureDebugInfo(-100, new DebugId(0, 0)), new ClosureDebugInfo(10, new DebugId(1, 0)), new ClosureDebugInfo(-200, new DebugId(2, 0))); var lambdas = ImmutableArray.Create( new LambdaDebugInfo(20, new DebugId(0, 0), 1), new LambdaDebugInfo(-50, new DebugId(1, 0), 0), new LambdaDebugInfo(-180, new DebugId(2, 0), LambdaDebugInfo.StaticClosureOrdinal)); var debugInfo = new EditAndContinueMethodDebugInformation(1, slots, closures, lambdas); var records = new ArrayBuilder<Cci.PooledBlobBuilder>(); Cci.CustomDebugInfoWriter.SerializeCustomDebugInformation(debugInfo, records); var cdi = Cci.CustomDebugInfoWriter.SerializeCustomDebugMetadata(records); Assert.Equal(2, records.Count); AssertEx.Equal(new byte[] { 0x04, // version 0x06, // record kind 0x00, 0x02, // alignment size // aligned record size 0x18, 0x00, 0x00, 0x00, // payload (4B aligned) 0xFF, 0xC0, 0x00, 0x4E, 0x20, 0x81, 0xC0, 0x00, 0x4E, 0x1F, 0x0A, 0x9A, 0x00, 0x0A, 0x00, 0x00 }, records[0].ToArray()); AssertEx.Equal(new byte[] { 0x04, // version 0x07, // record kind 0x00, 0x00, // alignment size // aligned record size 0x18, 0x00, 0x00, 0x00, // payload (4B aligned) 0x02, 0x80, 0xC8, 0x03, 0x64, 0x80, 0xD2, 0x00, 0x80, 0xDC, 0x03, 0x80, 0x96, 0x02, 0x14, 0x01 }, records[1].ToArray()); var deserialized = CustomDebugInfoReader.GetCustomDebugInfoRecords(cdi).ToArray(); Assert.Equal(CustomDebugInfoKind.EditAndContinueLocalSlotMap, deserialized[0].Kind); Assert.Equal(4, deserialized[0].Version); Assert.Equal(new byte[] { 0xFF, 0xC0, 0x00, 0x4E, 0x20, 0x81, 0xC0, 0x00, 0x4E, 0x1F, 0x0A, 0x9A, 0x00, 0x0A }, deserialized[0].Data); Assert.Equal(CustomDebugInfoKind.EditAndContinueLambdaMap, deserialized[1].Kind); Assert.Equal(4, deserialized[1].Version); Assert.Equal(new byte[] { 0x02, 0x80, 0xC8, 0x03, 0x64, 0x80, 0xD2, 0x00, 0x80, 0xDC, 0x03, 0x80, 0x96, 0x02, 0x14, 0x01 }, deserialized[1].Data); }
protected abstract ImmutableArray <EncLocalInfo> GetLocalSlotMapFromMetadata(StandaloneSignatureHandle handle, EditAndContinueMethodDebugInformation debugInfo);
private static void SerializeCustomDebugInformation(EditAndContinueMethodDebugInformation debugInfo, ArrayBuilder<MemoryStream> customDebugInfo) { if (!debugInfo.LocalSlots.IsDefaultOrEmpty) { customDebugInfo.Add(SerializeRecord(CDI.CdiKindEditAndContinueLocalSlotMap, debugInfo.SerializeLocalSlots)); } if (!debugInfo.Lambdas.IsDefaultOrEmpty) { customDebugInfo.Add(SerializeRecord(CDI.CdiKindEditAndContinueLambdaMap, debugInfo.SerializeLambdaMap)); } }
private static PooledBlobBuilder SerializeRecord( byte kind, EditAndContinueMethodDebugInformation debugInfo, Action<EditAndContinueMethodDebugInformation, BlobBuilder> recordSerializer) { var cmw = PooledBlobBuilder.GetInstance(); cmw.WriteByte(CDI.CdiVersion); cmw.WriteByte(kind); cmw.WriteByte(0); // alignment size and length (will be patched) var alignmentSizeAndLengthWriter = cmw.ReserveBytes(sizeof(byte) + sizeof(uint)); recordSerializer(debugInfo, cmw); int length = cmw.Position; int alignedLength = 4 * ((length + 3) / 4); byte alignmentSize = (byte)(alignedLength - length); cmw.WriteBytes(0, alignmentSize); // fill in alignment size and length: alignmentSizeAndLengthWriter.WriteByte(alignmentSize); alignmentSizeAndLengthWriter.WriteUInt32((uint)alignedLength); return cmw; }
// internal for testing internal static void SerializeCustomDebugInformation(EditAndContinueMethodDebugInformation debugInfo, ArrayBuilder<PooledBlobBuilder> customDebugInfo) { // PERF: note that we pass debugInfo as explicit parameter // that is intentional to avoid capturing debugInfo as that // would result in a lot of delegate allocations here that are otherwise can be avoided. if (!debugInfo.LocalSlots.IsDefaultOrEmpty) { customDebugInfo.Add( SerializeRecord( CDI.CdiKindEditAndContinueLocalSlotMap, debugInfo, (info, builder) => info.SerializeLocalSlots(builder))); } if (!debugInfo.Lambdas.IsDefaultOrEmpty) { customDebugInfo.Add( SerializeRecord( CDI.CdiKindEditAndContinueLambdaMap, debugInfo, (info, builder) => info.SerializeLambdaMap(builder))); } }
public byte[] SerializeMethodDebugInfo(EmitContext context, IMethodBody methodBody, uint methodToken, bool isEncDelta, 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) //UNDONE: || edit-and-continue { // 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()) { _methodTokenWithModuleInfo = methodToken; _methodBodyWithModuleInfo = methodBody; emitExternNamespaces = true; } } var customDebugInfo = ArrayBuilder<MemoryStream>.GetInstance(); SerializeIteratorClassMetadata(methodBody, customDebugInfo); // 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 ForwardIterator and IteratorLocal // 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) { SerializeNamespaceScopeMetadata(context, methodBody, customDebugInfo); SerializeStateMachineLocalScopes(methodBody, customDebugInfo); } if (!suppressNewCustomDebugInfo) { SerializeDynamicLocalInfo(methodBody, customDebugInfo); // delta doesn't need this information - we use information recorded by previous generation emit if (!isEncDelta) { ImmutableArray<LocalSlotDebugInfo> encLocalSlots; // Kickoff method of a state machine (async/iterator method) doens't have any interesting locals, // so we use its EnC method debug info to store information about locals hoisted to the state machine. var encSlotInfo = methodBody.StateMachineHoistedLocalSlots; if (encSlotInfo.IsDefault) { encLocalSlots = GetLocalSlotDebugInfos(methodBody.LocalVariables); } else { encLocalSlots = GetLocalSlotDebugInfos(encSlotInfo); } var encMethodInfo = new EditAndContinueMethodDebugInformation(methodBody.MethodOrdinal, encLocalSlots, methodBody.ClosureDebugInfo, methodBody.LambdaDebugInfo); encMethodInfo.SerializeCustomDebugInformation(customDebugInfo); } } byte[] result = SerializeCustomDebugMetadata(customDebugInfo); customDebugInfo.Free(); return result; }