diff options
Diffstat (limited to 'src/ToolBox/SOS/NETCore')
-rw-r--r-- | src/ToolBox/SOS/NETCore/.gitmirror | 1 | ||||
-rw-r--r-- | src/ToolBox/SOS/NETCore/SOS.NETCore.csproj | 61 | ||||
-rw-r--r-- | src/ToolBox/SOS/NETCore/SOS.NETCore.sln | 22 | ||||
-rw-r--r-- | src/ToolBox/SOS/NETCore/SymbolReader.cs | 694 | ||||
-rw-r--r-- | src/ToolBox/SOS/NETCore/project.json | 20 |
5 files changed, 798 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/NETCore/.gitmirror b/src/ToolBox/SOS/NETCore/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/ToolBox/SOS/NETCore/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/ToolBox/SOS/NETCore/SOS.NETCore.csproj b/src/ToolBox/SOS/NETCore/SOS.NETCore.csproj new file mode 100644 index 0000000000..bd9d2395f8 --- /dev/null +++ b/src/ToolBox/SOS/NETCore/SOS.NETCore.csproj @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> + + <PropertyGroup> + <AssemblyName>SOS.NETCore</AssemblyName> + <AssemblyVersion>1.0.0.0</AssemblyVersion> + <ProjectGuid>{20513BA2-A156-4A17-4C70-5AC2DBD4F833}</ProjectGuid> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <OutputType>Library</OutputType> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <NoStdLib>true</NoStdLib> + <NoCompilerStandardLib>true</NoCompilerStandardLib> + <UseOpenKey Condition="'$(UseOpenKey)'==''">true</UseOpenKey> + </PropertyGroup> + + <!-- Default configurations to help VS understand the options --> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'" /> + + <!-- Configuration specific properties --> + <PropertyGroup Condition="'$(Configuration)' == 'Debug' or '$(Configuration)' == 'Checked'"> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <DefineConstants>_DEBUG;DEBUG;TRACE;$(DefineConstants)</DefineConstants> + </PropertyGroup> + + <PropertyGroup Condition="'$(OsEnvironment)' == 'Unix'"> + <DebugSymbols>false</DebugSymbols> + <DebugType>none</DebugType> + </PropertyGroup> + + <ItemGroup> + <Compile Include="SymbolReader.cs" /> + </ItemGroup> + + <ItemGroup> + <None Include="project.json" /> + </ItemGroup> + + <Target Name="CopyItemsToDirectory" AfterTargets="Build"> + <Copy + SourceFiles="$(OutputPath)$(AssemblyName).dll" + DestinationFolder="$(BinDir)" + SkipUnchangedFiles="false" + OverwriteReadOnlyFiles="false" + UseHardlinksIfPossible="false"> + </Copy> + + <Copy + Condition="'$(OsEnvironment)' != 'Unix'" + SourceFiles="$(OutputPath)$(AssemblyName).pdb" + DestinationFolder="$(BinDir)\PDB" + SkipUnchangedFiles="false" + OverwriteReadOnlyFiles="false" + UseHardlinksIfPossible="false"> + </Copy> + </Target> + + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" /> +</Project> diff --git a/src/ToolBox/SOS/NETCore/SOS.NETCore.sln b/src/ToolBox/SOS/NETCore/SOS.NETCore.sln new file mode 100644 index 0000000000..0106883b9c --- /dev/null +++ b/src/ToolBox/SOS/NETCore/SOS.NETCore.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SOS.NETCore", "SOS.NETCore.csproj", "{20513BA2-A156-4A17-4C70-5AC2DBD4F833}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20513BA2-A156-4A17-4C70-5AC2DBD4F833}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ToolBox/SOS/NETCore/SymbolReader.cs b/src/ToolBox/SOS/NETCore/SymbolReader.cs new file mode 100644 index 0000000000..f4a3ce30d8 --- /dev/null +++ b/src/ToolBox/SOS/NETCore/SymbolReader.cs @@ -0,0 +1,694 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using System.Runtime.InteropServices; + +namespace SOS +{ + internal class SymbolReader + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct DebugInfo + { + public int lineNumber; + public int ilOffset; + public string fileName; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MethodDebugInfo + { + public IntPtr points; + public int size; + } + + /// <summary> + /// Read memory callback + /// </summary> + /// <returns>number of bytes read or 0 for error</returns> + internal unsafe delegate int ReadMemoryDelegate(IntPtr address, byte* buffer, int count); + + private sealed class OpenedReader : IDisposable + { + public readonly MetadataReaderProvider Provider; + public readonly MetadataReader Reader; + + public OpenedReader(MetadataReaderProvider provider, MetadataReader reader) + { + Debug.Assert(provider != null); + Debug.Assert(reader != null); + + Provider = provider; + Reader = reader; + } + + public void Dispose() => Provider.Dispose(); + } + + /// <summary> + /// Stream implementation to read debugger target memory for in-memory PDBs + /// </summary> + private class TargetStream : Stream + { + readonly IntPtr _address; + readonly ReadMemoryDelegate _readMemory; + + public override long Position { get; set; } + public override long Length { get; } + public override bool CanSeek { get { return true; } } + public override bool CanRead { get { return true; } } + public override bool CanWrite { get { return false; } } + + public TargetStream(IntPtr address, int size, ReadMemoryDelegate readMemory) + : base() + { + _address = address; + _readMemory = readMemory; + Length = size; + Position = 0; + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (Position + count > Length) + { + throw new ArgumentOutOfRangeException(); + } + unsafe + { + fixed (byte* p = &buffer[offset]) + { + int read = _readMemory(new IntPtr(_address.ToInt64() + Position), p, count); + Position += read; + return read; + } + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + switch (origin) + { + case SeekOrigin.Begin: + Position = offset; + break; + case SeekOrigin.End: + Position = Length + offset; + break; + case SeekOrigin.Current: + Position += offset; + break; + } + return Position; + } + + public override void Flush() + { + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + } + + /// <summary> + /// Checks availability of debugging information for given assembly. + /// </summary> + /// <param name="assemblyPath"> + /// File path of the assembly or null if the module is in-memory or dynamic (generated by Reflection.Emit) + /// </param> + /// <param name="isFileLayout">type of in-memory PE layout, if true, file based layout otherwise, loaded layout</param> + /// <param name="loadedPeAddress"> + /// Loaded PE image address or zero if the module is dynamic (generated by Reflection.Emit). + /// Dynamic modules have their PDBs (if any) generated to an in-memory stream + /// (pointed to by <paramref name="inMemoryPdbAddress"/> and <paramref name="inMemoryPdbSize"/>). + /// </param> + /// <param name="loadedPeSize">loaded PE image size</param> + /// <param name="inMemoryPdbAddress">in memory PDB address or zero</param> + /// <param name="inMemoryPdbSize">in memory PDB size</param> + /// <returns>Symbol reader handle or zero if error</returns> + internal static IntPtr LoadSymbolsForModule(string assemblyPath, bool isFileLayout, IntPtr loadedPeAddress, int loadedPeSize, + IntPtr inMemoryPdbAddress, int inMemoryPdbSize, ReadMemoryDelegate readMemory) + { + try + { + TargetStream peStream = null; + if (assemblyPath == null && loadedPeAddress != IntPtr.Zero) + { + peStream = new TargetStream(loadedPeAddress, loadedPeSize, readMemory); + } + TargetStream pdbStream = null; + if (inMemoryPdbAddress != IntPtr.Zero) + { + pdbStream = new TargetStream(inMemoryPdbAddress, inMemoryPdbSize, readMemory); + } + OpenedReader openedReader = GetReader(assemblyPath, isFileLayout, peStream, pdbStream); + if (openedReader != null) + { + GCHandle gch = GCHandle.Alloc(openedReader); + return GCHandle.ToIntPtr(gch); + } + } + catch + { + } + return IntPtr.Zero; + } + + /// <summary> + /// Cleanup and dispose of symbol reader handle + /// </summary> + /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param> + internal static void Dispose(IntPtr symbolReaderHandle) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + try + { + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + ((OpenedReader)gch.Target).Dispose(); + gch.Free(); + } + catch + { + } + } + + /// <summary> + /// Returns method token and IL offset for given source line number. + /// </summary> + /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param> + /// <param name="filePath">source file name and path</param> + /// <param name="lineNumber">source line number</param> + /// <param name="methodToken">method token return</param> + /// <param name="ilOffset">IL offset return</param> + /// <returns> true if information is available</returns> + internal static bool ResolveSequencePoint(IntPtr symbolReaderHandle, string filePath, int lineNumber, out int methodToken, out int ilOffset) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + methodToken = 0; + ilOffset = 0; + + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + MetadataReader reader = ((OpenedReader)gch.Target).Reader; + + try + { + string fileName = Path.GetFileName(filePath); + foreach (MethodDebugInformationHandle methodDebugInformationHandle in reader.MethodDebugInformation) + { + MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugInformationHandle); + SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); + foreach (SequencePoint point in sequencePoints) + { + string sourceName = reader.GetString(reader.GetDocument(point.Document).Name); + if (point.StartLine == lineNumber && Path.GetFileName(sourceName) == fileName) + { + methodToken = MetadataTokens.GetToken(methodDebugInformationHandle.ToDefinitionHandle()); + ilOffset = point.Offset; + return true; + } + } + } + } + catch + { + } + return false; + } + + /// <summary> + /// Returns source line number and source file name for given IL offset and method token. + /// </summary> + /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param> + /// <param name="methodToken">method token</param> + /// <param name="ilOffset">IL offset</param> + /// <param name="lineNumber">source line number return</param> + /// <param name="fileName">source file name return</param> + /// <returns> true if information is available</returns> + internal static bool GetLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out IntPtr fileName) + { + lineNumber = 0; + fileName = IntPtr.Zero; + + string sourceFileName = null; + + if (!GetSourceLineByILOffset(symbolReaderHandle, methodToken, ilOffset, out lineNumber, out sourceFileName)) + { + return false; + } + fileName = Marshal.StringToBSTR(sourceFileName); + sourceFileName = null; + return true; + } + + /// <summary> + /// Helper method to return source line number and source file name for given IL offset and method token. + /// </summary> + /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param> + /// <param name="methodToken">method token</param> + /// <param name="ilOffset">IL offset</param> + /// <param name="lineNumber">source line number return</param> + /// <param name="fileName">source file name return</param> + /// <returns> true if information is available</returns> + private static bool GetSourceLineByILOffset(IntPtr symbolReaderHandle, int methodToken, long ilOffset, out int lineNumber, out string fileName) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + lineNumber = 0; + fileName = null; + + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + MetadataReader reader = ((OpenedReader)gch.Target).Reader; + + try + { + Handle handle = MetadataTokens.Handle(methodToken); + if (handle.Kind != HandleKind.MethodDefinition) + return false; + + MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); + MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugHandle); + SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); + + SequencePoint nearestPoint = sequencePoints.GetEnumerator().Current; + foreach (SequencePoint point in sequencePoints) + { + if (point.Offset < ilOffset) + { + nearestPoint = point; + } + else + { + if (point.Offset == ilOffset) + nearestPoint = point; + + if (nearestPoint.StartLine == 0 || nearestPoint.StartLine == SequencePoint.HiddenLine) + return false; + + lineNumber = nearestPoint.StartLine; + fileName = reader.GetString(reader.GetDocument(nearestPoint.Document).Name); + return true; + } + } + } + catch + { + } + return false; + } + + /// <summary> + /// Returns local variable name for given local index and IL offset. + /// </summary> + /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param> + /// <param name="methodToken">method token</param> + /// <param name="localIndex">local variable index</param> + /// <param name="localVarName">local variable name return</param> + /// <returns>true if name has been found</returns> + internal static bool GetLocalVariableName(IntPtr symbolReaderHandle, int methodToken, int localIndex, out IntPtr localVarName) + { + localVarName = IntPtr.Zero; + + string localVar = null; + if (!GetLocalVariableByIndex(symbolReaderHandle, methodToken, localIndex, out localVar)) + return false; + + localVarName = Marshal.StringToBSTR(localVar); + localVar = null; + return true; + } + + /// <summary> + /// Helper method to return local variable name for given local index and IL offset. + /// </summary> + /// <param name="symbolReaderHandle">symbol reader handle returned by LoadSymbolsForModule</param> + /// <param name="methodToken">method token</param> + /// <param name="localIndex">local variable index</param> + /// <param name="localVarName">local variable name return</param> + /// <returns>true if name has been found</returns> + internal static bool GetLocalVariableByIndex(IntPtr symbolReaderHandle, int methodToken, int localIndex, out string localVarName) + { + Debug.Assert(symbolReaderHandle != IntPtr.Zero); + localVarName = null; + + GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle); + MetadataReader reader = ((OpenedReader)gch.Target).Reader; + + try + { + Handle handle = MetadataTokens.Handle(methodToken); + if (handle.Kind != HandleKind.MethodDefinition) + return false; + + MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); + LocalScopeHandleCollection localScopes = reader.GetLocalScopes(methodDebugHandle); + foreach (LocalScopeHandle scopeHandle in localScopes) + { + LocalScope scope = reader.GetLocalScope(scopeHandle); + LocalVariableHandleCollection localVars = scope.GetLocalVariables(); + foreach (LocalVariableHandle varHandle in localVars) + { + LocalVariable localVar = reader.GetLocalVariable(varHandle); + if (localVar.Index == localIndex) + { + if (localVar.Attributes == LocalVariableAttributes.DebuggerHidden) + return false; + + localVarName = reader.GetString(localVar.Name); + return true; + } + } + } + } + catch + { + } + return false; + } + + /// <summary> + /// Returns source name, line numbers and IL offsets for given method token. + /// </summary> + /// <param name="assemblyPath">file path of the assembly</param> + /// <param name="methodToken">method token</param> + /// <param name="debugInfo">structure with debug information return</param> + /// <returns>true if information is available</returns> + /// <remarks>used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs</remarks> + internal static bool GetInfoForMethod(string assemblyPath, int methodToken, ref MethodDebugInfo debugInfo) + { + try + { + List<DebugInfo> points = null; + + if (!GetDebugInfoForMethod(assemblyPath, methodToken, out points)) + { + return false; + } + var structSize = Marshal.SizeOf<DebugInfo>(); + + debugInfo.size = points.Count; + var ptr = debugInfo.points; + + foreach (var info in points) + { + Marshal.StructureToPtr(info, ptr, false); + ptr = (IntPtr)(ptr.ToInt64() + structSize); + } + return true; + } + catch + { + } + return false; + } + + /// <summary> + /// Helper method to return source name, line numbers and IL offsets for given method token. + /// </summary> + /// <param name="assemblyPath">file path of the assembly</param> + /// <param name="methodToken">method token</param> + /// <param name="points">list of debug information for each sequence point return</param> + /// <returns>true if information is available</returns> + /// <remarks>used by the gdb JIT support (not SOS). Does not support in-memory PEs or PDBs</remarks> + private static bool GetDebugInfoForMethod(string assemblyPath, int methodToken, out List<DebugInfo> points) + { + points = null; + + OpenedReader openedReader = GetReader(assemblyPath, isFileLayout: true, peStream: null, pdbStream: null); + if (openedReader == null) + return false; + + using (openedReader) + { + try + { + Handle handle = MetadataTokens.Handle(methodToken); + if (handle.Kind != HandleKind.MethodDefinition) + return false; + + points = new List<DebugInfo>(); + MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle(); + MethodDebugInformation methodDebugInfo = openedReader.Reader.GetMethodDebugInformation(methodDebugHandle); + SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints(); + + foreach (SequencePoint point in sequencePoints) + { + if (point.StartLine == 0 || point.StartLine == SequencePoint.HiddenLine) + continue; + + DebugInfo debugInfo = new DebugInfo(); + debugInfo.lineNumber = point.StartLine; + debugInfo.fileName = openedReader.Reader.GetString(openedReader.Reader.GetDocument(point.Document).Name); + debugInfo.ilOffset = point.Offset; + points.Add(debugInfo); + } + } + catch + { + return false; + } + } + return true; + } + + /// <summary> + /// Returns the portable PDB reader for the assembly path + /// </summary> + /// <param name="assemblyPath">file path of the assembly or null if the module is in-memory or dynamic</param> + /// <param name="isFileLayout">type of in-memory PE layout, if true, file based layout otherwise, loaded layout</param> + /// <param name="peStream">optional in-memory PE stream</param> + /// <param name="pdbStream">optional in-memory PDB stream</param> + /// <returns>reader/provider wrapper instance</returns> + /// <remarks> + /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around. + /// </remarks> + private static OpenedReader GetReader(string assemblyPath, bool isFileLayout, Stream peStream, Stream pdbStream) + { + return (pdbStream != null) ? TryOpenReaderForInMemoryPdb(pdbStream) : TryOpenReaderFromAssembly(assemblyPath, isFileLayout, peStream); + } + + private static OpenedReader TryOpenReaderForInMemoryPdb(Stream pdbStream) + { + Debug.Assert(pdbStream != null); + + byte[] buffer = new byte[sizeof(uint)]; + if (pdbStream.Read(buffer, 0, sizeof(uint)) != sizeof(uint)) + { + return null; + } + uint signature = BitConverter.ToUInt32(buffer, 0); + + // quick check to avoid throwing exceptions below in common cases: + const uint ManagedMetadataSignature = 0x424A5342; + if (signature != ManagedMetadataSignature) + { + // not a Portable PDB + return null; + } + + OpenedReader result = null; + MetadataReaderProvider provider = null; + try + { + pdbStream.Position = 0; + provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); + result = new OpenedReader(provider, provider.GetMetadataReader()); + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + return null; + } + finally + { + if (result == null) + { + provider?.Dispose(); + } + } + + return result; + } + + private static OpenedReader TryOpenReaderFromAssembly(string assemblyPath, bool isFileLayout, Stream peStream) + { + if (assemblyPath == null && peStream == null) + return null; + + PEStreamOptions options = isFileLayout ? PEStreamOptions.Default : PEStreamOptions.IsLoadedImage; + if (peStream == null) + { + peStream = TryOpenFile(assemblyPath); + if (peStream == null) + return null; + + options = PEStreamOptions.Default; + } + + try + { + using (var peReader = new PEReader(peStream, options)) + { + DebugDirectoryEntry codeViewEntry, embeddedPdbEntry; + ReadPortableDebugTableEntries(peReader, out codeViewEntry, out embeddedPdbEntry); + + // First try .pdb file specified in CodeView data (we prefer .pdb file on disk over embedded PDB + // since embedded PDB needs decompression which is less efficient than memory-mapping the file). + if (codeViewEntry.DataSize != 0) + { + var result = TryOpenReaderFromCodeView(peReader, codeViewEntry, assemblyPath); + if (result != null) + { + return result; + } + } + + // if it failed try Embedded Portable PDB (if available): + if (embeddedPdbEntry.DataSize != 0) + { + return TryOpenReaderFromEmbeddedPdb(peReader, embeddedPdbEntry); + } + } + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + // nop + } + + return null; + } + + private static void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry) + { + // See spec: https://github.com/dotnet/corefx/blob/master/src/System.Reflection.Metadata/specs/PE-COFF.md + + codeViewEntry = default(DebugDirectoryEntry); + embeddedPdbEntry = default(DebugDirectoryEntry); + + foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) + { + if (entry.Type == DebugDirectoryEntryType.CodeView) + { + const ushort PortableCodeViewVersionMagic = 0x504d; + if (entry.MinorVersion != PortableCodeViewVersionMagic) + { + continue; + } + + codeViewEntry = entry; + } + else if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb) + { + embeddedPdbEntry = entry; + } + } + } + + private static OpenedReader TryOpenReaderFromCodeView(PEReader peReader, DebugDirectoryEntry codeViewEntry, string assemblyPath) + { + OpenedReader result = null; + MetadataReaderProvider provider = null; + try + { + var data = peReader.ReadCodeViewDebugDirectoryData(codeViewEntry); + + string pdbPath = data.Path; + if (assemblyPath != null) + { + try + { + pdbPath = Path.Combine(Path.GetDirectoryName(assemblyPath), Path.GetFileName(pdbPath)); + } + catch + { + // invalid characters in CodeView path + return null; + } + } + + var pdbStream = TryOpenFile(pdbPath); + if (pdbStream == null) + { + return null; + } + + provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); + var reader = provider.GetMetadataReader(); + + // Validate that the PDB matches the assembly version + if (data.Age == 1 && new BlobContentId(reader.DebugMetadataHeader.Id) == new BlobContentId(data.Guid, codeViewEntry.Stamp)) + { + result = new OpenedReader(provider, reader); + } + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + return null; + } + finally + { + if (result == null) + { + provider?.Dispose(); + } + } + + return result; + } + + private static OpenedReader TryOpenReaderFromEmbeddedPdb(PEReader peReader, DebugDirectoryEntry embeddedPdbEntry) + { + OpenedReader result = null; + MetadataReaderProvider provider = null; + + try + { + // TODO: We might want to cache this provider globally (across stack traces), + // since decompressing embedded PDB takes some time. + provider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry); + result = new OpenedReader(provider, provider.GetMetadataReader()); + } + catch (Exception e) when (e is BadImageFormatException || e is IOException) + { + return null; + } + finally + { + if (result == null) + { + provider?.Dispose(); + } + } + + return result; + } + + private static Stream TryOpenFile(string path) + { + if (!File.Exists(path)) + { + return null; + } + try + { + return File.OpenRead(path); + } + catch + { + return null; + } + } + } +} diff --git a/src/ToolBox/SOS/NETCore/project.json b/src/ToolBox/SOS/NETCore/project.json new file mode 100644 index 0000000000..e9e4f0999f --- /dev/null +++ b/src/ToolBox/SOS/NETCore/project.json @@ -0,0 +1,20 @@ +{ + "dependencies": { + "Microsoft.NETCore.Platforms": "1.0.1", + "System.IO.FileSystem": "4.0.1", + "System.Runtime.InteropServices": "4.1.0", + "System.Reflection.Metadata": "1.4.1-beta-24417-02" + }, + "frameworks": { + "netcoreapp1.0": {} + }, + "runtimes": { + "win7-x86": {}, + "win7-x64": {}, + "ubuntu.14.04-x64": {}, + "osx.10.10-x64": {}, + "centos.7-x64": {}, + "rhel.7-x64": {}, + "debian.8-x64": {} + } +} |