--- /dev/null
+// 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 readonly Lazy<MethodInfo> s_readUtf8NullTerminated = new Lazy<MethodInfo>(() =>
+ {
+ 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?)s_readUtf8NullTerminated.Value.Invoke(reader, null);
+
+ private static readonly Lazy<ConstructorInfo> s_codeViewDebugDirectoryDataCtor = new Lazy<ConstructorInfo>(() =>
+ {
+ 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)s_codeViewDebugDirectoryDataCtor.Value.Invoke(new object[] { guid, age, path });
+
+ private static readonly Lazy<ConstructorInfo> s_pdbChecksumDebugDirectoryDataCtor = new Lazy<ConstructorInfo>(() =>
+ {
+ var types = new Type[] { typeof(string), typeof(ImmutableArray<byte>) };
+ 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<byte> checksum) => (PdbChecksumDebugDirectoryData)s_pdbChecksumDebugDirectoryDataCtor.Value.Invoke(new object[] { algorithmName, checksum });
+ }
+}
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,
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<byte> checksum) => Reflection.MakePdbChecksumDebugDirectoryData(algorithmName, checksum);
public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry)
{
}
+ 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)
{
if (_sections == null)
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)
{
- var entries = peReader.ReadDebugDirectory();
- CodeViewDebugDirectoryData? codeViewData = null;
- var isPortableCodeView = false;
- List<PdbChecksum> pdbChecksums = new();
- foreach (var entry in peReader.ReadDebugDirectory())
- {
- if (entry.Type == DebugDirectoryEntryType.CodeView)
- {
- codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
- if (entry.IsPortableCodeView)
- isPortableCodeView = true;
- }
- if (entry.Type == DebugDirectoryEntryType.PdbChecksum)
- {
- var checksum = peReader.ReadPdbChecksumDebugDirectoryData(entry);
- pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray()));
- }
- }
+
+ var debugProvider = new PortableExecutableDebugMetadataProvider(peReader);
+
var asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader);
string name = ReadAssemblyName(asmMetadataReader);
+ var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token);
- 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 = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry).GetMetadataReader();
- }
- }
-
- var assemblyInfo = new AssemblyInfo(peReader, name, asmMetadataReader, codeViewData, pdbChecksums.ToArray(), isPortableCodeView, 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)
{
- var entries = wcReader.ReadDebugDirectory();
- CodeViewDebugDirectoryData? codeViewData = null;
- var isPortableCodeView = false;
- List<PdbChecksum> 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();
- }
- }
+ 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, logger);
return assemblyInfo;
}
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;
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();
}
--- /dev/null
+// 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;
+
+/// <summary>
+/// An adapter on top of MetadataReader and WebcilReader for DebugStore compensating
+/// for the lack of a common base class on those two types.
+/// </summary>
+public interface IDebugMetadataProvider
+{
+ public ImmutableArray<DebugDirectoryEntry> ReadDebugDirectory();
+ public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry);
+ public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry);
+
+ public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry);
+}
--- /dev/null
+// 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;
+
+/// <summary>
+/// Information we can extract directly from the assembly image using metadata readers
+/// </summary>
+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<PdbChecksum> 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);
+ }
+}
--- /dev/null
+// 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<DebugDirectoryEntry> 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);
+}
--- /dev/null
+// 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<DebugDirectoryEntry> 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);
+}