Cleanup SymbolService; move code out of symbol service wrapper (#2794)
authorMike McLaughlin <mikem@microsoft.com>
Sat, 29 Jan 2022 22:38:30 +0000 (14:38 -0800)
committerGitHub <noreply@github.com>
Sat, 29 Jan 2022 22:38:30 +0000 (14:38 -0800)
* Make SymbolServiceWrapper per-target

Since SymbolServiceWrapper uses the MemoryService then it makes sense to make
it a per-target instead of global. Refactor the native get service wrapper into
ServiceWrapper.

Cleanup/replace InitializeSymbolService with call to GetSymbolService() directly.

* DownloadModule API - need to dispose of streams

* Cleanup SymbolService; move code out of symbol service wrapper

Allows new commands to use the source/line number, local var, etc. symbol
service APIs. Now the symbol service wrapper is just about interop.

Add ISymbolFile.

22 files changed:
src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolFile.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs
src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs
src/Microsoft.Diagnostics.DebugServices/ISymbolFile.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs
src/SOS/SOS.Extensions/HostServices.cs
src/SOS/SOS.Hosting/HostWrapper.cs
src/SOS/SOS.Hosting/SOSHost.cs
src/SOS/SOS.Hosting/SOSLibrary.cs
src/SOS/SOS.Hosting/ServiceWrapper.cs [new file with mode: 0644]
src/SOS/SOS.Hosting/SymbolServiceWrapper.cs
src/SOS/SOS.Hosting/TargetWrapper.cs
src/SOS/Strike/platform/runtimeimpl.cpp
src/SOS/Strike/platform/targetimpl.cpp
src/SOS/Strike/platform/targetimpl.h
src/SOS/Strike/symbols.cpp
src/SOS/Strike/symbols.h
src/SOS/extensions/extensions.cpp
src/SOS/inc/host.h
src/SOS/inc/target.h

index c38f459a568e8b369be191b1177d3cd074f5425c..325705a4bc654b9754e85ca71bc598ba03dc3bd4 100644 (file)
@@ -48,12 +48,12 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             ServiceProvider = new ServiceProvider();
             ServiceProvider.AddServiceFactoryWithNoCaching<PEFile>(() => GetPEInfo());
 
-            ServiceProvider.AddServiceFactory<PEReader>(() => ModuleService.GetPEReader(this));
+            ServiceProvider.AddServiceFactory<PEReader>(() => Utilities.OpenPEReader(ModuleService.SymbolService.DownloadModule(this)));
             if (target.OperatingSystem == OSPlatform.Linux) {
-                ServiceProvider.AddServiceFactory<ELFFile>(() => ModuleService.GetELFFile(this));
+                ServiceProvider.AddServiceFactory<ELFFile>(() => Utilities.OpenELFFile(ModuleService.SymbolService.DownloadModule(this)));
             }
             if (target.OperatingSystem == OSPlatform.OSX) {
-                ServiceProvider.AddServiceFactory<MachOFile>(() => ModuleService.GetMachOFile(this));
+                ServiceProvider.AddServiceFactory<MachOFile>(() => Utilities.OpenMachOFile(ModuleService.SymbolService.DownloadModule(this)));
             }
             _onChangeEvent = target.Services.GetService<ISymbolService>()?.OnChangeEvent.Register(() => {
                 ServiceProvider.RemoveService(typeof(MachOFile)); 
index db9576604089ab33ce3c94fa744250961aba2a45..480c587d4b6d04b2a801fbaf5b710d3328256a4e 100644 (file)
@@ -6,14 +6,11 @@ using Microsoft.FileFormats;
 using Microsoft.FileFormats.ELF;
 using Microsoft.FileFormats.MachO;
 using Microsoft.FileFormats.PE;
-using Microsoft.SymbolStore;
-using Microsoft.SymbolStore.KeyGenerators;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
-using System.Reflection.PortableExecutable;
 using System.Runtime.InteropServices;
 using System.Text;
 
@@ -279,256 +276,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return null;
         }
 
-        /// <summary>
-        /// Finds or downloads the module and creates a PEReader for it.
-        /// </summary>
-        /// <param name="module">module instance</param>
-        /// <returns>reader or null</returns>
-        internal PEReader GetPEReader(IModule module)
-        {
-            if (!module.IndexTimeStamp.HasValue || !module.IndexFileSize.HasValue)
-            {
-                Trace.TraceWarning($"GetPEReader: module {module.FileName} has no index timestamp/filesize");
-                return null;
-            }
-
-            SymbolStoreKey moduleKey = PEFileKeyGenerator.GetKey(Path.GetFileName(module.FileName), module.IndexTimeStamp.Value, module.IndexFileSize.Value);
-            if (moduleKey is null)
-            {
-                Trace.TraceWarning($"GetPEReader: no index generated for module {module.FileName} ");
-                return null;
-            }
-
-            if (File.Exists(module.FileName))
-            {
-                Stream stream = OpenFile(module.FileName);
-                if (stream is not null)
-                {
-                    var peFile = new PEFile(new StreamAddressSpace(stream), false);
-                    var generator = new PEFileKeyGenerator(Tracer.Instance, peFile, module.FileName);
-                    IEnumerable<SymbolStoreKey> keys = generator.GetKeys(KeyTypeFlags.IdentityKey);
-                    foreach (SymbolStoreKey key in keys)
-                    {
-                        if (moduleKey.Equals(key))
-                        {
-                            Trace.TraceInformation("GetPEReader: local file match {0}", module.FileName);
-                            return OpenPEReader(module.FileName);
-                        }
-                    }
-                }
-            }
-
-            // Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
-            string downloadFilePath = SymbolService.DownloadFile(moduleKey);
-            if (!string.IsNullOrEmpty(downloadFilePath))
-            {
-                Trace.TraceInformation("GetPEReader: downloaded {0}", downloadFilePath);
-                return OpenPEReader(downloadFilePath);
-            }
-
-            return null;
-        }
-
-        /// <summary>
-        /// Opens and returns an PEReader instance from the local file path
-        /// </summary>
-        /// <param name="filePath">PE file to open</param>
-        /// <returns>PEReader instance or null</returns>
-        private PEReader OpenPEReader(string filePath)
-        {
-            Stream stream = OpenFile(filePath);
-            if (stream is not null)
-            {
-                try
-                {
-                    var reader = new PEReader(stream);
-                    if (reader.PEHeaders == null || reader.PEHeaders.PEHeader == null)
-                    {
-                        Trace.TraceError($"OpenPEReader: PEReader invalid headers");
-                        return null;
-                    }
-                    return reader;
-                }
-                catch (Exception ex) when (ex is BadImageFormatException || ex is IOException)
-                {
-                    Trace.TraceError($"OpenPEReader: PEReader exception {ex.Message}");
-                }
-            }
-            return null;
-        }
-
-        /// <summary>
-        /// Finds or downloads the ELF module and creates a ELFFile instance for it.
-        /// </summary>
-        /// <param name="module">module instance</param>
-        /// <returns>ELFFile instance or null</returns>
-        internal ELFFile GetELFFile(IModule module)
-        {
-            if (module.BuildId.IsDefaultOrEmpty)
-            {
-                Trace.TraceWarning($"GetELFFile: module {module.FileName} has no build id");
-                return null;
-            }
-
-            SymbolStoreKey moduleKey = ELFFileKeyGenerator.GetKeys(KeyTypeFlags.IdentityKey, module.FileName, module.BuildId.ToArray(), symbolFile: false, symbolFileName: null).SingleOrDefault();
-            if (moduleKey is null)
-            {
-                Trace.TraceWarning($"GetELFFile: no index generated for module {module.FileName} ");
-                return null;
-            }
-
-            if (File.Exists(module.FileName))
-            {
-                ELFFile elfFile = OpenELFFile(module.FileName);
-                if (elfFile is not null)
-                {
-                    var generator = new ELFFileKeyGenerator(Tracer.Instance, elfFile, module.FileName);
-                    IEnumerable<SymbolStoreKey> keys = generator.GetKeys(KeyTypeFlags.IdentityKey);
-                    foreach (SymbolStoreKey key in keys)
-                    {
-                        if (moduleKey.Equals(key))
-                        {
-                            Trace.TraceInformation("GetELFFile: local file match {0}", module.FileName);
-                            return elfFile;
-                        }
-                    }
-                }
-            }
-
-            // Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
-            string downloadFilePath = SymbolService.DownloadFile(moduleKey);
-            if (!string.IsNullOrEmpty(downloadFilePath))
-            {
-                Trace.TraceInformation("GetELFFile: downloaded {0}", downloadFilePath);
-                return OpenELFFile(downloadFilePath);
-            }
-
-            return null;
-        }
-
-        /// <summary>
-        /// Opens and returns an ELFFile instance from the local file path
-        /// </summary>
-        /// <param name="filePath">ELF file to open</param>
-        /// <returns>ELFFile instance or null</returns>
-        private ELFFile OpenELFFile(string filePath)
-        {
-            Stream stream = OpenFile(filePath);
-            if (stream is not null)
-            {
-                try
-                {
-                    ELFFile elfFile = new ELFFile(new StreamAddressSpace(stream), position: 0, isDataSourceVirtualAddressSpace: false);
-                    if (!elfFile.IsValid())
-                    {
-                        Trace.TraceError($"OpenELFFile: not a valid file");
-                        return null;
-                    }
-                    return elfFile;
-                }
-                catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
-                {
-                    Trace.TraceError($"OpenELFFile: exception {ex.Message}");
-                }
-            }
-            return null;
-        }
-
-        /// <summary>
-        /// Finds or downloads the ELF module and creates a MachOFile instance for it.
-        /// </summary>
-        /// <param name="module">module instance</param>
-        /// <returns>MachO file instance or null</returns>
-        internal MachOFile GetMachOFile(IModule module)
-        {
-            if (module.BuildId.IsDefaultOrEmpty)
-            {
-                Trace.TraceWarning($"GetMachOFile: module {module.FileName} has no build id");
-                return null;
-            }
-
-            SymbolStoreKey moduleKey = MachOFileKeyGenerator.GetKeys(KeyTypeFlags.IdentityKey, module.FileName, module.BuildId.ToArray(), symbolFile: false, symbolFileName: null).SingleOrDefault();
-            if (moduleKey is null)
-            {
-                Trace.TraceWarning($"GetMachOFile: no index generated for module {module.FileName} ");
-                return null;
-            }
-
-            if (File.Exists(module.FileName))
-            {
-                MachOFile machOFile = OpenMachOFile(module.FileName);
-                if (machOFile is not null)
-                {
-                    var generator = new MachOFileKeyGenerator(Tracer.Instance, machOFile, module.FileName);
-                    IEnumerable<SymbolStoreKey> keys = generator.GetKeys(KeyTypeFlags.IdentityKey);
-                    foreach (SymbolStoreKey key in keys)
-                    {
-                        if (moduleKey.Equals(key))
-                        {
-                            Trace.TraceInformation("GetMachOFile: local file match {0}", module.FileName);
-                            return machOFile;
-                        }
-                    }
-                }
-            }
-
-            // Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
-            string downloadFilePath = SymbolService.DownloadFile(moduleKey);
-            if (!string.IsNullOrEmpty(downloadFilePath))
-            {
-                Trace.TraceInformation("GetMachOFile: downloaded {0}", downloadFilePath);
-                return OpenMachOFile(downloadFilePath);
-            }
-
-            return null;
-        }
-
-        /// <summary>
-        /// Opens and returns an MachOFile instance from the local file path
-        /// </summary>
-        /// <param name="filePath">MachO file to open</param>
-        /// <returns>MachOFile instance or null</returns>
-        private MachOFile OpenMachOFile(string filePath)
-        {
-            Stream stream = OpenFile(filePath);
-            if (stream is not null)
-            {
-                try
-                {
-                    var machoFile = new MachOFile(new StreamAddressSpace(stream), position: 0, dataSourceIsVirtualAddressSpace: false);
-                    if (!machoFile.IsValid())
-                    {
-                        Trace.TraceError($"OpenMachOFile: not a valid file");
-                        return null;
-                    }
-                    return machoFile;
-                }
-                catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
-                {
-                    Trace.TraceError($"OpenMachOFile: exception {ex.Message}");
-                }
-            }
-            return null;
-        }
-
-        /// <summary>
-        /// Opens and returns a file stream
-        /// </summary>
-        /// <param name="filePath">file to open</param>
-        /// <returns>stream or null</returns>
-        private Stream OpenFile(string filePath)
-        {
-            try
-            {
-                return File.OpenRead(filePath);
-            }
-            catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException || ex is UnauthorizedAccessException || ex is IOException)
-            {
-                Trace.TraceError($"OpenFile: OpenRead exception {ex.Message}");
-                return null;
-            }
-        }
-
         /// <summary>
         /// Returns the ELF module build id or the MachO module uuid
         /// </summary>
diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolFile.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolFile.cs
new file mode 100644 (file)
index 0000000..da2394e
--- /dev/null
@@ -0,0 +1,169 @@
+// 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.Diagnostics;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+namespace Microsoft.Diagnostics.DebugServices.Implementation
+{
+    internal sealed class SymbolFile : ISymbolFile, IDisposable
+    {
+        private readonly MetadataReaderProvider _provider;
+        private readonly MetadataReader _reader;
+
+        public SymbolFile(MetadataReaderProvider provider, MetadataReader reader)
+        {
+            Debug.Assert(provider != null);
+            Debug.Assert(reader != null);
+
+            _provider = provider;
+            _reader = reader;
+        }
+
+        public void Dispose() => _provider.Dispose();
+
+        /// <summary>
+        /// Returns method token and IL offset for given source line number.
+        /// </summary>
+        /// <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>
+        public bool ResolveSequencePoint(
+            string filePath,
+            int lineNumber,
+            out int methodToken,
+            out int ilOffset)
+        {
+            methodToken = 0;
+            ilOffset = 0;
+            try
+            {
+                string fileName = SymbolService.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 && SymbolService.GetFileName(sourceName) == fileName)
+                        {
+                            methodToken = MetadataTokens.GetToken(methodDebugInformationHandle.ToDefinitionHandle());
+                            ilOffset = point.Offset;
+                            return true;
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                Trace.TraceError($"ResolveSequencePoint: {ex.Message}");
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns source line number and source file name for given IL offset and method token.
+        /// </summary>
+        /// <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>
+        public bool GetSourceLineByILOffset(
+            int methodToken,
+            long ilOffset,
+            out int lineNumber,
+            out string fileName)
+        {
+            lineNumber = 0;
+            fileName = null;
+            try
+            {
+                Handle handle = MetadataTokens.Handle(methodToken);
+                if (handle.Kind != HandleKind.MethodDefinition)
+                    return false;
+
+                MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle();
+                if (methodDebugHandle.IsNil)
+                    return false;
+
+                MethodDebugInformation methodDebugInfo = _reader.GetMethodDebugInformation(methodDebugHandle);
+                SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints();
+
+                SequencePoint? nearestPoint = null;
+                foreach (SequencePoint point in sequencePoints)
+                {
+                    if (point.Offset > ilOffset)
+                        break;
+
+                    if (point.StartLine != 0 && !point.IsHidden)
+                        nearestPoint = point;
+                }
+
+                if (nearestPoint.HasValue)
+                {
+                    lineNumber = nearestPoint.Value.StartLine;
+                    fileName = _reader.GetString(_reader.GetDocument(nearestPoint.Value.Document).Name);
+                    return true;
+                }
+            }
+            catch (Exception ex)
+            {
+                Trace.TraceError($"GetSourceLineByILOffset: {ex.Message}");
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns local variable name for given local index and IL offset.
+        /// </summary>
+        /// <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>
+        public bool GetLocalVariableByIndex(
+            int methodToken,
+            int localIndex,
+            out string localVarName)
+        {
+            localVarName = null;
+            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 (Exception ex)
+            {
+                Trace.TraceError($"GetLocalVariableByIndex: {ex.Message}");
+            }
+            return false;
+        }
+    }
+}
index 35fe3fed19b88838ddaec1f07edf186a466ae858..fad23056283716965c6c4df282cf92e5c27551f8 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using Microsoft.FileFormats;
+using Microsoft.FileFormats.PE;
 using Microsoft.SymbolStore;
 using Microsoft.SymbolStore.KeyGenerators;
 using Microsoft.SymbolStore.SymbolStores;
@@ -13,6 +14,7 @@ using System.Collections.Immutable;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
+using System.Reflection.Metadata;
 using System.Reflection.PortableExecutable;
 using System.Runtime.InteropServices;
 using System.Text;
@@ -41,6 +43,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             OnChangeEvent = new ServiceEvent();
         }
 
+        #region ISymbolService
+
         /// <summary>
         /// Invoked when anything changes in the symbol service (adding servers, caches, or directories, clearing store, etc.)
         /// </summary>
@@ -325,6 +329,28 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             SetSymbolStore(null);
         }
 
+        /// <summary>
+        /// Downloads module file
+        /// </summary>
+        /// <param name="module">module interface</param>
+        /// <returns>module path or null</returns>
+        public string DownloadModule(IModule module)
+        {
+            string downloadFilePath = DownloadPE(module);
+            if (downloadFilePath is null)
+            {
+                if (module.Target.OperatingSystem == OSPlatform.Linux)
+                {
+                    downloadFilePath = DownloadELF(module);
+                }
+                else if (module.Target.OperatingSystem == OSPlatform.OSX)
+                {
+                    downloadFilePath = DownloadMachO(module);
+                }
+            }
+            return downloadFilePath;
+        }
+
         /// <summary>
         /// Download a file from the symbol stores/server.
         /// </summary>
@@ -367,27 +393,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return downloadFilePath;
         }
 
-        /// <summary>
-        /// Attempts to download/retrieve from cache the key.
-        /// </summary>
-        /// <param name="key">index of the file to retrieve</param>
-        /// <returns>stream or null</returns>
-        public SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key)
-        {
-            if (IsSymbolStoreEnabled)
-            {
-                try
-                {
-                    return _symbolStore.GetFile(key, CancellationToken.None).GetAwaiter().GetResult();
-                }
-                catch (Exception ex) when (ex is UnauthorizedAccessException || ex is BadImageFormatException || ex is IOException)
-                {
-                    Trace.TraceError("Exception: {0}", ex.ToString());
-                }
-            }
-            return null;
-        }
-
         /// <summary>
         /// Returns the metadata for the assembly
         /// </summary>
@@ -402,7 +407,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 Stream peStream = null;
                 if (imagePath != null && File.Exists(imagePath))
                 {
-                    peStream = TryOpenFile(imagePath);
+                    peStream = Utilities.TryOpenFile(imagePath);
                 }
                 else if (IsSymbolStoreEnabled)
                 {
@@ -430,6 +435,380 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return ImmutableArray<byte>.Empty;
         }
 
+        /// <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">in-memory PE stream or null</param>
+        /// <returns>symbol file or null</returns>
+        /// <remarks>
+        /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around.
+        /// </remarks>
+        public ISymbolFile OpenSymbolFile(string assemblyPath, bool isFileLayout, Stream peStream)
+        {
+            if (assemblyPath == null && peStream == null) throw new ArgumentNullException(nameof(assemblyPath));
+            if (peStream is not null && !peStream.CanSeek) throw new ArgumentException(nameof(peStream));
+
+            PEStreamOptions options = isFileLayout ? PEStreamOptions.Default : PEStreamOptions.IsLoadedImage;
+            if (peStream == null)
+            {
+                peStream = Utilities.TryOpenFile(assemblyPath);
+                if (peStream == null)
+                    return null;
+                
+                options = PEStreamOptions.Default;
+            }
+
+            try
+            {
+                using (var peReader = new PEReader(peStream, options))
+                {
+                    ReadPortableDebugTableEntries(peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry 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;
+        }
+
+        /// <summary>
+        /// Returns the portable PDB reader for the portable PDB stream
+        /// </summary>
+        /// <param name="pdbStream">portable PDB memory or file stream</param>
+        /// <returns>symbol file or null</returns>
+        /// <remarks>
+        /// Assumes that the PDB loaded into memory can be unloaded or moved around.
+        /// </remarks>
+        public ISymbolFile OpenSymbolFile(Stream pdbStream)
+        {
+            if (pdbStream != null) throw new ArgumentNullException(nameof(pdbStream));
+            if (!pdbStream.CanSeek) throw new ArgumentException(nameof(pdbStream));
+
+            byte[] buffer = new byte[sizeof(uint)];
+            pdbStream.Position = 0;
+            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;
+            }
+
+            SymbolFile result = null;
+            MetadataReaderProvider provider = null;
+            try
+            {
+                pdbStream.Position = 0;
+                provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
+                result = new SymbolFile(provider, provider.GetMetadataReader());
+            }
+            catch (Exception e) when (e is BadImageFormatException || e is IOException)
+            {
+                return null;
+            }
+            finally
+            {
+                if (result == null)
+                {
+                    provider?.Dispose();
+                }
+            }
+
+            return result;
+        }
+
+        #endregion
+
+        /// <summary>
+        /// Finds or downloads the PE module
+        /// </summary>
+        /// <param name="module">module instance</param>
+        /// <returns>module path or null</returns>
+        private string DownloadPE(IModule module)
+        {
+            if (!module.IndexTimeStamp.HasValue || !module.IndexFileSize.HasValue)
+            {
+                Trace.TraceWarning($"DownLoadPE: module {module.FileName} has no index timestamp/filesize");
+                return null;
+            }
+
+            SymbolStoreKey moduleKey = PEFileKeyGenerator.GetKey(Path.GetFileName(module.FileName), module.IndexTimeStamp.Value, module.IndexFileSize.Value);
+            if (moduleKey is null)
+            {
+                Trace.TraceWarning($"DownLoadPE: no index generated for module {module.FileName} ");
+                return null;
+            }
+
+            if (File.Exists(module.FileName))
+            {
+                using Stream stream = Utilities.TryOpenFile(module.FileName);
+                if (stream is not null)
+                {
+                    var peFile = new PEFile(new StreamAddressSpace(stream), false);
+                    var generator = new PEFileKeyGenerator(Tracer.Instance, peFile, module.FileName);
+                    IEnumerable<SymbolStoreKey> keys = generator.GetKeys(KeyTypeFlags.IdentityKey);
+                    foreach (SymbolStoreKey key in keys)
+                    {
+                        if (moduleKey.Equals(key))
+                        {
+                            Trace.TraceInformation("DownloadPE: local file match {0}", module.FileName);
+                            return module.FileName;
+                        }
+                    }
+                }
+            }
+
+            // Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
+            string downloadFilePath = DownloadFile(moduleKey);
+            if (!string.IsNullOrEmpty(downloadFilePath))
+            {
+                Trace.TraceInformation("DownloadPE: downloaded {0}", downloadFilePath);
+                return downloadFilePath;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Finds or downloads the ELF module
+        /// </summary>
+        /// <param name="module">module instance</param>
+        /// <returns>module path or null</returns>
+        private string DownloadELF(IModule module)
+        {
+            if (module.BuildId.IsDefaultOrEmpty)
+            {
+                Trace.TraceWarning($"DownloadELF: module {module.FileName} has no build id");
+                return null;
+            }
+
+            SymbolStoreKey moduleKey = ELFFileKeyGenerator.GetKeys(KeyTypeFlags.IdentityKey, module.FileName, module.BuildId.ToArray(), symbolFile: false, symbolFileName: null).SingleOrDefault();
+            if (moduleKey is null)
+            {
+                Trace.TraceWarning($"DownloadELF: no index generated for module {module.FileName} ");
+                return null;
+            }
+
+            if (File.Exists(module.FileName))
+            {
+                using Utilities.ELFModule elfModule = Utilities.OpenELFFile(module.FileName);
+                if (elfModule is not null)
+                {
+                    var generator = new ELFFileKeyGenerator(Tracer.Instance, elfModule, module.FileName);
+                    IEnumerable<SymbolStoreKey> keys = generator.GetKeys(KeyTypeFlags.IdentityKey);
+                    foreach (SymbolStoreKey key in keys)
+                    {
+                        if (moduleKey.Equals(key))
+                        {
+                            Trace.TraceInformation("DownloadELF: local file match {0}", module.FileName);
+                            return module.FileName;
+                        }
+                    }
+                }
+            }
+
+            // Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
+            string downloadFilePath = DownloadFile(moduleKey);
+            if (!string.IsNullOrEmpty(downloadFilePath))
+            {
+                Trace.TraceInformation("DownloadELF: downloaded {0}", downloadFilePath);
+                return downloadFilePath;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Finds or downloads the MachO module.
+        /// </summary>
+        /// <param name="module">module instance</param>
+        /// <returns>module path or null</returns>
+        private string DownloadMachO(IModule module)
+        {
+            if (module.BuildId.IsDefaultOrEmpty)
+            {
+                Trace.TraceWarning($"DownloadMachO: module {module.FileName} has no build id");
+                return null;
+            }
+
+            SymbolStoreKey moduleKey = MachOFileKeyGenerator.GetKeys(KeyTypeFlags.IdentityKey, module.FileName, module.BuildId.ToArray(), symbolFile: false, symbolFileName: null).SingleOrDefault();
+            if (moduleKey is null)
+            {
+                Trace.TraceWarning($"DownloadMachO: no index generated for module {module.FileName} ");
+                return null;
+            }
+
+            if (File.Exists(module.FileName))
+            {
+                using Utilities.MachOModule machOModule = Utilities.OpenMachOFile(module.FileName);
+                if (machOModule is not null)
+                {
+                    var generator = new MachOFileKeyGenerator(Tracer.Instance, machOModule, module.FileName);
+                    IEnumerable<SymbolStoreKey> keys = generator.GetKeys(KeyTypeFlags.IdentityKey);
+                    foreach (SymbolStoreKey key in keys)
+                    {
+                        if (moduleKey.Equals(key))
+                        {
+                            Trace.TraceInformation("DownloadMachO: local file match {0}", module.FileName);
+                            return module.FileName;
+                        }
+                    }
+                }
+            }
+
+            // Now download the module from the symbol server if local file doesn't exists or doesn't have the right key
+            string downloadFilePath = DownloadFile(moduleKey);
+            if (!string.IsNullOrEmpty(downloadFilePath))
+            {
+                Trace.TraceInformation("DownloadMachO: downloaded {0}", downloadFilePath);
+                return downloadFilePath;
+            }
+
+            return null;
+        }
+
+        private void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry)
+        {
+            // See spec: https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md
+
+            codeViewEntry = default;
+            embeddedPdbEntry = default;
+
+            foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory())
+            {
+                if (entry.Type == DebugDirectoryEntryType.CodeView)
+                {
+                    if (entry.MinorVersion != ImageDebugDirectory.PortablePDBMinorVersion)
+                    {
+                        continue;
+                    }
+                    codeViewEntry = entry;
+                }
+                else if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
+                {
+                    embeddedPdbEntry = entry;
+                }
+            }
+        }
+
+        private SymbolFile TryOpenReaderFromCodeView(PEReader peReader, DebugDirectoryEntry codeViewEntry, string assemblyPath)
+        {
+            SymbolFile result = null;
+            MetadataReaderProvider provider = null;
+            try
+            {
+                CodeViewDebugDirectoryData data = peReader.ReadCodeViewDebugDirectoryData(codeViewEntry);
+                string pdbPath = data.Path;
+                Stream pdbStream = null;
+
+                if (assemblyPath != null) 
+                {
+                    try
+                    {
+                        pdbPath = Path.Combine(Path.GetDirectoryName(assemblyPath), GetFileName(pdbPath));
+                    }
+                    catch
+                    {
+                        // invalid characters in CodeView path
+                        return null;
+                    }
+                    pdbStream = Utilities.TryOpenFile(pdbPath);
+                }
+
+                if (pdbStream == null)
+                {
+                    if (IsSymbolStoreEnabled)
+                    {
+                        Debug.Assert(codeViewEntry.MinorVersion == ImageDebugDirectory.PortablePDBMinorVersion);
+                        SymbolStoreKey key = PortablePDBFileKeyGenerator.GetKey(pdbPath, data.Guid);
+                        pdbStream = GetSymbolStoreFile(key)?.Stream;
+                    }
+                    if (pdbStream == null)
+                    {
+                        return null;
+                    }
+                    // Make sure the stream is at the beginning of the pdb.
+                    pdbStream.Position = 0;
+                }
+
+                provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
+                MetadataReader 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 SymbolFile(provider, reader);
+                }
+            }
+            catch (Exception e) when (e is BadImageFormatException || e is IOException)
+            {
+                return null;
+            }
+            finally
+            {
+                if (result == null)
+                {
+                    provider?.Dispose();
+                }
+            }
+
+            return result;
+        }
+
+        private SymbolFile TryOpenReaderFromEmbeddedPdb(PEReader peReader, DebugDirectoryEntry embeddedPdbEntry)
+        {
+            SymbolFile 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 SymbolFile(provider, provider.GetMetadataReader());
+            }
+            catch (Exception e) when (e is BadImageFormatException || e is IOException)
+            {
+                return null;
+            }
+            finally
+            {
+                if (result == null)
+                {
+                    provider?.Dispose();
+                }
+            }
+
+            return result;
+        }
+
         /// <summary>
         /// Displays the symbol server and cache configuration
         /// </summary>
@@ -445,6 +824,25 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return sb.ToString();
         }
 
+        /// <summary>
+        /// Attempts to download/retrieve from cache the key.
+        /// </summary>
+        /// <param name="key">index of the file to retrieve</param>
+        /// <returns>stream or null</returns>
+        private SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key)
+        {
+            Debug.Assert(IsSymbolStoreEnabled);
+            try
+            {
+                return _symbolStore.GetFile(key, CancellationToken.None).GetAwaiter().GetResult();
+            }
+            catch (Exception ex) when (ex is UnauthorizedAccessException || ex is BadImageFormatException || ex is IOException)
+            {
+                Trace.TraceError("Exception: {0}", ex.ToString());
+            }
+            return null;
+        }
+
         /// <summary>
         /// Sets a new store store head.
         /// </summary>
@@ -476,6 +874,21 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
             return false;
         }
 
