Implement support for Embedded PDB

This commit is contained in:
Tomas Matousek 2016-08-26 16:53:40 -07:00
parent d76501bce1
commit b5cfb9d660
7 changed files with 165 additions and 41 deletions

View file

@ -51,25 +51,32 @@ namespace Microsoft.Extensions.Testing.Abstractions
private static ISymUnmanagedReader3 CreateNativeSymReader(Stream pdbStream)
{
object symReader = null;
var guid = default(Guid);
if (IntPtr.Size == 4)
try
{
CreateSymReader32(ref guid, out symReader);
object symReader = null;
var guid = default(Guid);
if (IntPtr.Size == 4)
{
CreateSymReader32(ref guid, out symReader);
}
else
{
CreateSymReader64(ref guid, out symReader);
}
var reader = (ISymUnmanagedReader3)symReader;
var hr = reader.Initialize(new DummyMetadataImport(), null, null, new ComStreamWrapper(pdbStream));
SymUnmanagedReaderExtensions.ThrowExceptionForHR(hr);
return reader;
}
else
catch (Exception e)
{
CreateSymReader64(ref guid, out symReader);
throw new IOException(e.Message, e);
}
var reader = (ISymUnmanagedReader3)symReader;
var hr = reader.Initialize(new DummyMetadataImport(), null, null, new ComStreamWrapper(pdbStream));
SymUnmanagedReaderExtensions.ThrowExceptionForHR(hr);
return reader;
}
public void Dispose()
{
((ISymUnmanagedDispose) _symReader).Destroy();
((ISymUnmanagedDispose)_symReader).Destroy();
}
}

View file

@ -1,12 +1,21 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.IO;
namespace Microsoft.Extensions.Testing.Abstractions
{
public interface IPdbReaderFactory
{
/// <summary>
/// Creates <see cref="IPdbReader"/> for given file.
/// </summary>
/// <param name="pdbPath">
/// Path to the .pdb file or a PE file that refers to the .pdb file in its Debug Directory Table.
/// </param>
/// <exception cref="IOException">File <paramref name="pdbPath"/> does not exist or can't be read.</exception>
/// <exception cref="ArgumentNullException"><paramref name="pdbPath"/> is null.</exception>
IPdbReader Create(string pdbPath);
}
}

View file

@ -0,0 +1,12 @@
using System.Reflection;
namespace Microsoft.Extensions.Testing.Abstractions
{
internal sealed class MissingPdbReader : IPdbReader
{
public static readonly MissingPdbReader Instance = new MissingPdbReader();
public SourceInformation GetSourceInformation(MethodInfo methodInfo) => null;
public void Dispose() { }
}
}

View file

@ -3,31 +3,103 @@
using System;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
namespace Microsoft.Extensions.Testing.Abstractions
{
public class PdbReaderFactory : IPdbReaderFactory
{
/// <summary>
/// Creates <see cref="IPdbReader"/> for given file.
/// </summary>
/// <param name="pdbPath">
/// Path to the .pdb file or a PE file that refers to the .pdb file in its Debug Directory Table.
/// </param>
/// <exception cref="IOException">File <paramref name="pdbPath"/> does not exist or can't be read.</exception>
/// <exception cref="ArgumentNullException"><paramref name="pdbPath"/> is null.</exception>
public IPdbReader Create(string pdbPath)
{
var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read);
if (IsPortable(pdbStream))
if (pdbPath == null)
{
return new PortablePdbReader(pdbStream);
throw new ArgumentNullException(nameof(pdbPath));
}
else
Stream stream = OpenRead(pdbPath);
if (IsPE(stream))
{
return new FullPdbReader(pdbStream);
return CreateFromPortableExecutable(stream, pdbPath);
}
if (IsPortable(stream))
{
return new PortablePdbReader(stream);
}
return new FullPdbReader(stream);
}
private static bool IsPortable(Stream stream)
{
bool result = stream.ReadByte() == 'B' && stream.ReadByte() == 'S' && stream.ReadByte() == 'J' && stream.ReadByte() == 'B';
stream.Position = 0;
return result;
}
private static bool IsPE(Stream stream)
{
bool result = stream.ReadByte() == 'M' && stream.ReadByte() == 'Z';
stream.Position = 0;
return result;
}
private IPdbReader CreateFromPortableExecutable(Stream peStream, string pePath)
{
using (var peReader = new PEReader(peStream))
{
MetadataReaderProvider pdbProvider;
string pdbPath;
if (peReader.TryOpenAssociatedPortablePdb(pePath, TryOpenRead, out pdbProvider, out pdbPath))
{
return new PortablePdbReader(pdbProvider);
}
return MissingPdbReader.Instance;
}
}
private static bool IsPortable(Stream pdbStream)
private static Stream OpenRead(string path)
{
return pdbStream.ReadByte() == 'B' &&
pdbStream.ReadByte() == 'S' &&
pdbStream.ReadByte() == 'J' &&
pdbStream.ReadByte() == 'B';
try
{
return File.OpenRead(path);
}
catch (Exception e) when (!(e is IOException))
{
throw new IOException(e.Message, e);
}
}
private static Stream TryOpenRead(string path)
{
if (!File.Exists(path))
{
return null;
}
try
{
return File.OpenRead(path);
}
catch (FileNotFoundException)
{
return null;
}
catch (Exception e) when (!(e is IOException))
{
throw new IOException(e.Message, e);
}
}
}
}

