diff options
author | Vitek Karas <vitek.karas@microsoft.com> | 2021-11-11 20:40:42 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-11 20:40:42 +0300 |
commit | ff616bf90e3aff2292a0b1536f7af13cd8810276 (patch) | |
tree | 1c3734dea16e9796a8aeb198ea659ae9435e31d9 | |
parent | 3bef7fc7b7910f826790ebfeccb381b76a650571 (diff) |
Fix deterministic MVID and add PdbChecksum (#31)
-rw-r--r-- | Mono.Cecil.Cil/PortablePdb.cs | 179 | ||||
-rw-r--r-- | Mono.Cecil.Cil/Symbols.cs | 6 | ||||
-rw-r--r-- | Mono.Cecil.PE/ImageReader.cs | 5 | ||||
-rw-r--r-- | Mono.Cecil.PE/ImageWriter.cs | 34 | ||||
-rw-r--r-- | Mono.Cecil/AssemblyWriter.cs | 49 | ||||
-rw-r--r-- | Mono.Security.Cryptography/CryptoService.cs | 2 | ||||
-rw-r--r-- | Test/Mono.Cecil.Tests/ImageReadTests.cs | 18 | ||||
-rw-r--r-- | Test/Mono.Cecil.Tests/PortablePdbTests.cs | 303 | ||||
-rw-r--r-- | Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll | bin | 0 -> 9728 bytes | |||
-rw-r--r-- | Test/Resources/assemblies/PdbChecksumLib.dll | bin | 0 -> 4096 bytes | |||
-rw-r--r-- | Test/Resources/assemblies/PdbChecksumLib.pdb | bin | 0 -> 9576 bytes | |||
-rw-r--r-- | symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs | 11 |
12 files changed, 500 insertions, 107 deletions
diff --git a/Mono.Cecil.Cil/PortablePdb.cs b/Mono.Cecil.Cil/PortablePdb.cs index aff6b29..d8c43f6 100644 --- a/Mono.Cecil.Cil/PortablePdb.cs +++ b/Mono.Cecil.Cil/PortablePdb.cs @@ -12,7 +12,7 @@ using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; - +using System.Security.Cryptography; using Mono.Cecil.Metadata; using Mono.Cecil.PE; @@ -39,7 +39,7 @@ namespace Mono.Cecil.Cil { ISymbolReader GetSymbolReader (ModuleDefinition module, Disposable<Stream> symbolStream, string fileName) { - return new PortablePdbReader (ImageReader.ReadPortablePdb (symbolStream, fileName), module); + return new PortablePdbReader (ImageReader.ReadPortablePdb (symbolStream, fileName, out _), module); } } @@ -234,8 +234,8 @@ namespace Mono.Cecil.Cil { Mixin.CheckModule (module); Mixin.CheckFileName (fileName); - var file = File.OpenWrite (Mixin.GetPdbFileName (fileName)); - return GetSymbolWriter (module, Disposable.Owned (file as Stream)); + var file = File.Open (Mixin.GetPdbFileName (fileName), FileMode.OpenOrCreate, FileAccess.ReadWrite); + return GetSymbolWriter (module, Disposable.Owned (file as Stream), Disposable.NotOwned ((Stream)null)); } public ISymbolWriter GetSymbolWriter (ModuleDefinition module, Stream symbolStream) @@ -243,15 +243,18 @@ namespace Mono.Cecil.Cil { Mixin.CheckModule (module); Mixin.CheckStream (symbolStream); - return GetSymbolWriter (module, Disposable.NotOwned (symbolStream)); + // In order to compute the PDB checksum, the stream we're writing to needs to be able to + // seek and read as well. We can't assume this about a stream provided by the user. + // So in this case, create a memory stream to cache the PDB. + return GetSymbolWriter (module, Disposable.Owned (new MemoryStream() as Stream), Disposable.NotOwned (symbolStream)); } - ISymbolWriter GetSymbolWriter (ModuleDefinition module, Disposable<Stream> stream) + ISymbolWriter GetSymbolWriter (ModuleDefinition module, Disposable<Stream> stream, Disposable<Stream> final_stream) { var metadata = new MetadataBuilder (module, this); var writer = ImageWriter.CreateDebugWriter (module, metadata, stream); - return new PortablePdbWriter (metadata, module, writer); + return new PortablePdbWriter (metadata, module, writer, final_stream); } } @@ -260,9 +263,14 @@ namespace Mono.Cecil.Cil { readonly MetadataBuilder pdb_metadata; readonly ModuleDefinition module; readonly ImageWriter writer; + readonly Disposable<Stream> final_stream; MetadataBuilder module_metadata; + internal byte [] pdb_checksum; + internal Guid pdb_id_guid; + internal uint pdb_id_age; + bool IsEmbedded { get { return writer == null; } } internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module) @@ -278,10 +286,11 @@ namespace Mono.Cecil.Cil { pdb_metadata.AddCustomDebugInformations (module); } - internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module, ImageWriter writer) + internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module, ImageWriter writer, Disposable<Stream> final_stream) : this (pdb_metadata, module) { this.writer = writer; + this.final_stream = final_stream; } public ISymbolReaderProvider GetReaderProvider () @@ -289,45 +298,82 @@ namespace Mono.Cecil.Cil { return new PortablePdbReaderProvider (); } + public void Write (MethodDebugInformation info) + { + CheckMethodDebugInformationTable (); + + pdb_metadata.AddMethodDebugInformation (info); + } + public ImageDebugHeader GetDebugHeader () { if (IsEmbedded) return new ImageDebugHeader (); - var directory = new ImageDebugDirectory () { - MajorVersion = 256, - MinorVersion = 20557, - Type = ImageDebugType.CodeView, - TimeDateStamp = (int) module.timestamp, - }; + WritePdbFile (); + + if (final_stream.value != null) { + writer.BaseStream.Seek (0, SeekOrigin.Begin); + var buffer = new byte [8192]; + CryptoService.CopyStreamChunk (writer.BaseStream, final_stream.value, buffer, (int)writer.BaseStream.Length); + } - var buffer = new ByteBuffer (); - // RSDS - buffer.WriteUInt32 (0x53445352); - // Module ID - buffer.WriteBytes (module.Mvid.ToByteArray ()); - // PDB Age - buffer.WriteUInt32 (1); - // PDB Path - var fileName = writer.BaseStream.GetFileName (); - if (string.IsNullOrEmpty (fileName)) { - fileName = module.Assembly.Name.Name + ".pdb"; + ImageDebugHeaderEntry codeViewEntry; + { + var codeViewDirectory = new ImageDebugDirectory () { + MajorVersion = 256, + MinorVersion = 20557, + Type = ImageDebugType.CodeView, + TimeDateStamp = (int)module.timestamp, + }; + + var buffer = new ByteBuffer (); + // RSDS + buffer.WriteUInt32 (0x53445352); + // Module ID + buffer.WriteBytes (pdb_id_guid.ToByteArray ()); + // PDB Age + buffer.WriteUInt32 (pdb_id_age); + // PDB Path + var fileName = writer.BaseStream.GetFileName (); + if (string.IsNullOrEmpty (fileName)) { + fileName = module.Assembly.Name.Name + ".pdb"; + } + buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes (fileName)); + buffer.WriteByte (0); + + var data = new byte [buffer.length]; + Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length); + codeViewDirectory.SizeOfData = data.Length; + + codeViewEntry = new ImageDebugHeaderEntry (codeViewDirectory, data); } - buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes (fileName)); - buffer.WriteByte (0); - var data = new byte [buffer.length]; - Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length); - directory.SizeOfData = data.Length; + ImageDebugHeaderEntry pdbChecksumEntry; + { + var pdbChecksumDirectory = new ImageDebugDirectory () { + MajorVersion = 1, + MinorVersion = 0, + Type = ImageDebugType.PdbChecksum, + TimeDateStamp = 0 + }; - return new ImageDebugHeader (new ImageDebugHeaderEntry (directory, data)); - } + var buffer = new ByteBuffer (); + // SHA256 - Algorithm name + buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes ("SHA256")); + buffer.WriteByte (0); - public void Write (MethodDebugInformation info) - { - CheckMethodDebugInformationTable (); + // Checksum - 32 bytes + buffer.WriteBytes (pdb_checksum); - pdb_metadata.AddMethodDebugInformation (info); + var data = new byte [buffer.length]; + Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length); + pdbChecksumDirectory.SizeOfData = data.Length; + + pdbChecksumEntry = new ImageDebugHeaderEntry (pdbChecksumDirectory, data); + } + + return new ImageDebugHeader (new ImageDebugHeaderEntry [] { codeViewEntry, pdbChecksumEntry }); } void CheckMethodDebugInformationTable () @@ -343,10 +389,8 @@ namespace Mono.Cecil.Cil { public void Dispose () { - if (IsEmbedded) - return; - - WritePdbFile (); + writer.stream.Dispose (); + final_stream.Dispose (); } void WritePdbFile () @@ -360,15 +404,18 @@ namespace Mono.Cecil.Cil { writer.WriteMetadata (); writer.Flush (); - writer.stream.Dispose (); + + ComputeChecksumAndPdbId (); + + WritePdbId (); } void WritePdbHeap () { var pdb_heap = pdb_metadata.pdb_heap; - pdb_heap.WriteBytes (module.Mvid.ToByteArray ()); - pdb_heap.WriteUInt32 (module_metadata.timestamp); + // PDB ID ( GUID + TimeStamp ) are left zeroed out for now. Will be filled at the end with a hash. + pdb_heap.WriteBytes (20); pdb_heap.WriteUInt32 (module_metadata.entry_point.ToUInt32 ()); @@ -399,6 +446,32 @@ namespace Mono.Cecil.Cil { pdb_metadata.table_heap.ComputeTableInformations (); pdb_metadata.table_heap.WriteTableHeap (); } + + void ComputeChecksumAndPdbId () + { + var buffer = new byte [8192]; + + // Compute the has of the entire file - PDB ID is zeroes still + writer.BaseStream.Seek (0, SeekOrigin.Begin); + var sha256 = SHA256.Create (); + using (var crypto_stream = new CryptoStream (Stream.Null, sha256, CryptoStreamMode.Write)) { + CryptoService.CopyStreamChunk (writer.BaseStream, crypto_stream, buffer, (int)writer.BaseStream.Length); + } + + pdb_checksum = sha256.Hash; + + var hashBytes = new ByteBuffer (pdb_checksum); + pdb_id_guid = new Guid (hashBytes.ReadBytes (16)); + pdb_id_age = hashBytes.ReadUInt32 (); + } + + void WritePdbId () + { + // PDB ID is the first 20 bytes of the PdbHeap + writer.MoveToRVA (TextSegment.PdbHeap); + writer.WriteBytes (pdb_id_guid.ToByteArray ()); + writer.WriteUInt32 (pdb_id_age); + } } public sealed class EmbeddedPortablePdbWriterProvider : ISymbolWriterProvider { @@ -435,9 +508,14 @@ namespace Mono.Cecil.Cil { return new EmbeddedPortablePdbReaderProvider (); } + public void Write (MethodDebugInformation info) + { + writer.Write (info); + } + public ImageDebugHeader GetDebugHeader () { - writer.Dispose (); + ImageDebugHeader pdbDebugHeader = writer.GetDebugHeader (); var directory = new ImageDebugDirectory { Type = ImageDebugType.EmbeddedPortablePdb, @@ -462,15 +540,12 @@ namespace Mono.Cecil.Cil { directory.SizeOfData = (int) data.Length; - return new ImageDebugHeader (new [] { - writer.GetDebugHeader ().Entries [0], - new ImageDebugHeaderEntry (directory, data.ToArray ()) - }); - } + var debugHeaderEntries = new ImageDebugHeaderEntry [pdbDebugHeader.Entries.Length + 1]; + for (int i = 0; i < pdbDebugHeader.Entries.Length; i++) + debugHeaderEntries [i] = pdbDebugHeader.Entries [i]; + debugHeaderEntries [debugHeaderEntries.Length - 1] = new ImageDebugHeaderEntry (directory, data.ToArray ()); - public void Write (MethodDebugInformation info) - { - writer.Write (info); + return new ImageDebugHeader (debugHeaderEntries); } public void Dispose () diff --git a/Mono.Cecil.Cil/Symbols.cs b/Mono.Cecil.Cil/Symbols.cs index 5e92b67..a669067 100644 --- a/Mono.Cecil.Cil/Symbols.cs +++ b/Mono.Cecil.Cil/Symbols.cs @@ -39,6 +39,7 @@ namespace Mono.Cecil.Cil { CodeView = 2, Deterministic = 16, EmbeddedPortablePdb = 17, + PdbChecksum = 19, } public sealed class ImageDebugHeader { @@ -1174,6 +1175,11 @@ namespace Mono.Cecil { return GetEntry (header, ImageDebugType.EmbeddedPortablePdb); } + public static ImageDebugHeaderEntry GetPdbChecksumEntry (this ImageDebugHeader header) + { + return GetEntry (header, ImageDebugType.PdbChecksum); + } + private static ImageDebugHeaderEntry GetEntry (this ImageDebugHeader header, ImageDebugType type) { if (!header.HasEntries) diff --git a/Mono.Cecil.PE/ImageReader.cs b/Mono.Cecil.PE/ImageReader.cs index 5358129..a34e64d 100644 --- a/Mono.Cecil.PE/ImageReader.cs +++ b/Mono.Cecil.PE/ImageReader.cs @@ -27,6 +27,7 @@ namespace Mono.Cecil.PE { DataDirectory metadata; uint table_heap_offset; + uint pdb_heap_offset; public ImageReader (Disposable<Stream> stream, string file_name) : base (stream.value) @@ -400,6 +401,7 @@ namespace Mono.Cecil.PE { break; case "#Pdb": image.PdbHeap = new PdbHeap (data); + pdb_heap_offset = offset; break; } } @@ -768,7 +770,7 @@ namespace Mono.Cecil.PE { } } - public static Image ReadPortablePdb (Disposable<Stream> stream, string file_name) + public static Image ReadPortablePdb (Disposable<Stream> stream, string file_name, out uint pdb_heap_offset) { try { var reader = new ImageReader (stream, file_name); @@ -785,6 +787,7 @@ namespace Mono.Cecil.PE { reader.metadata = new DataDirectory (0, length); reader.ReadMetadata (); + pdb_heap_offset = reader.pdb_heap_offset; return reader.image; } catch (EndOfStreamException e) { throw new BadImageFormatException (stream.value.GetFileName (), e); diff --git a/Mono.Cecil.PE/ImageWriter.cs b/Mono.Cecil.PE/ImageWriter.cs index a8a3fa8..0648f59 100644 --- a/Mono.Cecil.PE/ImageWriter.cs +++ b/Mono.Cecil.PE/ImageWriter.cs @@ -48,7 +48,9 @@ namespace Mono.Cecil.PE { ushort sections; - ImageWriter (ModuleDefinition module, string runtime_version, MetadataBuilder metadata, Disposable<Stream> stream, bool metadataOnly = false) + internal long debug_header_entries_position; + + ImageWriter (ModuleDefinition module, string runtime_version, MetadataBuilder metadata, Disposable<Stream> stream, bool metadataOnly = false, ImageDebugHeader debugHeader = null) : base (stream.value) { this.module = module; @@ -61,26 +63,18 @@ namespace Mono.Cecil.PE { this.pe64 = module.Architecture == TargetArchitecture.AMD64 || module.Architecture == TargetArchitecture.IA64 || module.Architecture == TargetArchitecture.ARM64; this.has_reloc = module.Architecture == TargetArchitecture.I386; - this.GetDebugHeader (); this.GetWin32Resources (); - this.BuildTextMap (); - this.sections = (ushort) (has_reloc ? 2 : 1); // text + reloc? - } - - void GetDebugHeader () - { - var symbol_writer = metadata.symbol_writer; - if (symbol_writer != null) - debug_header = symbol_writer.GetDebugHeader (); + debug_header = debugHeader; if (module.HasDebugHeader) { var header = module.GetDebugHeader (); var deterministic = header.GetDeterministicEntry (); - if (deterministic == null) - return; - - debug_header = debug_header.AddDeterministicEntry (); + if (deterministic != null) + debug_header = debug_header.AddDeterministicEntry (); } + + this.BuildTextMap (); + this.sections = (ushort)(has_reloc ? 2 : 1); // text + reloc? } void GetWin32Resources () @@ -96,9 +90,9 @@ namespace Mono.Cecil.PE { } } - public static ImageWriter CreateWriter (ModuleDefinition module, MetadataBuilder metadata, Disposable<Stream> stream) + public static ImageWriter CreateWriter (ModuleDefinition module, MetadataBuilder metadata, Disposable<Stream> stream, ImageDebugHeader debugHeader) { - var writer = new ImageWriter (module, module.runtime_version, metadata, stream); + var writer = new ImageWriter (module, module.runtime_version, metadata, stream, metadataOnly: false, debugHeader: debugHeader); writer.BuildSections (); return writer; } @@ -379,7 +373,7 @@ namespace Mono.Cecil.PE { BaseStream.Seek (GetRVAFileOffset (section, rva), SeekOrigin.Begin); } - void MoveToRVA (TextSegment segment) + internal void MoveToRVA (TextSegment segment) { MoveToRVA (text, text_map.GetRVA (segment)); } @@ -600,7 +594,9 @@ namespace Mono.Cecil.PE { data_start += entry.Data.Length; } - + + debug_header_entries_position = BaseStream.Position; + for (var i = 0; i < debug_header.Entries.Length; i++) { var entry = debug_header.Entries [i]; WriteBytes (entry.Data); diff --git a/Mono.Cecil/AssemblyWriter.cs b/Mono.Cecil/AssemblyWriter.cs index c83f997..e60ab1d 100644 --- a/Mono.Cecil/AssemblyWriter.cs +++ b/Mono.Cecil/AssemblyWriter.cs @@ -118,13 +118,17 @@ namespace Mono.Cecil { metadata.SetSymbolWriter (symbol_writer); BuildMetadata (module, metadata); - if (parameters.DeterministicMvid) - metadata.ComputeDeterministicMvid (); + ImageDebugHeader debugHeader = null; + if (symbol_writer != null) + debugHeader = symbol_writer.GetDebugHeader (); - var writer = ImageWriter.CreateWriter (module, metadata, stream); + var writer = ImageWriter.CreateWriter (module, metadata, stream, debugHeader); stream.value.SetLength (0); writer.WriteImage (); + if (parameters.DeterministicMvid) + ComputeDeterministicMvid (writer, module); + if (parameters.HasStrongNameKey) CryptoService.StrongName (stream.value, writer, parameters); } @@ -156,6 +160,26 @@ namespace Mono.Cecil { return symbol_writer_provider.GetSymbolWriter (module, fq_name); } + + static void ComputeDeterministicMvid (ImageWriter writer, ModuleDefinition module) + { + long previousPosition = writer.BaseStream.Position; + writer.BaseStream.Seek(0, SeekOrigin.Begin); + + // The hash should be computed with the MVID set to all zeroes + // which it is - we explicitly write all zeroes GUID into the heap + // as the MVID. + // Same goes for strong name signature, which also already in the image but all zeroes right now. + Guid guid = CryptoService.ComputeGuid (CryptoService.ComputeHash (writer.BaseStream)); + + // The MVID GUID is always the first GUID in the GUID heap + writer.MoveToRVA (TextSegment.GuidHeap); + writer.WriteBytes (guid.ToByteArray ()); + writer.Flush (); + module.Mvid = guid; + + writer.BaseStream.Seek(previousPosition, SeekOrigin.Begin); + } } abstract class MetadataTable { @@ -2642,25 +2666,6 @@ namespace Mono.Cecil { method_debug_information_table.rows [rid - 1].Col2 = GetBlobIndex (signature); } - - public void ComputeDeterministicMvid () - { - var guid = CryptoService.ComputeGuid (CryptoService.ComputeHash ( - data, - resources, - string_heap, - user_string_heap, - blob_heap, - table_heap, - code)); - - var position = guid_heap.position; - guid_heap.position = 0; - guid_heap.WriteBytes (guid.ToByteArray ()); - guid_heap.position = position; - - module.Mvid = guid; - } } sealed class SignatureWriter : ByteBuffer { diff --git a/Mono.Security.Cryptography/CryptoService.cs b/Mono.Security.Cryptography/CryptoService.cs index dd9613f..3877deb 100644 --- a/Mono.Security.Cryptography/CryptoService.cs +++ b/Mono.Security.Cryptography/CryptoService.cs @@ -109,7 +109,7 @@ namespace Mono.Cecil { return sha1.Hash; } - static void CopyStreamChunk (Stream stream, Stream dest_stream, byte [] buffer, int length) + public static void CopyStreamChunk (Stream stream, Stream dest_stream, byte [] buffer, int length) { while (length > 0) { int read = stream.Read (buffer, 0, System.Math.Min (buffer.Length, length)); diff --git a/Test/Mono.Cecil.Tests/ImageReadTests.cs b/Test/Mono.Cecil.Tests/ImageReadTests.cs index ef6ab06..f663b60 100644 --- a/Test/Mono.Cecil.Tests/ImageReadTests.cs +++ b/Test/Mono.Cecil.Tests/ImageReadTests.cs @@ -237,9 +237,16 @@ namespace Mono.Cecil.Tests { var header = module.GetDebugHeader (); - Assert.AreEqual (2, header.Entries.Length); + Assert.IsTrue (header.Entries.Length >= 2); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.CodeView)); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.Deterministic)); + + // If read directly from a file the PdbChecksum may not be persent (in this test case it isn't) + // but when written through Cecil it will always be there. + if (header.Entries.Length > 2) { + Assert.AreEqual (3, header.Entries.Length); + Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.PdbChecksum)); + } }, symbolReaderProvider: typeof (PortablePdbReaderProvider), symbolWriterProvider: typeof (PortablePdbWriterProvider)); } @@ -251,10 +258,17 @@ namespace Mono.Cecil.Tests { var header = module.GetDebugHeader (); - Assert.AreEqual (3, header.Entries.Length); + Assert.IsTrue (header.Entries.Length >= 3); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.CodeView)); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.Deterministic)); Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.EmbeddedPortablePdb)); + + // If read directly from a file the PdbChecksum may not be persent (in this test case it isn't) + // but when written through Cecil it will always be there. + if (header.Entries.Length > 3) { + Assert.AreEqual (4, header.Entries.Length); + Assert.IsTrue (header.Entries.Any (e => e.Directory.Type == ImageDebugType.PdbChecksum)); + } }, symbolReaderProvider: typeof (EmbeddedPortablePdbReaderProvider), symbolWriterProvider: typeof (EmbeddedPortablePdbWriterProvider)); } } diff --git a/Test/Mono.Cecil.Tests/PortablePdbTests.cs b/Test/Mono.Cecil.Tests/PortablePdbTests.cs index d9786cb..1401f63 100644 --- a/Test/Mono.Cecil.Tests/PortablePdbTests.cs +++ b/Test/Mono.Cecil.Tests/PortablePdbTests.cs @@ -1,6 +1,8 @@ using System; using System.IO; +using System.IO.Compression; using System.Linq; +using System.Security.Cryptography; using System.Text; using NUnit.Framework; @@ -357,15 +359,23 @@ namespace Mono.Cecil.Tests { var header = module.GetDebugHeader (); Assert.IsNotNull (header); - Assert.AreEqual (2, header.Entries.Length); + Assert.IsTrue (header.Entries.Length >= 2); - var cv = header.Entries [0]; + int i = 0; + var cv = header.Entries [i++]; Assert.AreEqual (ImageDebugType.CodeView, cv.Directory.Type); - var eppdb = header.Entries [1]; + if (header.Entries.Length > 2) { + Assert.AreEqual (3, header.Entries.Length); + var pdbChecksum = header.Entries [i++]; + Assert.AreEqual (ImageDebugType.PdbChecksum, pdbChecksum.Directory.Type); + } + + var eppdb = header.Entries [i++]; Assert.AreEqual (ImageDebugType.EmbeddedPortablePdb, eppdb.Directory.Type); Assert.AreEqual (0x0100, eppdb.Directory.MajorVersion); Assert.AreEqual (0x0100, eppdb.Directory.MinorVersion); + }, symbolReaderProvider: typeof (EmbeddedPortablePdbReaderProvider), symbolWriterProvider: typeof (EmbeddedPortablePdbWriterProvider)); } @@ -400,7 +410,7 @@ namespace Mono.Cecil.Tests { { TestModule ("PdbTarget.exe", test, symbolReaderProvider: typeof (PortablePdbReaderProvider), symbolWriterProvider: typeof (PortablePdbWriterProvider)); TestModule ("EmbeddedPdbTarget.exe", test, verify: !Platform.OnMono); - TestModule ("EmbeddedCompressedPdbTarget.exe", test, symbolReaderProvider: typeof(EmbeddedPortablePdbReaderProvider), symbolWriterProvider: typeof(EmbeddedPortablePdbWriterProvider)); + TestModule ("EmbeddedCompressedPdbTarget.exe", test, symbolReaderProvider: typeof(EmbeddedPortablePdbReaderProvider), symbolWriterProvider: typeof (EmbeddedPortablePdbWriterProvider)); } [Test] @@ -604,7 +614,7 @@ class Program } [Test] - public void PortablePdbLineInfo () + public void PortablePdbLineInfo() { TestModule ("line.exe", module => { var type = module.GetType ("Tests"); @@ -809,5 +819,288 @@ class Program } }); } + + [Test] + public void DoubleWriteAndReadWithDeterministicMvidAndVariousChanges () + { + Guid mvidIn, mvidARM64Out, mvidX64Out; + + const string resource = "mylib.dll"; + { + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + mvidIn = module.Mvid; + module.Architecture = TargetArchitecture.ARM64; // Can't use I386 as it writes different import table size -> differnt MVID + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + mvidARM64Out = module.Mvid; + } + + Assert.AreNotEqual (mvidIn, mvidARM64Out); + } + + { + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + Assert.AreEqual (mvidIn, module.Mvid); + module.Architecture = TargetArchitecture.AMD64; + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + mvidX64Out = module.Mvid; + } + + Assert.AreNotEqual (mvidARM64Out, mvidX64Out); + } + + { + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + Assert.AreEqual (mvidIn, module.Mvid); + module.Architecture = TargetArchitecture.AMD64; + module.timestamp = 42; + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + Guid mvidDifferentTimeStamp; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + mvidDifferentTimeStamp = module.Mvid; + } + + Assert.AreNotEqual (mvidX64Out, mvidDifferentTimeStamp); + } + } + + [Test] + public void ReadPortablePdbChecksum () + { + const string resource = "PdbChecksumLib.dll"; + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + GetPdbChecksumData (module.GetDebugHeader (), out string algorithmName, out byte [] checksum); + Assert.AreEqual ("SHA256", algorithmName); + + string pdbPath = GetDebugHeaderPdbPath (module); + CalculatePdbChecksumAndId (pdbPath, out byte [] expectedChecksum, out byte [] pdbId); + + CollectionAssert.AreEqual (expectedChecksum, checksum); + } + } + + [Test] + public void ReadEmbeddedPortablePdbChecksum () + { + const string resource = "EmbeddedPdbChecksumLib.dll"; + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + var debugHeader = module.GetDebugHeader (); + GetPdbChecksumData (debugHeader, out string algorithmName, out byte [] checksum); + Assert.AreEqual ("SHA256", algorithmName); + + GetEmbeddedPdb (debugHeader, out byte [] embeddedPdb); + CalculatePdbChecksumAndId (embeddedPdb, out byte [] expectedChecksum, out byte [] pdbId); + + CollectionAssert.AreEqual (expectedChecksum, checksum); + } + } + + [Test] + public void WritePortablePdbChecksum () + { + const string resource = "PdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + GetPdbChecksumData (module.GetDebugHeader (), out string algorithmName, out byte [] checksum); + Assert.AreEqual ("SHA256", algorithmName); + + string pdbPath = GetDebugHeaderPdbPath (module); + CalculatePdbChecksumAndId (pdbPath, out byte [] expectedChecksum, out byte [] pdbId); + + CollectionAssert.AreEqual (expectedChecksum, checksum); + } + } + + [Test] + public void WritePortablePdbToWriteOnlyStream () + { + const string resource = "PdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + // Note that the module stream already requires read access even on writing to be able to compute strong name + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) + using (var pdbStream = new FileStream (destination + ".pdb", FileMode.Create, FileAccess.Write)) { + module.Write (destination, new WriterParameters { + DeterministicMvid = true, + WriteSymbols = true, + SymbolWriterProvider = new PortablePdbWriterProvider (), + SymbolStream = pdbStream + }); + } + } + + [Test] + public void DoubleWritePortablePdbDeterministicPdbId () + { + const string resource = "PdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + byte [] pdbIdOne; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + string pdbPath = GetDebugHeaderPdbPath (module); + CalculatePdbChecksumAndId (pdbPath, out byte [] expectedChecksum, out pdbIdOne); + } + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + byte [] pdbIdTwo; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + string pdbPath = GetDebugHeaderPdbPath (module); + CalculatePdbChecksumAndId (pdbPath, out byte [] expectedChecksum, out pdbIdTwo); + } + + CollectionAssert.AreEqual (pdbIdOne, pdbIdTwo); + } + + [Test] + public void WriteEmbeddedPortablePdbChecksum () + { + const string resource = "EmbeddedPdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + var debugHeader = module.GetDebugHeader (); + GetPdbChecksumData (debugHeader, out string algorithmName, out byte [] checksum); + Assert.AreEqual ("SHA256", algorithmName); + + GetEmbeddedPdb (debugHeader, out byte [] embeddedPdb); + CalculatePdbChecksumAndId (embeddedPdb, out byte [] expectedChecksum, out byte [] pdbId); + + CollectionAssert.AreEqual (expectedChecksum, checksum); + } + } + + [Test] + public void DoubleWriteEmbeddedPortablePdbChecksum () + { + const string resource = "EmbeddedPdbChecksumLib.dll"; + string destination = Path.GetTempFileName (); + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + byte [] pdbIdOne; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + var debugHeader = module.GetDebugHeader (); + GetEmbeddedPdb (debugHeader, out byte [] embeddedPdb); + CalculatePdbChecksumAndId (embeddedPdb, out byte [] expectedChecksum, out pdbIdOne); + } + + using (var module = GetResourceModule (resource, new ReaderParameters { ReadSymbols = true })) { + module.Write (destination, new WriterParameters { DeterministicMvid = true, WriteSymbols = true }); + } + + byte [] pdbIdTwo; + using (var module = ModuleDefinition.ReadModule (destination, new ReaderParameters { ReadSymbols = true })) { + var debugHeader = module.GetDebugHeader (); + GetEmbeddedPdb (debugHeader, out byte [] embeddedPdb); + CalculatePdbChecksumAndId (embeddedPdb, out byte [] expectedChecksum, out pdbIdTwo); + } + + CollectionAssert.AreEqual (pdbIdOne, pdbIdTwo); + } + + private void GetEmbeddedPdb (ImageDebugHeader debugHeader, out byte [] embeddedPdb) + { + var entry = Mixin.GetEmbeddedPortablePdbEntry (debugHeader); + Assert.IsNotNull (entry); + + var compressed_stream = new MemoryStream (entry.Data); + var reader = new BinaryStreamReader (compressed_stream); + Assert.AreEqual (0x4244504D, reader.ReadInt32 ()); + var length = reader.ReadInt32 (); + var decompressed_stream = new MemoryStream (length); + + using (var deflate = new DeflateStream (compressed_stream, CompressionMode.Decompress, leaveOpen: true)) + deflate.CopyTo (decompressed_stream); + + embeddedPdb = decompressed_stream.ToArray (); + } + + private void GetPdbChecksumData (ImageDebugHeader debugHeader, out string algorithmName, out byte [] checksum) + { + var entry = Mixin.GetPdbChecksumEntry (debugHeader); + Assert.IsNotNull (entry); + + var length = Array.IndexOf (entry.Data, (byte)0, 0); + var bytes = new byte [length]; + Buffer.BlockCopy (entry.Data, 0, bytes, 0, length); + algorithmName = Encoding.UTF8.GetString (bytes); + int checksumSize = 0; + switch (algorithmName) { + case "SHA256": checksumSize = 32; break; + case "SHA384": checksumSize = 48; break; + case "SHA512": checksumSize = 64; break; + } + checksum = new byte [checksumSize]; + Buffer.BlockCopy (entry.Data, length + 1, checksum, 0, checksumSize); + } + + private void CalculatePdbChecksumAndId (string filePath, out byte [] pdbChecksum, out byte [] pdbId) + { + using (var fs = File.OpenRead (filePath)) + CalculatePdbChecksumAndId (fs, out pdbChecksum, out pdbId); + } + + private void CalculatePdbChecksumAndId (byte [] data, out byte [] pdbChecksum, out byte [] pdbId) + { + using (var pdb = new MemoryStream (data)) + CalculatePdbChecksumAndId (pdb, out pdbChecksum, out pdbId); + } + + private void CalculatePdbChecksumAndId (Stream pdbStream, out byte [] pdbChecksum, out byte [] pdbId) + { + // Get the offset of the PDB heap (this requires parsing several headers + // so it's easier to use the ImageReader directly for this) + Image image = ImageReader.ReadPortablePdb (new Disposable<Stream> (pdbStream, false), "test.pdb", out uint pdbHeapOffset); + pdbId = new byte [20]; + Array.Copy (image.PdbHeap.data, 0, pdbId, 0, 20); + + pdbStream.Seek (0, SeekOrigin.Begin); + byte [] rawBytes = pdbStream.ReadAll (); + + var bytes = new byte [rawBytes.Length]; + + Array.Copy (rawBytes, 0, bytes, 0, pdbHeapOffset); + + // Zero out the PDB ID (20 bytes) + for (int i = 0; i < 20; bytes [i + pdbHeapOffset] = 0, i++) ; + + Array.Copy (rawBytes, pdbHeapOffset + 20, bytes, pdbHeapOffset + 20, rawBytes.Length - pdbHeapOffset - 20); + + var sha256 = SHA256.Create (); + pdbChecksum = sha256.ComputeHash (bytes); + } } } diff --git a/Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll b/Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll Binary files differnew file mode 100644 index 0000000..2de939b --- /dev/null +++ b/Test/Resources/assemblies/EmbeddedPdbChecksumLib.dll diff --git a/Test/Resources/assemblies/PdbChecksumLib.dll b/Test/Resources/assemblies/PdbChecksumLib.dll Binary files differnew file mode 100644 index 0000000..9c28db7 --- /dev/null +++ b/Test/Resources/assemblies/PdbChecksumLib.dll diff --git a/Test/Resources/assemblies/PdbChecksumLib.pdb b/Test/Resources/assemblies/PdbChecksumLib.pdb Binary files differnew file mode 100644 index 0000000..81975e7 --- /dev/null +++ b/Test/Resources/assemblies/PdbChecksumLib.pdb diff --git a/symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs b/symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs index 01b6c7a..9a9166b 100644 --- a/symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs +++ b/symbols/pdb/Mono.Cecil.Pdb/NativePdbWriter.cs @@ -44,9 +44,15 @@ namespace Mono.Cecil.Pdb { public ImageDebugHeader GetDebugHeader () { + var entry_point = module.EntryPoint; + if (entry_point != null) + writer.SetUserEntryPoint (entry_point.MetadataToken.ToInt32 ()); + ImageDebugDirectory directory; var data = writer.GetDebugInfo (out directory); directory.TimeDateStamp = (int) module.timestamp; + + writer.Close (); return new ImageDebugHeader (new ImageDebugHeaderEntry (directory, data)); } @@ -255,11 +261,6 @@ namespace Mono.Cecil.Pdb { public void Dispose () { - var entry_point = module.EntryPoint; - if (entry_point != null) - writer.SetUserEntryPoint (entry_point.MetadataToken.ToInt32 ()); - - writer.Close (); } } |