+        /// <summary>
+        /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux
+        /// </summary>
+        /// <param name="pathName"> File path to be processed </param>
+        /// <returns>Last component of path</returns>
+        internal static string GetFileName(string pathName)
+        {
+            int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'});
+            if (pos < 0)
+            {
+                return pathName;
+            }
+            return pathName.Substring(pos + 1);
+        }
+
         /// <summary>
         /// Compares two file paths using OS specific casing.
         /// </summary>
@@ -490,26 +903,5 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
                 return string.Equals(path1, path2);
             }
         }
-
-        /// <summary>
-        /// Attempt to open a file stream.
-        /// </summary>
-        /// <param name="path">file path</param>
-        /// <returns>stream or null if doesn't exist or error</returns>
-        private Stream TryOpenFile(string path)
-        {
-            if (File.Exists(path))
-            {
-                try
-                {
-                    return File.OpenRead(path);
-                }
-                catch (Exception ex) when (ex is UnauthorizedAccessException || ex is NotSupportedException || ex is IOException)
-                {
-                    Trace.TraceError($"TryOpenFile: {ex.Message}");
-                }
-            }
-            return null;
-        }
     }
 }
index 081188214cc2f8595b5d4e489a53da33c2f9e591..71acaf5390fb515bdfa1093f48717ae5b2255119 100644 (file)
@@ -2,7 +2,14 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using Microsoft.FileFormats;
+using Microsoft.FileFormats.ELF;
+using Microsoft.FileFormats.MachO;
 using Microsoft.FileFormats.PE;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection.PortableExecutable;
 
 namespace Microsoft.Diagnostics.DebugServices.Implementation
 {
@@ -57,5 +64,142 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation
         {
             return new PdbFileInfo(pdbInfo.Path, pdbInfo.Signature, pdbInfo.Age, pdbInfo.IsPortablePDB);
         }
+
+        /// <summary>
+        /// Opens and returns an PEReader instance from the local file path
+        /// </summary>
+        /// <param name="filePath">PE file to open</param>
+        /// <returns>PEReader instance or null</returns>
+        public static PEReader OpenPEReader(string filePath)
+        {
+            Stream stream = TryOpenFile(filePath);
+            if (stream is not null)
+            {
+                try
+                {
+                    var reader = new PEReader(stream);
+                    if (reader.PEHeaders == null || reader.PEHeaders.PEHeader == null)
+                    {
+                        Trace.TraceError($"OpenPEReader: PEReader invalid headers");
+                        return null;
+                    }
+                    return reader;
+                }
+                catch (Exception ex) when (ex is BadImageFormatException || ex is IOException)
+                {
+                    Trace.TraceError($"OpenPEReader: PEReader exception {ex.Message}");
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Disposable ELFFile wrapper
+        /// </summary>
+        public class ELFModule : ELFFile, IDisposable
+        {
+            private readonly Stream _stream;
+
+            public ELFModule(Stream stream) :
+                base(new StreamAddressSpace(stream), position: 0, isDataSourceVirtualAddressSpace: false)
+            {
+                _stream = stream;
+            }
+
+            public void Dispose() => _stream.Dispose();
+        }
+
+        /// <summary>
+        /// Opens and returns an ELFFile instance from the local file path
+        /// </summary>
+        /// <param name="filePath">ELF file to open</param>
+        /// <returns>ELFFile instance or null</returns>
+        public static ELFModule OpenELFFile(string filePath)
+        {
+            Stream stream = TryOpenFile(filePath);
+            if (stream is not null)
+            {
+                try
+                {
+                    ELFModule elfModule = new (stream);
+                    if (!elfModule.IsValid())
+                    {
+                        Trace.TraceError($"OpenELFFile: not a valid file");
+                        return null;
+                    }
+                    return elfModule;
+                }
+                catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
+                {
+                    Trace.TraceError($"OpenELFFile: exception {ex.Message}");
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Disposable MachOFile wrapper
+        /// </summary>
+        public class MachOModule : MachOFile, IDisposable
+        {
+            private readonly Stream _stream;
+
+            public MachOModule(Stream stream) :
+                base(new StreamAddressSpace(stream), position: 0, dataSourceIsVirtualAddressSpace: false)
+            {
+                _stream = stream;
+            }
+
+            public void Dispose() => _stream.Dispose();
+        }
+
+        /// <summary>
+        /// Opens and returns an MachOFile instance from the local file path
+        /// </summary>
+        /// <param name="filePath">MachO file to open</param>
+        /// <returns>MachOFile instance or null</returns>
+        public static MachOModule OpenMachOFile(string filePath)
+        {
+            Stream stream = TryOpenFile(filePath);
+            if (stream is not null)
+            {
+                try
+                {
+                    var machoModule = new MachOModule(stream);
+                    if (!machoModule.IsValid())
+                    {
+                        Trace.TraceError($"OpenMachOFile: not a valid file");
+                        return null;
+                    }
+                    return machoModule;
+                }
+                catch (Exception ex) when (ex is InvalidVirtualAddressException || ex is BadInputFormatException || ex is IOException)
+                {
+                    Trace.TraceError($"OpenMachOFile: exception {ex.Message}");
+                }
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Attempt to open a file stream.
+        /// </summary>
+        /// <param name="path">file path</param>
+        /// <returns>stream or null if doesn't exist or error</returns>
+        public static Stream TryOpenFile(string path)
+        {
+            if (path is not null && File.Exists(path))
+            {
+                try
+                {
+                    return File.OpenRead(path);
+                }
+                catch (Exception ex) when (ex is UnauthorizedAccessException || ex is NotSupportedException || ex is IOException)
+                {
+                    Trace.TraceError($"TryOpenFile: {ex.Message}");
+                }
+            }
+            return null;
+        }
     }
 }
diff --git a/src/Microsoft.Diagnostics.DebugServices/ISymbolFile.cs b/src/Microsoft.Diagnostics.DebugServices/ISymbolFile.cs
new file mode 100644 (file)
index 0000000..af939a6
--- /dev/null
@@ -0,0 +1,38 @@
+// 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.
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    public interface ISymbolFile
+    {
+        /// <summary>
+        /// Returns method token and IL offset for given source line number.
+        /// </summary>
+        /// <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>
+        bool ResolveSequencePoint(string filePath, int lineNumber, out int methodToken, out int ilOffset);
+
+        /// <summary>
+        /// Returns source line number and source file name for given IL offset and method token.
+        /// </summary>
+        /// <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>
+        bool GetSourceLineByILOffset(int methodToken, long ilOffset, out int lineNumber, out string fileName);
+
+        /// <summary>
+        /// Returns local variable name for given local index and IL offset.
+        /// </summary>
+        /// <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>
+        bool GetLocalVariableByIndex(int methodToken, int localIndex, out string localVarName);
+    }
+}
index 1b2c7733298ece6f9df23d5624163da07692e0cd..4900510cc548073dfba32c8c5c6bbf688a9ce3f0 100644 (file)
@@ -3,12 +3,24 @@
 // See the LICENSE file in the project root for more information.
 
 using Microsoft.SymbolStore;
+using System;
 using System.Collections.Immutable;
+using System.IO;
 
 namespace Microsoft.Diagnostics.DebugServices
 {
     public interface ISymbolService
     {
+        /// <summary>
+        /// Symbol file reader instance
+        /// </summary>
+        public class SymbolFile : IDisposable
+        {
+            public virtual void Dispose() 
+            { 
+            }
+        }
+
         /// <summary>
         /// Invoked when anything changes in the symbol service (adding servers, caches, or directories, clearing store, etc.)
         /// </summary>
@@ -63,6 +75,13 @@ namespace Microsoft.Diagnostics.DebugServices
         /// </summary>
         void DisableSymbolStore();
 
+        /// <summary>
+        /// Downloads module file
+        /// </summary>
+        /// <param name="module">module interface</param>
+        /// <returns>module path or null</returns>
+        string DownloadModule(IModule module);
+        
         /// <summary>
         /// Download a file from the symbol stores/server.
         /// </summary>
@@ -70,13 +89,6 @@ namespace Microsoft.Diagnostics.DebugServices
         /// <returns>path to the downloaded file either in the cache or in the temp directory or null if error</returns>
         string DownloadFile(SymbolStoreKey key);
 
-        /// <summary>
-        /// Attempts to download/retrieve from cache the key.
-        /// </summary>
-        /// <param name="key">index of the file to retrieve</param>
-        /// <returns>stream or null</returns>
-        SymbolStoreFile GetSymbolStoreFile(SymbolStoreKey key);
-
         /// <summary>
         /// Returns the metadata for the assembly
         /// </summary>
@@ -85,5 +97,27 @@ namespace Microsoft.Diagnostics.DebugServices
         /// <param name="imageSize">size of PE image</param>
         /// <returns>metadata</returns>
         ImmutableArray<byte> GetMetadata(string imagePath, uint imageTimestamp, uint imageSize);
+
+        /// <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">in-memory PE stream</param>
+        /// <returns>symbol file or null</returns>
+        /// <remarks>
+        /// Assumes that neither PE image nor PDB loaded into memory can be unloaded or moved around.
+        /// </remarks>
+        ISymbolFile OpenSymbolFile(string assemblyPath, bool isFileLayout, Stream peStream);
+
+        /// <summary>
+        /// Returns the portable PDB reader for the portable PDB stream
+        /// </summary>
+        /// <param name="pdbStream">portable PDB memory or file stream</param>
+        /// <returns>symbol file or null</returns>
+        /// <remarks>
+        /// Assumes that the PDB loaded into memory can be unloaded or moved around.
+        /// </remarks>
+        ISymbolFile OpenSymbolFile(Stream pdbStream);
     }
 }
index aac0399996a0186521931811816c5425b9239e01..4a062b55518a73187eb29ccd039e95c062abc55d 100644 (file)
@@ -44,7 +44,6 @@ namespace SOS.Extensions
         private int _targetIdFactory;
         private ITarget _target;
         private TargetWrapper _targetWrapper;
-        private IMemoryService _memoryService;
 
         /// <summary>
         /// Enable the assembly resolver to get the right versions in the same directory as this assembly.
@@ -107,8 +106,7 @@ namespace SOS.Extensions
             _serviceProvider.AddService<ISymbolService>(_symbolService);
 
             _hostWrapper = new HostWrapper(this, () => _targetWrapper);
-            _hostWrapper.AddServiceWrapper(IID_IHostServices, this);
-            _hostWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(this, () => _memoryService));
+            _hostWrapper.ServiceWrapper.AddServiceWrapper(IID_IHostServices, this);
 
             VTableBuilder builder = AddInterface(IID_IHostServices, validate: false);
             builder.AddMethod(new GetHostDelegate(GetHost));
@@ -128,7 +126,7 @@ namespace SOS.Extensions
         protected override void Destroy()
         {
             Trace.TraceInformation("HostServices.Destroy");
-            _hostWrapper.RemoveServiceWrapper(IID_IHostServices);
+            _hostWrapper.ServiceWrapper.RemoveServiceWrapper(IID_IHostServices);
             _hostWrapper.Release();
         }
 
@@ -151,7 +149,6 @@ namespace SOS.Extensions
             if (target == _target)
             {
                 _target = null;
-                _memoryService = null;
                 if (_targetWrapper != null)
                 {
                     _targetWrapper.Release();
@@ -260,7 +257,7 @@ namespace SOS.Extensions
                 _target = new TargetFromDebuggerServices(DebuggerServices, this, _targetIdFactory++);
                 _contextService.SetCurrentTarget(_target);
                 _targetWrapper = new TargetWrapper(_contextService.Services);
-                _memoryService = _contextService.Services.GetService<IMemoryService>();
+                _targetWrapper.ServiceWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(_symbolService, _target.Services.GetService<IMemoryService>()));
             }
             catch (Exception ex)
             {
index 042bb40ca518471af36b8ec0d35c1b1b053db706..e1fb16bb270191d0f9fddf81f3c38a56bfc4f551 100644 (file)
@@ -5,8 +5,6 @@
 using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.Runtime.Utilities;
 using System;
-using System.Collections.Generic;
-using System.Diagnostics;
 using System.Runtime.InteropServices;
 
 namespace SOS.Hosting
@@ -17,8 +15,8 @@ namespace SOS.Hosting
 
         private readonly IHost _host;
         private readonly Func<TargetWrapper> _getTarget;
-        private readonly Dictionary<Guid, Func<COMCallableIUnknown>> _factories = new Dictionary<Guid, Func<COMCallableIUnknown>>();
-        private readonly Dictionary<Guid, COMCallableIUnknown> _wrappers = new Dictionary<Guid, COMCallableIUnknown>();
+
+        public ServiceWrapper ServiceWrapper { get; } = new ServiceWrapper();
 
         public IntPtr IHost { get; }
 
@@ -29,73 +27,14 @@ namespace SOS.Hosting
 
             VTableBuilder builder = AddInterface(IID_IHost, validate: false);
             builder.AddMethod(new GetHostTypeDelegate(GetHostType));
-            builder.AddMethod(new GetServiceDelegate(GetService));
+            builder.AddMethod(new GetServiceDelegate(ServiceWrapper.GetService));
             builder.AddMethod(new GetCurrentTargetDelegate(GetCurrentTarget));
             IHost = builder.Complete();
 
             AddRef();
         }
 
-        protected override void Destroy()
-        {
-            Trace.TraceInformation("HostWrapper.Destroy");
-            foreach (var wrapper in _wrappers.Values)
-            {
-                wrapper.Release();
-            }
-            _wrappers.Clear();
-        }
-
-        /// <summary>
-        /// Add service instance factory
-        /// </summary>
-        /// <param name="serviceId">guid</param>
-        /// <param name="factory">factory delegate</param>
-        public void AddServiceWrapper(in Guid serviceId, Func<COMCallableIUnknown> factory)
-        {
-            _factories.Add(serviceId, factory);
-        }
-
-        /// <summary>
-        /// Add service instance
-        /// </summary>
-        /// <param name="serviceId">guid</param>
-        /// <param name="service">instance</param>
-        public void AddServiceWrapper(in Guid serviceId, COMCallableIUnknown service)
-        {
-            _wrappers.Add(serviceId, service);
-        }
-
-        /// <summary>
-        /// Remove the service instance
-        /// </summary>
-        /// <param name="serviceId">guid</param>
-        public void RemoveServiceWrapper(in Guid serviceId)
-        {
-            _factories.Remove(serviceId);
-            _wrappers.Remove(serviceId);
-        }
-
-        /// <summary>
-        /// Returns the wrapper instance for the guid
-        /// </summary>
-        /// <param name="serviceId">service guid</param>
-        /// <returns>instance or null</returns>
-        public COMCallableIUnknown GetServiceWrapper(in Guid serviceId)
-        {
-            if (!_wrappers.TryGetValue(serviceId, out COMCallableIUnknown service))
-            {
-                if (_factories.TryGetValue(serviceId, out Func<COMCallableIUnknown> factory))
-                {
-                    service = factory();
-                    if (service != null)
-                    {
-                        _wrappers.Add(serviceId, service);
-                    }
-                }
-            }
-            return service;
-        }
+        protected override void Destroy() => ServiceWrapper.Dispose();
 
         #region IHost
 
@@ -104,26 +43,6 @@ namespace SOS.Hosting
         /// </summary>
         private HostType GetHostType(IntPtr self) => _host.HostType;
 
-        /// <summary>
-        /// Returns the native service for the given interface id. There is 
-        /// only a limited set of services that can be queried through this
-        /// function. Adds a reference like QueryInterface.
-        /// </summary>
-        /// <param name="serviceId">guid of the service</param>
-        /// <param name="service">pointer to return service instance</param>
-        /// <returns>S_OK or E_NOINTERFACE</returns>
-        private HResult GetService(IntPtr self, in Guid guid, out IntPtr ptr)
-        {
-            ptr = IntPtr.Zero;
-
-            COMCallableIUnknown wrapper = GetServiceWrapper(guid);
-            if (wrapper == null) {
-                return HResult.E_NOINTERFACE;
-            }
-            wrapper.AddRef();
-            return COMHelper.QueryInterface(wrapper.IUnknownObject, guid, out ptr);
-        }
-
         /// <summary>
         /// Returns the current target wrapper or null
         /// </summary>
@@ -151,7 +70,7 @@ namespace SOS.Hosting
             [In] IntPtr self);
 
         [UnmanagedFunctionPointer(CallingConvention.Winapi)]
-        private delegate HResult GetServiceDelegate(
+        internal delegate HResult GetServiceDelegate(
             [In] IntPtr self,
             [In] in Guid guid,
             [Out] out IntPtr ptr);
index be78691cdba10c7ca80c6a07bf4dc7121604f77d..171cb364e17729179064eaffaa22770681f1c70d 100644 (file)
@@ -52,6 +52,7 @@ namespace SOS.Hosting
             ModuleService = services.GetService<IModuleService>();
             ThreadService = services.GetService<IThreadService>();
             MemoryService = services.GetService<IMemoryService>();
+            TargetWrapper.ServiceWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(services.GetService<ISymbolService>(), MemoryService));
             _ignoreAddressBitsMask = MemoryService.SignExtensionMask();
             _sosLibrary = services.GetService<SOSLibrary>();
 
index 47205a5202ccaa2ec9e945e3c7c801b0dea3235d..1009e5a7bedbef418e6f28ad23408932b4543e90 100644 (file)
@@ -75,7 +75,6 @@ namespace SOS.Hosting
             SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid);
 
             _hostWrapper = new HostWrapper(host, () => GetSOSHost()?.TargetWrapper);
-            _hostWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(host, () => GetSOSHost()?.MemoryService));
         }
 
         /// <summary>
diff --git a/src/SOS/SOS.Hosting/ServiceWrapper.cs b/src/SOS/SOS.Hosting/ServiceWrapper.cs
new file mode 100644 (file)
index 0000000..7dbf7dc
--- /dev/null
@@ -0,0 +1,102 @@
+// 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 Microsoft.Diagnostics.Runtime.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace SOS.Hosting
+{
+    public sealed class ServiceWrapper : IDisposable
+    {
+        private readonly Dictionary<Guid, Func<COMCallableIUnknown>> _factories = new Dictionary<Guid, Func<COMCallableIUnknown>>();
+        private readonly Dictionary<Guid, COMCallableIUnknown> _wrappers = new Dictionary<Guid, COMCallableIUnknown>();
+
+        public ServiceWrapper()
+        {
+        }
+
+        public void Dispose()
+        {
+            Trace.TraceInformation("ServiceWrapper.Dispose");
+            foreach (var wrapper in _wrappers.Values)
+            {
+                wrapper.Release();
+            }
+            _wrappers.Clear();
+        }
+
+        /// <summary>
+        /// Add service instance factory
+        /// </summary>
+        /// <param name="serviceId">guid</param>
+        /// <param name="factory">factory delegate</param>
+        public void AddServiceWrapper(in Guid serviceId, Func<COMCallableIUnknown> factory)
+        {
+            _factories.Add(serviceId, factory);
+        }
+
+        /// <summary>
+        /// Add service instance
+        /// </summary>
+        /// <param name="serviceId">guid</param>
+        /// <param name="service">instance</param>
+        public void AddServiceWrapper(in Guid serviceId, COMCallableIUnknown service)
+        {
+            _wrappers.Add(serviceId, service);
+        }
+
+        /// <summary>
+        /// Remove the service instance
+        /// </summary>
+        /// <param name="serviceId">guid</param>
+        public void RemoveServiceWrapper(in Guid serviceId)
+        {
+            _factories.Remove(serviceId);
+            _wrappers.Remove(serviceId);
+        }
+
+        /// <summary>
+        /// Returns the wrapper instance for the guid
+        /// </summary>
+        /// <param name="serviceId">service guid</param>
+        /// <returns>instance or null</returns>
+        public COMCallableIUnknown GetServiceWrapper(in Guid serviceId)
+        {
+            if (!_wrappers.TryGetValue(serviceId, out COMCallableIUnknown service))
+            {
+                if (_factories.TryGetValue(serviceId, out Func<COMCallableIUnknown> factory))
+                {
+                    service = factory();
+                    if (service != null)
+                    {
+                        _wrappers.Add(serviceId, service);
+                    }
+                }
+            }
+            return service;
+        }
+
+        /// <summary>
+        /// Returns the native service for the given interface id. There is 
+        /// only a limited set of services that can be queried through this
+        /// function. Adds a reference like QueryInterface.
+        /// </summary>
+        /// <param name="serviceId">guid of the service</param>
+        /// <param name="service">pointer to return service instance</param>
+        /// <returns>S_OK or E_NOINTERFACE</returns>
+        public HResult GetService(IntPtr self, in Guid guid, out IntPtr ptr)
+        {
+            ptr = IntPtr.Zero;
+
+            COMCallableIUnknown wrapper = GetServiceWrapper(guid);
+            if (wrapper == null) {
+                return HResult.E_NOINTERFACE;
+            }
+            wrapper.AddRef();
+            return COMHelper.QueryInterface(wrapper.IUnknownObject, guid, out ptr);
+        }
+    }
+}
index 25dd90b3abf72307437d7a112e3fd46153e833cd..b3316bbb4da92c156f741b1695c32294e9d5028e 100644 (file)
@@ -12,14 +12,9 @@ using Microsoft.SymbolStore;
 using Microsoft.SymbolStore.KeyGenerators;
 using System;
 using System.Collections.Generic;
-using System.Collections.Immutable;
 using System.Diagnostics;
 using System.Globalization;
 using System.IO;
-using System.Linq;
-using System.Reflection.Metadata;
-using System.Reflection.Metadata.Ecma335;
-using System.Reflection.PortableExecutable;
 using System.Runtime.InteropServices;
 using System.Threading.Tasks;
 
@@ -38,23 +33,6 @@ namespace SOS.Hosting
             OSXCore         = 3
         }
 
