Skip to content

Commit 79b43e8

Browse files
authored
Fix deterministic MVID and add PdbChecksum (#810)
* Fix deterministic MVID and add PdbChecksum (#31) * Fix how pdb path is calculated in the tests * Fix portable PDB stamp in CodeView header (#32) * Introduce ISymbolWriter.Write This mostly cleans up the code to make it easier to understand. `ISymbolWriter.GetDebugHeader` no longer actually writes the symbols, there's a new `Write` method for just that. The assembly writer calls `Write` first and then the image writer calls `GetDebugHeader` when it's needed. This is partially taken from #617.
1 parent 8b593d5 commit 79b43e8

File tree

13 files changed

+542
-90
lines changed

13 files changed

+542
-90
lines changed

Mono.Cecil.Cil/PortablePdb.cs

+138-51
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using System.Collections.Generic;
1313
using System.IO;
1414
using System.IO.Compression;
15-
15+
using System.Security.Cryptography;
1616
using Mono.Cecil.Metadata;
1717
using Mono.Cecil.PE;
1818

@@ -39,7 +39,7 @@ public ISymbolReader GetSymbolReader (ModuleDefinition module, Stream symbolStre
3939

4040
ISymbolReader GetSymbolReader (ModuleDefinition module, Disposable<Stream> symbolStream, string fileName)
4141
{
42-
return new PortablePdbReader (ImageReader.ReadPortablePdb (symbolStream, fileName), module);
42+
return new PortablePdbReader (ImageReader.ReadPortablePdb (symbolStream, fileName, out _), module);
4343
}
4444
}
4545

@@ -234,24 +234,27 @@ public ISymbolWriter GetSymbolWriter (ModuleDefinition module, string fileName)
234234
Mixin.CheckModule (module);
235235
Mixin.CheckFileName (fileName);
236236

237-
var file = File.OpenWrite (Mixin.GetPdbFileName (fileName));
238-
return GetSymbolWriter (module, Disposable.Owned (file as Stream));
237+
var file = File.Open (Mixin.GetPdbFileName (fileName), FileMode.OpenOrCreate, FileAccess.ReadWrite);
238+
return GetSymbolWriter (module, Disposable.Owned (file as Stream), Disposable.NotOwned ((Stream)null));
239239
}
240240

241241
public ISymbolWriter GetSymbolWriter (ModuleDefinition module, Stream symbolStream)
242242
{
243243
Mixin.CheckModule (module);
244244
Mixin.CheckStream (symbolStream);
245245

246-
return GetSymbolWriter (module, Disposable.NotOwned (symbolStream));
246+
// In order to compute the PDB checksum, the stream we're writing to needs to be able to
247+
// seek and read as well. We can't assume this about a stream provided by the user.
248+
// So in this case, create a memory stream to cache the PDB.
249+
return GetSymbolWriter (module, Disposable.Owned (new MemoryStream() as Stream), Disposable.NotOwned (symbolStream));
247250
}
248251

249-
ISymbolWriter GetSymbolWriter (ModuleDefinition module, Disposable<Stream> stream)
252+
ISymbolWriter GetSymbolWriter (ModuleDefinition module, Disposable<Stream> stream, Disposable<Stream> final_stream)
250253
{
251254
var metadata = new MetadataBuilder (module, this);
252255
var writer = ImageWriter.CreateDebugWriter (module, metadata, stream);
253256

254-
return new PortablePdbWriter (metadata, module, writer);
257+
return new PortablePdbWriter (metadata, module, writer, final_stream);
255258
}
256259
}
257260

@@ -260,9 +263,14 @@ public sealed class PortablePdbWriter : ISymbolWriter {
260263
readonly MetadataBuilder pdb_metadata;
261264
readonly ModuleDefinition module;
262265
readonly ImageWriter writer;
266+
readonly Disposable<Stream> final_stream;
263267

264268
MetadataBuilder module_metadata;
265269

270+
internal byte [] pdb_checksum;
271+
internal Guid pdb_id_guid;
272+
internal uint pdb_id_stamp;
273+
266274
bool IsEmbedded { get { return writer == null; } }
267275

268276
internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module)
@@ -278,56 +286,100 @@ internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition modul
278286
pdb_metadata.AddCustomDebugInformations (module);
279287
}
280288

