Skip to content

Commit ed276e7

Browse files
sbomerjtschusteragocke
authored
Preserve custom debug information on types (#185)
Portable PDBs may have CustomDebugInformation on many metadata entities (see HasCustomDebugInformation in https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#customdebuginformation-table-0x37). For a select few of the CustomDebugInformation kinds (for example state machine hoisted local scopes), cecil has dedicated types to represent these in the object model. For the rest, cecil just reads the custom debug info out of the blob heap as a `byte[]`. When writing back a module, cecil walks the metadata as represented in its object model, building the debug information as it goes. So to support custom debug information for a new metadata token type: - the corresponding cecil type should be made to implement `ICustomDebugInformationProvider`, - the backing data for these getters should be populated on-demand, and also by the top-down walk in the immediate module reader, and - the top-down walk of metadata for writing should visit the cecil object and write its debug information. This change implements the above for `TypeDefinition`. It extends the `ISymbolReader`/`ISymbolWriter` interfaces with a new method for reading/writing custom debug info for any `ICustomDebugInformationProvider`, and provides helpers for calling the symbol reader that can be used when adding custom debug information to other types in the future. It doesn't include support for decoding the `TypeDefinitionDocument` debug info - it continues representing these as `BinaryCustomDebugInformation`. --------- Co-authored-by: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Co-authored-by: Andy Gocke <angocke@microsoft.com>
1 parent a5915a2 commit ed276e7

File tree

15 files changed

+200
-51
lines changed

15 files changed

+200
-51
lines changed

Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\cecil.snk</AssemblyOriginatorKeyFile>
1818
<DefineConstants>$(DefineConstants);NET_CORE</DefineConstants>
1919
<RootNamespace></RootNamespace>
20+
<LangVersion>latest</LangVersion>
2021
</PropertyGroup>
2122
<PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
2223
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>

Mono.Cecil.Cil/PortablePdb.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using System.IO;
1414
using System.IO.Compression;
1515
using System.Security.Cryptography;
16+
using Mono.Collections.Generic;
1617
using Mono.Cecil.Metadata;
1718
using Mono.Cecil.PE;
1819

@@ -145,6 +146,11 @@ void ReadStateMachineKickOffMethod (MethodDebugInformation method_info)
145146
method_info.kickoff_method = debug_reader.ReadStateMachineKickoffMethod (method_info.method);
146147
}
147148

149+
public Collection<CustomDebugInformation> Read (ICustomDebugInformationProvider provider)
150+
{
151+
return debug_reader.GetCustomDebugInformation (provider);
152+
}
153+
148154
void ReadCustomDebugInformations (MethodDebugInformation info)
149155
{
150156
info.method.custom_infos = debug_reader.GetCustomDebugInformation (info.method);
@@ -221,6 +227,11 @@ public MethodDebugInformation Read (MethodDefinition method)
221227
return reader.Read (method);
222228
}
223229

230+
public Collection<CustomDebugInformation> Read (ICustomDebugInformationProvider provider)
231+
{
232+
return reader.Read (provider);
233+
}
234+
224235
public void Dispose ()
225236
{
226237
reader.Dispose ();
@@ -319,6 +330,11 @@ public void Write ()
319330
}
320331
}
321332

333+
public void Write (ICustomDebugInformationProvider provider)
334+
{
335+
pdb_metadata.AddCustomDebugInformations (provider);
336+
}
337+
322338
public ImageDebugHeader GetDebugHeader ()
323339
{
324340
if (IsEmbedded)
@@ -519,6 +535,11 @@ public void Write (MethodDebugInformation info)
519535
writer.Write (info);
520536
}
521537