-        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>
         /// Writeline delegate for symbol store logging
         /// </summary>
@@ -73,15 +51,15 @@ namespace SOS.Hosting
 
         public static readonly Guid IID_ISymbolService = new Guid("7EE88D46-F8B3-4645-AD3E-01FE7D4F70F1");
 
-        private readonly Func<IMemoryService> _getMemoryService;
         private readonly ISymbolService _symbolService;
+        private readonly IMemoryService _memoryService;
 
-        public SymbolServiceWrapper(IHost host, Func<IMemoryService> getMemoryService)
+        public SymbolServiceWrapper(ISymbolService symbolService, IMemoryService memoryService)
         {
-            Debug.Assert(host != null);
-            Debug.Assert(getMemoryService != null);
-            _getMemoryService = getMemoryService;
-            _symbolService = host.Services.GetService<ISymbolService>();
+            Debug.Assert(symbolService != null);
+            Debug.Assert(memoryService != null);
+            _symbolService = symbolService;
+            _memoryService = memoryService;
             Debug.Assert(_symbolService != null);
 
             VTableBuilder builder = AddInterface(IID_ISymbolService, validate: false);
@@ -203,19 +181,19 @@ namespace SOS.Hosting
                     KeyGenerator generator = null;
                     if (config == RuntimeConfiguration.UnixCore)
                     {
-                        Stream stream = MemoryService.CreateMemoryStream();
+                        Stream stream = _memoryService.CreateMemoryStream();
                         var elfFile = new ELFFile(new StreamAddressSpace(stream), address, true);
                         generator = new ELFFileKeyGenerator(Tracer.Instance, elfFile, moduleFilePath);
                     }
                     else if (config == RuntimeConfiguration.OSXCore)
                     {
-                        Stream stream = MemoryService.CreateMemoryStream();
+                        Stream stream = _memoryService.CreateMemoryStream();
                         var machOFile = new MachOFile(new StreamAddressSpace(stream), address, true);
                         generator = new MachOFileKeyGenerator(Tracer.Instance, machOFile, moduleFilePath);
                     }
                     else if (config == RuntimeConfiguration.WindowsCore || config ==  RuntimeConfiguration.WindowsDesktop)
                     {
-                        Stream stream = MemoryService.CreateMemoryStream(address, size);
+                        Stream stream = _memoryService.CreateMemoryStream(address, size);
                         var peFile = new PEFile(new StreamAddressSpace(stream), true);
                         generator = new PEFileKeyGenerator(Tracer.Instance, peFile, moduleFilePath);
                     }
@@ -324,6 +302,25 @@ namespace SOS.Hosting
             }
         }
 
