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

@ -50,6 +50,8 @@ namespace Microsoft.Extensions.Testing.Abstractions
} }
private static ISymUnmanagedReader3 CreateNativeSymReader(Stream pdbStream) private static ISymUnmanagedReader3 CreateNativeSymReader(Stream pdbStream)
{
try
{ {
object symReader = null; object symReader = null;
var guid = default(Guid); var guid = default(Guid);
@ -66,10 +68,15 @@ namespace Microsoft.Extensions.Testing.Abstractions
SymUnmanagedReaderExtensions.ThrowExceptionForHR(hr); SymUnmanagedReaderExtensions.ThrowExceptionForHR(hr);
return reader; return reader;
} }
catch (Exception e)
{
throw new IOException(e.Message, e);
}
}
public void Dispose() public void Dispose()
{ {
((ISymUnmanagedDispose) _symReader).Destroy(); ((ISymUnmanagedDispose)_symReader).Destroy();
} }
} }

View file

@ -1,12 +1,21 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. // 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. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.IO; using System.IO;
namespace Microsoft.Extensions.Testing.Abstractions namespace Microsoft.Extensions.Testing.Abstractions
{ {
public interface IPdbReaderFactory 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); 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;
using System.IO; using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
namespace Microsoft.Extensions.Testing.Abstractions namespace Microsoft.Extensions.Testing.Abstractions
{ {
public class PdbReaderFactory : IPdbReaderFactory 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) public IPdbReader Create(string pdbPath)
{ {
var pdbStream = new FileStream(pdbPath, FileMode.Open, FileAccess.Read); if (pdbPath == null)
if (IsPortable(pdbStream))
{ {
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' && try
pdbStream.ReadByte() == 'S' && {
pdbStream.ReadByte() == 'J' && return File.OpenRead(path);
pdbStream.ReadByte() == 'B'; }
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.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Runtime.InteropServices;
namespace Microsoft.Extensions.Testing.Abstractions namespace Microsoft.Extensions.Testing.Abstractions
{ {
public class PortablePdbReader : IPdbReader public class PortablePdbReader : IPdbReader
{ {
private MetadataReader _reader; 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) public SourceInformation GetSourceInformation(MethodInfo methodInfo)
@ -43,6 +39,11 @@ namespace Microsoft.Extensions.Testing.Abstractions
private SourceInformation GetSourceInformation(MethodDebugInformationHandle handle) private SourceInformation GetSourceInformation(MethodDebugInformationHandle handle)
{ {
if (_reader == null)
{
throw new ObjectDisposedException(nameof(PortablePdbReader));
}
SourceInformation sourceInformation = null; SourceInformation sourceInformation = null;
try try
{ {
@ -81,7 +82,9 @@ namespace Microsoft.Extensions.Testing.Abstractions
public void Dispose() 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 public class SourceInformationProvider : ISourceInformationProvider
{ {
private readonly string _pdbPath; private readonly string _filePath;
private readonly IPdbReader _pdbReader; 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) : public SourceInformationProvider(string pdbPath) :
this(pdbPath, new PdbReaderFactory()) 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) 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); _pdbReader = pdbReaderFactory.Create(pdbPath);
} }

View file

@ -13,7 +13,7 @@
}, },
"Microsoft.DiaSymReader": "1.0.8", "Microsoft.DiaSymReader": "1.0.8",
"Microsoft.DiaSymReader.Native": "1.4.0-rc2", "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": { "frameworks": {
"net451": {}, "net451": {},