Skip to content

Commit d92e70f

Browse files
lambdageekradical
andauthored
BrowserDebugProxy: unify debug metadata reading for PE and Webcil (#81099)
* DebugStore: factor common PE and Webcil reading logic * Move common logic to a MetadataDebugSummary class Also switch from cascade of 'if's to a 'switch' when looking at debug entries * Implement PDB checksum reader for WebcilReader * Move WebcilReader reflection to a helper; add lazy initialization Co-authored-by: Ankit Jain <[email protected]>
1 parent 4a156cc commit d92e70f

File tree

7 files changed

+284
-105
lines changed

7 files changed

+284
-105
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Immutable;
6+
using System.IO;
7+
using System.Reflection;
8+
using System.Runtime.InteropServices;
9+
10+
using System.Reflection.Metadata;
11+
using System.Reflection.PortableExecutable;
12+
13+
namespace Microsoft.NET.WebAssembly.Webcil;
14+
15+
16+
public sealed partial class WebcilReader
17+
{
18+
19+
// Helpers to call into System.Reflection.Metadata internals
20+
internal static class Reflection
21+
{
22+
private static readonly Lazy<MethodInfo> s_readUtf8NullTerminated = new Lazy<MethodInfo>(() =>
23+
{
24+
var mi = typeof(BlobReader).GetMethod("ReadUtf8NullTerminated", BindingFlags.NonPublic | BindingFlags.Instance);
25+
if (mi == null)
26+
{
27+
throw new InvalidOperationException("Could not find BlobReader.ReadUtf8NullTerminated");
28+
}
29+
return mi;
30+
});
31+
32+
internal static string? ReadUtf8NullTerminated(BlobReader reader) => (string?)s_readUtf8NullTerminated.Value.Invoke(reader, null);
33+
34+
private static readonly Lazy<ConstructorInfo> s_codeViewDebugDirectoryDataCtor = new Lazy<ConstructorInfo>(() =>
35+
{
36+
var types = new Type[] { typeof(Guid), typeof(int), typeof(string) };
37+
var mi = typeof(CodeViewDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
38+
if (mi == null)
39+
{
40+
throw new InvalidOperationException("Could not find CodeViewDebugDirectoryData constructor");
41+
}
42+
return mi;
43+
});
44+
45+
internal static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid guid, int age, string path) => (CodeViewDebugDirectoryData)s_codeViewDebugDirectoryDataCtor.Value.Invoke(new object[] { guid, age, path });
46+
47+
private static readonly Lazy<ConstructorInfo> s_pdbChecksumDebugDirectoryDataCtor = new Lazy<ConstructorInfo>(() =>
48+
{
49+
var types = new Type[] { typeof(string), typeof(ImmutableArray<byte>) };
50+
var mi = typeof(PdbChecksumDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
51+
if (mi == null)
52+
{
53+
throw new InvalidOperationException("Could not find PdbChecksumDebugDirectoryData constructor");
54+
}
55+
return mi;
56+
});
57+
internal static PdbChecksumDebugDirectoryData MakePdbChecksumDebugDirectoryData(string algorithmName, ImmutableArray<byte> checksum) => (PdbChecksumDebugDirectoryData)s_pdbChecksumDebugDirectoryDataCtor.Value.Invoke(new object[] { algorithmName, checksum });
58+
}
59+
}

src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
namespace Microsoft.NET.WebAssembly.Webcil;
1414

1515

16-
public sealed class WebcilReader : IDisposable
16+
public sealed partial class WebcilReader : IDisposable
1717
{
1818
// WISH:
1919
// This should be implemented in terms of System.Reflection.Internal.MemoryBlockProvider like the PEReader,
@@ -219,26 +219,11 @@ private static CodeViewDebugDirectoryData DecodeCodeViewDebugDirectoryData(BlobR
219219
return MakeCodeViewDebugDirectoryData(guid, age, path);
220220
}
221221

222-
private static string? ReadUtf8NullTerminated(BlobReader reader)
223-
{
224-
var mi = typeof(BlobReader).GetMethod("ReadUtf8NullTerminated", BindingFlags.NonPublic | BindingFlags.Instance);
225-
if (mi == null)
226-
{
227-
throw new InvalidOperationException("Could not find BlobReader.ReadUtf8NullTerminated");
228-
}
229-
return (string?)mi.Invoke(reader, null);
230-
}
222+
private static string? ReadUtf8NullTerminated(BlobReader reader) => Reflection.ReadUtf8NullTerminated(reader);
231223

232-
private static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid guid, int age, string path)
233-
{
234-
var types = new Type[] { typeof(Guid), typeof(int), typeof(string) };
235-
var mi = typeof(CodeViewDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null);
236-
if (mi == null)
237-
{
238-
throw new InvalidOperationException("Could not find CodeViewDebugDirectoryData constructor");
239-
}
240-
return (CodeViewDebugDirectoryData)mi.Invoke(new object[] { guid, age, path });
241-
}
224+
private static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid guid, int age, string path) => Reflection.MakeCodeViewDebugDirectoryData(guid, age, path);
225+
226+
private static PdbChecksumDebugDirectoryData MakePdbChecksumDebugDirectoryData(string algorithmName, ImmutableArray<byte> checksum) => Reflection.MakePdbChecksumDebugDirectoryData(algorithmName, checksum);
242227

243228
public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry)
244229
{
@@ -297,6 +282,44 @@ private static MetadataReaderProvider DecodeEmbeddedPortablePdbDirectoryData(Blo
297282

298283
}
299284

285+
public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry)
286+
{
287+
if (entry.Type != DebugDirectoryEntryType.PdbChecksum)
288+
{
289+
throw new ArgumentException($"expected debug directory entry type {nameof(DebugDirectoryEntryType.PdbChecksum)}", nameof(entry));
290+
}
291+
292+
var pos = entry.DataPointer;
293+
var buffer = new byte[entry.DataSize];
294+
if (_stream.Seek(pos, SeekOrigin.Begin) != pos)
295+
{
296+
throw new BadImageFormatException("Could not seek to CodeView debug directory data", nameof(_stream));
297+
}
298+
if (_stream.Read(buffer, 0, buffer.Length) != buffer.Length)
299+
{
300+
throw new BadImageFormatException("Could not read CodeView debug directory data", nameof(_stream));
301+
}
302+
unsafe
303+
{
304+
fixed (byte* p = buffer)
305+
{
306+
return DecodePdbChecksumDebugDirectoryData(new BlobReader(p, buffer.Length));
307+
}
308+
}
309+
}
310+
311+
private static PdbChecksumDebugDirectoryData DecodePdbChecksumDebugDirectoryData(BlobReader reader)
312+
{
313+
var algorithmName = ReadUtf8NullTerminated(reader);
314+
byte[]? checksum = reader.ReadBytes(reader.RemainingBytes);
315+
if (string.IsNullOrEmpty(algorithmName) || checksum == null || checksum.Length == 0)
316+
{
317+
throw new BadImageFormatException("Invalid PdbChecksum data format");
318+
}
319+
320+
return MakePdbChecksumDebugDirectoryData(algorithmName, ImmutableArray.Create(checksum));
321+
}
322+
300323
private long TranslateRVA(uint rva)
301324
{
302325
if (_sections == null)

src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs

Lines changed: 13 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -894,100 +894,27 @@ private AssemblyInfo(ILogger logger)
894894
this.id = Interlocked.Increment(ref next_id);
895895
this.logger = logger;
896896
}
897-
898897
private static AssemblyInfo FromPEReader(MonoProxy monoProxy, SessionId sessionId, PEReader peReader, byte[] pdb, ILogger logger, CancellationToken token)
899898
{
900-
var entries = peReader.ReadDebugDirectory();
901-
CodeViewDebugDirectoryData? codeViewData = null;
902-
var isPortableCodeView = false;
903-
List<PdbChecksum> pdbChecksums = new();
904-
foreach (var entry in peReader.ReadDebugDirectory())
905-
{
906-
if (entry.Type == DebugDirectoryEntryType.CodeView)
907-
{
908-
codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
909-
if (entry.IsPortableCodeView)
910-
isPortableCodeView = true;
911-
}
912-
if (entry.Type == DebugDirectoryEntryType.PdbChecksum)
913-
{
914-
var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
915-
pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray()));
916-
}
917-
}
899+
900+
var debugProvider = new PortableExecutableDebugMetadataProvider(peReader);
901+
918902
var asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader);
919903
string name = ReadAssemblyName(asmMetadataReader);
904+
var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token);
920905