+        /// <summary>
+        /// Get expression helper for native SOS.
+        /// </summary>
+        /// <param name="expression">hex number</param>
+        /// <returns>value</returns>
+        internal static ulong GetExpressionValue(
+            IntPtr self,
+            string expression)
+        {
+            if (expression != null)
+            {
+                if (ulong.TryParse(expression.Replace("0x", ""), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong result))
+                {
+                    return result;
+                }
+            }
+            return 0;
+        }
+
         /// <summary>
         /// Checks availability of debugging information for given assembly.
         /// </summary>
@@ -351,20 +348,20 @@ namespace SOS.Hosting
         {
             try
             {
-                Stream peStream = null;
+                ISymbolFile symbolFile = null;
                 if (loadedPeAddress != 0)
                 {
-                    peStream = MemoryService.CreateMemoryStream(loadedPeAddress, loadedPeSize);
+                    Stream peStream = _memoryService.CreateMemoryStream(loadedPeAddress, loadedPeSize);
+                    symbolFile = _symbolService.OpenSymbolFile(assemblyPath, isFileLayout, peStream);
                 }
-                Stream pdbStream = null;
                 if (inMemoryPdbAddress != 0)
                 {
-                    pdbStream = MemoryService.CreateMemoryStream(inMemoryPdbAddress, inMemoryPdbSize);
+                    Stream pdbStream = _memoryService.CreateMemoryStream(inMemoryPdbAddress, inMemoryPdbSize);
+                    symbolFile = _symbolService.OpenSymbolFile(pdbStream);
                 }
-                OpenedReader openedReader = GetReader(assemblyPath, isFileLayout, peStream, pdbStream);
-                if (openedReader != null)
+                if (symbolFile != null)
                 {
-                    GCHandle gch = GCHandle.Alloc(openedReader);
+                    GCHandle gch = GCHandle.Alloc(symbolFile);
                     return GCHandle.ToIntPtr(gch);
                 }
             }
@@ -387,7 +384,10 @@ namespace SOS.Hosting
             try
             {
                 GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
-                ((OpenedReader)gch.Target).Dispose();
+                if (gch.Target is IDisposable disposable)
+                {
+                    disposable.Dispose();
+                }
                 gch.Free();
             }
             catch (Exception ex)
@@ -396,25 +396,6 @@ namespace SOS.Hosting
             }
         }
 