281-
internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module, ImageWriter writer)
289+
internal PortablePdbWriter (MetadataBuilder pdb_metadata, ModuleDefinition module, ImageWriter writer, Disposable<Stream> final_stream)
282290
: this (pdb_metadata, module)
283291
{
284292
this.writer = writer;
293+
this.final_stream = final_stream;
285294
}
286295

287296
public ISymbolReaderProvider GetReaderProvider ()
288297
{
289298
return new PortablePdbReaderProvider ();
290299
}
291300

301+
public void Write (MethodDebugInformation info)
302+
{
303+
CheckMethodDebugInformationTable ();
304+
305+
pdb_metadata.AddMethodDebugInformation (info);
306+
}
307+
308+
public void Write ()
309+
{
310+
if (IsEmbedded)
311+
return;
312+
313+
WritePdbFile ();
314+
315+
if (final_stream.value != null) {
316+
writer.BaseStream.Seek (0, SeekOrigin.Begin);
317+
var buffer = new byte [8192];
318+
CryptoService.CopyStreamChunk (writer.BaseStream, final_stream.value, buffer, (int)writer.BaseStream.Length);
319+
}
320+
}
321+
292322
public ImageDebugHeader GetDebugHeader ()
293323
{
294324
if (IsEmbedded)
295325
return new ImageDebugHeader ();
296326

297-
var directory = new ImageDebugDirectory () {
298-
MajorVersion = 256,
299-
MinorVersion = 20557,
300-
Type = ImageDebugType.CodeView,
301-
TimeDateStamp = (int) module.timestamp,
302-
};
303-
304-
var buffer = new ByteBuffer ();
305-
// RSDS
306-
buffer.WriteUInt32 (0x53445352);
307-
// Module ID
308-
buffer.WriteBytes (module.Mvid.ToByteArray ());
309-
// PDB Age
310-
buffer.WriteUInt32 (1);
311-
// PDB Path
312-
var fileName = writer.BaseStream.GetFileName ();
313-
if (string.IsNullOrEmpty (fileName)) {
314-
fileName = module.Assembly.Name.Name + ".pdb";
327+
ImageDebugHeaderEntry codeViewEntry;
328+
{
329+
var codeViewDirectory = new ImageDebugDirectory () {
330+
MajorVersion = 256,
331+
MinorVersion = 20557,
332+
Type = ImageDebugType.CodeView,
333+
TimeDateStamp = (int)pdb_id_stamp,
334+
};
335+
336+
var buffer = new ByteBuffer ();
337+
// RSDS
338+
buffer.WriteUInt32 (0x53445352);
339+
// Module ID
340+
buffer.WriteBytes (pdb_id_guid.ToByteArray ());
341+
// PDB Age
342+
buffer.WriteUInt32 (1);
343+
// PDB Path
344+
var fileName = writer.BaseStream.GetFileName ();
345+
if (string.IsNullOrEmpty (fileName)) {
346+
fileName = module.Assembly.Name.Name + ".pdb";
347+
}
348+
buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes (fileName));
349+
buffer.WriteByte (0);
350+
351+
var data = new byte [buffer.length];
352+
Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length);
353+
codeViewDirectory.SizeOfData = data.Length;
354+
355+
codeViewEntry = new ImageDebugHeaderEntry (codeViewDirectory, data);
315356
}
316-
buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes (fileName));
317-
buffer.WriteByte (0);
318357

319-
var data = new byte [buffer.length];
320-
Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length);
321-
directory.SizeOfData = data.Length;
358+
ImageDebugHeaderEntry pdbChecksumEntry;
359+
{
360+
var pdbChecksumDirectory = new ImageDebugDirectory () {
361+
MajorVersion = 1,
362+
MinorVersion = 0,
363+
Type = ImageDebugType.PdbChecksum,
364+
TimeDateStamp = 0
365+
};
322366

323-
return new ImageDebugHeader (new ImageDebugHeaderEntry (directory, data));
324-
}
367+
var buffer = new ByteBuffer ();
368+
// SHA256 - Algorithm name
369+
buffer.WriteBytes (System.Text.Encoding.UTF8.GetBytes ("SHA256"));
370+
buffer.WriteByte (0);
325371

