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