| File: Navigation\PortablePdbReader.cs | Web Access |
| Project: src\vstest\src\Microsoft.TestPlatform.ObjectModel\Microsoft.TestPlatform.ObjectModel.csproj (Microsoft.VisualStudio.TestPlatform.ObjectModel) |
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.IO; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Navigation; /// <summary> /// The portable pdb reader. /// </summary> internal class PortablePdbReader : IDisposable { /// <summary> /// Use to get method token /// </summary> private static readonly PropertyInfo MethodInfoMethodTokenProperty = typeof(MethodInfo).GetProperty("MetadataToken")!; /// <summary> /// Metadata reader provider from portable pdb stream /// To get Metadata reader /// </summary> private readonly MetadataReaderProvider _provider; /// <summary> /// Metadata reader from portable pdb stream /// To get method debug info from method info /// </summary> private readonly MetadataReader _reader; /// <summary> /// Initializes a new instance of the <see cref="PortablePdbReader"/> class. /// </summary> /// <param name="stream"> /// Portable pdb stream /// </param> /// <exception cref="Exception"> /// Raises Exception on given stream is not portable pdb stream /// </exception> public PortablePdbReader(Stream stream) { if (!IsPortable(stream)) { throw new Exception("Given stream is not portable stream"); } _provider = MetadataReaderProvider.FromPortablePdbStream(stream); _reader = _provider.GetMetadataReader(); } /// <summary> /// Reads the pdb using a provided metadata reader, when the pdb is embedded in the dll, or found by /// path that is in the dll metadata. /// </summary> /// <param name="metadataReaderProvider"></param> public PortablePdbReader(MetadataReaderProvider metadataReaderProvider) { _provider = metadataReaderProvider ?? throw new ArgumentNullException(nameof(metadataReaderProvider)); _reader = _provider.GetMetadataReader(); } /// <summary> /// Dispose Metadata reader /// </summary> public void Dispose() { _provider?.Dispose(); } /// <summary> /// Gets dia navigation data from Metadata reader /// </summary> /// <param name="methodInfo"> /// Method info. /// </param> /// <returns> /// The <see cref="DiaNavigationData"/>. /// </returns> public DiaNavigationData? GetDiaNavigationData(MethodInfo? methodInfo) { if (methodInfo == null) { return null; } var handle = GetMethodDebugInformationHandle(methodInfo); return GetDiaNavigationData(handle); } /// <summary> /// Checks gives stream is from portable pdb or not /// </summary> /// <param name="stream"> /// Stream. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> internal static bool IsPortable(Stream stream) { // First four bytes should be 'BSJB' #pragma warning disable IDE0078 // Use pattern matching (may change code meaning) var result = (stream.ReadByte() == 'B') && (stream.ReadByte() == 'S') && (stream.ReadByte() == 'J') && (stream.ReadByte() == 'B'); #pragma warning restore IDE0078 // Use pattern matching (may change code meaning) stream.Position = 0; return result; } internal static MethodDebugInformationHandle GetMethodDebugInformationHandle(MethodInfo methodInfo) { var methodToken = (int)MethodInfoMethodTokenProperty.GetValue(methodInfo)!; var handle = ((MethodDefinitionHandle)MetadataTokens.Handle(methodToken)).ToDebugInformationHandle(); return handle; } private static void GetMethodMinAndMaxLineNumber( MethodDebugInformation methodDebugDefinition, out int minLineNumber, out int maxLineNumber) { minLineNumber = int.MaxValue; maxLineNumber = int.MinValue; var orderedSequencePoints = methodDebugDefinition.GetSequencePoints(); foreach (var sequencePoint in orderedSequencePoints) { if (sequencePoint.IsHidden) { // Special sequence point with startLine is Magic number 0xFEEFEE // Magic number comes from Potable CodeGen source code continue; } minLineNumber = Math.Min(minLineNumber, sequencePoint.StartLine); maxLineNumber = Math.Max(maxLineNumber, sequencePoint.StartLine); } } private DiaNavigationData? GetDiaNavigationData(MethodDebugInformationHandle handle) { if (_reader == null) { throw new ObjectDisposedException(nameof(PortablePdbReader)); } DiaNavigationData? diaNavigationData = null; try { var methodDebugDefinition = _reader.GetMethodDebugInformation(handle); var fileName = GetMethodFileName(methodDebugDefinition); GetMethodMinAndMaxLineNumber(methodDebugDefinition, out var minLineNumber, out var maxLineNumber); diaNavigationData = new DiaNavigationData(fileName, minLineNumber, maxLineNumber); } catch (BadImageFormatException exception) { EqtTrace.Error("failed to get dia navigation data: {0}", exception); } return diaNavigationData; } private string GetMethodFileName(MethodDebugInformation methodDebugDefinition) { var fileName = string.Empty; if (!methodDebugDefinition.Document.IsNil) { var document = _reader.GetDocument(methodDebugDefinition.Document); fileName = _reader.GetString(document.Name); } return fileName; } }