Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BrowserDebugProxy: unify debug metadata reading for PE and Webcil #81099

Merged
merged 7 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 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 });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -219,26 +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<byte> checksum) => Reflection.MakePdbChecksumDebugDirectoryData(algorithmName, checksum);

public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry)
{
Expand Down Expand Up @@ -297,6 +282,44 @@ private static MetadataReaderProvider DecodeEmbeddedPortablePdbDirectoryData(Blo

}

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)
Expand Down
98 changes: 13 additions & 85 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -894,100 +894,27 @@ 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)
{
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;
}

Expand All @@ -997,23 +924,24 @@ 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;
PdbGuid = codeViewData.Value.Guid;
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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
lambdageek marked this conversation as resolved.
Show resolved Hide resolved

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);
}
Original file line number Diff line number Diff line change
@@ -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;

/// <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);
}
}
Loading