Implement support for Embedded PDB
This commit is contained in:
parent
d76501bce1
commit
b5cfb9d660
7 changed files with 165 additions and 41 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() { }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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": {},
|
||||
|
|
Loading…
Reference in a new issue