113 lines
3.8 KiB
C#
113 lines
3.8 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System.IO;
|
|
using System.Reflection.Metadata;
|
|
using System.Reflection.Metadata.Ecma335;
|
|
using System.Reflection.PortableExecutable;
|
|
|
|
namespace DTLib.Demystifier.Internal;
|
|
|
|
// Adapted from https://github.com/aspnet/Common/blob/dev/shared/Microsoft.Extensions.StackTrace.Sources/StackFrame/PortablePdbReader.cs
|
|
internal class PortablePdbReader : IDisposable
|
|
{
|
|
private readonly Dictionary<string, MetadataReaderProvider> _cache = new(StringComparer.Ordinal);
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var entry in _cache) entry.Value.Dispose();
|
|
|
|
_cache.Clear();
|
|
}
|
|
|
|
public void PopulateStackFrame(StackFrame frameInfo, MethodBase method, int IlOffset, out string fileName,
|
|
out int row, out int column)
|
|
{
|
|
fileName = "";
|
|
row = 0;
|
|
column = 0;
|
|
|
|
if (method.Module.Assembly.IsDynamic) return;
|
|
|
|
var metadataReader = GetMetadataReader(method.Module.Assembly.Location);
|
|
|
|
if (metadataReader is null) return;
|
|
|
|
var methodToken = MetadataTokens.Handle(method.MetadataToken);
|
|
|
|
Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition);
|
|
|
|
var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle();
|
|
|
|
if (!handle.IsNil)
|
|
{
|
|
var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle);
|
|
var sequencePoints = methodDebugInfo.GetSequencePoints();
|
|
SequencePoint? bestPointSoFar = null;
|
|
|
|
foreach (var point in sequencePoints)
|
|
{
|
|
if (point.Offset > IlOffset) break;
|
|
|
|
if (point.StartLine != SequencePoint.HiddenLine) bestPointSoFar = point;
|
|
}
|
|
|
|
if (bestPointSoFar.HasValue)
|
|
{
|
|
row = bestPointSoFar.Value.StartLine;
|
|
column = bestPointSoFar.Value.StartColumn;
|
|
fileName = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
private MetadataReader? GetMetadataReader(string assemblyPath)
|
|
{
|
|
if (!_cache.TryGetValue(assemblyPath, out var provider) && provider is not null)
|
|
{
|
|
var pdbPath = GetPdbPath(assemblyPath);
|
|
|
|
if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath!))
|
|
{
|
|
var pdbStream = File.OpenRead(pdbPath);
|
|
provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
|
|
}
|
|
|
|
_cache[assemblyPath] = provider;
|
|
}
|
|
|
|
return provider?.GetMetadataReader();
|
|
}
|
|
|
|
private static string? GetPdbPath(string assemblyPath)
|
|
{
|
|
if (string.IsNullOrEmpty(assemblyPath)) return null;
|
|
|
|
if (File.Exists(assemblyPath))
|
|
{
|
|
var peStream = File.OpenRead(assemblyPath);
|
|
|
|
using var peReader = new PEReader(peStream);
|
|
foreach (var entry in peReader.ReadDebugDirectory())
|
|
if (entry.Type == DebugDirectoryEntryType.CodeView)
|
|
{
|
|
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
|
|
var peDirectory = Path.GetDirectoryName(assemblyPath);
|
|
return peDirectory is null
|
|
? null
|
|
: Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path));
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static bool IsPortable(string pdbPath)
|
|
{
|
|
using var pdbStream = File.OpenRead(pdbPath);
|
|
return pdbStream.ReadByte() == 'B' &&
|
|
pdbStream.ReadByte() == 'S' &&
|
|
pdbStream.ReadByte() == 'J' &&
|
|
pdbStream.ReadByte() == 'B';
|
|
}
|
|
} |