View file

@ -6,27 +6,23 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
namespace Microsoft.Extensions.Testing.Abstractions
{
public class PortablePdbReader : IPdbReader
{
private MetadataReader _reader;
private GCHandle _gcHandle;
private MetadataReaderProvider _provider;
public PortablePdbReader(Stream pdbStream)
public PortablePdbReader(Stream stream)
: this(MetadataReaderProvider.FromPortablePdbStream(stream))
{
pdbStream.Position = 0;
var buffer = new byte[pdbStream.Length];
pdbStream.Read(buffer, 0, buffer.Length);
_gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
unsafe
{
var address = _gcHandle.AddrOfPinnedObject();
_reader = new MetadataReader((byte*)address.ToPointer(), buffer.Length);
}
}
internal PortablePdbReader(MetadataReaderProvider provider)
{
_provider = provider;
_reader = provider.GetMetadataReader();
}
public SourceInformation GetSourceInformation(MethodInfo methodInfo)
@ -43,6 +39,11 @@ namespace Microsoft.Extensions.Testing.Abstractions
private SourceInformation GetSourceInformation(MethodDebugInformationHandle handle)
{
if (_reader == null)
{
throw new ObjectDisposedException(nameof(PortablePdbReader));
}
SourceInformation sourceInformation = null;
try
{
@ -81,7 +82,9 @@ namespace Microsoft.Extensions.Testing.Abstractions
public void Dispose()
{
_gcHandle.Free();
_provider?.Dispose();
_provider = null;
_reader = null;
}
}
}

View file

@ -10,23 +10,44 @@ namespace Microsoft.Extensions.Testing.Abstractions
{
public class SourceInformationProvider : ISourceInformationProvider
{
private readonly string _pdbPath;
private readonly string _filePath;
private readonly IPdbReader _pdbReader;
/// <summary>
/// Creates a source info provide from a specified file path.
/// </summary>
/// <param name="pdbPath">
/// Path to the .pdb file or a PE file that refers to the .pdb file in its Debug Directory Table.
/// </param>
public SourceInformationProvider(string pdbPath) :
this(pdbPath, new PdbReaderFactory())
{
}
/// <summary>
/// Creates a source info provide from a specified file path.
/// </summary>
/// <param name="pdbPath">
/// Path to the .pdb file or a PE file that refers to the .pdb file in its Debug Directory Table.
/// </param>
/// <param name="pdbReaderFactory">
/// Factory that creates <see cref="IPdbReader"/> instance used to read the PDB.
/// </param>
/// <exception cref="IOException">File <paramref name="pdbPath"/> does not exist or can't be read.</exception>
/// <exception cref="ArgumentNullException"><paramref name="pdbPath"/> or <paramref name="pdbReaderFactory"/> is null.</exception>
public SourceInformationProvider(string pdbPath, IPdbReaderFactory pdbReaderFactory)
{
if (string.IsNullOrWhiteSpace(pdbPath) || !File.Exists(pdbPath))
if (pdbPath == null)
{
throw new ArgumentException($"The file '{pdbPath}' does not exist.", nameof(pdbPath));
throw new ArgumentNullException(nameof(pdbPath));
}
_pdbPath = pdbPath;
if (pdbReaderFactory == null)
{
throw new ArgumentNullException(nameof(pdbReaderFactory));
}
_filePath = pdbPath;
_pdbReader = pdbReaderFactory.Create(pdbPath);
}

View file

@ -13,7 +13,7 @@
},
"Microsoft.DiaSymReader": "1.0.8",
"Microsoft.DiaSymReader.Native": "1.4.0-rc2",
"System.Reflection.Metadata": "1.4.1-beta-24410-02"
"System.Reflection.Metadata": "1.4.1-beta-24426-02"
},
"frameworks": {
"net451": {},