-        /// <summary>
-        /// Get expression helper for native SOS.
-        /// </summary>
-        /// <param name="expression">hex number</param>
-        /// <returns>value</returns>
-        internal static ulong GetExpressionValue(
-            IntPtr self,
-            string expression)
-        {
-            if (expression != null)
-            {
-                if (ulong.TryParse(expression.Replace("0x", ""), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong result))
-                {
-                    return result;
-                }
-            }
-            return 0;
-        }
-
         /// <summary>
         /// Returns method token and IL offset for given source line number.
         /// </summary>
@@ -433,36 +414,9 @@ namespace SOS.Hosting
             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 = 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 && GetFileName(sourceName) == fileName)
-                        {
-                            methodToken = MetadataTokens.GetToken(methodDebugInformationHandle.ToDefinitionHandle());
-                            ilOffset = point.Offset;
-                            return true;
-                        }
-                    }
-                }
-            }
-            catch (Exception ex)
-            {
-                Trace.TraceError($"ResolveSequencePoint: {ex.Message}");
-            }
-            return false;
+            ISymbolFile symbolFile = (ISymbolFile)gch.Target;
+            return symbolFile.ResolveSequencePoint(filePath, lineNumber, out methodToken, out ilOffset);
         }
 
         /// <summary>
@@ -486,8 +440,8 @@ namespace SOS.Hosting
             fileName = IntPtr.Zero;
 
             GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