921-
MetadataReader pdbMetadataReader = null;
922-
if (pdb != null)
923-
{
924-
var pdbStream = new MemoryStream(pdb);
925-
try
926-
{
927-
// MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream
928-
pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
929-
}
930-
catch (BadImageFormatException)
931-
{
932-
monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token);
933-
}
934-
}
935-
else
936-
{
937-
var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb);
938-
if (embeddedPdbEntry.DataSize != 0)
939-
{
940-
pdbMetadataReader = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader();
941-
}
942-
}
943-
944-
var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger);
906+
var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, summary, logger);
945907
return assemblyInfo;
946908
}
947-
948909
private static AssemblyInfo FromWebcilReader(MonoProxy monoProxy, SessionId sessionId, WebcilReader wcReader, byte[] pdb, ILogger logger, CancellationToken token)
949910
{
950-
var entries = wcReader.ReadDebugDirectory();
951-
CodeViewDebugDirectoryData? codeViewData = null;
952-
var isPortableCodeView = false;
953-
List<PdbChecksum> pdbChecksums = new();
954-
foreach (var entry in entries)
955-
{
956-
var codeView = entries[0];
957-
if (codeView.Type == DebugDirectoryEntryType.CodeView)
958-
{
959-
codeViewData = wcReader.ReadCodeViewDebugDirectoryData(codeView);
960-
if (codeView.IsPortableCodeView)
961-
isPortableCodeView = true;
962-
}
963-
}
911+
var debugProvider = new WebcilDebugMetadataProvider(wcReader);
964912
var asmMetadataReader = wcReader.GetMetadataReader();
965913
string name = ReadAssemblyName(asmMetadataReader);
966914

967-
MetadataReader pdbMetadataReader = null;
968-
if (pdb != null)
969-
{
970-
var pdbStream = new MemoryStream(pdb);
971-
try
972-
{
973-
// MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream
974-
pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
975-
}
976-
catch (BadImageFormatException)
977-
{
978-
monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token);
979-
}
980-
}
981-
else
982-
{
983-
var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb);
984-
if (embeddedPdbEntry.DataSize != 0)
985-
{
986-
pdbMetadataReader = wcReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader();
987-
}
988-
}
915+
var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token);
989916