326-
public void Write (MethodDebugInformation info)
327-
{
328-
CheckMethodDebugInformationTable ();
372+
// Checksum - 32 bytes
373+
buffer.WriteBytes (pdb_checksum);
329374

330-
pdb_metadata.AddMethodDebugInformation (info);
375+
var data = new byte [buffer.length];
376+
Buffer.BlockCopy (buffer.buffer, 0, data, 0, buffer.length);
377+
pdbChecksumDirectory.SizeOfData = data.Length;
378+
379+
pdbChecksumEntry = new ImageDebugHeaderEntry (pdbChecksumDirectory, data);
380+
}
381+
382+
return new ImageDebugHeader (new ImageDebugHeaderEntry [] { codeViewEntry, pdbChecksumEntry });
331383
}
332384

333385
void CheckMethodDebugInformationTable ()
@@ -343,10 +395,8 @@ void CheckMethodDebugInformationTable ()
343395

344396
public void Dispose ()
345397
{
346-
if (IsEmbedded)
347-
return;
348-
349-
WritePdbFile ();
398+
writer.stream.Dispose ();
399+
final_stream.Dispose ();
350400
}
351401

352402
void WritePdbFile ()
@@ -360,15 +410,18 @@ void WritePdbFile ()
360410
writer.WriteMetadata ();
361411

362412
writer.Flush ();
363-
writer.stream.Dispose ();
413+
414+
ComputeChecksumAndPdbId ();
415+
416+
WritePdbId ();
364417
}
365418

366419
void WritePdbHeap ()
367420
{
368421
var pdb_heap = pdb_metadata.pdb_heap;
369422

370-
pdb_heap.WriteBytes (module.Mvid.ToByteArray ());
371-
pdb_heap.WriteUInt32 (module_metadata.timestamp);
423+
// PDB ID ( GUID + TimeStamp ) are left zeroed out for now. Will be filled at the end with a hash.
424+
pdb_heap.WriteBytes (20);
372425

373426
pdb_heap.WriteUInt32 (module_metadata.entry_point.ToUInt32 ());
374427

@@ -399,6 +452,32 @@ void WriteTableHeap ()
399452
pdb_metadata.table_heap.ComputeTableInformations ();
400453
pdb_metadata.table_heap.WriteTableHeap ();
401454
}
455+
456+
void ComputeChecksumAndPdbId ()
457+
{
458+
var buffer = new byte [8192];
459+
460+
// Compute the has of the entire file - PDB ID is zeroes still
461+
writer.BaseStream.Seek (0, SeekOrigin.Begin);
462+
var sha256 = SHA256.Create ();
463+
using (var crypto_stream = new CryptoStream (Stream.Null, sha256, CryptoStreamMode.Write)) {
464+
CryptoService.CopyStreamChunk (writer.BaseStream, crypto_stream, buffer, (int)writer.BaseStream.Length);
465+
}
466+
467+
pdb_checksum = sha256.Hash;
468+
469+
var hashBytes = new ByteBuffer (pdb_checksum);
470+
pdb_id_guid = new Guid (hashBytes.ReadBytes (16));
471+
pdb_id_stamp = hashBytes.ReadUInt32 ();
472+
}
473+
474+
void WritePdbId ()
475+
{
476+
// PDB ID is the first 20 bytes of the PdbHeap
477+
writer.MoveToRVA (TextSegment.PdbHeap);
478+
writer.WriteBytes (pdb_id_guid.ToByteArray ());
479+
writer.WriteUInt32 (pdb_id_stamp);
480+
}
402481
}
403482

404483
public sealed class EmbeddedPortablePdbWriterProvider : ISymbolWriterProvider {
@@ -435,9 +514,14 @@ public ISymbolReaderProvider GetReaderProvider ()
435514
return new EmbeddedPortablePdbReaderProvider ();
436515
}
437516