-            OpenedReader openedReader = (OpenedReader)gch.Target;
-            if (!GetSourceLineByILOffset(openedReader, methodToken, ilOffset, out lineNumber, out string sourceFileName))
+            ISymbolFile symbolFile = (ISymbolFile)gch.Target;
+            if (!symbolFile.GetSourceLineByILOffset(methodToken, ilOffset, out lineNumber, out string sourceFileName))
             {
                 return false;
             }
@@ -495,62 +449,6 @@ namespace SOS.Hosting
             return true;
         }
 
-        /// <summary>
-        /// Helper method to return source line number and source file name for given IL offset and method token.
-        /// </summary>
-        /// <param name="openedReader">symbol reader 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 bool GetSourceLineByILOffset(
-            OpenedReader openedReader,
-            int methodToken,
-            long ilOffset,
-            out int lineNumber,
-            out string fileName)
-        {
-            lineNumber = 0;
-            fileName = null;
-            MetadataReader reader = openedReader.Reader;
-            try
-            {
-                Handle handle = MetadataTokens.Handle(methodToken);
-                if (handle.Kind != HandleKind.MethodDefinition)
-                    return false;
-
-                MethodDebugInformationHandle methodDebugHandle = ((MethodDefinitionHandle)handle).ToDebugInformationHandle();
-                if (methodDebugHandle.IsNil)
-                    return false;
-
-                MethodDebugInformation methodDebugInfo = reader.GetMethodDebugInformation(methodDebugHandle);
-                SequencePointCollection sequencePoints = methodDebugInfo.GetSequencePoints();
-
-                SequencePoint? nearestPoint = null;
-                foreach (SequencePoint point in sequencePoints)
-                {
-                    if (point.Offset > ilOffset)
-                        break;
-
-                    if (point.StartLine != 0 && !point.IsHidden)
-                        nearestPoint = point;
-                }
-
-                if (nearestPoint.HasValue)
-                {
-                    lineNumber = nearestPoint.Value.StartLine;
-                    fileName = reader.GetString(reader.GetDocument(nearestPoint.Value.Document).Name);
-                    return true;
-                }
-            }
-            catch (Exception ex)
-            {
-                Trace.TraceError($"GetSourceLineByILOffset: {ex.Message}");
-            }
-            return false;
-        }
-
         /// <summary>
         /// Returns local variable name for given local index and IL offset.
         /// </summary>
