diff --git a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs index 00bd9bd6676d9b1a2d019b54eeaa963c881d8b17..14328f0e4ead02c19d008260c748a8c6ba9277c3 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs @@ -46,7 +46,7 @@ private ImmutableArray EmitDeterministic(string source, Platform platform, return compilation.EmitToArray(); } - [Fact(Skip = "900646"), WorkItem(900646)] + [Fact, WorkItem(372, "https://github.com/dotnet/roslyn/issues/372")] public void Simple() { var source = @@ -80,7 +80,7 @@ public void CompareAllBytesEmitted_Release() AssertEx.Equal(result3, result4); } - [Fact(Skip="https://github.com/dotnet/roslyn/issues/926"), WorkItem(926)] + [Fact, WorkItem(926)] public void CompareAllBytesEmitted_Debug() { var source = diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index 86b2d06ccb289f4b0ccc650365ff41012af73b44..2b4ec371aa7a5eb1ea430980e03a32ff3390771e 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -333,8 +333,9 @@ - - + + + @@ -362,8 +363,8 @@ - - + + @@ -376,8 +377,8 @@ - - + + @@ -664,6 +665,14 @@ ..\..\..\..\packages\System.Reflection.Metadata.$(SystemReflectionMetadataVersion)\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + PreserveNewest + + + PreserveNewest + + diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs index 8e9ed79d03a8a7201995b44335f29d0775e4ada7..a9311781c54eb717da63100a3ea6ad744a7595f2 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.Designer.cs @@ -917,6 +917,15 @@ internal class CodeAnalysisResources { } } + /// + /// Looks up a localized string similar to SymWriter doesn't support deterministic compilation. + /// + internal static string SymWriterNotDeterministic { + get { + return ResourceManager.GetString("SymWriterNotDeterministic", resourceCulture); + } + } + /// /// Looks up a localized string similar to type must be a subclass of SyntaxAnnotation.. /// diff --git a/src/Compilers/Core/Portable/CodeAnalysisResources.resx b/src/Compilers/Core/Portable/CodeAnalysisResources.resx index 67832ccbfe1778a1e5aa92aa725e14e4034af811..b20c7d139d3d6827affee028c1cc718b1d4a485a 100644 --- a/src/Compilers/Core/Portable/CodeAnalysisResources.resx +++ b/src/Compilers/Core/Portable/CodeAnalysisResources.resx @@ -433,4 +433,7 @@ Invalid data at offset {0}: {1}{2}*{3}{4} + + SymWriter doesn't support deterministic compilation + \ No newline at end of file diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index 9ebd4265e4c1a611ac8fcfffb152699a9a5ae6e9..7cd65e576a0dcb247151790cf781a3149e099bca 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -1639,7 +1639,7 @@ private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool su // The calls ISymUnmanagedWriter2.GetDebugInfo require a file name in order to succeed. This is // frequently used during PDB writing. Ensure a name is provided here in the case we were given // only a Stream value. - nativePdbWriter = new Cci.PdbWriter(pdbPath, testSymWriterFactory); + nativePdbWriter = new Cci.PdbWriter(pdbPath, testSymWriterFactory, deterministic); } Func getPeStream = () => @@ -1779,7 +1779,8 @@ private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool su { using (var pdbWriter = new Cci.PdbWriter( moduleBeingBuilt.EmitOptions.PdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb"), - testSymWriterFactory)) + testSymWriterFactory, + deterministic: false)) { var context = new EmitContext((Cci.IModule)moduleBeingBuilt, null, diagnostics); var encId = Guid.NewGuid(); diff --git a/src/Compilers/Core/Portable/PEWriter/ComMemoryStream.cs b/src/Compilers/Core/Portable/NativePdbWriter/ComMemoryStream.cs similarity index 100% rename from src/Compilers/Core/Portable/PEWriter/ComMemoryStream.cs rename to src/Compilers/Core/Portable/NativePdbWriter/ComMemoryStream.cs diff --git a/src/Compilers/Core/Portable/PEWriter/ISymUnmanagedAsyncMethodPropertiesWriter.cs b/src/Compilers/Core/Portable/NativePdbWriter/ISymUnmanagedAsyncMethodPropertiesWriter.cs similarity index 100% rename from src/Compilers/Core/Portable/PEWriter/ISymUnmanagedAsyncMethodPropertiesWriter.cs rename to src/Compilers/Core/Portable/NativePdbWriter/ISymUnmanagedAsyncMethodPropertiesWriter.cs diff --git a/src/Compilers/Core/Portable/PEWriter/ISymbolWriter.cs b/src/Compilers/Core/Portable/NativePdbWriter/ISymUnmanagedWriter.cs similarity index 92% rename from src/Compilers/Core/Portable/PEWriter/ISymbolWriter.cs rename to src/Compilers/Core/Portable/NativePdbWriter/ISymUnmanagedWriter.cs index 075b09351a872100d9121b838179b50605ae3e24..efd3511b8127f1b8f1b3a12015b2b579f5f77c38 100644 --- a/src/Compilers/Core/Portable/PEWriter/ISymbolWriter.cs +++ b/src/Compilers/Core/Portable/NativePdbWriter/ISymUnmanagedWriter.cs @@ -133,7 +133,7 @@ internal struct VariantPadding } [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("DCF7780D-BDE9-45DF-ACFE-21731A32000C"), SuppressUnmanagedCodeSecurity] - internal interface ISymUnmanagedWriter5 + internal interface ISymUnmanagedWriter5 : ISymUnmanagedWriter2 { // ISymUnmanagedWriter, ISymUnmanagedWriter2, ISymUnmanagedWriter3, ISymUnmanagedWriter4 void _VtblGap1_30(); @@ -144,6 +144,17 @@ internal interface ISymUnmanagedWriter5 void MapTokenToSourceSpan(uint token, ISymUnmanagedDocumentWriter document, uint startLine, uint startColumn, uint endLine, uint endColumn); } + [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("CA6C2ED9-103D-46A9-B03B-05446485848B"), SuppressUnmanagedCodeSecurity] + internal interface ISymUnmanagedWriter6 : ISymUnmanagedWriter5 + { + // ISymUnmanagedWriter, ISymUnmanagedWriter2, ISymUnmanagedWriter3, ISymUnmanagedWriter4, ISymUnmanagedWriter5 + void _VtblGap1_33(); + + // ISymUnmanagedWriter6 + void InitializeDeterministic([MarshalAs(UnmanagedType.IUnknown)] object emitter, [MarshalAs(UnmanagedType.IUnknown)] object stream); + void SetSignature(uint sig, Guid sig70); + } + [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct ImageDebugDirectory { diff --git a/src/Compilers/Core/Portable/PEWriter/IUnsafeComStream.cs b/src/Compilers/Core/Portable/NativePdbWriter/IUnsafeComStream.cs similarity index 100% rename from src/Compilers/Core/Portable/PEWriter/IUnsafeComStream.cs rename to src/Compilers/Core/Portable/NativePdbWriter/IUnsafeComStream.cs diff --git a/src/Compilers/Core/Portable/PEWriter/PdbMetadataWrapper.cs b/src/Compilers/Core/Portable/NativePdbWriter/PdbMetadataWrapper.cs similarity index 100% rename from src/Compilers/Core/Portable/PEWriter/PdbMetadataWrapper.cs rename to src/Compilers/Core/Portable/NativePdbWriter/PdbMetadataWrapper.cs diff --git a/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs b/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs similarity index 90% rename from src/Compilers/Core/Portable/PEWriter/PdbWriter.cs rename to src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs index b06fc7a015d3546fc8bc98ed0b275906347e9a04..d4d71f71a41ea22ce3043b00e2931f924ad7c18d 100644 --- a/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs +++ b/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; @@ -34,9 +35,11 @@ internal sealed class PdbWriter : IDisposable { internal const uint HiddenLocalAttributesValue = 1u; internal const uint DefaultLocalAttributesValue = 0u; + internal const uint Age = 1; private static Type s_lazyCorSymWriterSxSType; + private readonly bool _deterministic; private readonly string _fileName; private readonly Func _symWriterFactory; private ComMemoryStream _pdbStream; @@ -55,10 +58,12 @@ internal sealed class PdbWriter : IDisposable private uint[] _sequencePointEndLines; private uint[] _sequencePointEndColumns; - public PdbWriter(string fileName, Func symWriterFactory = null) + public PdbWriter(string fileName, Func symWriterFactory, bool deterministic) { _fileName = fileName; _symWriterFactory = symWriterFactory; + _deterministic = deterministic; + CreateSequencePointBuffers(capacity: 64); } @@ -544,31 +549,89 @@ private void DefineScopeLocals(LocalScope currentScope, uint localSignatureToken #region SymWriter calls + const string SymWriterClsid = "0AE2DEB0-F901-478b-BB9F-881EE8066788"; + + private static bool s_MicrosoftDiaSymReaderNativeLoadFailed; + + [DllImport("Microsoft.DiaSymReader.Native.x86.dll", EntryPoint = "CreateSymWriter")] + private extern static void CreateSymWriter32(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)]out object symWriter); + + [DllImport("Microsoft.DiaSymReader.Native.amd64.dll", EntryPoint = "CreateSymWriter")] + private extern static void CreateSymWriter64(ref Guid id, [MarshalAs(UnmanagedType.IUnknown)]out object symWriter); + private static Type GetCorSymWriterSxSType() { if (s_lazyCorSymWriterSxSType == null) { // If an exception is thrown we propagate it - we want to report it every time. - s_lazyCorSymWriterSxSType = Marshal.GetTypeFromCLSID(new Guid("0AE2DEB0-F901-478b-BB9F-881EE8066788")); + s_lazyCorSymWriterSxSType = Marshal.GetTypeFromCLSID(new Guid(SymWriterClsid)); } return s_lazyCorSymWriterSxSType; } + private static object CreateSymWriterWorker() + { + object symWriter = null; + + // First try to load an implementation from Microsoft.DiaSymReader.Native, which supports determinism. + if (!s_MicrosoftDiaSymReaderNativeLoadFailed) + { + try + { + var guid = new Guid(SymWriterClsid); + if (IntPtr.Size == 4) + { + CreateSymWriter32(ref guid, out symWriter); + } + else + { + CreateSymWriter64(ref guid, out symWriter); + } + } + catch (Exception) + { + s_MicrosoftDiaSymReaderNativeLoadFailed = true; + symWriter = null; + } + } + + if (symWriter == null) + { + // Try to find a registered CLR implementation + symWriter = Activator.CreateInstance(GetCorSymWriterSxSType()); + } + + return symWriter; + } + public void SetMetadataEmitter(MetadataWriter metadataWriter) { try { - var instance = (ISymUnmanagedWriter2)(_symWriterFactory != null ? _symWriterFactory() : Activator.CreateInstance(GetCorSymWriterSxSType())); + var symWriter = (ISymUnmanagedWriter2)(_symWriterFactory != null ? _symWriterFactory() : CreateSymWriterWorker()); // Correctness: If the stream is not specified or if it is non-empty the SymWriter appends data to it (provided it contains valid PDB) // and the resulting PDB has Age = existing_age + 1. _pdbStream = new ComMemoryStream(); - instance.Initialize(new PdbMetadataWrapper(metadataWriter), _fileName, _pdbStream, fullBuild: true); + if (_deterministic) + { + var deterministicSymWriter = symWriter as ISymUnmanagedWriter6; + if (symWriter == null) + { + throw new NotSupportedException(CodeAnalysisResources.SymWriterNotDeterministic); + } + + deterministicSymWriter.InitializeDeterministic(new PdbMetadataWrapper(metadataWriter), _pdbStream); + } + else + { + symWriter.Initialize(new PdbMetadataWrapper(metadataWriter), _fileName, _pdbStream, fullBuild: true); + } _metadataWriter = metadataWriter; - _symWriter = instance; + _symWriter = symWriter; } catch (Exception ex) { @@ -576,8 +639,31 @@ public void SetMetadataEmitter(MetadataWriter metadataWriter) } } - public unsafe void GetDebugDirectoryGuidAndStampAndAge(out Guid guid, out uint stamp, out uint age) + public unsafe ContentId GetContentId() { + if (_deterministic) + { + // Call to GetDebugInfo fails for SymWriter initialized using InitializeDeterministic. + // We already have all the info we need though. + + // TODO (https://github.com/dotnet/roslyn/issues/926): calculate sha1 hash + var id = new ContentId( + new byte[] { 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, }, + new byte[] { 0x12, 0x12, 0x12, 0x12 }); + + try + { + Debug.Assert(BitConverter.IsLittleEndian); + ((ISymUnmanagedWriter6)_symWriter).SetSignature(BitConverter.ToUInt32(id.Stamp, 0), new Guid(id.Guid)); + } + catch (Exception ex) + { + throw new PdbWritingException(ex); + } + + return id; + } + // See symwrite.cpp - the data byte[] doesn't depend on the content of metadata tables or IL. // The writer only sets two values of the ImageDebugDirectory struct. // @@ -625,12 +711,16 @@ public unsafe void GetDebugDirectoryGuidAndStampAndAge(out Guid guid, out uint s byte[] guidBytes = new byte[GuidSize]; Buffer.BlockCopy(data, 4, guidBytes, 0, guidBytes.Length); - guid = new Guid(guidBytes); - // Retrieve the timestamp the PDB writer generates when creating a new PDB stream. // Note that ImageDebugDirectory.TimeDateStamp is not set by GetDebugInfo, // we need to go thru IPdbWriter interface to get it. + uint stamp; + uint age; ((IPdbWriter)_symWriter).GetSignatureAge(out stamp, out age); + Debug.Assert(age == Age); + + Debug.Assert(BitConverter.IsLittleEndian); + return new ContentId(guidBytes, BitConverter.GetBytes(stamp)); } public void SetEntryPoint(uint entryMethodToken) diff --git a/src/Compilers/Core/Portable/PEWriter/ContentId.cs b/src/Compilers/Core/Portable/PEWriter/ContentId.cs new file mode 100644 index 0000000000000000000000000000000000000000..0fea2533cd01c80ab60738a3daf226e779427c7b --- /dev/null +++ b/src/Compilers/Core/Portable/PEWriter/ContentId.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Diagnostics; + +namespace Microsoft.Cci +{ + internal struct ContentId + { + public readonly byte[] Guid; + public readonly byte[] Stamp; + + public ContentId(byte[] guid, byte[] stamp) + { + Debug.Assert(guid.Length == 16 && stamp.Length == 4); + + Guid = guid; + Stamp = stamp; + } + + internal static ContentId FromSha1(ImmutableArray sha1) + { + var guid = new byte[16]; + for (var i = 0; i < guid.Length; i++) + { + guid[i] = sha1[i]; + } + + // modify the guid data so it decodes to the form of a "random" guid ala rfc4122 + var t = guid[7]; + t = (byte)((t & 0xf) | (4 << 4)); + guid[7] = t; + t = guid[8]; + t = (byte)((t & 0x3f) | (2 << 6)); + guid[8] = t; + + // compute a random-looking stamp from the remaining bits, but with the upper bit set + var stamp = new byte[4]; + stamp[0] = sha1[16]; + stamp[1] = sha1[17]; + stamp[2] = sha1[18]; + stamp[3] = (byte)(sha1[19] | 0x80); + + return new ContentId(guid, stamp); + } + } +} diff --git a/src/Compilers/Core/Portable/PEWriter/PeWriter.cs b/src/Compilers/Core/Portable/PEWriter/PeWriter.cs index b2b03c07842e27400c9389242101e9271e0580a8..a77eb16d4d9434b606874ba305976c44da240f43 100644 --- a/src/Compilers/Core/Portable/PEWriter/PeWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/PeWriter.cs @@ -22,7 +22,6 @@ internal sealed class PeWriter private readonly bool _deterministic; private readonly IModule _module; - private readonly PdbWriter _nativePdbWriterOpt; private readonly string _pdbPathOpt; private readonly bool _emitRuntimeStartupStub; private readonly int _sizeOfImportAddressTable; @@ -47,11 +46,10 @@ internal sealed class PeWriter private SectionHeader _textSection; private SectionHeader _tlsSection; - private PeWriter(IModule module, PdbWriter nativePdbWriterOpt, string pdbPathOpt, bool deterministic) + private PeWriter(IModule module, string pdbPathOpt, bool deterministic) { _module = module; _emitRuntimeStartupStub = module.RequiresStartupStub; - _nativePdbWriterOpt = nativePdbWriterOpt; _pdbPathOpt = pdbPathOpt; _deterministic = deterministic; _sizeOfImportAddressTable = _emitRuntimeStartupStub ? (!_module.Requires64bits ? 8 : 16) : 0; @@ -72,38 +70,13 @@ private PeWriter(IModule module, PdbWriter nativePdbWriterOpt, string pdbPathOpt // If PDB writer is given, we have to have PDB path. Debug.Assert(nativePdbWriterOpt == null || pdbPathOpt != null); - var peWriter = new PeWriter(context.Module, nativePdbWriterOpt, pdbPathOpt, deterministic); - + var peWriter = new PeWriter(context.Module, pdbPathOpt, deterministic); var mdWriter = FullMetadataWriter.Create(context, messageProvider, allowMissingMethodBodies, deterministic, cancellationToken); - nativePdbWriterOpt?.SetMetadataEmitter(mdWriter); - - uint entryPointToken; - if (!peWriter.WritePeToStream(mdWriter, getPeStream, nativePdbWriterOpt, out entryPointToken)) - { - return false; - } - - if (nativePdbWriterOpt != null) - { - if (entryPointToken != 0) - { - nativePdbWriterOpt.SetEntryPoint(entryPointToken); - } - - var assembly = context.Module.AsAssembly; - if (assembly != null && assembly.Kind == ModuleKind.WindowsRuntimeMetadata) - { - // Dev12: If compiling to winmdobj, we need to add to PDB source spans of - // all types and members for better error reporting by WinMDExp. - nativePdbWriterOpt.WriteDefinitionLocations(context.Module.GetSymbolToLocationMap()); - } - } - - return true; + return peWriter.WritePeToStream(mdWriter, getPeStream, nativePdbWriterOpt); } - private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream, PdbWriter nativePdbWriterOpt, out uint entryPointToken) + private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream, PdbWriter nativePdbWriterOpt) { // TODO: we can precalculate the exact size of IL stream var ilBuffer = new MemoryStream(32 * 1024); @@ -115,6 +88,8 @@ private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream, var managedResourceBuffer = new MemoryStream(1024); var managedResourceWriter = new BinaryWriter(managedResourceBuffer); + nativePdbWriterOpt?.SetMetadataEmitter(mdWriter); + // Since we are producing a full assembly, we should not have a module version ID // imposed ahead-of time. Instead we will compute a deterministic module version ID // based on the contents of the generated stream. @@ -128,6 +103,7 @@ private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream, }); MetadataSizes metadataSizes; + uint entryPointToken; mdWriter.SerializeMetadataAndIL( nativePdbWriterOpt, metadataWriter, @@ -140,6 +116,32 @@ private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream, out entryPointToken, out metadataSizes); + ContentId pdbContentId; + if (nativePdbWriterOpt != null) + { + if (entryPointToken != 0) + { + nativePdbWriterOpt.SetEntryPoint(entryPointToken); + } + + var assembly = _module.AsAssembly; + if (assembly != null && assembly.Kind == ModuleKind.WindowsRuntimeMetadata) + { + // Dev12: If compiling to winmdobj, we need to add to PDB source spans of + // all types and members for better error reporting by WinMDExp. + nativePdbWriterOpt.WriteDefinitionLocations(_module.GetSymbolToLocationMap()); + } + + pdbContentId = nativePdbWriterOpt.GetContentId(); + + // the writer shall not be used after this point for writing: + nativePdbWriterOpt = null; + } + else + { + pdbContentId = default(ContentId); + } + FillInSectionHeaders(); // fill in header fields. @@ -147,18 +149,17 @@ private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream, var corHeader = CreateCorHeader(metadataSizes, entryPointToken); // write to pe stream. - long positionOfHeaderTimestamp; Stream peStream = getPeStream(); if (peStream == null) { return false; } - WriteHeaders(peStream, out positionOfHeaderTimestamp); - - long startOfMetadataStream; - long positionOfDebugTableTimestamp; + long ntHeaderTimestampPosition; + long metadataPosition; + WriteHeaders(peStream, out ntHeaderTimestampPosition); + WriteTextSection( peStream, corHeader, @@ -167,8 +168,8 @@ private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream, mappedFieldDataBuffer, managedResourceBuffer, metadataSizes, - out startOfMetadataStream, - out positionOfDebugTableTimestamp); + pdbContentId, + out metadataPosition); WriteRdataSection(peStream); WriteSdataSection(peStream); @@ -179,8 +180,8 @@ private bool WritePeToStream(MetadataWriter mdWriter, Func getPeStream, if (_deterministic) { - var positionOfModuleVersionId = startOfMetadataStream + moduleVersionIdOffsetInMetadataStream; - WriteDeterministicGuidAndTimestamps(peStream, positionOfModuleVersionId, positionOfHeaderTimestamp, positionOfDebugTableTimestamp); + var mvidPosition = metadataPosition + moduleVersionIdOffsetInMetadataStream; + WriteDeterministicGuidAndTimestamps(peStream, mvidPosition, ntHeaderTimestampPosition); } return true; @@ -198,37 +199,34 @@ private int CalculateMappedFieldDataStreamRva(MetadataSizes metadataSizes) /// Compute a deterministic Guid and timestamp based on the contents of the stream, and replace /// the 16 zero bytes at the given position and one or two 4-byte values with that computed Guid and timestamp. /// - /// Stream of data - /// Position in the stream of 16 zero bytes to be replaced by a Guid - /// Position in the stream of four zero bytes to be replaced by a timestamp - /// Position in the stream of four zero bytes to be replaced by a timestamp, or 0 if there is no second timestamp to be replaced - private static void WriteDeterministicGuidAndTimestamps(Stream stream, long positionOfModuleVersionId, long positionOfHeaderTimestamp, long positionOfDebugTableTimestamp) + /// PE stream. + /// Position in the stream of 16 zero bytes to be replaced by a Guid + /// Position in the stream of four zero bytes to be replaced by a timestamp + private static void WriteDeterministicGuidAndTimestamps( + Stream peStream, + long mvidPosition, + long ntHeaderTimestampPosition) { - var previousPosition = stream.Position; - - // The existing Guid in the data should be empty, as we are about to compute it. - // Check to be sure. - CheckZeroDataInStream(stream, positionOfModuleVersionId, 16); + Debug.Assert(mvidPosition != 0); + Debug.Assert(ntHeaderTimestampPosition != 0); + var previousPosition = peStream.Position; + // Compute and write deterministic guid data over the relevant portion of the stream - byte[] timestamp; - var guidData = ComputeSerializedGuidFromData(stream, out timestamp); - stream.Position = positionOfModuleVersionId; - stream.Write(guidData, 0, 16); - - // Write a deterministic timestamp over the relevant portion(s) of the stream - Debug.Assert(positionOfHeaderTimestamp != 0); - CheckZeroDataInStream(stream, positionOfHeaderTimestamp, 4); - stream.Position = positionOfHeaderTimestamp; - stream.Write(timestamp, 0, 4); - if (positionOfDebugTableTimestamp != 0) - { - CheckZeroDataInStream(stream, positionOfDebugTableTimestamp, 4); - stream.Position = positionOfDebugTableTimestamp; - stream.Write(timestamp, 0, 4); - } + peStream.Position = 0; + var contentId = ContentId.FromSha1(CryptographicHashProvider.ComputeSha1(peStream)); + + // The existing Guid should be zero. + CheckZeroDataInStream(peStream, mvidPosition, contentId.Guid.Length); + peStream.Position = mvidPosition; + peStream.Write(contentId.Guid, 0, contentId.Guid.Length); - stream.Position = previousPosition; + // The existing timestamp should be zero. + CheckZeroDataInStream(peStream, ntHeaderTimestampPosition, contentId.Stamp.Length); + peStream.Position = ntHeaderTimestampPosition; + peStream.Write(contentId.Stamp, 0, contentId.Stamp.Length); + + peStream.Position = previousPosition; } [Conditional("DEBUG")] @@ -242,38 +240,6 @@ private static void CheckZeroDataInStream(Stream stream, long position, int byte } } - /// - /// Compute a random-looking but deterministic Guid from a hash of the stream's data, and produce a "timestamp" from the remaining bits. - /// - private static byte[] ComputeSerializedGuidFromData(Stream stream, out byte[] timestamp) - { - stream.Position = 0; // rewind the stream - - var hashData = CryptographicHashProvider.ComputeSha1(stream); - var guidData = new byte[16]; - for (var i = 0; i < guidData.Length; i++) - { - guidData[i] = hashData[i]; - } - - // modify the guid data so it decodes to the form of a "random" guid ala rfc4122 - var t = guidData[7]; - t = (byte)((t & 0xf) | (4 << 4)); - guidData[7] = t; - t = guidData[8]; - t = (byte)((t & 0x3f) | (2 << 6)); - guidData[8] = t; - - // compute a random-looking timestamp from the remaining bits, but with the upper bit set - timestamp = new byte[4]; - timestamp[0] = hashData[16]; - timestamp[1] = hashData[17]; - timestamp[2] = hashData[18]; - timestamp[3] = (byte)(hashData[19] | 0x80); - - return guidData; - } - private int ComputeStrongNameSignatureSize() { IAssembly assembly = _module.AsAssembly; @@ -1054,7 +1020,7 @@ private void SerializeWin32Resources(ResourceSection resourceSections, uint reso 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - private void WriteHeaders(Stream peStream, out long timestampOffset) + private void WriteHeaders(Stream peStream, out long ntHeaderTimestampPosition) { IModule module = _module; NtHeader ntHeader = _ntHeader; @@ -1069,7 +1035,7 @@ private void WriteHeaders(Stream peStream, out long timestampOffset) // COFF Header 20 bytes writer.WriteUshort((ushort)module.Machine); writer.WriteUshort(ntHeader.NumberOfSections); - timestampOffset = (uint)(writer.BaseStream.Position + peStream.Position); + ntHeaderTimestampPosition = writer.BaseStream.Position + peStream.Position; writer.WriteUint(ntHeader.TimeDateStamp); writer.WriteUint(ntHeader.PointerToSymbolTable); writer.WriteUint(0); // NumberOfSymbols @@ -1251,8 +1217,8 @@ private static void WriteSectionHeader(SectionHeader sectionHeader, BinaryWriter MemoryStream mappedFieldDataStream, MemoryStream managedResourceStream, MetadataSizes metadataSizes, - out long startOfMetadata, - out long positionOfTimestamp) + ContentId pdbContentId, + out long metadataPosition) { peStream.Position = _textSection.PointerToRawData; if (_emitRuntimeStartupStub) @@ -1263,12 +1229,12 @@ private static void WriteSectionHeader(SectionHeader sectionHeader, BinaryWriter WriteCorHeader(peStream, corHeader); WriteIL(peStream, ilStream); - startOfMetadata = peStream.Position; + metadataPosition = peStream.Position; WriteMetadata(peStream, metadataStream); WriteManagedResources(peStream, managedResourceStream); WriteSpaceForHash(peStream, (int)corHeader.StrongNameSignature.Size); - WriteDebugTable(peStream, metadataSizes, out positionOfTimestamp); + WriteDebugTable(peStream, pdbContentId, metadataSizes); if (_emitRuntimeStartupStub) { @@ -1431,37 +1397,21 @@ private static void WriteManagedResources(Stream peStream, MemoryStream managedR } } - private void WriteDebugTable(Stream peStream, MetadataSizes metadataSizes, out long timestampOffset) + private void WriteDebugTable(Stream peStream, ContentId pdbContentId, MetadataSizes metadataSizes) { if (!EmitPdb) { - timestampOffset = 0; return; } MemoryStream stream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(stream); - Guid pdbId; - uint pdbStamp; - uint age; - if (_nativePdbWriterOpt != null) - { - _nativePdbWriterOpt.GetDebugDirectoryGuidAndStampAndAge(out pdbId, out pdbStamp, out age); - } - else - { - pdbId = Guid.NewGuid(); - pdbStamp = 0; - age = 1; - } - // characteristics: writer.WriteUint(0); // PDB stamp - timestampOffset = writer.BaseStream.Position + peStream.Position; - writer.WriteUint(pdbStamp); + writer.WriteBytes(pdbContentId.Stamp); // version writer.WriteUint(0); @@ -1487,10 +1437,10 @@ private void WriteDebugTable(Stream peStream, MetadataSizes metadataSizes, out l writer.WriteByte((byte)'S'); // PDB id: - writer.WriteBytes(pdbId.ToByteArray()); + writer.WriteBytes(pdbContentId.Guid); // age - writer.WriteUint(age); + writer.WriteUint(PdbWriter.Age); // UTF-8 encoded zero-terminated path to PDB writer.WriteString(_pdbPathOpt, emitNullTerminator: true); diff --git a/src/Compilers/Core/Portable/packages.config b/src/Compilers/Core/Portable/packages.config index 569e1bea867b8f83ba8a14afb0b410af4e139127..d3b9de13e376aed01da35c668b0eba025223c36a 100644 --- a/src/Compilers/Core/Portable/packages.config +++ b/src/Compilers/Core/Portable/packages.config @@ -1,3 +1,4 @@  + diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb index bedf1cd7432bd1e00ac49063732bda6b324c6ccb..c8a579e641b100583cc46771371902235c4c5c0c 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb @@ -42,7 +42,7 @@ End Class" AssertEx.Equal(result3, result4) End Sub - + Public Sub CompareAllBytesEmitted_Debug() Dim source = "Class Program