538+
public void Write (ICustomDebugInformationProvider provider)
539+
{
540+
writer.Write (provider);
541+
}
542+
522543
public ImageDebugHeader GetDebugHeader ()
523544
{
524545
ImageDebugHeader pdbDebugHeader = writer.GetDebugHeader ();

Mono.Cecil.Cil/Symbols.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ public interface ISymbolReader : IDisposable {
854854
ISymbolWriterProvider GetWriterProvider ();
855855
bool ProcessDebugHeader (ImageDebugHeader header);
856856
MethodDebugInformation Read (MethodDefinition method);
857+
Collection<CustomDebugInformation> Read (ICustomDebugInformationProvider provider);
857858
}
858859

859860
public interface ISymbolReaderProvider {
@@ -1116,6 +1117,7 @@ public interface ISymbolWriter : IDisposable {
11161117
ImageDebugHeader GetDebugHeader ();
11171118
void Write (MethodDebugInformation info);
11181119
void Write ();
1120+
void Write (ICustomDebugInformationProvider provider);
11191121
}
11201122

11211123
public interface ISymbolWriterProvider {
@@ -1224,5 +1226,40 @@ public static bool IsPortablePdb (Stream stream)
12241226
stream.Position = position;
12251227
}
12261228
}
1229+
1230+
public static bool GetHasCustomDebugInformations (
1231+
this ICustomDebugInformationProvider self,
1232+
ref Collection<CustomDebugInformation> collection,
1233+
ModuleDefinition module)
1234+
{
1235+
if (module.HasImage ()) {
1236+
module.Read (ref collection, self, static (provider, reader) => {
1237+
var symbol_reader = reader.module.symbol_reader;
1238+
if (symbol_reader != null)
1239+
return symbol_reader.Read (provider);
1240+
return null;
1241+
});
1242+
}
1243+
1244+
return !collection.IsNullOrEmpty ();
1245+
}
1246+
1247+
public static Collection<CustomDebugInformation> GetCustomDebugInformations (
1248+
this ICustomDebugInformationProvider self,
1249+
ref Collection<CustomDebugInformation> collection,
1250+
ModuleDefinition module)
1251+
{
1252+
if (module.HasImage ()) {
1253+
module.Read (ref collection, self, static (provider, reader) => {
1254+
var symbol_reader = reader.module.symbol_reader;
1255+
if (symbol_reader != null)
1256+
return symbol_reader.Read (provider);
1257+
return null;
1258+
});
1259+
}
1260+
1261+
Interlocked.CompareExchange (ref collection, new Collection<CustomDebugInformation> (), null);
1262+
return collection;
1263+
}
12271264
}
12281265
}