517+
public void Write (MethodDebugInformation info)
518+
{
519+
writer.Write (info);
520+
}
521+
438522
public ImageDebugHeader GetDebugHeader ()
439523
{
440-
writer.Dispose ();
524+
ImageDebugHeader pdbDebugHeader = writer.GetDebugHeader ();
441525

442526
var directory = new ImageDebugDirectory {
443527
Type = ImageDebugType.EmbeddedPortablePdb,
@@ -462,19 +546,22 @@ public ImageDebugHeader GetDebugHeader ()
462546

463547
directory.SizeOfData = (int) data.Length;
464548

465-
return new ImageDebugHeader (new [] {
466-
writer.GetDebugHeader ().Entries [0],
467-
new ImageDebugHeaderEntry (directory, data.ToArray ())
468-
});
549+
var debugHeaderEntries = new ImageDebugHeaderEntry [pdbDebugHeader.Entries.Length + 1];
550+
for (int i = 0; i < pdbDebugHeader.Entries.Length; i++)
551+
debugHeaderEntries [i] = pdbDebugHeader.Entries [i];
552+
debugHeaderEntries [debugHeaderEntries.Length - 1] = new ImageDebugHeaderEntry (directory, data.ToArray ());
553+
554+
return new ImageDebugHeader (debugHeaderEntries);
469555
}
470556

471-
public void Write (MethodDebugInformation info)
557+
public void Write ()
472558
{
473-
writer.Write (info);
559+
writer.Write ();
474560
}
475561

476562
public void Dispose ()
477563
{
564+
writer.Dispose ();
478565
}
479566
}
480567

Mono.Cecil.Cil/Symbols.cs

+7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public enum ImageDebugType {
3939
CodeView = 2,
4040
Deterministic = 16,
4141
EmbeddedPortablePdb = 17,
42+
PdbChecksum = 19,
4243
}
4344

4445
public sealed class ImageDebugHeader {
@@ -1114,6 +1115,7 @@ public interface ISymbolWriter : IDisposable {
11141115
ISymbolReaderProvider GetReaderProvider ();
11151116
ImageDebugHeader GetDebugHeader ();
11161117
void Write (MethodDebugInformation info);
1118+
void Write ();
11171119
}
11181120

11191121
public interface ISymbolWriterProvider {
@@ -1174,6 +1176,11 @@ public static ImageDebugHeaderEntry GetEmbeddedPortablePdbEntry (this ImageDebug
11741176
return GetEntry (header, ImageDebugType.EmbeddedPortablePdb);
11751177
}
11761178

1179+
public static ImageDebugHeaderEntry GetPdbChecksumEntry (this ImageDebugHeader header)
1180+
{
1181+
return GetEntry (header, ImageDebugType.PdbChecksum);
1182+
}
1183+
11771184
private static ImageDebugHeaderEntry GetEntry (this ImageDebugHeader header, ImageDebugType type)
11781185
{
11791186
if (!header.HasEntries)

Mono.Cecil.PE/ImageReader.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ sealed class ImageReader : BinaryStreamReader {
2727
DataDirectory metadata;
2828

2929
uint table_heap_offset;
30+
uint pdb_heap_offset;
3031

3132
public ImageReader (Disposable<Stream> stream, string file_name)
3233
: base (stream.value)
@@ -400,6 +401,7 @@ void ReadMetadataStream (Section section)
400401
break;
401402
case "#Pdb":
402403
image.PdbHeap = new PdbHeap (data);
404+
pdb_heap_offset = offset;
403405
break;
404406
}
405407
}
@@ -768,7 +770,7 @@ public static Image ReadImage (Disposable<Stream> stream, string file_name)
768770
}
769771
}
770772

771-
public static Image ReadPortablePdb (Disposable<Stream> stream, string file_name)
773+
public static Image ReadPortablePdb (Disposable<Stream> stream, string file_name, out uint pdb_heap_offset)
772774
{
773775
try {
774776
var reader = new ImageReader (stream, file_name);
@@ -785,6 +787,7 @@ public static Image ReadPortablePdb (Disposable<Stream> stream, string file_name
785787

786788
reader.metadata = new DataDirectory (0, length);
787789
reader.ReadMetadata ();
790+
pdb_heap_offset = reader.pdb_heap_offset;
788791
return reader.image;
789792
} catch (EndOfStreamException e) {
790793
throw new BadImageFormatException (stream.value.GetFileName (), e);

0 commit comments

Comments
 (0)