990-
var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger);
917+
var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, summary, logger);
991918
return assemblyInfo;
992919
}
993920

@@ -997,23 +924,24 @@ private static string ReadAssemblyName(MetadataReader asmMetadataReader)
997924
return asmDef.GetAssemblyName().Name + ".dll";
998925
}
999926

1000-
private unsafe AssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, CodeViewDebugDirectoryData? codeViewData, PdbChecksum[] pdbChecksums, bool isPortableCodeView, MetadataReader pdbMetadataReader, ILogger logger)
927+
private unsafe AssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, MetadataDebugSummary summary, ILogger logger)
1001928
: this(logger)
1002929
{
1003930
peReaderOrWebcilReader = owningReader;
931+
var codeViewData = summary.CodeViewData;
1004932
if (codeViewData != null)
1005933
{
1006934
PdbAge = codeViewData.Value.Age;
1007935
PdbGuid = codeViewData.Value.Guid;
1008936
PdbName = codeViewData.Value.Path;
1009937
CodeViewInformationAvailable = true;
1010938
}
1011-
IsPortableCodeView = isPortableCodeView;
1012-
PdbChecksums = pdbChecksums;
939+
IsPortableCodeView = summary.IsPortableCodeView;
940+
PdbChecksums = summary.PdbChecksums;
1013941
this.asmMetadataReader = asmMetadataReader;
1014942
Name = name;
1015943
logger.LogTrace($"Info: loading AssemblyInfo with name {Name}");
1016-
this.pdbMetadataReader = pdbMetadataReader;
944+
this.pdbMetadataReader = summary.PdbMetadataReader;
1017945
Populate();
1018946
}
1019947

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
using System;
7+
using System.Collections.Immutable;
8+
using System.Reflection.Metadata;
9+
using System.Reflection.PortableExecutable;
10+
11+
namespace Microsoft.WebAssembly.Diagnostics;
12+
13+
/// <summary>
14+
/// An adapter on top of MetadataReader and WebcilReader for DebugStore compensating
15+
/// for the lack of a common base class on those two types.
16+
/// </summary>
17+
public interface IDebugMetadataProvider
18+
{
19+
public ImmutableArray<DebugDirectoryEntry> ReadDebugDirectory();
20+
public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry);
21+
public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry);
22+
23+
public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry);
24+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Collections.Immutable;
9+
using System.IO;
10+
using System.Linq;
11+
using System.Reflection.Metadata;
12+
using System.Reflection.PortableExecutable;
13+
using System.Threading;
14+
using Microsoft.FileFormats.PE;
15+
16+
namespace Microsoft.WebAssembly.Diagnostics;
17+
18+
/// <summary>
19+
/// Information we can extract directly from the assembly image using metadata readers
20+
/// </summary>
21+
internal sealed class MetadataDebugSummary
22+
{
23+
internal MetadataReader? PdbMetadataReader { get; private init; }
24+
internal bool IsPortableCodeView { get; private init; }
25+
internal PdbChecksum[] PdbChecksums { get; private init; }
26+
27+
internal CodeViewDebugDirectoryData? CodeViewData { get; private init; }
28+
29+
private MetadataDebugSummary(MetadataReader? pdbMetadataReader, bool isPortableCodeView, PdbChecksum[] pdbChecksums, CodeViewDebugDirectoryData? codeViewData)
30+
{
31+
PdbMetadataReader = pdbMetadataReader;
32+
IsPortableCodeView = isPortableCodeView;
33+
PdbChecksums = pdbChecksums;
34+
CodeViewData = codeViewData;
35+
}
36+
37+
internal static MetadataDebugSummary Create(MonoProxy monoProxy, SessionId sessionId, string name, IDebugMetadataProvider provider, byte[]? pdb, CancellationToken token)
38+
{
39+
var entries = provider.ReadDebugDirectory();
40+
CodeViewDebugDirectoryData? codeViewData = null;
41+
bool isPortableCodeView = false;
42+
List<PdbChecksum> pdbChecksums = new();
43+
DebugDirectoryEntry? embeddedPdbEntry = null;
44+
foreach (var entry in entries)
45+
{
46+
switch (entry.Type)
47+
{
48+
case DebugDirectoryEntryType.CodeView:
49+
codeViewData = provider.ReadCodeViewDebugDirectoryData(entry);
50+
if (entry.IsPortableCodeView)
51+
isPortableCodeView = true;
52+
break;
53+
case DebugDirectoryEntryType.PdbChecksum:
54+
var checksum = provider.ReadPdbChecksumDebugDirectoryData(entry);
55+
pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray()));
56+
break;
57+
case DebugDirectoryEntryType.EmbeddedPortablePdb:
58+
embeddedPdbEntry = entry;
59+
break;
60+
default:
61+
break;
62+
}
63+
}
64+
65+
MetadataReader? pdbMetadataReader = null;
66+
if (pdb != null)
67+
{
68+
var pdbStream = new MemoryStream(pdb);
69+
try
70+
{
71+
// MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream
72+
pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
73+
}
74+
catch (BadImageFormatException)
75+
{
76+
monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token);
77+
}
78+
}
79+
else
80+
{
81+
if (embeddedPdbEntry != null && embeddedPdbEntry.Value.DataSize != 0)
82+
{
83+
pdbMetadataReader = provider.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry.Value).GetMetadataReader();
84+
}
85+
}
86+
87+
return new MetadataDebugSummary(pdbMetadataReader, isPortableCodeView, pdbChecksums.ToArray(), codeViewData);
88+
}
89+
}

0 commit comments

Comments
 (0)