Mono.Cecil/AssemblyReader.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ void ReadTypesSymbols (Collection<TypeDefinition> types, ISymbolReader symbol_re
404404
{
405405
for (int i = 0; i < types.Count; i++) {
406406
var type = types [i];
407+
type.custom_infos = symbol_reader.Read (type);
407408

408409
if (type.HasNestedTypes)
409410
ReadTypesSymbols (type.NestedTypes, symbol_reader);
@@ -3160,6 +3161,17 @@ void InitializeCustomDebugInformations ()
31603161
}
31613162
}
31623163

3164+
public bool HasCustomDebugInformation (ICustomDebugInformationProvider provider)
3165+
{
3166+
InitializeCustomDebugInformations ();
3167+
3168+
Row<Guid, uint, uint> [] rows;
3169+
if (!metadata.CustomDebugInformations.TryGetValue (provider.MetadataToken, out rows))
3170+
return false;
3171+
3172+
return rows.Length > 0;
3173+
}
3174+
31633175
public Collection<CustomDebugInformation> GetCustomDebugInformation (ICustomDebugInformationProvider provider)
31643176
{
31653177
InitializeCustomDebugInformations ();

Mono.Cecil/AssemblyWriter.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,9 @@ void AddType (TypeDefinition type)
14931493
if (type.HasNestedTypes)
14941494
AddNestedTypes (type);
14951495

1496+
if (symbol_writer != null && type.HasCustomDebugInformations)
1497+
symbol_writer.Write (type);
1498+
14961499
WindowsRuntimeProjections.ApplyProjection (type, treatment);
14971500
}
14981501

Mono.Cecil/TypeDefinition.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@
1010

1111
using System;
1212
using System.Threading;
13+
using Mono.Cecil.Cil;
1314
using Mono.Cecil.Metadata;
1415
using Mono.Collections.Generic;
1516

1617
namespace Mono.Cecil {
1718

18-
public sealed class TypeDefinition : TypeReference, IMemberDefinition, ISecurityDeclarationProvider {
19+
public sealed class TypeDefinition : TypeReference, IMemberDefinition, ISecurityDeclarationProvider, ICustomDebugInformationProvider {
1920

2021
uint attributes;
2122
TypeReference base_type;
@@ -34,6 +35,8 @@ public sealed class TypeDefinition : TypeReference, IMemberDefinition, ISecurity
3435
Collection<CustomAttribute> custom_attributes;
3536
Collection<SecurityDeclaration> security_declarations;
3637

38+
internal Collection<CustomDebugInformation> custom_infos;
39+
3740
public TypeAttributes Attributes {
3841
get { return (TypeAttributes) attributes; }
3942
set {
@@ -284,6 +287,19 @@ public override Collection<GenericParameter> GenericParameters {
284287
get { return generic_parameters ?? (this.GetGenericParameters (ref generic_parameters, Module)); }
285288
}
286289

290+
public bool HasCustomDebugInformations {
291+
get {
292+
if (custom_infos != null)
293+
return custom_infos.Count > 0;
294+
295+
return this.GetHasCustomDebugInformations (ref custom_infos, Module);
296+
}
297+
}
298+
299+
public Collection<CustomDebugInformation> CustomDebugInformations {
300+
get { return custom_infos ?? (this.GetCustomDebugInformations (ref custom_infos, module)); }
301+
}
302+
287303
#region TypeAttributes
288304

289305
public bool IsNotPublic {

Test/Mono.Cecil.Tests/BaseTestFixture.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.IO;
33
using System.Runtime.CompilerServices;
44
using Mono.Cecil.Cil;
5+
using Mono.Cecil.Pdb;
56
using NUnit.Framework;
67

78
using Mono.Cecil.PE;
@@ -148,6 +149,54 @@ static void Run (TestCase testCase)
148149
using (var runner = new TestRunner (testCase, TestCaseType.WriteFromImmediate))
149150
runner.RunTest ();
150151
}
152+
153+
public enum RoundtripType {
154+
None,
155+
Pdb,
156+
PortablePdb
157+
}
158+
159+
protected static ModuleDefinition RoundtripModule(ModuleDefinition module, RoundtripType roundtripType)
160+
{
161+
if (roundtripType == RoundtripType.None)
162+
return module;
163+
164+
var file = Path.Combine (Path.GetTempPath (), "RoundtripModule.dll");
165+
if (File.Exists (file))
166+
File.Delete (file);
167+
168+
ISymbolWriterProvider symbolWriterProvider;
169+
switch (roundtripType) {
170+
case RoundtripType.Pdb when Platform.HasNativePdbSupport:
171+
symbolWriterProvider = new PdbWriterProvider ();
172+
break;
173+
case RoundtripType.PortablePdb:
174+
default:
175+
symbolWriterProvider = new PortablePdbWriterProvider ();
176+
break;
177+
}
178+
179+
module.Write (file, new WriterParameters {
180+
SymbolWriterProvider = symbolWriterProvider,
181+
});
182+
module.Dispose ();
183+
184+
ISymbolReaderProvider symbolReaderProvider;
185+
switch (roundtripType) {
186+
case RoundtripType.Pdb when Platform.HasNativePdbSupport:
187+
symbolReaderProvider = new PdbReaderProvider ();
188+
break;
189+
case RoundtripType.PortablePdb:
190+
default:
191+
symbolReaderProvider = new PortablePdbReaderProvider ();
192+
break;
193+
}
194+
195+
return ModuleDefinition.ReadModule (file, new ReaderParameters {
196+
SymbolReaderProvider = symbolReaderProvider,
197+
InMemory = true
198+
});
199+
}
151200
}
152201

153202
abstract class TestCase {

Test/Mono.Cecil.Tests/ILProcessorTests.cs

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
using System.IO;
44
using System.Linq;
55

6-
using Mono.Cecil;
76
using Mono.Cecil.Cil;
87
using Mono.Cecil.Mdb;
9-
using Mono.Cecil.Pdb;
108
using NUnit.Framework;
119

1210
namespace Mono.Cecil.Tests {
@@ -499,12 +497,6 @@ static MethodBody CreateTestMethodWithLocalScopes (RoundtripType roundtripType,
499497
return methodBody;
500498
}
501499

502-
public enum RoundtripType {
503-
None,
504-
Pdb,
505-
PortablePdb
506-
}
507-
508500
static MethodBody RoundtripMethodBody(MethodBody methodBody, RoundtripType roundtripType, bool forceUnresolvedScopes = false, bool reverseScopeOrder = false)
509501
{
510502
var newModule = RoundtripModule (methodBody.Method.Module, roundtripType);
@@ -540,47 +532,5 @@ static void ReverseScopeOrder(ScopeDebugInformation scope)
540532
foreach (var subScope in scope.Scopes)
541533
ReverseScopeOrder (subScope);
542534
}
543-
544-
static ModuleDefinition RoundtripModule(ModuleDefinition module, RoundtripType roundtripType)
545-
{
546-
if (roundtripType == RoundtripType.None)
547-
return module;
548-
549-
var file = Path.Combine (Path.GetTempPath (), "TestILProcessor.dll");
550-
if (File.Exists (file))
551-
File.Delete (file);
552-
553-
ISymbolWriterProvider symbolWriterProvider;
554-
switch (roundtripType) {
555-
case RoundtripType.Pdb when Platform.HasNativePdbSupport:
556-
symbolWriterProvider = new PdbWriterProvider ();
557-
break;
558-
case RoundtripType.PortablePdb:
559-
default:
560-
symbolWriterProvider = new PortablePdbWriterProvider ();
561-
break;
562-
}
563-
564-
module.Write (file, new WriterParameters {
565-
SymbolWriterProvider = symbolWriterProvider,
566-
});
567-
module.Dispose ();
568-
569-
ISymbolReaderProvider symbolReaderProvider;
570-
switch (roundtripType) {
571-
case RoundtripType.Pdb when Platform.HasNativePdbSupport:
572-
symbolReaderProvider = new PdbReaderProvider ();
573-
break;
574-
case RoundtripType.PortablePdb:
575-
default:
576-
symbolReaderProvider = new PortablePdbReaderProvider ();
577-
break;
578-
}
579-
580-
return ModuleDefinition.ReadModule (file, new ReaderParameters {
581-
SymbolReaderProvider = symbolReaderProvider,
582-
InMemory = true
583-
});
584-
}
585535
}
586536
}

Test/Mono.Cecil.Tests/PortablePdbTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,43 @@ public void PortablePdbLineInfo()
646646
}, symbolReaderProvider: typeof (PortablePdbReaderProvider), symbolWriterProvider: typeof (PortablePdbWriterProvider));
647647
}
648648

649+
[Test]
650+
public void TypeDefinitionDebugInformation ()
651+
{
652+
TestModule ("TypeDefinitionDebugInformation.dll", module => {
653+
var enum_type = module.GetType ("TypeDefinitionDebugInformation.Enum");
654+
Assert.IsTrue (enum_type.HasCustomDebugInformations);
655+
var binary_custom_debug_info = enum_type.CustomDebugInformations.OfType<BinaryCustomDebugInformation> ().FirstOrDefault ();
656+
Assert.IsNotNull (binary_custom_debug_info);
657+
Assert.AreEqual (new Guid ("932E74BC-DBA9-4478-8D46-0F32A7BAB3D3"), binary_custom_debug_info.Identifier);
658+
Assert.AreEqual (new byte [] { 0x1 }, binary_custom_debug_info.Data);
659+
660+
var interface_type = module.GetType ("TypeDefinitionDebugInformation.Interface");
661+
Assert.IsTrue (interface_type.HasCustomDebugInformations);
662+
binary_custom_debug_info = interface_type.CustomDebugInformations.OfType<BinaryCustomDebugInformation> ().FirstOrDefault ();
663+
Assert.IsNotNull (binary_custom_debug_info);
664+
Assert.AreEqual (new Guid ("932E74BC-DBA9-4478-8D46-0F32A7BAB3D3"), binary_custom_debug_info.Identifier);
665+
Assert.AreEqual (new byte [] { 0x1 }, binary_custom_debug_info.Data);
666+
}, symbolReaderProvider: typeof (PortablePdbReaderProvider), symbolWriterProvider: typeof (PortablePdbWriterProvider));
667+
}
668+
669+
[Test]
670+
public void ModifyTypeDefinitionDebugInformation ()
671+
{
672+
using (var module = GetResourceModule ("TypeDefinitionDebugInformation.dll", new ReaderParameters { SymbolReaderProvider = new PortablePdbReaderProvider () })) {
673+
var enum_type = module.GetType ("TypeDefinitionDebugInformation.Enum");
674+
var binary_custom_debug_info = enum_type.CustomDebugInformations.OfType<BinaryCustomDebugInformation> ().FirstOrDefault ();
675+
Assert.AreEqual (new byte [] { 0x1 }, binary_custom_debug_info.Data);
676+
binary_custom_debug_info.Data = new byte [] { 0x2 };
677+
678+
var outputModule = RoundtripModule (module, RoundtripType.None);
679+
enum_type = outputModule.GetType ("TypeDefinitionDebugInformation.Enum");
680+
binary_custom_debug_info = enum_type.CustomDebugInformations.OfType<BinaryCustomDebugInformation> ().FirstOrDefault ();
681+
Assert.IsNotNull (binary_custom_debug_info);
682+
Assert.AreEqual (new byte [] { 0x2 }, binary_custom_debug_info.Data);
683+
}
684+
}
685+
649686
public sealed class SymbolWriterProvider : ISymbolWriterProvider {
650687

651688
readonly DefaultSymbolWriterProvider writer_provider = new DefaultSymbolWriterProvider ();
@@ -730,6 +767,11 @@ public void Write ()
730767
symbol_writer.Write ();
731768
}
732769

770+
public void Write (ICustomDebugInformationProvider provider)
771+
{
772+
symbol_writer.Write (provider);
773+
}
774+
733775
public void Dispose ()
734776
{
735777
symbol_writer.Dispose ();
4.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)