@@ -570,8 +468,8 @@ namespace SOS.Hosting
             localVarName = IntPtr.Zero;
 
             GCHandle gch = GCHandle.FromIntPtr(symbolReaderHandle);
-            OpenedReader openedReader = (OpenedReader)gch.Target;
-            if (!GetLocalVariableByIndex(openedReader, methodToken, localIndex, out string localVar))
+            ISymbolFile symbolFile = (ISymbolFile)gch.Target;
+            if (!symbolFile.GetLocalVariableByIndex(methodToken, localIndex, out string localVar))
             {
                 return false;
             }
@@ -579,55 +477,6 @@ namespace SOS.Hosting
             return true;
         }
 
-        /// <summary>
-        /// Helper method to return local variable name for given local index and IL offset.
-        /// </summary>
-        /// <param name="openedReader">symbol reader 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>
-        private bool GetLocalVariableByIndex(
-            OpenedReader openedReader,
-            int methodToken,
-            int localIndex,
-            out string localVarName)
-        {
-            localVarName = null;
-            MetadataReader reader = openedReader.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 (Exception ex)
-            {
-                Trace.TraceError($"GetLocalVariableByIndex: {ex.Message}");
-            }
-            return false;
-        }
-
         /// <summary>
         /// Metadata locator helper for the DAC.
         /// </summary>
@@ -653,31 +502,16 @@ namespace SOS.Hosting
             IntPtr pMetadata,
             IntPtr pMetadataSize)
         {
-            Debug.Assert(imageTimestamp != 0);
-            Debug.Assert(imageSize != 0);
-
-            if (pMetadata == IntPtr.Zero) {
-                return HResult.E_INVALIDARG;
-            }
-            int hr = HResult.S_OK;
-            int dataSize = 0;
-
-            ImmutableArray<byte> metadata = _symbolService.GetMetadata(imagePath, imageTimestamp, imageSize);
-            if (!metadata.IsEmpty)
-            {
-                dataSize = metadata.Length;
-                int size = Math.Min((int)bufferSize, dataSize);
-                Marshal.Copy(metadata.ToArray(), 0, pMetadata, size);
-            }
-            else
-            {
-                hr = HResult.E_FAIL;
-            }
-
-            if (pMetadataSize != IntPtr.Zero) {
-                Marshal.WriteInt32(pMetadataSize, dataSize);
-            }
-            return hr;
+            return _symbolService.GetMetadataLocator(
+                imagePath,
+                imageTimestamp,
+                imageSize,
+                mvid, 
+                mdRva,
+                flags,
+                bufferSize,
+                pMetadata,
+                pMetadataSize);
         }
 
         /// <summary>
@@ -699,266 +533,15 @@ namespace SOS.Hosting
             IntPtr pPathBufferSize,
             IntPtr pwszPathBuffer)
         {
-            return _symbolService.GetICorDebugMetadataLocator(imagePath, imageTimestamp, imageSize, pathBufferSize, pPathBufferSize, pwszPathBuffer);
+            return _symbolService.GetICorDebugMetadataLocator(
+                imagePath,
+                imageTimestamp,
+                imageSize,
+                pathBufferSize,
+                pPathBufferSize,
+                pwszPathBuffer);
         }
 
