From 9145ad69c491bbecaf01c2de75341f21bed7fedb Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 24 Jan 2023 11:39:56 -0500 Subject: [PATCH 1/7] DebugStore: factor common PE and Webcil reading logic --- .../src/Webcil/WebcilReader.cs | 2 + .../debugger/BrowserDebugProxy/DebugStore.cs | 75 +++++++------------ .../IDebugMetadataProvider.cs | 24 ++++++ ...PortableExecutableDebugMetadataProvider.cs | 27 +++++++ .../WebcilDebugMetadataProvider.cs | 29 +++++++ 5 files changed, 108 insertions(+), 49 deletions(-) create mode 100644 src/mono/wasm/debugger/BrowserDebugProxy/IDebugMetadataProvider.cs create mode 100644 src/mono/wasm/debugger/BrowserDebugProxy/PortableExecutableDebugMetadataProvider.cs create mode 100644 src/mono/wasm/debugger/BrowserDebugProxy/WebcilDebugMetadataProvider.cs diff --git a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs index 6782ebf4a5aae6..309c249b1a4a60 100644 --- a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs +++ b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs @@ -297,6 +297,8 @@ private static MetadataReaderProvider DecodeEmbeddedPortablePdbDirectoryData(Blo } + public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry) => throw new NotImplementedException("read pdb checksum"); + private long TranslateRVA(uint rva) { if (_sections == null) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 06ddcb08de6771..92aa0615ec47ba 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -897,28 +897,39 @@ private AssemblyInfo(ILogger logger) private static AssemblyInfo FromPEReader(MonoProxy monoProxy, SessionId sessionId, PEReader peReader, byte[] pdb, ILogger logger, CancellationToken token) { - var entries = peReader.ReadDebugDirectory(); - CodeViewDebugDirectoryData? codeViewData = null; - var isPortableCodeView = false; - List pdbChecksums = new(); - foreach (var entry in peReader.ReadDebugDirectory()) + + var debugProvider = new PortableExecutableDebugMetadataProvider(peReader); + + var asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader); + string name = ReadAssemblyName(asmMetadataReader); + ReadDebugEntries(monoProxy, sessionId, name, debugProvider, pdb, out var codeViewData, out var isPortableCodeView, out var pdbChecksums, out var pdbMetadataReader, token); + + var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger); + return assemblyInfo; + } + + private static void ReadDebugEntries(MonoProxy monoProxy, SessionId sessionId, string name, IDebugMetadataProvider provider, byte[] pdb, out CodeViewDebugDirectoryData? codeViewData, out bool isPortableCodeView, out List pdbChecksums, out MetadataReader pdbMetadataReader, CancellationToken token) + { + var entries = provider.ReadDebugDirectory(); + codeViewData = null; + isPortableCodeView = false; + pdbChecksums = new(); + foreach (var entry in entries) { if (entry.Type == DebugDirectoryEntryType.CodeView) { - codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); + codeViewData = provider.ReadCodeViewDebugDirectoryData(entry); if (entry.IsPortableCodeView) isPortableCodeView = true; } if (entry.Type == DebugDirectoryEntryType.PdbChecksum) { - var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry); + var checksum = provider.ReadPdbChecksumDebugDirectoryData(entry); pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray())); } } - var asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader); - string name = ReadAssemblyName(asmMetadataReader); - MetadataReader pdbMetadataReader = null; + pdbMetadataReader = null; if (pdb != null) { var pdbStream = new MemoryStream(pdb); @@ -937,55 +948,21 @@ private static AssemblyInfo FromPEReader(MonoProxy monoProxy, SessionId sessionI var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); if (embeddedPdbEntry.DataSize != 0) { - pdbMetadataReader = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader(); + pdbMetadataReader = provider.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader(); } } - var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger); - return assemblyInfo; + } private static AssemblyInfo FromWebcilReader(MonoProxy monoProxy, SessionId sessionId, WebcilReader wcReader, byte[] pdb, ILogger logger, CancellationToken token) { - var entries = wcReader.ReadDebugDirectory(); - CodeViewDebugDirectoryData? codeViewData = null; - var isPortableCodeView = false; - List pdbChecksums = new(); - foreach (var entry in entries) - { - var codeView = entries[0]; - if (codeView.Type == DebugDirectoryEntryType.CodeView) - { - codeViewData = wcReader.ReadCodeViewDebugDirectoryData(codeView); - if (codeView.IsPortableCodeView) - isPortableCodeView = true; - } - } + var debugProvider = new WebcilDebugMetadataProvider(wcReader); var asmMetadataReader = wcReader.GetMetadataReader(); string name = ReadAssemblyName(asmMetadataReader); - MetadataReader pdbMetadataReader = null; - if (pdb != null) - { - var pdbStream = new MemoryStream(pdb); - try - { - // MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream - pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); - } - catch (BadImageFormatException) - { - monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token); - } - } - else - { - var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); - if (embeddedPdbEntry.DataSize != 0) - { - pdbMetadataReader = wcReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader(); - } - } + ReadDebugEntries(monoProxy, sessionId, name, debugProvider, pdb, out var codeViewData, out var isPortableCodeView, out var pdbChecksums, out var pdbMetadataReader, token); + var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger); return assemblyInfo; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/IDebugMetadataProvider.cs b/src/mono/wasm/debugger/BrowserDebugProxy/IDebugMetadataProvider.cs new file mode 100644 index 00000000000000..413a85cf762c91 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/IDebugMetadataProvider.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; + +namespace Microsoft.WebAssembly.Diagnostics; + +/// +/// An adapter on top of MetadataReader and WebcilReader for DebugStore compensating +/// for the lack of a common base class on those two types. +/// +public interface IDebugMetadataProvider +{ + public ImmutableArray ReadDebugDirectory(); + public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry); + public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry); + + public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry); +} diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/PortableExecutableDebugMetadataProvider.cs b/src/mono/wasm/debugger/BrowserDebugProxy/PortableExecutableDebugMetadataProvider.cs new file mode 100644 index 00000000000000..94aebb35772d23 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/PortableExecutableDebugMetadataProvider.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; + +namespace Microsoft.WebAssembly.Diagnostics; + +public class PortableExecutableDebugMetadataProvider : IDebugMetadataProvider +{ + private readonly PEReader _peReader; + public PortableExecutableDebugMetadataProvider(PEReader peReader) + { + _peReader = peReader; + } + public ImmutableArray ReadDebugDirectory() => _peReader.ReadDebugDirectory(); + + public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry) => _peReader.ReadCodeViewDebugDirectoryData(entry); + + public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry) => _peReader.ReadPdbChecksumDebugDirectoryData(entry); + + public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry) => _peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry); +} diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/WebcilDebugMetadataProvider.cs b/src/mono/wasm/debugger/BrowserDebugProxy/WebcilDebugMetadataProvider.cs new file mode 100644 index 00000000000000..993bbb78957398 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/WebcilDebugMetadataProvider.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using Microsoft.NET.WebAssembly.Webcil; + +namespace Microsoft.WebAssembly.Diagnostics; + +public class WebcilDebugMetadataProvider : IDebugMetadataProvider +{ + private readonly WebcilReader _webcilReader; + + public WebcilDebugMetadataProvider(WebcilReader webcilReader) + { + _webcilReader = webcilReader; + } + public ImmutableArray ReadDebugDirectory() => _webcilReader.ReadDebugDirectory(); + + public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry) => _webcilReader.ReadCodeViewDebugDirectoryData(entry); + + public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry) => _webcilReader.ReadPdbChecksumDebugDirectoryData(entry); + + public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry) => _webcilReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry); +} From 5b5e84435f5e2d205575fd7a92ee1304669613cb Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 24 Jan 2023 11:52:49 -0500 Subject: [PATCH 2/7] Move common logic to a MetadataDebugSummary class --- .../debugger/BrowserDebugProxy/DebugStore.cs | 125 ++++++++++-------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 92aa0615ec47ba..243e82ba0283ff 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -837,6 +837,73 @@ void AppendToBrowsable(Dictionary dict, CustomA public override string ToString() => "TypeInfo('" + FullName + "')"; } + /// + /// Information we can extract directly from the assembly image using metadata readers + /// + internal sealed class MetadataDebugSummary + { + internal MetadataReader PdbMetadataReader { get; private init; } + internal bool IsPortableCodeView { get; private init; } + internal PdbChecksum[] PdbChecksums { get; private init; } + + internal CodeViewDebugDirectoryData? CodeViewData { get; private init; } + + private MetadataDebugSummary(MetadataReader pdbMetadataReader, bool isPortableCodeView, PdbChecksum[] pdbChecksums, CodeViewDebugDirectoryData? codeViewData) + { + PdbMetadataReader = pdbMetadataReader; + IsPortableCodeView = isPortableCodeView; + PdbChecksums = pdbChecksums; + CodeViewData = codeViewData; + } + + internal static MetadataDebugSummary Create(MonoProxy monoProxy, SessionId sessionId, string name, IDebugMetadataProvider provider, byte[] pdb, CancellationToken token) + { + var entries = provider.ReadDebugDirectory(); + CodeViewDebugDirectoryData? codeViewData = null; + bool isPortableCodeView = false; + List pdbChecksums = new(); + foreach (var entry in entries) + { + if (entry.Type == DebugDirectoryEntryType.CodeView) + { + codeViewData = provider.ReadCodeViewDebugDirectoryData(entry); + if (entry.IsPortableCodeView) + isPortableCodeView = true; + } + if (entry.Type == DebugDirectoryEntryType.PdbChecksum) + { + var checksum = provider.ReadPdbChecksumDebugDirectoryData(entry); + pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray())); + } + } + + MetadataReader pdbMetadataReader = null; + if (pdb != null) + { + var pdbStream = new MemoryStream(pdb); + try + { + // MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream + pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); + } + catch (BadImageFormatException) + { + monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token); + } + } + else + { + var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); + if (embeddedPdbEntry.DataSize != 0) + { + pdbMetadataReader = provider.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader(); + } + } + + return new MetadataDebugSummary(pdbMetadataReader, isPortableCodeView, pdbChecksums.ToArray(), codeViewData); + } + } + internal sealed class AssemblyInfo { private static int next_id; @@ -894,7 +961,6 @@ private AssemblyInfo(ILogger logger) this.id = Interlocked.Increment(ref next_id); this.logger = logger; } - private static AssemblyInfo FromPEReader(MonoProxy monoProxy, SessionId sessionId, PEReader peReader, byte[] pdb, ILogger logger, CancellationToken token) { @@ -902,69 +968,20 @@ private static AssemblyInfo FromPEReader(MonoProxy monoProxy, SessionId sessionI var asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader); string name = ReadAssemblyName(asmMetadataReader); - ReadDebugEntries(monoProxy, sessionId, name, debugProvider, pdb, out var codeViewData, out var isPortableCodeView, out var pdbChecksums, out var pdbMetadataReader, token); + var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token); - var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger); + var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, summary.CodeViewData, summary.PdbChecksums, summary.IsPortableCodeView, summary.PdbMetadataReader, logger); return assemblyInfo; } - - private static void ReadDebugEntries(MonoProxy monoProxy, SessionId sessionId, string name, IDebugMetadataProvider provider, byte[] pdb, out CodeViewDebugDirectoryData? codeViewData, out bool isPortableCodeView, out List pdbChecksums, out MetadataReader pdbMetadataReader, CancellationToken token) - { - var entries = provider.ReadDebugDirectory(); - codeViewData = null; - isPortableCodeView = false; - pdbChecksums = new(); - foreach (var entry in entries) - { - if (entry.Type == DebugDirectoryEntryType.CodeView) - { - codeViewData = provider.ReadCodeViewDebugDirectoryData(entry); - if (entry.IsPortableCodeView) - isPortableCodeView = true; - } - if (entry.Type == DebugDirectoryEntryType.PdbChecksum) - { - var checksum = provider.ReadPdbChecksumDebugDirectoryData(entry); - pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray())); - } - } - - pdbMetadataReader = null; - if (pdb != null) - { - var pdbStream = new MemoryStream(pdb); - try - { - // MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream - pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); - } - catch (BadImageFormatException) - { - monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token); - } - } - else - { - var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); - if (embeddedPdbEntry.DataSize != 0) - { - pdbMetadataReader = provider.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader(); - } - } - - - } - private static AssemblyInfo FromWebcilReader(MonoProxy monoProxy, SessionId sessionId, WebcilReader wcReader, byte[] pdb, ILogger logger, CancellationToken token) { var debugProvider = new WebcilDebugMetadataProvider(wcReader); var asmMetadataReader = wcReader.GetMetadataReader(); string name = ReadAssemblyName(asmMetadataReader); - ReadDebugEntries(monoProxy, sessionId, name, debugProvider, pdb, out var codeViewData, out var isPortableCodeView, out var pdbChecksums, out var pdbMetadataReader, token); - + var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token); - var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, pdbMetadataReader, logger); + var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, summary.CodeViewData, summary.PdbChecksums, summary.IsPortableCodeView, summary.PdbMetadataReader, logger); return assemblyInfo; } From a172349ea90f002ed678a13264b0a39b9e924d3b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 24 Jan 2023 13:05:12 -0500 Subject: [PATCH 3/7] Implement PDB checksum reader for WebcilReader --- .../src/Webcil/WebcilReader.cs | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs index 309c249b1a4a60..36c33159721d4d 100644 --- a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs +++ b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs @@ -240,6 +240,17 @@ private static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid gu return (CodeViewDebugDirectoryData)mi.Invoke(new object[] { guid, age, path }); } + private static PdbChecksumDebugDirectoryData MakePdbChecksumDebugDirectoryData(string algorithmName, ImmutableArray checksum) + { + var types = new Type[] { typeof(string), typeof(ImmutableArray) }; + var mi = typeof(PdbChecksumDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null); + if (mi == null) + { + throw new InvalidOperationException("Could not find PdbChecksumDebugDirectoryData constructor"); + } + return (PdbChecksumDebugDirectoryData)mi.Invoke(new object[] { algorithmName, checksum }); + } + public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry) { var pos = entry.DataPointer; @@ -297,7 +308,45 @@ private static MetadataReaderProvider DecodeEmbeddedPortablePdbDirectoryData(Blo } - public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry) => throw new NotImplementedException("read pdb checksum"); + public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry) + { + if (entry.Type != DebugDirectoryEntryType.PdbChecksum) + { + throw new ArgumentException($"expected debug directory entry type {nameof(DebugDirectoryEntryType.PdbChecksum)}", nameof(entry)); + } + + var pos = entry.DataPointer; + var buffer = new byte[entry.DataSize]; + if (_stream.Seek(pos, SeekOrigin.Begin) != pos) + { + throw new BadImageFormatException("Could not seek to CodeView debug directory data", nameof(_stream)); + } + if (_stream.Read(buffer, 0, buffer.Length) != buffer.Length) + { + throw new BadImageFormatException("Could not read CodeView debug directory data", nameof(_stream)); + } + unsafe + { + fixed (byte* p = buffer) + { + return DecodePdbChecksumDebugDirectoryData(new BlobReader(p, buffer.Length)); + } + } + + } + + private static PdbChecksumDebugDirectoryData DecodePdbChecksumDebugDirectoryData(BlobReader reader) + { + var algorithmName = ReadUtf8NullTerminated(reader); + byte[]? checksum = reader.ReadBytes(reader.RemainingBytes); + if (string.IsNullOrEmpty(algorithmName) || checksum == null || checksum.Length == 0) + { + throw new BadImageFormatException("Invalid PdbChecksum data format"); + } + + return MakePdbChecksumDebugDirectoryData(algorithmName, ImmutableArray.Create(checksum)); + + } private long TranslateRVA(uint rva) { From fe8f87432000ea4887d9c606b6004d231710eb29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aleksey=20Kliger=20=28=CE=BBgeek=29?= Date: Tue, 24 Jan 2023 14:24:12 -0500 Subject: [PATCH 4/7] Fix whitespace Co-authored-by: Ankit Jain --- .../Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs index 36c33159721d4d..95ad53e6e9a84a 100644 --- a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs +++ b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs @@ -332,7 +332,6 @@ public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDire return DecodePdbChecksumDebugDirectoryData(new BlobReader(p, buffer.Length)); } } - } private static PdbChecksumDebugDirectoryData DecodePdbChecksumDebugDirectoryData(BlobReader reader) @@ -345,7 +344,6 @@ private static PdbChecksumDebugDirectoryData DecodePdbChecksumDebugDirectoryData } return MakePdbChecksumDebugDirectoryData(algorithmName, ImmutableArray.Create(checksum)); - } private long TranslateRVA(uint rva) From 6eaee63d629877eb58c5f5cf6df23ae2d5cf89e0 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 24 Jan 2023 14:42:41 -0500 Subject: [PATCH 5/7] Move MetadataDebugSummary to a new file Also switch from cascade of 'if's to a 'switch' when looking at debug entries --- .../debugger/BrowserDebugProxy/DebugStore.cs | 80 ++--------------- .../BrowserDebugProxy/MetadataDebugSummary.cs | 89 +++++++++++++++++++ 2 files changed, 96 insertions(+), 73 deletions(-) create mode 100644 src/mono/wasm/debugger/BrowserDebugProxy/MetadataDebugSummary.cs diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 243e82ba0283ff..8596ca4bcf3026 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -837,73 +837,6 @@ void AppendToBrowsable(Dictionary dict, CustomA public override string ToString() => "TypeInfo('" + FullName + "')"; } - /// - /// Information we can extract directly from the assembly image using metadata readers - /// - internal sealed class MetadataDebugSummary - { - internal MetadataReader PdbMetadataReader { get; private init; } - internal bool IsPortableCodeView { get; private init; } - internal PdbChecksum[] PdbChecksums { get; private init; } - - internal CodeViewDebugDirectoryData? CodeViewData { get; private init; } - - private MetadataDebugSummary(MetadataReader pdbMetadataReader, bool isPortableCodeView, PdbChecksum[] pdbChecksums, CodeViewDebugDirectoryData? codeViewData) - { - PdbMetadataReader = pdbMetadataReader; - IsPortableCodeView = isPortableCodeView; - PdbChecksums = pdbChecksums; - CodeViewData = codeViewData; - } - - internal static MetadataDebugSummary Create(MonoProxy monoProxy, SessionId sessionId, string name, IDebugMetadataProvider provider, byte[] pdb, CancellationToken token) - { - var entries = provider.ReadDebugDirectory(); - CodeViewDebugDirectoryData? codeViewData = null; - bool isPortableCodeView = false; - List pdbChecksums = new(); - foreach (var entry in entries) - { - if (entry.Type == DebugDirectoryEntryType.CodeView) - { - codeViewData = provider.ReadCodeViewDebugDirectoryData(entry); - if (entry.IsPortableCodeView) - isPortableCodeView = true; - } - if (entry.Type == DebugDirectoryEntryType.PdbChecksum) - { - var checksum = provider.ReadPdbChecksumDebugDirectoryData(entry); - pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray())); - } - } - - MetadataReader pdbMetadataReader = null; - if (pdb != null) - { - var pdbStream = new MemoryStream(pdb); - try - { - // MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream - pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); - } - catch (BadImageFormatException) - { - monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token); - } - } - else - { - var embeddedPdbEntry = entries.FirstOrDefault(e => e.Type == DebugDirectoryEntryType.EmbeddedPortablePdb); - if (embeddedPdbEntry.DataSize != 0) - { - pdbMetadataReader = provider.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader(); - } - } - - return new MetadataDebugSummary(pdbMetadataReader, isPortableCodeView, pdbChecksums.ToArray(), codeViewData); - } - } - internal sealed class AssemblyInfo { private static int next_id; @@ -970,7 +903,7 @@ private static AssemblyInfo FromPEReader(MonoProxy monoProxy, SessionId sessionI string name = ReadAssemblyName(asmMetadataReader); var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token); - var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, summary.CodeViewData, summary.PdbChecksums, summary.IsPortableCodeView, summary.PdbMetadataReader, logger); + var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, summary, logger); return assemblyInfo; } private static AssemblyInfo FromWebcilReader(MonoProxy monoProxy, SessionId sessionId, WebcilReader wcReader, byte[] pdb, ILogger logger, CancellationToken token) @@ -981,7 +914,7 @@ private static AssemblyInfo FromWebcilReader(MonoProxy monoProxy, SessionId sess var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token); - var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, summary.CodeViewData, summary.PdbChecksums, summary.IsPortableCodeView, summary.PdbMetadataReader, logger); + var assemblyInfo = new AssemblyInfo(wcReader, name, asmMetadataReader, summary, logger); return assemblyInfo; } @@ -991,10 +924,11 @@ private static string ReadAssemblyName(MetadataReader asmMetadataReader) return asmDef.GetAssemblyName().Name + ".dll"; } - private unsafe AssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, CodeViewDebugDirectoryData? codeViewData, PdbChecksum[] pdbChecksums, bool isPortableCodeView, MetadataReader pdbMetadataReader, ILogger logger) + private unsafe AssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, MetadataDebugSummary summary, ILogger logger) : this(logger) { peReaderOrWebcilReader = owningReader; + var codeViewData = summary.CodeViewData; if (codeViewData != null) { PdbAge = codeViewData.Value.Age; @@ -1002,12 +936,12 @@ private unsafe AssemblyInfo(IDisposable owningReader, string name, MetadataReade PdbName = codeViewData.Value.Path; CodeViewInformationAvailable = true; } - IsPortableCodeView = isPortableCodeView; - PdbChecksums = pdbChecksums; + IsPortableCodeView = summary.IsPortableCodeView; + PdbChecksums = summary.PdbChecksums; this.asmMetadataReader = asmMetadataReader; Name = name; logger.LogTrace($"Info: loading AssemblyInfo with name {Name}"); - this.pdbMetadataReader = pdbMetadataReader; + this.pdbMetadataReader = summary.PdbMetadataReader; Populate(); } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MetadataDebugSummary.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MetadataDebugSummary.cs new file mode 100644 index 00000000000000..714f0e9f1e4f65 --- /dev/null +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MetadataDebugSummary.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Threading; +using Microsoft.FileFormats.PE; + +namespace Microsoft.WebAssembly.Diagnostics; + +/// +/// Information we can extract directly from the assembly image using metadata readers +/// +internal sealed class MetadataDebugSummary +{ + internal MetadataReader? PdbMetadataReader { get; private init; } + internal bool IsPortableCodeView { get; private init; } + internal PdbChecksum[] PdbChecksums { get; private init; } + + internal CodeViewDebugDirectoryData? CodeViewData { get; private init; } + + private MetadataDebugSummary(MetadataReader? pdbMetadataReader, bool isPortableCodeView, PdbChecksum[] pdbChecksums, CodeViewDebugDirectoryData? codeViewData) + { + PdbMetadataReader = pdbMetadataReader; + IsPortableCodeView = isPortableCodeView; + PdbChecksums = pdbChecksums; + CodeViewData = codeViewData; + } + + internal static MetadataDebugSummary Create(MonoProxy monoProxy, SessionId sessionId, string name, IDebugMetadataProvider provider, byte[]? pdb, CancellationToken token) + { + var entries = provider.ReadDebugDirectory(); + CodeViewDebugDirectoryData? codeViewData = null; + bool isPortableCodeView = false; + List pdbChecksums = new(); + DebugDirectoryEntry? embeddedPdbEntry = null; + foreach (var entry in entries) + { + switch (entry.Type) + { + case DebugDirectoryEntryType.CodeView: + codeViewData = provider.ReadCodeViewDebugDirectoryData(entry); + if (entry.IsPortableCodeView) + isPortableCodeView = true; + break; + case DebugDirectoryEntryType.PdbChecksum: + var checksum = provider.ReadPdbChecksumDebugDirectoryData(entry); + pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray())); + break; + case DebugDirectoryEntryType.EmbeddedPortablePdb: + embeddedPdbEntry = entry; + break; + default: + break; + } + } + + MetadataReader? pdbMetadataReader = null; + if (pdb != null) + { + var pdbStream = new MemoryStream(pdb); + try + { + // MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream + pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); + } + catch (BadImageFormatException) + { + monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token); + } + } + else + { + if (embeddedPdbEntry != null && embeddedPdbEntry.Value.DataSize != 0) + { + pdbMetadataReader = provider.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry.Value).GetMetadataReader(); + } + } + + return new MetadataDebugSummary(pdbMetadataReader, isPortableCodeView, pdbChecksums.ToArray(), codeViewData); + } +} From 45b7c47ef60242107e3db6fbb78e57c3ace1718f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 24 Jan 2023 15:04:30 -0500 Subject: [PATCH 6/7] Move WebcilReader reflection to a helper; add lazy initialization --- .../src/Webcil/WebcilReader.Reflection.cs | 59 +++++++++++++++++++ .../src/Webcil/WebcilReader.cs | 34 ++--------- 2 files changed, 63 insertions(+), 30 deletions(-) create mode 100644 src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.Reflection.cs diff --git a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.Reflection.cs b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.Reflection.cs new file mode 100644 index 00000000000000..601dc5e5f66c89 --- /dev/null +++ b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.Reflection.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Immutable; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; + +namespace Microsoft.NET.WebAssembly.Webcil; + + +public sealed partial class WebcilReader +{ + + // Helpers to call into System.Reflection.Metadata internals + internal static class Reflection + { + private static Lazy _readUtf8NullTerminated = new Lazy(() => + { + var mi = typeof(BlobReader).GetMethod("ReadUtf8NullTerminated", BindingFlags.NonPublic | BindingFlags.Instance); + if (mi == null) + { + throw new InvalidOperationException("Could not find BlobReader.ReadUtf8NullTerminated"); + } + return mi; + }); + + internal static string? ReadUtf8NullTerminated(BlobReader reader) => (string?)_readUtf8NullTerminated.Value.Invoke(reader, null); + + private static Lazy _codeViewDebugDirectoryDataCtor = new Lazy(() => + { + var types = new Type[] { typeof(Guid), typeof(int), typeof(string) }; + var mi = typeof(CodeViewDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null); + if (mi == null) + { + throw new InvalidOperationException("Could not find CodeViewDebugDirectoryData constructor"); + } + return mi; + }); + + internal static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid guid, int age, string path) => (CodeViewDebugDirectoryData)_codeViewDebugDirectoryDataCtor.Value.Invoke(new object[] { guid, age, path }); + + private static Lazy _pdbChecksumDebugDirectoryDataCtor = new Lazy(() => + { + var types = new Type[] { typeof(string), typeof(ImmutableArray) }; + var mi = typeof(PdbChecksumDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null); + if (mi == null) + { + throw new InvalidOperationException("Could not find PdbChecksumDebugDirectoryData constructor"); + } + return mi; + }); + internal static PdbChecksumDebugDirectoryData MakePdbChecksumDebugDirectoryData(string algorithmName, ImmutableArray checksum) => (PdbChecksumDebugDirectoryData)_pdbChecksumDebugDirectoryDataCtor.Value.Invoke(new object[] { algorithmName, checksum }); + } +} diff --git a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs index 95ad53e6e9a84a..39d58134ce6b48 100644 --- a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs +++ b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.cs @@ -13,7 +13,7 @@ namespace Microsoft.NET.WebAssembly.Webcil; -public sealed class WebcilReader : IDisposable +public sealed partial class WebcilReader : IDisposable { // WISH: // This should be implemented in terms of System.Reflection.Internal.MemoryBlockProvider like the PEReader, @@ -219,37 +219,11 @@ private static CodeViewDebugDirectoryData DecodeCodeViewDebugDirectoryData(BlobR return MakeCodeViewDebugDirectoryData(guid, age, path); } - private static string? ReadUtf8NullTerminated(BlobReader reader) - { - var mi = typeof(BlobReader).GetMethod("ReadUtf8NullTerminated", BindingFlags.NonPublic | BindingFlags.Instance); - if (mi == null) - { - throw new InvalidOperationException("Could not find BlobReader.ReadUtf8NullTerminated"); - } - return (string?)mi.Invoke(reader, null); - } + private static string? ReadUtf8NullTerminated(BlobReader reader) => Reflection.ReadUtf8NullTerminated(reader); - private static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid guid, int age, string path) - { - var types = new Type[] { typeof(Guid), typeof(int), typeof(string) }; - var mi = typeof(CodeViewDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null); - if (mi == null) - { - throw new InvalidOperationException("Could not find CodeViewDebugDirectoryData constructor"); - } - return (CodeViewDebugDirectoryData)mi.Invoke(new object[] { guid, age, path }); - } + private static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid guid, int age, string path) => Reflection.MakeCodeViewDebugDirectoryData(guid, age, path); - private static PdbChecksumDebugDirectoryData MakePdbChecksumDebugDirectoryData(string algorithmName, ImmutableArray checksum) - { - var types = new Type[] { typeof(string), typeof(ImmutableArray) }; - var mi = typeof(PdbChecksumDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null); - if (mi == null) - { - throw new InvalidOperationException("Could not find PdbChecksumDebugDirectoryData constructor"); - } - return (PdbChecksumDebugDirectoryData)mi.Invoke(new object[] { algorithmName, checksum }); - } + private static PdbChecksumDebugDirectoryData MakePdbChecksumDebugDirectoryData(string algorithmName, ImmutableArray checksum) => Reflection.MakePdbChecksumDebugDirectoryData(algorithmName, checksum); public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry) { From df5175b6edadfb10de502e09a349d8a6196f114c Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 25 Jan 2023 09:36:46 -0500 Subject: [PATCH 7/7] Mark fields readonly; use naming convention Co-Authored-By: Ankit Jain --- .../src/Webcil/WebcilReader.Reflection.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.Reflection.cs b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.Reflection.cs index 601dc5e5f66c89..56eae848a4aa5b 100644 --- a/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.Reflection.cs +++ b/src/libraries/Microsoft.NET.WebAssembly.Webcil/src/Webcil/WebcilReader.Reflection.cs @@ -19,7 +19,7 @@ public sealed partial class WebcilReader // Helpers to call into System.Reflection.Metadata internals internal static class Reflection { - private static Lazy _readUtf8NullTerminated = new Lazy(() => + private static readonly Lazy s_readUtf8NullTerminated = new Lazy(() => { var mi = typeof(BlobReader).GetMethod("ReadUtf8NullTerminated", BindingFlags.NonPublic | BindingFlags.Instance); if (mi == null) @@ -29,9 +29,9 @@ internal static class Reflection return mi; }); - internal static string? ReadUtf8NullTerminated(BlobReader reader) => (string?)_readUtf8NullTerminated.Value.Invoke(reader, null); + internal static string? ReadUtf8NullTerminated(BlobReader reader) => (string?)s_readUtf8NullTerminated.Value.Invoke(reader, null); - private static Lazy _codeViewDebugDirectoryDataCtor = new Lazy(() => + private static readonly Lazy s_codeViewDebugDirectoryDataCtor = new Lazy(() => { var types = new Type[] { typeof(Guid), typeof(int), typeof(string) }; var mi = typeof(CodeViewDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null); @@ -42,9 +42,9 @@ internal static class Reflection return mi; }); - internal static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid guid, int age, string path) => (CodeViewDebugDirectoryData)_codeViewDebugDirectoryDataCtor.Value.Invoke(new object[] { guid, age, path }); + internal static CodeViewDebugDirectoryData MakeCodeViewDebugDirectoryData(Guid guid, int age, string path) => (CodeViewDebugDirectoryData)s_codeViewDebugDirectoryDataCtor.Value.Invoke(new object[] { guid, age, path }); - private static Lazy _pdbChecksumDebugDirectoryDataCtor = new Lazy(() => + private static readonly Lazy s_pdbChecksumDebugDirectoryDataCtor = new Lazy(() => { var types = new Type[] { typeof(string), typeof(ImmutableArray) }; var mi = typeof(PdbChecksumDebugDirectoryData).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, types, null); @@ -54,6 +54,6 @@ internal static class Reflection } return mi; }); - internal static PdbChecksumDebugDirectoryData MakePdbChecksumDebugDirectoryData(string algorithmName, ImmutableArray checksum) => (PdbChecksumDebugDirectoryData)_pdbChecksumDebugDirectoryDataCtor.Value.Invoke(new object[] { algorithmName, checksum }); + internal static PdbChecksumDebugDirectoryData MakePdbChecksumDebugDirectoryData(string algorithmName, ImmutableArray checksum) => (PdbChecksumDebugDirectoryData)s_pdbChecksumDebugDirectoryDataCtor.Value.Invoke(new object[] { algorithmName, checksum }); } }