-        /// <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">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 OpenedReader GetReader(string assemblyPath, bool isFileLayout, Stream peStream, Stream pdbStream)
-        {
-            return (pdbStream != null) ? TryOpenReaderForInMemoryPdb(pdbStream) : TryOpenReaderFromAssembly(assemblyPath, isFileLayout, peStream);
-        }
-
-        private 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 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))
-                {
-                    ReadPortableDebugTableEntries(peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry 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 void ReadPortableDebugTableEntries(PEReader peReader, out DebugDirectoryEntry codeViewEntry, out DebugDirectoryEntry embeddedPdbEntry)
-        {
-            // See spec: https://github.com/dotnet/runtime/blob/main/docs/design/specs/PE-COFF.md
-
-            codeViewEntry = default;
-            embeddedPdbEntry = default;
-
-            foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory())
-            {
-                if (entry.Type == DebugDirectoryEntryType.CodeView)
-                {
-                    if (entry.MinorVersion != ImageDebugDirectory.PortablePDBMinorVersion)
-                    {
-                        continue;
-                    }
-                    codeViewEntry = entry;
-                }
-                else if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
-                {
-                    embeddedPdbEntry = entry;
-                }
-            }
-        }
-
-        private OpenedReader TryOpenReaderFromCodeView(PEReader peReader, DebugDirectoryEntry codeViewEntry, string assemblyPath)
-        {
-            OpenedReader result = null;
-            MetadataReaderProvider provider = null;
-            try
-            {
-                CodeViewDebugDirectoryData data = peReader.ReadCodeViewDebugDirectoryData(codeViewEntry);
-                string pdbPath = data.Path;
-                Stream pdbStream = null;
-
-                if (assemblyPath != null) 
-                {
-                    try
-                    {
-                        pdbPath = Path.Combine(Path.GetDirectoryName(assemblyPath), GetFileName(pdbPath));
-                    }
-                    catch
-                    {
-                        // invalid characters in CodeView path
-                        return null;
-                    }
-                    pdbStream = TryOpenFile(pdbPath);
-                }
-
-                if (pdbStream == null)
-                {
-                    if (_symbolService.IsSymbolStoreEnabled)
-                    {
-                        Debug.Assert(codeViewEntry.MinorVersion == ImageDebugDirectory.PortablePDBMinorVersion);
-                        SymbolStoreKey key = PortablePDBFileKeyGenerator.GetKey(pdbPath, data.Guid);
-                        pdbStream = _symbolService.GetSymbolStoreFile(key)?.Stream;
-                    }
-                    if (pdbStream == null)
-                    {
-                        return null;
-                    }
-                    // Make sure the stream is at the beginning of the pdb.
-                    pdbStream.Position = 0;
-                }
-
-                provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
-                MetadataReader 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 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;
-        }
-
-        /// <summary>
-        /// Attempt to open a file stream.
-        /// </summary>
-        /// <param name="path">file path</param>
-        /// <returns>stream or null if doesn't exist or error</returns>
-        private Stream TryOpenFile(string path)
-        {
-            if (File.Exists(path))
-            {
-                try
-                {
-                    return File.OpenRead(path);
-                }
-                catch (Exception ex) when (ex is UnauthorizedAccessException || ex is NotSupportedException || ex is IOException)
-                {
-                }
-            }
-            return null;
-        }
-
-        /// <summary>
-        /// Quick fix for Path.GetFileName which incorrectly handles Windows-style paths on Linux
-        /// </summary>
-        /// <param name="pathName"> File path to be processed </param>
-        /// <returns>Last component of path</returns>
-        private static string GetFileName(string pathName)
-        {
-            int pos = pathName.LastIndexOfAny(new char[] { '/', '\\'});
-            if (pos < 0)
-            {
-                return pathName;
-            }
-            return pathName.Substring(pos + 1);
-        }
-
-        private IMemoryService MemoryService => _getMemoryService() ?? throw new DiagnosticsException("SymbolServiceWrapper: no current target");
-
         #region Symbol service delegates
 
         [UnmanagedFunctionPointer(CallingConvention.Winapi)]
index 1ff3de96aa1db4781a402abf8130363f0a302d6a..e20be17d988b2191d2685bf3e23bafd263892096 100644 (file)
@@ -22,10 +22,12 @@ namespace SOS.Hosting
             OSX             = 3,
         }
 
-        public IntPtr ITarget { get; }
-
         public static readonly Guid IID_ITarget = new Guid("B4640016-6CA0-468E-BA2C-1FFF28DE7B72");
 
+        public ServiceWrapper ServiceWrapper { get; } = new ServiceWrapper();
+
+        public IntPtr ITarget { get; }
+
         private readonly IServiceProvider _services;
         private readonly ITarget _target;
         private readonly Dictionary<IRuntime, RuntimeWrapper> _wrappers = new Dictionary<IRuntime, RuntimeWrapper>();
@@ -38,6 +40,7 @@ namespace SOS.Hosting
             VTableBuilder builder = AddInterface(IID_ITarget, validate: false);
 
             builder.AddMethod(new GetOperatingSystemDelegate(GetOperatingSystem));
+            builder.AddMethod(new HostWrapper.GetServiceDelegate(ServiceWrapper.GetService));
             builder.AddMethod(new GetTempDirectoryDelegate(GetTempDirectory));
             builder.AddMethod(new GetRuntimeDelegate(GetRuntime));
             builder.AddMethod(new FlushDelegate(Flush));
@@ -50,6 +53,7 @@ namespace SOS.Hosting
         protected override void Destroy()
         {
             Trace.TraceInformation("TargetWrapper.Destroy");
+            ServiceWrapper.Dispose();
             foreach (RuntimeWrapper wrapper in _wrappers.Values)
             {
                 wrapper.Release();
index c0ec1143c4a72eab9dd21d9a14afe4507e58dff7..6c96ef017394acf344cafa7edc3693c963b61d17 100644 (file)
@@ -705,12 +705,12 @@ void Runtime::DisplayStatus()
 \**********************************************************************/
 void Runtime::LoadRuntimeModules()
 {
-    HRESULT hr = InitializeSymbolService();
-    if (SUCCEEDED(hr))
+    ISymbolService* symbolService = GetSymbolService();
+    if (symbolService != nullptr)
     {
         if (m_runtimeInfo != nullptr)
         {
-            GetSymbolService()->LoadNativeSymbolsFromIndex(
+            symbolService->LoadNativeSymbolsFromIndex(
                 SymbolFileCallback,
                 this,
                 GetRuntimeConfiguration(),
@@ -721,7 +721,7 @@ void Runtime::LoadRuntimeModules()
         }
         else
         {
-            GetSymbolService()->LoadNativeSymbols(
+            symbolService->LoadNativeSymbols(
                 SymbolFileCallback,
                 this,
                 GetRuntimeConfiguration(),
index a1e3e9d243dfabf99a2c8e5ad765c27da6a0d96e..9b5dc7f0d276700249b2e92da4820a61f634c2fa 100644 (file)
@@ -241,6 +241,11 @@ ITarget::OperatingSystem Target::GetOperatingSystem()
 #endif
 }
 
+HRESULT Target::GetService(REFIID serviceId, PVOID* ppService)
+{
+    return E_NOINTERFACE;
+}
+
 LPCSTR Target::GetTempDirectory()
 {
     if (m_tmpPath == nullptr)
index 2300810f8b64132f23f43b252e8d8ae9a180ee19..4860eb2233f242325313b6238993b5b7683a90ff 100644 (file)
@@ -80,6 +80,8 @@ public:
 
     OperatingSystem STDMETHODCALLTYPE GetOperatingSystem();
 
+    HRESULT STDMETHODCALLTYPE GetService(REFIID serviceId, PVOID* ppService);
+
     LPCSTR STDMETHODCALLTYPE GetTempDirectory();
 
     HRESULT STDMETHODCALLTYPE GetRuntime(IRuntime** pRuntime);
index 649f369b124d06b8ac090251be173d64764e9dac..dbd92db0bd2484768817d82442e89f8c6feca16c 100644 (file)
@@ -58,8 +58,21 @@ extern "C" HRESULT STDMETHODCALLTYPE SOSInitializeByHost(IUnknown* punk, IDebugg
     if (FAILED(hr)) {
         return hr;
     }
-    // Ignore error so the C++ hosting fallback doesn't fail because there is no symbol service
-    InitializeSymbolService();
+#ifndef FEATURE_PAL
+    // When SOS is hosted on dotnet-dump on Windows, the ExtensionApis are not set so 
+    // the expression evaluation function needs to be supplied.
+    if (GetExpression == nullptr)
+    {
+        GetExpression = ([](const char* message) {
+                   ISymbolService* symbolService = GetSymbolService();
+                   if (symbolService == nullptr)
+                   {
+                       return (ULONG64)0;
+                   }
+            return symbolService->GetExpressionValue(message);
+        });
+    }
+#endif
     return S_OK;
 }
 
@@ -71,37 +84,6 @@ extern "C" void STDMETHODCALLTYPE SOSUninitializeByHost()
     OnUnloadTask::Run();
 }
 
-/**********************************************************************\
- * Get the symbol service callback entry points.
-\**********************************************************************/
-HRESULT InitializeSymbolService()
-{
-    static bool initialized = false;
-    if (!initialized)
-    {
-        ISymbolService* symbolService = GetSymbolService();
-        if (symbolService == nullptr) {
-            return E_NOINTERFACE;
-        }
-        initialized = true;
-#ifndef FEATURE_PAL
-        // When SOS is hosted on dotnet-dump on Windows, the ExtensionApis are not set so 
-        // the expression evaluation function needs to be supplied.
-        if (GetExpression == nullptr)
-        {
-            GetExpression = ([](const char* message) {
-                return GetSymbolService()->GetExpressionValue(message);
-            });
-        }
-#endif
-        OnUnloadTask::Register([]() {
-            initialized = false;
-            DisableSymbolStore();
-        });
-    }
-    return S_OK;
-}
-
 /**********************************************************************\
  * Setup and initialize the symbol server support.
 \**********************************************************************/
@@ -116,8 +98,12 @@ HRESULT InitializeSymbolStore(
     const char* windowsSymbolPath)
 {
     HRESULT Status = S_OK;
-    IfFailRet(InitializeSymbolService());
-    if (!GetSymbolService()->InitializeSymbolStore(
+    ISymbolService* symbolService = GetSymbolService();
+    if (symbolService == nullptr)
+    {
+        return E_NOINTERFACE;
+    }
+    if (!symbolService->InitializeSymbolStore(
         msdl,
         symweb,
         symbolServer,
@@ -131,7 +117,7 @@ HRESULT InitializeSymbolStore(
     }
     if (windowsSymbolPath != nullptr)
     {
-        if (!GetSymbolService()->ParseSymbolPath(windowsSymbolPath))
+        if (!symbolService->ParseSymbolPath(windowsSymbolPath))
         {
             ExtErr("Error parsing symbol path %s\n", windowsSymbolPath);
             return E_FAIL;
@@ -228,9 +214,12 @@ HRESULT GetMetadataLocator(
     BYTE* buffer,
     ULONG32* dataSize)
 {
-    HRESULT Status = S_OK;
-    IfFailRet(InitializeSymbolService());
-    return GetSymbolService()->GetMetadataLocator(imagePath, imageTimestamp, imageSize, mvid, mdRva, flags, bufferSize, buffer, dataSize);
+    ISymbolService* symbolService = GetSymbolService();
+    if (symbolService == nullptr)
+    {
+        return E_NOINTERFACE;
+    }
+    return symbolService->GetMetadataLocator(imagePath, imageTimestamp, imageSize, mvid, mdRva, flags, bufferSize, buffer, dataSize);
 }
 
 /**********************************************************************\
@@ -244,9 +233,12 @@ HRESULT GetICorDebugMetadataLocator(
     ULONG32 *pcchPathBuffer,
     WCHAR wszPathBuffer[])
 {
-    HRESULT Status = S_OK;
-    IfFailRet(InitializeSymbolService());
-    return GetSymbolService()->GetICorDebugMetadataLocator(imagePath, imageTimestamp, imageSize, cchPathBuffer, pcchPathBuffer, wszPathBuffer);
+    ISymbolService* symbolService = GetSymbolService();
+    if (symbolService == nullptr)
+    {
+        return E_NOINTERFACE;
+    }
+    return symbolService->GetICorDebugMetadataLocator(imagePath, imageTimestamp, imageSize, cchPathBuffer, pcchPathBuffer, wszPathBuffer);
 }
 
 #ifndef FEATURE_PAL
@@ -592,9 +584,11 @@ HRESULT SymbolReader::LoadSymbolsForWindowsPDB(___in IMetaDataImport* pMD, ___in
 HRESULT SymbolReader::LoadSymbolsForPortablePDB(__in_z WCHAR* pModuleName, ___in BOOL isInMemory, ___in BOOL isFileLayout,
     ___in ULONG64 peAddress, ___in ULONG64 peSize, ___in ULONG64 inMemoryPdbAddress, ___in ULONG64 inMemoryPdbSize)
 {
-    HRESULT Status = S_OK;
-    IfFailRet(InitializeSymbolService());
-
+    ISymbolService* symbolService = GetSymbolService();
+    if (symbolService == nullptr)
+    {
+        return E_NOINTERFACE;
+    }
     m_symbolReaderHandle = GetSymbolService()->LoadSymbolsForModule(
         pModuleName, isFileLayout, peAddress, (int)peSize, inMemoryPdbAddress, (int)inMemoryPdbSize);
 
@@ -602,8 +596,7 @@ HRESULT SymbolReader::LoadSymbolsForPortablePDB(__in_z WCHAR* pModuleName, ___in
     {
         return E_FAIL;
     }
-
-    return Status;
+    return S_OK;
 }
 
 /**********************************************************************\
index f953eab790e9cf67e3f7c0e9e5361b9376fca066..2d402d622d99694ea2dc47cfb4ccb4c425ea4b10 100644 (file)
@@ -12,8 +12,6 @@ extern HMODULE g_hInstance;
 extern HRESULT LoadNativeSymbols(bool runtimeOnly = false);
 #endif
 
-extern HRESULT InitializeSymbolService();
-
 extern HRESULT InitializeSymbolStore(
     BOOL msdl,
     BOOL symweb,
index 3e2466bd2415e2272ba6baa5a782e22fb3b29611..892baa32da88936f59d72d1a5479e048ce3bdc85 100644 (file)
@@ -133,7 +133,11 @@ ISymbolService* Extensions::GetSymbolService()
 {
     if (m_pSymbolService == nullptr)
     {
-        GetHost()->GetService(__uuidof(ISymbolService), (void**)&m_pSymbolService);
+           ITarget* target = GetTarget();
+        if (target != nullptr)
+        {
+            target->GetService(__uuidof(ISymbolService), (void**)&m_pSymbolService);
+        }
     }
     return m_pSymbolService;
 }
index 33e90039642f29e123c1c84a9359a11508185f56..d8a185bc5bae379476584a4287e255a989678c00 100644 (file)
@@ -37,8 +37,8 @@ public:
     virtual HostType STDMETHODCALLTYPE GetHostType() = 0;
 
     /// <summary>
-    /// Returns the native service for the given interface id. There is 
-    /// only a limited set of services that can be queried through this
+    /// Returns the global native service for the given interface id. There
+    /// is only a limited set of services that can be queried through this
     /// function. Adds a reference like QueryInterface.
     /// </summary>
     /// <param name="serviceId">guid of the service</param>
index 771a7b27626e8e83f151cb71f82bb3011bcbfc99..513ccf752f21cce2fdd928693c2f5cfa2bf58086 100644 (file)
@@ -37,6 +37,16 @@ public:
     /// <returns>target operating system</returns>
     virtual OperatingSystem STDMETHODCALLTYPE GetOperatingSystem() = 0;
 
+    /// <summary>
+    /// Returns the per-target native service for the given interface 
+    /// id. There is only a limited set of services that can be queried 
+    /// through this function. Adds a reference like QueryInterface.
+    /// </summary>
+    /// <param name="serviceId">guid of the service</param>
+    /// <param name="service">pointer to return service instance</param>
+    /// <returns>S_OK or E_NOINTERFACE</returns>
+    virtual HRESULT STDMETHODCALLTYPE GetService(REFIID serviceId, PVOID* service) = 0;
+
     /// <summary>
     /// Returns the unique temporary directory for this instance of SOS
     /// </summary>