From: Mike McLaughlin Date: Fri, 21 May 2021 23:10:48 +0000 (-0700) Subject: Add IContextService and implementation and other misc changes/fixed. (#2247) X-Git-Tag: submit/tizen/20210909.063632~15^2~11 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=1bbeecbd0dfa6a010368b2f8b4ea867421f15600;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add IContextService and implementation and other misc changes/fixed. (#2247) Add IContextService and implementation Moved all the "current context" properties and methods out of the individual services into IContextService. The context service is used by the "UI" (i.e. dotnet-dump command line) and the SOSHost service. The rest of the services shouldn't have current ambient state. Added Target property to IRuntime, IThread and IModule. Added Equals/GetHashCode too. Add Target.OnDestroyEvent, make DisposeOnClose an extension method Remove native ITarget.Close() Fixed some SOSHost life time issues by separating and making global the SOS module loading and host wrapper in the new SOSLibrary class. Once the SOS module is loaded, it stays around until the host/dotnet-dump exits. Fix soshelp on managed commands like parallelstacks or timerinfo before runtimes are loaded or the app is started. Properly flush the ClrRuntime instance with FlushCachedData() Add clrmodules export Add DOTNET_ENABLED_SOS_LOGGING env var. Fix initial target being created twice on MacOS under lldb. Initialize/read thread info table in GetCurrentProcessSystemId, etc. More COM wrapper cleanup. Release/cleanup the host, host service and symbol wrappers. Fix problem with a second target being created under dbgeng when the app terminates. The UpdateTarget in the ChangeEngineState handler isn't needed because the CreateProcess/ExitProcess events managed the target lifetime. Add cleanup for SOSHost on context change When a context change happens (thread, runtime or target), the SOSHost instance was discarded but not disposed/cleanup. Now SOSHost instance is disposed on context change. Added a one shot event register on IServiceEvent used by SOSHost context change handler. Setting a new target context would fire the context change event 3 times Changed the lifetime of the SOSHost (again) from discard/disposed on every context change to per-target. I made things to complicated. Add temporary workaround to single-file/export enumerate perf problem on dbgeng. Enabled Windows single-file support with env var: DOTNET_ENABLE_SOS_SINGLEFILE=1 Fix cross-bitness DAC support on Windows; go back to using PlatformSpecificFileName. Move RuntimeModuleDirectory to IRuntime Better hash code combining --- diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 29cb3d06d..51b3247c6 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -298,3 +298,27 @@ This set of code is covered by the following license: See the License for the specific language governing permissions and limitations under the License. +------------------------------------------------- +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ContextService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ContextService.cs new file mode 100644 index 000000000..a2836da09 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ContextService.cs @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Diagnostics.DebugServices.Implementation +{ + /// + /// Manages the current target, thread and runtime contexts + /// + public class ContextService : IContextService + { + protected readonly IHost Host; + private ITarget _currentTarget; + private IThread _currentThread; + private IRuntime _currentRuntime; + + public readonly ServiceProvider ServiceProvider; + + public ContextService(IHost host) + { + Host = host; + + ServiceProvider = new ServiceProvider(new Func[] { + // First check the current runtime for the service + () => GetCurrentRuntime()?.Services, + // If there is no target, then provide just the global services + () => GetCurrentTarget()?.Services ?? host.Services + }); + + // These services depend on no caching + ServiceProvider.AddServiceFactoryWithNoCaching(GetCurrentTarget); + ServiceProvider.AddServiceFactoryWithNoCaching(GetCurrentThread); + ServiceProvider.AddServiceFactoryWithNoCaching(GetCurrentRuntime); + } + + #region IContextService + + /// + /// Current context service provider. Contains the current ITarget, IThread + /// and IRuntime instances along with all per target and global services. + /// + public IServiceProvider Services => ServiceProvider; + + /// + /// Fires anytime the current context changes. + /// + public IServiceEvent OnContextChange { get; } = new ServiceEvent(); + + /// + /// Sets the current target. + /// + /// target id + /// invalid target id + public void SetCurrentTarget(int targetId) + { + ITarget target = Host.EnumerateTargets().FirstOrDefault((target) => target.Id == targetId); + if (target is null) { + throw new DiagnosticsException($"Invalid target id {targetId}"); + } + SetCurrentTarget(target); + } + + /// + /// Clears (nulls) the current target + /// + public void ClearCurrentTarget() => SetCurrentTarget(null); + + /// + /// Set the current thread. + /// + /// thread id + /// invalid thread id + public void SetCurrentThread(uint threadId) => SetCurrentThread(ThreadService?.GetThreadFromId(threadId)); + + /// + /// Clears (nulls) the current thread + /// + public void ClearCurrentThread() => SetCurrentThread(null); + + /// + /// Set the current runtime + /// + /// runtime id + /// invalid runtime id + public void SetCurrentRuntime(int runtimeId) + { + IRuntime runtime = RuntimeService?.EnumerateRuntimes().FirstOrDefault((runtime) => runtime.Id == runtimeId); + if (runtime is null) { + throw new DiagnosticsException($"Invalid runtime id {runtimeId}"); + } + SetCurrentRuntime(runtime); + } + + /// + /// Clears (nulls) the current runtime + /// + public void ClearCurrentRuntime() => SetCurrentRuntime(null); + + #endregion + + /// + /// Returns the current target. + /// + public ITarget GetCurrentTarget() => _currentTarget ??= Host.EnumerateTargets().FirstOrDefault(); + + /// + /// Allows hosts to set the initial current target + /// + /// + public void SetCurrentTarget(ITarget target) + { + if (!IsTargetEqual(target, _currentTarget)) + { + _currentTarget = target; + _currentThread = null; + _currentRuntime = null; + ServiceProvider.FlushServices(); + OnContextChange.Fire(); + } + } + + /// + /// Returns the current thread. + /// + public virtual IThread GetCurrentThread() => _currentThread ??= ThreadService?.EnumerateThreads().FirstOrDefault(); + + /// + /// Allows hosts to set the initial current thread + /// + /// + public virtual void SetCurrentThread(IThread thread) + { + if (!IsThreadEqual(thread, _currentThread)) + { + _currentThread = thread; + ServiceProvider.FlushServices(); + OnContextChange.Fire(); + } + } + + /// + /// Find the current runtime. + /// + public IRuntime GetCurrentRuntime() + { + if (_currentRuntime is null) + { + IEnumerable runtimes = RuntimeService?.EnumerateRuntimes(); + if (runtimes is not null) + { + // First check if there is a .NET Core runtime loaded + foreach (IRuntime runtime in runtimes) + { + if (runtime.RuntimeType == RuntimeType.NetCore || runtime.RuntimeType == RuntimeType.SingleFile) + { + _currentRuntime = runtime; + break; + } + } + // If no .NET Core runtime, then check for desktop runtime + if (_currentRuntime is null) + { + foreach (IRuntime runtime in runtimes) + { + if (runtime.RuntimeType == RuntimeType.Desktop) + { + _currentRuntime = runtime; + break; + } + } + } + // If no core or desktop runtime, get the first one if any + if (_currentRuntime is null) + { + _currentRuntime = runtimes.FirstOrDefault(); + } + } + } + return _currentRuntime; + } + + /// + /// Allows hosts to set the initial current runtime + /// + public void SetCurrentRuntime(IRuntime runtime) + { + if (!IsRuntimeEqual(runtime, _currentRuntime)) + { + _currentRuntime = runtime; + ServiceProvider.FlushServices(); + OnContextChange.Fire(); + } + } + + protected bool IsTargetEqual(ITarget left, ITarget right) + { + if (left is null || right is null) { + return left == right; + } + return left == right; + } + + protected bool IsThreadEqual(IThread left, IThread right) + { + if (left is null || right is null) { + return left == right; + } + return left == right; + } + + protected bool IsRuntimeEqual(IRuntime left, IRuntime right) + { + if (left is null || right is null) { + return left == right; + } + return left == right; + } + + protected IThreadService ThreadService => GetCurrentTarget()?.Services.GetService(); + + protected IRuntimeService RuntimeService => GetCurrentTarget()?.Services.GetService(); + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs index 5a65d80ed..44ceb9551 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ImageMappingMemoryService.cs @@ -39,7 +39,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation _memoryCache = new MemoryCache(ReadMemoryFromModule); _recursionProtection = new HashSet(); target.OnFlushEvent.Register(_memoryCache.FlushCache); - target.DisposeOnClose(target.Services.GetService()?.OnChangeEvent.Register(_memoryCache.FlushCache)); + target.DisposeOnDestroy(target.Services.GetService()?.OnChangeEvent.Register(_memoryCache.FlushCache)); } #region IMemoryService diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/MetadataMappingMemoryService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/MetadataMappingMemoryService.cs index 47526c65a..5b061c76e 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/MetadataMappingMemoryService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/MetadataMappingMemoryService.cs @@ -39,7 +39,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation _target = target; _memoryService = memoryService; target.OnFlushEvent.Register(Flush); - target.DisposeOnClose(SymbolService?.OnChangeEvent.Register(Flush)); + target.DisposeOnDestroy(SymbolService?.OnChangeEvent.Register(Flush)); } /// diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs index aef141ce5..88d418c0e 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs @@ -67,6 +67,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation #region IModule + public ITarget Target => ModuleService.Target; + public IServiceProvider Services => ServiceProvider; public abstract int ModuleIndex { get; } @@ -207,6 +209,17 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation protected abstract ModuleService ModuleService { get; } + public override bool Equals(object obj) + { + IModule module = (IModule)obj; + return Target == module.Target && ImageBase == module.ImageBase; + } + + public override int GetHashCode() + { + return Utilities.CombineHashCodes(Target.GetHashCode(), ImageBase.GetHashCode()); + } + public override string ToString() { return $"#{ModuleIndex} {ImageBase:X16} {_flags} {FileName ?? ""}"; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs index 017dde717..8dc1c0be0 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs @@ -39,7 +39,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation // MachO writable segment attribute const uint VmProtWrite = 0x02; - protected readonly ITarget Target; + internal protected readonly ITarget Target; private IMemoryService _memoryService; private ISymbolService _symbolService; private ReadVirtualCache _versionCache; @@ -60,8 +60,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { foreach (IModule module in _modules.Values) { - if (module is IDisposable disposable) - { + if (module is IDisposable disposable) { disposable.Dispose(); } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs index 9858a75b0..6eb88cb6a 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Runtime.cs @@ -20,8 +20,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// public class Runtime : IRuntime { - private readonly ITarget _target; - private readonly IRuntimeService _runtimeService; private readonly ClrInfo _clrInfo; private ISymbolService _symbolService; private ClrRuntime _clrRuntime; @@ -30,13 +28,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation public readonly ServiceProvider ServiceProvider; - public Runtime(ITarget target, IRuntimeService runtimeService, ClrInfo clrInfo, int id) + public Runtime(ITarget target, int id, ClrInfo clrInfo) { - Trace.TraceInformation($"Creating runtime #{id} {clrInfo.Flavor} {clrInfo}"); - _target = target; - _runtimeService = runtimeService; - _clrInfo = clrInfo; + Target = target ?? throw new ArgumentNullException(nameof(target)); Id = id; + _clrInfo = clrInfo ?? throw new ArgumentNullException(nameof(clrInfo)); RuntimeType = RuntimeType.Unknown; if (clrInfo.Flavor == ClrFlavor.Core) { @@ -51,21 +47,25 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation ServiceProvider.AddService(clrInfo); ServiceProvider.AddServiceFactoryWithNoCaching(() => CreateRuntime()); - target.OnFlushEvent.Register(() => { - _clrRuntime?.DacLibrary.DacPrivateInterface.Flush(); - }); + target.OnFlushEvent.Register(() => _clrRuntime?.FlushCachedData()); + + Trace.TraceInformation($"Created runtime #{id} {clrInfo.Flavor} {clrInfo}"); } #region IRuntime - public IServiceProvider Services => ServiceProvider; - public int Id { get; } + public ITarget Target { get; } + + public IServiceProvider Services => ServiceProvider; + public RuntimeType RuntimeType { get; } public IModule RuntimeModule { get; } + public string RuntimeModuleDirectory { get; set; } + public string GetDacFilePath() { if (_dacFilePath is null) @@ -133,15 +133,20 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation private string GetDacFileName() { - return ClrInfoProvider.GetDacFileName(_clrInfo.Flavor, _target.OperatingSystem); + if (_clrInfo.SingleFileRuntimeInfo.HasValue) + { + return ClrInfoProvider.GetDacFileName(_clrInfo.Flavor, Target.OperatingSystem); + } + Debug.Assert(!string.IsNullOrEmpty(_clrInfo.DacInfo.PlatformSpecificFileName)); + return _clrInfo.DacInfo.PlatformSpecificFileName; } private string GetLocalDacPath(string dacFileName) { string dacFilePath; - if (!string.IsNullOrEmpty(_runtimeService.RuntimeModuleDirectory)) + if (!string.IsNullOrEmpty(RuntimeModuleDirectory)) { - dacFilePath = Path.Combine(_runtimeService.RuntimeModuleDirectory, dacFileName); + dacFilePath = Path.Combine(RuntimeModuleDirectory, dacFileName); } else { @@ -162,10 +167,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation private string GetDbiFileName() { - string name = _target.GetPlatformModuleName("mscordbi"); + string name = Target.GetPlatformModuleName("mscordbi"); // If this is the Linux runtime module name, but we are running on Windows return the cross-OS DBI name. - if (_target.OperatingSystem == OSPlatform.Linux && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (Target.OperatingSystem == OSPlatform.Linux && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { name = "mscordbi.dll"; } @@ -175,9 +180,9 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation private string GetLocalPath(string fileName) { string localFilePath; - if (!string.IsNullOrEmpty(_runtimeService.RuntimeModuleDirectory)) + if (!string.IsNullOrEmpty(RuntimeModuleDirectory)) { - localFilePath = Path.Combine(_runtimeService.RuntimeModuleDirectory, fileName); + localFilePath = Path.Combine(RuntimeModuleDirectory, fileName); } else { @@ -192,7 +197,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation private string DownloadFile(string fileName) { - OSPlatform platform = _target.OperatingSystem; + OSPlatform platform = Target.OperatingSystem; string filePath = null; if (SymbolService.IsSymbolStoreEnabled) @@ -253,16 +258,17 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation return filePath; } - private ISymbolService SymbolService => _symbolService ??= _target.Services.GetService(); + private ISymbolService SymbolService => _symbolService ??= Target.Services.GetService(); public override bool Equals(object obj) { - return Id == ((Runtime)obj).Id; + IRuntime runtime = (IRuntime)obj; + return Target == runtime.Target && Id == runtime.Id; } public override int GetHashCode() { - return Id.GetHashCode(); + return Utilities.CombineHashCodes(Target.GetHashCode(), Id.GetHashCode()); } private static readonly string[] s_runtimeTypeNames = { @@ -283,6 +289,9 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation else { sb.AppendLine($" Runtime module path: {RuntimeModule.FileName}"); } + if (RuntimeModuleDirectory is not null) { + sb.AppendLine($" Runtime module directory: {RuntimeModuleDirectory}"); + } if (_dacFilePath is not null) { sb.AppendLine($" DAC: {_dacFilePath}"); } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeService.cs index 6949fff5c..c54516c91 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/RuntimeService.cs @@ -21,11 +21,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation public class RuntimeService : IRuntimeService, IDataReader, IExportReader { private readonly ITarget _target; + private readonly bool _exportReaderEnabled; private readonly IDisposable _onFlushEvent; private DataTarget _dataTarget; - private string _runtimeModuleDirectory; private List _runtimes; - private Runtime _currentRuntime; + private IContextService _contextService; private IModuleService _moduleService; private IThreadService _threadService; private IMemoryService _memoryService; @@ -33,6 +33,8 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation public RuntimeService(ITarget target) { _target = target; + // TODO - mikem 4/30/21 - remove when dbgeng services doesn't take so long looking up exports (attempts to load PDBs). + _exportReaderEnabled = target.Host.HostType != HostType.DbgEng || Environment.GetEnvironmentVariable("DOTNET_ENABLE_SOS_SINGLEFILE") == "1"; _onFlushEvent = target.OnFlushEvent.Register(() => { if (_runtimes is not null && _runtimes.Count == 0) { @@ -45,58 +47,38 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation // Can't make RuntimeService IDisposable directly because _dataTarget.Dispose() disposes the IDataReader // passed which is this RuntimeService instance which would call _dataTarget.Dispose again and causing a // stack overflow. - target.DisposeOnClose(new UnregisterCallback(() => { + target.OnDestroyEvent.Register(() => { _dataTarget?.Dispose(); _dataTarget = null; _onFlushEvent.Dispose(); - })); + }); } #region IRuntimeService - /// - /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.) - /// - public string RuntimeModuleDirectory - { - get { return _runtimeModuleDirectory; } - set - { - _runtimeModuleDirectory = value; - _runtimes = null; - _currentRuntime = null; - } - } - /// /// Returns the list of runtimes in the target /// - public IEnumerable EnumerateRuntimes() => BuildRuntimes(); - - /// - /// Returns the current runtime - /// - public IRuntime CurrentRuntime + public IEnumerable EnumerateRuntimes() { - get + if (_runtimes is null) { - if (_currentRuntime is null) { - _currentRuntime = FindRuntime(); + _runtimes = new List(); + if (_dataTarget is null) + { + _dataTarget = new DataTarget(new CustomDataTarget(this)) { + BinaryLocator = null + }; + } + if (_dataTarget is not null) + { + for (int i = 0; i < _dataTarget.ClrVersions.Length; i++) + { + _runtimes.Add(new Runtime(_target, i, _dataTarget.ClrVersions[i])); + } } - return _currentRuntime; - } - } - - /// - /// Set the current runtime - /// - /// runtime id - public void SetCurrentRuntime(int runtimeId) - { - if (_runtimes is null || runtimeId >= _runtimes.Count) { - throw new DiagnosticsException($"Invalid runtime id {runtimeId}"); } - _currentRuntime = _runtimes[runtimeId]; + return _runtimes; } #endregion @@ -126,15 +108,15 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation int IDataReader.ProcessId => unchecked((int)_target.ProcessId.GetValueOrDefault()); - IEnumerable IDataReader.EnumerateModules() => + IEnumerable IDataReader.EnumerateModules() => ModuleService.EnumerateModules().Select((module) => CreateModuleInfo(module)).ToList(); private ModuleInfo CreateModuleInfo(IModule module) => new ModuleInfo( this, - module.ImageBase, + module.ImageBase, module.FileName, - isVirtual:true, + isVirtual: true, unchecked((int)module.IndexFileSize.GetValueOrDefault(0)), unchecked((int)module.IndexTimeStamp.GetValueOrDefault(0)), new ImmutableArray()); @@ -186,7 +168,9 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation return false; } - void IDataReader.FlushCachedData() => _target.Flush(); + void IDataReader.FlushCachedData() + { + } #endregion @@ -235,16 +219,19 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation bool IExportReader.TryGetSymbolAddress(ulong baseAddress, string name, out ulong offset) { - try + if (_exportReaderEnabled) { - IExportSymbols exportSymbols = ModuleService.GetModuleFromBaseAddress(baseAddress).Services.GetService(); - if (exportSymbols is not null) + try + { + IExportSymbols exportSymbols = ModuleService.GetModuleFromBaseAddress(baseAddress).Services.GetService(); + if (exportSymbols is not null) + { + return exportSymbols.TryGetSymbolAddress(name, out offset); + } + } + catch (DiagnosticsException) { - return exportSymbols.TryGetSymbolAddress(name, out offset); } - } - catch (DiagnosticsException) - { } offset = 0; return false; @@ -252,61 +239,9 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation #endregion - /// - /// Find the runtime - /// - private Runtime FindRuntime() - { - IEnumerable runtimes = BuildRuntimes(); - Runtime runtime = null; + private IRuntime CurrentRuntime => ContextService.Services.GetService(); - // First check if there is a .NET Core runtime loaded - foreach (Runtime r in runtimes) - { - if (r.RuntimeType == RuntimeType.NetCore || r.RuntimeType == RuntimeType.SingleFile) - { - runtime = r; - break; - } - } - // If no .NET Core runtime, then check for desktop runtime - if (runtime is null) - { - foreach (Runtime r in runtimes) - { - if (r.RuntimeType == RuntimeType.Desktop) - { - runtime = r; - break; - } - } - } - return runtime; - } - - private IEnumerable BuildRuntimes() - { - if (_runtimes is null) - { - _runtimes = new List(); - if (_dataTarget is null) - { - // Don't use the default binary locator or provide one. clrmd uses it to download the DAC, download assemblies - // to get metadata in its data target or for invalid memory region mapping and we already do all of that. - _dataTarget = new DataTarget(new CustomDataTarget(this)) { - BinaryLocator = null - }; - } - if (_dataTarget is not null) - { - for (int i = 0; i < _dataTarget.ClrVersions.Length; i++) - { - _runtimes.Add(new Runtime(_target, this, _dataTarget.ClrVersions[i], i)); - } - } - } - return _runtimes; - } + private IContextService ContextService => _contextService ??= _target.Services.GetService(); private IModuleService ModuleService => _moduleService ??= _target.Services.GetService(); @@ -317,14 +252,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation public override string ToString() { var sb = new StringBuilder(); - if (_runtimeModuleDirectory is not null) { - sb.AppendLine($"Runtime module path: {_runtimeModuleDirectory}"); - } if (_runtimes is not null) { foreach (IRuntime runtime in _runtimes) { - string current = _runtimes.Count > 1 ? runtime == _currentRuntime ? "*" : " " : ""; + string current = _runtimes.Count > 1 ? runtime == CurrentRuntime ? "*" : " " : ""; sb.Append(current); sb.AppendLine(runtime.ToString()); } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs index a0f073d5f..79e9e88f2 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs @@ -15,9 +15,19 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { private readonly Action _callback; - internal EventNode(Action callback) + internal EventNode(bool oneshot, Action callback) { - _callback = callback; + if (oneshot) + { + _callback = () => { + callback(); + Remove(); + }; + } + else + { + _callback = callback; + } } internal void Fire() @@ -37,10 +47,14 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { } - public IDisposable Register(Action callback) + public IDisposable Register(Action callback) => Register(oneshot: false, callback); + + public IDisposable RegisterOneShot(Action callback) => Register(oneshot: true, callback); + + private IDisposable Register(bool oneshot, Action callback) { // Insert at the end of the list - var node = new EventNode(callback); + var node = new EventNode(oneshot, callback); _events.InsertBefore(node); return node; } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceProvider.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceProvider.cs index c779e7d1a..24b98961a 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceProvider.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceProvider.cs @@ -9,15 +9,15 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { public class ServiceProvider : IServiceProvider { - readonly IServiceProvider _parent; - readonly Dictionary> _factories; - readonly Dictionary _services; + private readonly Func[] _parents; + private readonly Dictionary> _factories; + private readonly Dictionary _services; /// /// Create a service provider instance /// public ServiceProvider() - : this(null, null) + : this(Array.Empty>()) { } @@ -26,19 +26,18 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// /// search this provider if service isn't found in this instance public ServiceProvider(IServiceProvider parent) - : this(parent, null) + : this(new Func[] { () => parent }) { } /// /// Create a service provider with parent provider and service factories /// - /// search this provider if service isn't found in this instance or null - /// a dictionary of the factories to create the services - public ServiceProvider(IServiceProvider parent, Dictionary> factories) + /// an array of functions to return the next provider to search if service isn't found in this instance + public ServiceProvider(Func[] parents) { - _parent = parent; - _factories = factories ?? new Dictionary>(); + _parents = parents; + _factories = new Dictionary>(); _services = new Dictionary(); } @@ -78,11 +77,16 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation public void AddService(Type type, object service) => _services.Add(type, service); /// - /// Remove the service instance for the type. + /// Flushes the cached service instance for the specified type. Does not remove the service factory registered for the type. /// /// service type public void RemoveService(Type type) => _services.Remove(type); + /// + /// Flushes all the cached instances of the services. Does not remove any of the service factories registered. + /// + public void FlushServices() => _services.Clear(); + /// /// Returns the instance of the service or returns null if service doesn't exist /// @@ -97,9 +101,16 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation service = factory(); } } - if (service == null && _parent != null) + if (service == null) { - service = _parent.GetService(type); + foreach (Func parent in _parents) + { + service = parent()?.GetService(type); + if (service != null) + { + break; + } + } } return service; } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs index a88bbdc7c..61337d2ae 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs @@ -15,23 +15,22 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// /// ITarget base implementation /// - public abstract class Target : ITarget + public abstract class Target : ITarget, IDisposable { - private static int _targetIdFactory; private readonly string _dumpPath; - private readonly List _disposables; private string _tempDirectory; public readonly ServiceProvider ServiceProvider; - public Target(IHost host, string dumpPath) + public Target(IHost host, int id, string dumpPath) { Trace.TraceInformation($"Creating target #{Id}"); Host = host; + Id = id; _dumpPath = dumpPath; - _disposables = new List(); OnFlushEvent = new ServiceEvent(); + OnDestroyEvent = new ServiceEvent(); // Initialize the per-target services ServiceProvider = new ServiceProvider(host.Services); @@ -48,15 +47,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// public IHost Host { get; } - /// - /// Invoked when this target is flushed (via the Flush() call). - /// - public IServiceEvent OnFlushEvent { get; } - /// /// The target id /// - public int Id { get; } = _targetIdFactory++; + public int Id { get; } /// /// Returns the target OS (which may be different from the OS this is running on) @@ -100,6 +94,11 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// public IServiceProvider Services => ServiceProvider; + /// + /// Invoked when this target is flushed (via the Flush() call). + /// + public IServiceEvent OnFlushEvent { get; } + /// /// Flushes any cached state in the target. /// @@ -110,36 +109,22 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation } /// - /// Registers an object to be disposed when ITarget.Close() is called. + /// Invoked when the target is closed. /// - /// object to be disposed on Close() or null - public void DisposeOnClose(IDisposable disposable) - { - if (disposable != null) - { - _disposables.Add(disposable); - } - } + public IServiceEvent OnDestroyEvent { get; } + + #endregion /// /// Releases the target and the target's resources. /// - public void Close() + public void Dispose() { - Trace.TraceInformation($"Closing target #{Id}"); - Flush(); - - foreach (var disposable in _disposables) - { - disposable.Dispose(); - } - _disposables.Clear(); - + Trace.TraceInformation($"Disposing target #{Id}"); + OnDestroyEvent.Fire(); CleanupTempDirectory(); } - #endregion - private void CleanupTempDirectory() { if (_tempDirectory != null) @@ -161,7 +146,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation public override bool Equals(object obj) { - return Id == ((Target)obj).Id; + return Id == ((ITarget)obj).Id; } public override int GetHashCode() diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs index 83b5cb317..493415975 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/TargetFromDataReader.cs @@ -22,9 +22,10 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// IDataReader /// target operating system /// the host instance + /// target id /// path of dump for this target - public TargetFromDataReader(IDataReader dataReader, OSPlatform targetOS, IHost host, string dumpPath) - : base(host, dumpPath) + public TargetFromDataReader(IDataReader dataReader, OSPlatform targetOS, IHost host, int id, string dumpPath) + : base(host, id, dumpPath) { _dataReader = dataReader; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs index 321dca7dd..577e1905a 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs @@ -26,12 +26,14 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation #region IThread - public IServiceProvider Services => ServiceProvider; - public int ThreadIndex { get; } public uint ThreadId { get; } + public ITarget Target => _threadService.Target; + + public IServiceProvider Services => ServiceProvider; + public bool TryGetRegisterValue(int index, out ulong value) { value = 0; @@ -88,6 +90,17 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation #endregion + public override bool Equals(object obj) + { + IThread thread = (IThread)obj; + return Target == thread.Target && ThreadId == thread.ThreadId; + } + + public override int GetHashCode() + { + return Utilities.CombineHashCodes(Target.GetHashCode(), ThreadId.GetHashCode()); + } + public override string ToString() { return $"#{ThreadIndex} {ThreadId:X8}"; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs index d1bcb1613..e201665a5 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs @@ -17,7 +17,7 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation /// public abstract class ThreadService : IThreadService { - protected readonly ITarget Target; + internal protected readonly ITarget Target; private readonly int _contextSize; private readonly uint _contextFlags; private readonly Dictionary _lookupByName; @@ -154,11 +154,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation return _lookupByIndex.TryGetValue(index, out info); } - /// - /// Current OS thread Id - /// - public virtual uint? CurrentThreadId { get; set; } - /// /// Enumerate all the native threads /// diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs index 29db285b3..68e57af5b 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs @@ -25,19 +25,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { _dataReader = dataReader; _threadReader = (IThreadReader)dataReader; - - if (dataReader is IThreadReader threadReader) - { - // Initialize the current thread - IEnumerable threads = threadReader.EnumerateOSThreadIds(); - if (threads.Any()) { - CurrentThreadId = threads.First(); - } - } - else - { - throw new InvalidOperationException("IThreadReader not implemented"); - } } protected override bool GetThreadContext(uint threadId, uint contextFlags, uint contextSize, byte[] context) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/UnregisterCallback.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/UnregisterCallback.cs deleted file mode 100644 index 396946d62..000000000 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/UnregisterCallback.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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; - -namespace Microsoft.Diagnostics.DebugServices.Implementation -{ - /// - /// Helper class that implements unregister callback - /// - public class UnregisterCallback : IDisposable - { - Action m_action; - - public UnregisterCallback(Action action) - { - m_action = action; - } - - public void Dispose() - { - var action = m_action; - if (action != null) { - m_action = null; - action(); - } - } - } -} diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs new file mode 100644 index 000000000..c1cc2618c --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs @@ -0,0 +1,27 @@ +// 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.Implementation +{ + public class Utilities + { + /// + /// Combines two hash codes into a single hash code, in an order-dependent manner. + /// + /// + /// This function is neither commutative nor associative; the hash codes must be combined in + /// a deterministic order. Do not use this when hashing collections whose contents are + /// nondeterministically ordered! + /// + public static int CombineHashCodes(int hashCode0, int hashCode1) + { + unchecked { + // This specific hash function is based on the Boost C++ library's CombineHash function: + // http://stackoverflow.com/questions/4948780/magic-numbers-in-boosthash-combine + // http://www.boost.org/doc/libs/1_46_1/doc/html/hash/combine.html + return hashCode0 ^ (hashCode1 + (int) 0x9e3779b9 + (hashCode0 << 6) + (hashCode0 >> 2)); + } + } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/ContextServiceExtensions.cs b/src/Microsoft.Diagnostics.DebugServices/ContextServiceExtensions.cs new file mode 100644 index 000000000..c8e1736de --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/ContextServiceExtensions.cs @@ -0,0 +1,35 @@ +// 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; + +namespace Microsoft.Diagnostics.DebugServices +{ + public static class ContextServiceExtensions + { + /// + /// Returns the current target + /// + public static ITarget GetCurrentTarget(this IContextService contextService) + { + return contextService.Services.GetService(); + } + + /// + /// Returns the current thread + /// + public static IThread GetCurrentThread(this IContextService contextService) + { + return contextService.Services.GetService(); + } + + /// + /// Returns the current runtime + /// + public static IRuntime GetCurrentRuntime(this IContextService contextService) + { + return contextService.Services.GetService(); + } + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/IContextService.cs b/src/Microsoft.Diagnostics.DebugServices/IContextService.cs new file mode 100644 index 000000000..1ecf28447 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/IContextService.cs @@ -0,0 +1,59 @@ +// 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.Threading; + +namespace Microsoft.Diagnostics.DebugServices +{ + /// + /// Console output service + /// + public interface IContextService + { + /// + /// Current context service provider. Contains the current ITarget, IThread + /// and IRuntime instances along with all per target and global services. + /// + IServiceProvider Services { get; } + + /// + /// Fires anytime the current context changes. + /// + IServiceEvent OnContextChange { get; } + + /// + /// Sets the current target. + /// + /// target id + void SetCurrentTarget(int targetId); + + /// + /// Clears (nulls) the current target + /// + void ClearCurrentTarget(); + + /// + /// Set the current thread. + /// + /// thread id + void SetCurrentThread(uint threadId); + + /// + /// Clears (nulls) the current thread. + /// + void ClearCurrentThread(); + + /// + /// Set the current runtime + /// + /// runtime id + void SetCurrentRuntime(int runtimeId); + + /// + /// Clears (nulls) the current runtime + /// + void ClearCurrentRuntime(); + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/IHost.cs b/src/Microsoft.Diagnostics.DebugServices/IHost.cs index 4031a2d14..4c60e6d15 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IHost.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IHost.cs @@ -45,14 +45,9 @@ namespace Microsoft.Diagnostics.DebugServices IEnumerable EnumerateTargets(); /// - /// Current target instances or null + /// Destroys/closes the specified target instance /// - ITarget CurrentTarget { get; } - - /// - /// Sets the current target. - /// - /// target id - void SetCurrentTarget(int targetid); + /// target instance + void DestroyTarget(ITarget target); } } diff --git a/src/Microsoft.Diagnostics.DebugServices/IModule.cs b/src/Microsoft.Diagnostics.DebugServices/IModule.cs index 234a50db9..3f346e4d2 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IModule.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IModule.cs @@ -13,6 +13,11 @@ namespace Microsoft.Diagnostics.DebugServices /// public interface IModule { + /// + /// Target for this module. + /// + ITarget Target { get; } + /// /// The per module services like an optional clrmd's PEImage or PEReader instances if a PE module. /// diff --git a/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs b/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs index 1ac6e6ee9..56bd7723e 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IRuntime.cs @@ -23,14 +23,19 @@ namespace Microsoft.Diagnostics.DebugServices public interface IRuntime { /// - /// The per target services like clrmd's ClrInfo and ClrRuntime. + /// Runtime id /// - IServiceProvider Services { get; } + int Id { get; } /// - /// Runtime id + /// The target for this runtime. /// - int Id { get; } + ITarget Target { get; } + + /// + /// The per target services like clrmd's ClrInfo and ClrRuntime. + /// + IServiceProvider Services { get; } /// /// Returns the runtime OS and type @@ -42,6 +47,11 @@ namespace Microsoft.Diagnostics.DebugServices /// IModule RuntimeModule { get; } + /// + /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.) + /// + string RuntimeModuleDirectory { get; set; } + /// /// Returns the DAC file path /// diff --git a/src/Microsoft.Diagnostics.DebugServices/IRuntimeService.cs b/src/Microsoft.Diagnostics.DebugServices/IRuntimeService.cs index 7d8c7b850..240ff67c6 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IRuntimeService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IRuntimeService.cs @@ -12,25 +12,9 @@ namespace Microsoft.Diagnostics.DebugServices /// public interface IRuntimeService { - /// - /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.) - /// - string RuntimeModuleDirectory { get; set; } - /// /// Returns the list of runtimes in the target /// IEnumerable EnumerateRuntimes(); - - /// - /// Returns the current runtime or null if no runtime was found - /// - IRuntime CurrentRuntime { get; } - - /// - /// Set the current runtime - /// - /// runtime id - void SetCurrentRuntime(int runtimeId); } } diff --git a/src/Microsoft.Diagnostics.DebugServices/IServiceEvent.cs b/src/Microsoft.Diagnostics.DebugServices/IServiceEvent.cs index 6bb2497bc..bcbaed50a 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IServiceEvent.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IServiceEvent.cs @@ -18,6 +18,14 @@ namespace Microsoft.Diagnostics.DebugServices /// An opaque IDisposable that will unregister the callback when disposed IDisposable Register(Action callback); + /// + /// Register for the event callback. Puts the new callback at the end of the list. Automatically + /// removed from the event list when fired. + /// + /// callback delegate + /// An opaque IDisposable that will unregister the callback when disposed + IDisposable RegisterOneShot(Action callback); + /// /// Fires the event /// diff --git a/src/Microsoft.Diagnostics.DebugServices/ITarget.cs b/src/Microsoft.Diagnostics.DebugServices/ITarget.cs index 1a674a7df..4043cb99c 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ITarget.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ITarget.cs @@ -13,11 +13,6 @@ namespace Microsoft.Diagnostics.DebugServices /// public interface ITarget { - /// - /// Invoked when this target is flushed (via the Flush() call). - /// - IServiceEvent OnFlushEvent { get; } - /// /// Returns the host interface instance /// @@ -60,19 +55,18 @@ namespace Microsoft.Diagnostics.DebugServices IServiceProvider Services { get; } /// - /// Flushes any cached state in the target. + /// Invoked when this target is flushed (via the Flush() call). /// - void Flush(); + IServiceEvent OnFlushEvent { get; } /// - /// Registers an object to be disposed when ITarget.Close() is called. + /// Flushes any cached state in the target. /// - /// object to be disposed on Close() or null - void DisposeOnClose(IDisposable disposable); + void Flush(); /// - /// Releases the target and the target's resources. + /// Invoked when the target is destroyed. /// - void Close(); + IServiceEvent OnDestroyEvent { get; } } } diff --git a/src/Microsoft.Diagnostics.DebugServices/IThread.cs b/src/Microsoft.Diagnostics.DebugServices/IThread.cs index c91b1b909..9d12c7da7 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IThread.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IThread.cs @@ -11,11 +11,6 @@ namespace Microsoft.Diagnostics.DebugServices /// public interface IThread { - /// - /// The per thread services. - /// - IServiceProvider Services { get; } - /// /// Debugger specific thread index. /// @@ -26,6 +21,16 @@ namespace Microsoft.Diagnostics.DebugServices /// uint ThreadId { get; } + /// + /// The target for this target. + /// + ITarget Target { get; } + + /// + /// The per thread services. + /// + IServiceProvider Services { get; } + /// /// Returns the register value for the thread and register index. This function /// can only return register values that are 64 bits or less and currently the diff --git a/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs b/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs index 35eadfc58..2d9cb395b 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs @@ -48,11 +48,6 @@ namespace Microsoft.Diagnostics.DebugServices /// true if index found bool TryGetRegisterInfo(int registerIndex, out RegisterInfo info); - /// - /// Current OS thread Id - /// - uint? CurrentThreadId { get; set; } - /// /// Enumerate all the native threads /// @@ -60,7 +55,7 @@ namespace Microsoft.Diagnostics.DebugServices IEnumerable EnumerateThreads(); /// - /// Get the thread info from the thread index + /// Get the thread from the thread index /// /// index /// thread info @@ -68,7 +63,7 @@ namespace Microsoft.Diagnostics.DebugServices IThread GetThreadFromIndex(int threadIndex); /// - /// Get the thread info from the OS thread id + /// Get the thread from the OS thread id /// /// os id /// thread info diff --git a/src/Microsoft.Diagnostics.DebugServices/TargetExtensions.cs b/src/Microsoft.Diagnostics.DebugServices/TargetExtensions.cs index 5e12a4251..a8beb0692 100644 --- a/src/Microsoft.Diagnostics.DebugServices/TargetExtensions.cs +++ b/src/Microsoft.Diagnostics.DebugServices/TargetExtensions.cs @@ -18,7 +18,7 @@ namespace Microsoft.Diagnostics.DebugServices /// platform module name public static string GetPlatformModuleName(this ITarget target, string moduleName) { - if (target.OperatingSystem == OSPlatform.Windows) + if (target.OperatingSystem == OSPlatform.Windows) { return moduleName + ".dll"; } @@ -30,7 +30,18 @@ namespace Microsoft.Diagnostics.DebugServices { return "lib" + moduleName + ".dylib"; } - else throw new PlatformNotSupportedException(target.OperatingSystem.ToString()); + throw new PlatformNotSupportedException(target.OperatingSystem.ToString()); + } + + /// + /// Registers an object to be disposed when target is destroyed. + /// + /// target instance + /// object to be disposed or null + /// IDisposable to unregister this event or null + public static IDisposable DisposeOnDestroy(this ITarget target, IDisposable disposable) + { + return disposable != null ? target.OnDestroyEvent.Register(() => disposable.Dispose()) : null; } } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs index 9bb491765..4f6a631cb 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ClrMDHelper.cs @@ -6,6 +6,7 @@ using Microsoft.Diagnostics.DebugServices; using Microsoft.Diagnostics.Runtime; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading.Tasks; namespace Microsoft.Diagnostics.ExtensionCommands @@ -17,7 +18,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands public ClrMDHelper(ClrRuntime clr) { - _clr = clr ?? throw new DiagnosticsException("No CLR runtime set"); + Debug.Assert(clr != null); + _clr = clr; _heap = _clr.Heap; } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs index 0e1195ef5..f5b1d5e19 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentDictionaryCommand.cs @@ -16,7 +16,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands public ClrRuntime Runtime { get; set; } - public override void Invoke() + public override void ExtensionInvoke() { if (string.IsNullOrEmpty(Address)) { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs index 857f6eb88..f886b5b92 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpConcurrentQueueCommand.cs @@ -16,7 +16,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands public ClrRuntime Runtime { get; set; } - public override void Invoke() + public override void ExtensionInvoke() { if (string.IsNullOrEmpty(Address)) { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/DumpGenCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/DumpGenCommand.cs index da86dcbd4..5c50bb0d0 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/DumpGenCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/DumpGenCommand.cs @@ -25,7 +25,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Option(Name = "-mt", Help = "The address pointing on a Method table.")] public string MethodTableAddress { get; set; } - public override void Invoke() + public override void ExtensionInvoke() { var generation = ParseGenerationArgument(Generation); if (generation != GCGeneration.NotSet) diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionCommandBase.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionCommandBase.cs index f123345e7..c2f4c099d 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionCommandBase.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ExtensionCommandBase.cs @@ -9,11 +9,21 @@ namespace Microsoft.Diagnostics.ExtensionCommands public abstract class ExtensionCommandBase : CommandBase { /// - /// Helper bound to the current ClrRuntime that provides - /// high level services on top of ClrMD. + /// Helper bound to the current ClrRuntime that provides high level services on top of ClrMD. /// public ClrMDHelper Helper { get; set; } + public override void Invoke() + { + if (Helper == null) + { + throw new DiagnosticsException("No CLR runtime set"); + } + ExtensionInvoke(); + } + + public abstract void ExtensionInvoke(); + [HelpInvoke] public void InvokeHelp() { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs index 97c846863..10ec2d68d 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/LoggingCommand.cs @@ -22,17 +22,36 @@ namespace Microsoft.Diagnostics.ExtensionCommands public override void Invoke() { if (Enable) { - if (Trace.Listeners[ListenerName] == null) { - Trace.Listeners.Add(new LoggingListener()); - Trace.AutoFlush = true; - } + EnableLogging(); } else if (Disable) { - Trace.Listeners.Remove(ListenerName); + DisableLogging(); } WriteLine("Logging is {0}", Trace.Listeners[ListenerName] != null ? "enabled" : "disabled"); } + public static void Initialize() + { + if (Environment.GetEnvironmentVariable("DOTNET_ENABLED_SOS_LOGGING") == "1") + { + EnableLogging(); + } + } + + public static void EnableLogging() + { + if (Trace.Listeners[ListenerName] == null) + { + Trace.Listeners.Add(new LoggingListener()); + Trace.AutoFlush = true; + } + } + + public static void DisableLogging() + { + Trace.Listeners.Remove(ListenerName); + } + class LoggingListener : TraceListener { internal LoggingListener() diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs index a00b55537..3b73de48c 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/RuntimesCommand.cs @@ -12,6 +12,8 @@ namespace Microsoft.Diagnostics.ExtensionCommands { public IRuntimeService RuntimeService { get; set; } + public IContextService ContextService { get; set; } + [Option(Name = "-netfx", Help = "Switches to the desktop .NET Framework if exists.")] public bool NetFx { get; set; } @@ -32,7 +34,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands if (NetFx && runtime.RuntimeType == RuntimeType.Desktop || NetCore && runtime.RuntimeType == RuntimeType.NetCore) { - RuntimeService.SetCurrentRuntime(runtime.Id); + ContextService.SetCurrentRuntime(runtime.Id); WriteLine("Switched to {0} runtime successfully", name); return; } @@ -46,7 +48,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands foreach (IRuntime runtime in RuntimeService.EnumerateRuntimes()) { - string current = displayStar ? (runtime == RuntimeService.CurrentRuntime ? "*" : " ") : ""; + string current = displayStar ? (runtime == ContextService.GetCurrentRuntime() ? "*" : " ") : ""; Write(current); Write(runtime.ToString()); } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetClrPathCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetClrPathCommand.cs index dfeb151ea..e7979568c 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetClrPathCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/SetClrPathCommand.cs @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Command(Name = "setclrpath", Help = "Set the path to load coreclr DAC/DBI files.")] public class SetClrPath: CommandBase { - public IRuntimeService RuntimeService { get; set; } + public IRuntime Runtime { get; set; } [Argument(Name = "path", Help = "Runtime directory path.")] public string Argument { get; set; } @@ -20,22 +20,22 @@ namespace Microsoft.Diagnostics.ExtensionCommands public override void Invoke() { - if (RuntimeService == null) + if (Runtime == null) { - throw new DiagnosticsException("Runtime service required"); + throw new DiagnosticsException("Runtime required"); } if (Clear) { - RuntimeService.RuntimeModuleDirectory = null; + Runtime.RuntimeModuleDirectory = null; } else if (Argument == null) { - WriteLine("Load path for DAC/DBI: '{0}'", RuntimeService.RuntimeModuleDirectory ?? ""); + WriteLine("Load path for DAC/DBI: '{0}'", Runtime.RuntimeModuleDirectory ?? ""); } else { - RuntimeService.RuntimeModuleDirectory = Path.GetFullPath(Argument); - WriteLine("Set load path for DAC/DBI to '{0}'", RuntimeService.RuntimeModuleDirectory); + Runtime.RuntimeModuleDirectory = Path.GetFullPath(Argument); + WriteLine("Set load path for DAC/DBI to '{0}'", Runtime.RuntimeModuleDirectory); } } } diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/Host/ThreadsCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ThreadsCommand.cs index 4c7c2d2ac..9f700c292 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/Host/ThreadsCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ThreadsCommand.cs @@ -18,8 +18,12 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Option(Name = "--verbose", Aliases = new string[] { "-v" }, Help = "Displays more details.")] public bool Verbose { get; set; } + public IThread CurrentThread { get; set; } + public IThreadService ThreadService { get; set; } + public IContextService ContextService { get; set; } + public override void Invoke() { if (Thread.HasValue) @@ -33,11 +37,11 @@ namespace Microsoft.Diagnostics.ExtensionCommands { thread = ThreadService.GetThreadFromIndex(unchecked((int)Thread.Value)); } - ThreadService.CurrentThreadId = thread.ThreadId; + ContextService.SetCurrentThread(thread.ThreadId); } else { - uint currentThreadId = ThreadService.CurrentThreadId.GetValueOrDefault(uint.MaxValue); + uint currentThreadId = CurrentThread != null ? CurrentThread.ThreadId : uint.MaxValue; foreach (IThread thread in ThreadService.EnumerateThreads()) { WriteLine("{0}{1} 0x{2:X4} ({2})", thread.ThreadId == currentThreadId ? "*" : " ", thread.ThreadIndex, thread.ThreadId); diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs index bb36d5fd4..ba07cac25 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ParallelStacksCommand.cs @@ -17,7 +17,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Option(Name = "--allthreads", Aliases = new string[] { "-a" }, Help = "Displays all threads per group instead of at most 4 by default.")] public bool AllThreads { get; set; } - public override void Invoke() + public override void ExtensionInvoke() { var ps = ParallelStacks.Runtime.ParallelStack.Build(Runtime); if (ps == null) @@ -39,7 +39,6 @@ namespace Microsoft.Diagnostics.ExtensionCommands } WriteLine($"==> {ps.ThreadIds.Count} threads with {ps.Stacks.Count} roots{Environment.NewLine}"); - } protected override string GetDetailedHelp() diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs index 21b5030c5..0a41b1898 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/TaskStateCommand.cs @@ -16,7 +16,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Option(Name = "--value", Aliases = new string[] { "-v" }, Help = " is the value of a Task m_stateFlags field.")] public ulong? Value { get; set; } - public override void Invoke() + public override void ExtensionInvoke() { if (string.IsNullOrEmpty(Address) && !Value.HasValue) { diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs index 6c263bf22..73b26e343 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/ThreadPoolQueueCommand.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Command(Name = "threadpoolqueue", Aliases = new string[] { "tpq" }, Help = "Display queued ThreadPool work items.")] public class ThreadPoolQueueCommand : ExtensionCommandBase { - public override void Invoke() + public override void ExtensionInvoke() { var workItems = new Dictionary(); int workItemCount = 0; diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs index fe61d53b8..cf415baa1 100644 --- a/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs +++ b/src/Microsoft.Diagnostics.ExtensionCommands/TimersCommand.cs @@ -12,7 +12,7 @@ namespace Microsoft.Diagnostics.ExtensionCommands [Command(Name = "timerinfo", Aliases = new string[] { "ti" }, Help = "Display running timers details.")] public class TimersCommand : ExtensionCommandBase { - public override void Invoke() + public override void ExtensionInvoke() { try { @@ -73,7 +73,6 @@ namespace Microsoft.Diagnostics.ExtensionCommands { WriteLine(x.Message); } - } static string GetTimerString(TimerInfo timer) diff --git a/src/SOS/SOS.Extensions/ContextServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/ContextServiceFromDebuggerServices.cs new file mode 100644 index 000000000..849d47294 --- /dev/null +++ b/src/SOS/SOS.Extensions/ContextServiceFromDebuggerServices.cs @@ -0,0 +1,51 @@ +// 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.DebugServices; +using Microsoft.Diagnostics.DebugServices.Implementation; +using Microsoft.Diagnostics.Runtime.Utilities; +using System.Diagnostics; + +namespace SOS.Extensions +{ + /// + /// Provides the context services on native debuggers + /// + internal class ContextServiceFromDebuggerServices : ContextService + { + private readonly DebuggerServices _debuggerServices; + + internal ContextServiceFromDebuggerServices(IHost host, DebuggerServices debuggerServices) + : base(host) + { + Debug.Assert(debuggerServices != null); + _debuggerServices = debuggerServices; + } + + public override IThread GetCurrentThread() + { + HResult hr = _debuggerServices.GetCurrentThreadId(out uint threadId); + if (hr != HResult.S_OK) + { + Trace.TraceError("GetCurrentThreadId() FAILED {0:X8}", hr); + return null; + } + return ThreadService?.GetThreadFromId(threadId); + } + + public override void SetCurrentThread(IThread thread) + { + if (thread != null) + { + HResult hr = _debuggerServices.SetCurrentThreadId(thread.ThreadId); + if (hr != HResult.S_OK) + { + Trace.TraceError("SetCurrentThreadId() FAILED {0:X8}", hr); + return; + } + } + base.SetCurrentThread(thread); + } + } +} diff --git a/src/SOS/SOS.Extensions/HostServices.cs b/src/SOS/SOS.Extensions/HostServices.cs index 30128a98c..7424a7ae7 100644 --- a/src/SOS/SOS.Extensions/HostServices.cs +++ b/src/SOS/SOS.Extensions/HostServices.cs @@ -42,7 +42,11 @@ namespace SOS.Extensions private readonly CommandProcessor _commandProcessor; private readonly SymbolService _symbolService; private readonly HostWrapper _hostWrapper; + private ContextServiceFromDebuggerServices _contextService; + private int _targetIdFactory; private ITarget _target; + private TargetWrapper _targetWrapper; + private IMemoryService _memoryService; /// /// Enable the assembly resolver to get the right versions in the same directory as this assembly. @@ -52,6 +56,7 @@ namespace SOS.Extensions if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) { AssemblyResolver.Enable(); } + LoggingCommand.Initialize(); } /// @@ -101,9 +106,9 @@ namespace SOS.Extensions _serviceProvider.AddService(_commandProcessor); _serviceProvider.AddService(_symbolService); - _hostWrapper = new HostWrapper(this); + _hostWrapper = new HostWrapper(this, () => _targetWrapper); _hostWrapper.AddServiceWrapper(IID_IHostServices, this); - _hostWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(this)); + _hostWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(this, () => _memoryService)); VTableBuilder builder = AddInterface(IID_IHostServices, validate: false); builder.AddMethod(new GetHostDelegate(GetHost)); @@ -123,6 +128,8 @@ namespace SOS.Extensions protected override void Destroy() { Trace.TraceInformation("HostServices.Destroy"); + _hostWrapper.RemoveServiceWrapper(IID_IHostServices); + _hostWrapper.Release(); } #region IHost @@ -135,9 +142,27 @@ namespace SOS.Extensions IEnumerable IHost.EnumerateTargets() => _target != null ? new ITarget[] { _target } : Array.Empty(); - ITarget IHost.CurrentTarget => _target; - - void IHost.SetCurrentTarget(int targetid) => throw new NotImplementedException(); + public void DestroyTarget(ITarget target) + { + if (target == null) { + throw new ArgumentNullException(nameof(target)); + } + Trace.TraceInformation("IHost.DestroyTarget #{0}", target.Id); + if (target == _target) + { + _target = null; + _memoryService = null; + if (_targetWrapper != null) + { + _targetWrapper.Release(); + _targetWrapper = null; + } + _contextService.ClearCurrentTarget(); + if (target is IDisposable disposable) { + disposable.Dispose(); + } + } + } #endregion @@ -156,6 +181,7 @@ namespace SOS.Extensions IntPtr self, IntPtr iunk) { + Trace.TraceInformation("HostServices.RegisterDebuggerServices"); if (iunk == IntPtr.Zero || DebuggerServices != null) { return HResult.E_FAIL; } @@ -182,8 +208,15 @@ namespace SOS.Extensions { var consoleService = new ConsoleServiceFromDebuggerServices(DebuggerServices); _serviceProvider.AddService(consoleService); + + _contextService = new ContextServiceFromDebuggerServices(this, DebuggerServices); + _serviceProvider.AddService(_contextService); _serviceProvider.AddServiceFactory(() => new ThreadUnwindServiceFromDebuggerServices(DebuggerServices)); - Trace.TraceInformation("HostServices.RegisterDebuggerServices"); + + _contextService.ServiceProvider.AddServiceFactory(() => { + ClrRuntime clrRuntime = _contextService.Services.GetService(); + return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null; + }); // Add each extension command to the native debugger foreach ((string name, string help, IEnumerable aliases) in _commandProcessor.Commands) @@ -219,13 +252,15 @@ namespace SOS.Extensions IntPtr self) { Trace.TraceInformation("HostServices.CreateTarget"); - if (_target != null) { + if (_target != null || DebuggerServices == null) { return HResult.E_FAIL; } try { - _target = new TargetFromDebuggerServices(DebuggerServices, this); - _serviceProvider.AddService(_target); + _target = new TargetFromDebuggerServices(DebuggerServices, this, _targetIdFactory++); + _contextService.SetCurrentTarget(_target); + _targetWrapper = new TargetWrapper(_contextService.Services); + _memoryService = _contextService.Services.GetService(); } catch (Exception ex) { @@ -239,7 +274,7 @@ namespace SOS.Extensions IntPtr self, uint processId) { - Trace.TraceInformation($"HostServices.UpdateTarget {processId}"); + Trace.TraceInformation("HostServices.UpdateTarget {0} #{1}", processId, _target != null ? _target.Id : ""); if (_target == null) { return CreateTarget(self); @@ -265,13 +300,10 @@ namespace SOS.Extensions private void DestroyTarget( IntPtr self) { - Trace.TraceInformation("HostServices.DestroyTarget {0}", _target != null ? _target.Id : ""); - _hostWrapper.DestroyTarget(); + Trace.TraceInformation("HostServices.DestroyTarget #{0}", _target != null ? _target.Id : ""); if (_target != null) { - _serviceProvider.RemoveService(typeof(ITarget)); - _target.Close(); - _target = null; + DestroyTarget(_target); } } @@ -285,7 +317,7 @@ namespace SOS.Extensions } try { - return _commandProcessor.Execute(commandLine, GetServices()); + return _commandProcessor.Execute(commandLine, _contextService.Services); } catch (Exception ex) { @@ -300,7 +332,7 @@ namespace SOS.Extensions { try { - if (!_commandProcessor.DisplayHelp(command, GetServices())) + if (!_commandProcessor.DisplayHelp(command, _contextService.Services)) { return HResult.E_INVALIDARG; } @@ -317,51 +349,22 @@ namespace SOS.Extensions IntPtr self) { Trace.TraceInformation("HostServices.Uninitialize"); - _hostWrapper.DestroyTarget(); - if (_target != null) + DestroyTarget(self); + + if (DebuggerServices != null) { - _target.Close(); - _target = null; + DebuggerServices.Release(); + DebuggerServices = null; } - DebuggerServices.Release(); - DebuggerServices = null; // Send shutdown event on exit OnShutdownEvent.Fire(); + + // Release the host services wrapper + Release(); } #endregion - - private IServiceProvider GetServices() - { - // If there is no target, then provide just the global services - ServiceProvider services = _serviceProvider; - if (_target != null) - { - // Create a per command invocation service provider. These services may change between each command invocation. - services = new ServiceProvider(_target.Services); - - // Add the current thread if any - services.AddServiceFactory(() => - { - IThreadService threadService = _target.Services.GetService(); - if (threadService != null && threadService.CurrentThreadId.HasValue) { - return threadService.GetThreadFromId(threadService.CurrentThreadId.Value); - } - return null; - }); - - // Add the current runtime and related services - var runtimeService = _target.Services.GetService(); - if (runtimeService != null) - { - services.AddServiceFactory(() => runtimeService.CurrentRuntime); - services.AddServiceFactory(() => services.GetService()?.Services.GetService()); - services.AddServiceFactory(() => new ClrMDHelper(services.GetService())); - } - } - return services; - } #region IHostServices delegates diff --git a/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs b/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs index f10075daf..3a44ba2f8 100644 --- a/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/TargetFromFromDebuggerServices.cs @@ -21,8 +21,8 @@ namespace SOS.Extensions /// /// Create a target instance from IDataReader /// - internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost host) - : base(host, dumpPath: null) + internal TargetFromDebuggerServices(DebuggerServices debuggerServices, IHost host, int id) + : base(host, id, dumpPath: null) { Debug.Assert(debuggerServices != null); diff --git a/src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs index dfb28e7be..63dc87364 100644 --- a/src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs @@ -24,32 +24,6 @@ namespace SOS.Extensions _debuggerServices = debuggerServices; } - #region IThreadService - - public override uint? CurrentThreadId - { - get - { - HResult hr = _debuggerServices.GetCurrentThreadId(out uint threadId); - if (hr != HResult.S_OK) - { - Trace.TraceError("GetCurrentThreadId() FAILED {0:X8}", hr); - return null; - } - return threadId; - } - set - { - HResult hr = _debuggerServices.SetCurrentThreadId(value.Value); - if (hr != HResult.S_OK) - { - Trace.TraceError("SetCurrentThreadId() FAILED {0:X8}", hr); - } - } - } - - #endregion - protected override bool GetThreadContext(uint threadId, uint contextFlags, uint contextSize, byte[] context) { return _debuggerServices.GetThreadContext(threadId, contextFlags, contextSize, context) == HResult.S_OK; diff --git a/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs b/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs index 2b6023fc3..5a4c3507d 100644 --- a/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs +++ b/src/SOS/SOS.Hosting/CorDebugDataTargetWrapper.cs @@ -18,23 +18,22 @@ namespace SOS.Hosting private static readonly Guid IID_ICorDebugMetaDataLocator = new Guid("7cef8ba9-2ef7-42bf-973f-4171474f87d9"); private readonly ITarget _target; + private readonly ISymbolService _symbolService; private readonly IMemoryService _memoryService; private readonly IThreadService _threadService; private readonly IThreadUnwindService _threadUnwindService; - private readonly SymbolServiceWrapper _symbolServiceWrapper; private readonly ulong _ignoreAddressBitsMask; public IntPtr ICorDebugDataTarget { get; } - internal CorDebugDataTargetWrapper(ITarget target, IRuntime runtime) + internal CorDebugDataTargetWrapper(IServiceProvider services) { - Debug.Assert(target != null); - Debug.Assert(runtime != null); - _target = target; - _memoryService = target.Services.GetService(); - _threadService = target.Services.GetService(); - _threadUnwindService = target.Services.GetService(); - _symbolServiceWrapper = new SymbolServiceWrapper(target.Host); + Debug.Assert(services != null); + _target = services.GetService(); + _symbolService = services.GetService(); + _memoryService = services.GetService(); + _threadService = services.GetService(); + _threadUnwindService = services.GetService(); _ignoreAddressBitsMask = _memoryService.SignExtensionMask(); VTableBuilder builder = AddInterface(IID_ICorDebugDataTarget, validate: false); @@ -215,8 +214,7 @@ namespace SOS.Hosting IntPtr pPathBufferSize, IntPtr pPathBuffer) { - return _symbolServiceWrapper.GetICorDebugMetadataLocator( - IntPtr.Zero, imagePath, imageTimestamp, imageSize, pathBufferSize, pPathBufferSize, pPathBuffer); + return _symbolService.GetICorDebugMetadataLocator(imagePath, imageTimestamp, imageSize, pathBufferSize, pPathBufferSize, pPathBuffer); } #endregion diff --git a/src/SOS/SOS.Hosting/DataTargetWrapper.cs b/src/SOS/SOS.Hosting/DataTargetWrapper.cs index 61a77b861..57ec91d4f 100644 --- a/src/SOS/SOS.Hosting/DataTargetWrapper.cs +++ b/src/SOS/SOS.Hosting/DataTargetWrapper.cs @@ -23,30 +23,30 @@ namespace SOS.Hosting // For ClrMD's magic hand shake private const ulong MagicCallbackConstant = 0x43; - private readonly ITarget _target; + private readonly IServiceProvider _services; private readonly ulong _runtimeBaseAddress; + private readonly ISymbolService _symbolService; private readonly IMemoryService _memoryService; private readonly IThreadService _threadService; private readonly IModuleService _moduleService; private readonly IThreadUnwindService _threadUnwindService; private readonly IRemoteMemoryService _remoteMemoryService; - private readonly SymbolServiceWrapper _symbolServiceWrapper; private readonly ulong _ignoreAddressBitsMask; public IntPtr IDataTarget { get; } - public DataTargetWrapper(ITarget target, IRuntime runtime) + public DataTargetWrapper(IServiceProvider services, IRuntime runtime) { - Debug.Assert(target != null); + Debug.Assert(services != null); Debug.Assert(runtime != null); - _target = target; + _services = services; _runtimeBaseAddress = runtime.RuntimeModule.ImageBase; - _memoryService = target.Services.GetService(); - _threadService = target.Services.GetService(); - _threadUnwindService = target.Services.GetService(); - _moduleService = target.Services.GetService(); - _remoteMemoryService = target.Services.GetService(); - _symbolServiceWrapper = new SymbolServiceWrapper(target.Host); + _symbolService = services.GetService(); + _memoryService = services.GetService(); + _threadService = services.GetService(); + _threadUnwindService = services.GetService(); + _moduleService = services.GetService(); + _remoteMemoryService = services.GetService(); _ignoreAddressBitsMask = _memoryService.SignExtensionMask(); VTableBuilder builder = AddInterface(IID_ICLRDataTarget, false); @@ -103,7 +103,12 @@ namespace SOS.Hosting IntPtr self, out IMAGE_FILE_MACHINE machineType) { - machineType = _target.Architecture switch + ITarget target = _services.GetService(); + if (target == null) { + machineType = IMAGE_FILE_MACHINE.UNKNOWN; + return HResult.E_FAIL; + } + machineType = target.Architecture switch { Architecture.X64 => IMAGE_FILE_MACHINE.AMD64, Architecture.X86 => IMAGE_FILE_MACHINE.I386, @@ -195,7 +200,7 @@ namespace SOS.Hosting IntPtr self, out uint threadId) { - uint? id = _threadService.CurrentThreadId; + uint? id = _services.GetService()?.ThreadId; if (id.HasValue) { threadId = id.Value; @@ -330,8 +335,7 @@ namespace SOS.Hosting IntPtr buffer, IntPtr dataSize) { - return _symbolServiceWrapper.GetMetadataLocator( - IntPtr.Zero, fileName, imageTimestamp, imageSize, mvid, mdRva, flags, bufferSize, buffer, dataSize); + return _symbolService.GetMetadataLocator(fileName, imageTimestamp, imageSize, mvid, mdRva, flags, bufferSize, buffer, dataSize); } #endregion @@ -484,4 +488,4 @@ namespace SOS.Hosting #endregion } -} \ No newline at end of file +} diff --git a/src/SOS/SOS.Hosting/HostWrapper.cs b/src/SOS/SOS.Hosting/HostWrapper.cs index 86c707477..042bb40ca 100644 --- a/src/SOS/SOS.Hosting/HostWrapper.cs +++ b/src/SOS/SOS.Hosting/HostWrapper.cs @@ -16,16 +16,16 @@ namespace SOS.Hosting private static readonly Guid IID_IHost = new Guid("E0CD8534-A88B-40D7-91BA-1B4C925761E9"); private readonly IHost _host; + private readonly Func _getTarget; private readonly Dictionary> _factories = new Dictionary>(); private readonly Dictionary _wrappers = new Dictionary(); - private TargetWrapper _targetWrapper; - public IntPtr IHost { get; } - public HostWrapper(IHost host) + public HostWrapper(IHost host, Func getTarget) { _host = host; + _getTarget = getTarget; VTableBuilder builder = AddInterface(IID_IHost, validate: false); builder.AddMethod(new GetHostTypeDelegate(GetHostType)); @@ -39,6 +39,11 @@ namespace SOS.Hosting protected override void Destroy() { Trace.TraceInformation("HostWrapper.Destroy"); + foreach (var wrapper in _wrappers.Values) + { + wrapper.Release(); + } + _wrappers.Clear(); } /// @@ -61,6 +66,16 @@ namespace SOS.Hosting _wrappers.Add(serviceId, service); } + /// + /// Remove the service instance + /// + /// guid + public void RemoveServiceWrapper(in Guid serviceId) + { + _factories.Remove(serviceId); + _wrappers.Remove(serviceId); + } + /// /// Returns the wrapper instance for the guid /// @@ -82,15 +97,6 @@ namespace SOS.Hosting return service; } - public void DestroyTarget() - { - if (_targetWrapper != null) - { - _targetWrapper.Release(); - _targetWrapper = null; - } - } - #region IHost /// @@ -111,8 +117,7 @@ namespace SOS.Hosting ptr = IntPtr.Zero; COMCallableIUnknown wrapper = GetServiceWrapper(guid); - if (wrapper == null) - { + if (wrapper == null) { return HResult.E_NOINTERFACE; } wrapper.AddRef(); @@ -122,22 +127,18 @@ namespace SOS.Hosting /// /// Returns the current target wrapper or null /// - /// target wrapper address returned + /// target wrapper address returned /// S_OK - private HResult GetCurrentTarget(IntPtr self, out IntPtr target) + private HResult GetCurrentTarget(IntPtr self, out IntPtr targetWrapper) { - target = IntPtr.Zero; - - if (_host.CurrentTarget == null) { + TargetWrapper wrapper = _getTarget(); + if (wrapper == null) + { + targetWrapper = IntPtr.Zero; return HResult.E_NOINTERFACE; } - // TODO: this only supports one target instance. Need to add a dictionary lookup for multi-target support. - if (_targetWrapper == null) { - _targetWrapper = new TargetWrapper(_host.CurrentTarget); - } - _targetWrapper.AddRef(); - target = _targetWrapper.ITarget; - + wrapper.AddRef(); + targetWrapper = wrapper.ITarget; return HResult.S_OK; } diff --git a/src/SOS/SOS.Hosting/LLDBServices.cs b/src/SOS/SOS.Hosting/LLDBServices.cs index ad130b310..f954c3991 100644 --- a/src/SOS/SOS.Hosting/LLDBServices.cs +++ b/src/SOS/SOS.Hosting/LLDBServices.cs @@ -99,9 +99,8 @@ namespace SOS.Hosting string GetCoreClrDirectory( IntPtr self) { - IRuntime currentRuntime = _soshost.Target.Services.GetService()?.CurrentRuntime; - if (currentRuntime != null) - { + IRuntime currentRuntime = _soshost.Services.GetService(); + if (currentRuntime is not null) { return Path.GetDirectoryName(currentRuntime.RuntimeModule.FileName); } return null; diff --git a/src/SOS/SOS.Hosting/RuntimeWrapper.cs b/src/SOS/SOS.Hosting/RuntimeWrapper.cs index bcfd1fe6a..87f9b31d2 100644 --- a/src/SOS/SOS.Hosting/RuntimeWrapper.cs +++ b/src/SOS/SOS.Hosting/RuntimeWrapper.cs @@ -21,11 +21,11 @@ namespace SOS.Hosting /// enum RuntimeConfiguration { - WindowsDesktop = 0, - WindowsCore = 1, - UnixCore = 2, - OSXCore = 3, - Unknown = 4 + WindowsDesktop = 0, + WindowsCore = 1, + UnixCore = 2, + OSXCore = 3, + Unknown = 4 } private static readonly Guid IID_IRuntime = new Guid("A5F152B9-BA78-4512-9228-5091A4CB7E35"); @@ -81,7 +81,7 @@ namespace SOS.Hosting #endregion - private readonly ITarget _target; + private readonly IServiceProvider _services; private readonly IRuntime _runtime; private readonly IDisposable _onFlushEvent; private IntPtr _clrDataProcess = IntPtr.Zero; @@ -91,25 +91,20 @@ namespace SOS.Hosting public IntPtr IRuntime { get; } - internal RuntimeWrapper(ITarget target, IRuntime runtime) + internal RuntimeWrapper(IServiceProvider services, IRuntime runtime) { - Debug.Assert(target != null); + Debug.Assert(services != null); Debug.Assert(runtime != null); - _target = target; + _services = services; _runtime = runtime; - - _onFlushEvent = target.OnFlushEvent.Register(() => { - // TODO: there is a better way to flush _corDebugProcess with ICorDebugProcess4::ProcessStateChanged(FLUSH_ALL) - _corDebugProcess = IntPtr.Zero; - // TODO: there is a better way to flush _clrDataProcess with ICLRDataProcess::Flush() - _clrDataProcess = IntPtr.Zero; - }); + _onFlushEvent = runtime.Target.OnFlushEvent.Register(Flush); VTableBuilder builder = AddInterface(IID_IRuntime, validate: false); builder.AddMethod(new GetRuntimeConfigurationDelegate(GetRuntimeConfiguration)); builder.AddMethod(new GetModuleAddressDelegate(GetModuleAddress)); builder.AddMethod(new GetModuleSizeDelegate(GetModuleSize)); + builder.AddMethod(new SetRuntimeDirectoryDelegate(SetRuntimeDirectory)); builder.AddMethod(new GetRuntimeDirectoryDelegate(GetRuntimeDirectory)); builder.AddMethod(new GetClrDataProcessDelegate(GetClrDataProcess)); builder.AddMethod(new GetCorDebugInterfaceDelegate(GetCorDebugInterface)); @@ -124,6 +119,7 @@ namespace SOS.Hosting { Trace.TraceInformation("RuntimeWrapper.Destroy"); _onFlushEvent.Dispose(); + Flush(); if (_dacHandle != IntPtr.Zero) { DataTarget.PlatformFunctions.FreeLibrary(_dacHandle); @@ -136,6 +132,22 @@ namespace SOS.Hosting } } + private void Flush() + { + // TODO: there is a better way to flush _corDebugProcess with ICorDebugProcess4::ProcessStateChanged(FLUSH_ALL) + if (_corDebugProcess == IntPtr.Zero) + { + COMHelper.Release(_corDebugProcess); + _corDebugProcess = IntPtr.Zero; + } + // TODO: there is a better way to flush _clrDataProcess with ICLRDataProcess::Flush() + if (_clrDataProcess == IntPtr.Zero) + { + COMHelper.Release(_clrDataProcess); + _clrDataProcess = IntPtr.Zero; + } + } + #region IRuntime (native) private RuntimeConfiguration GetRuntimeConfiguration( @@ -148,11 +160,11 @@ namespace SOS.Hosting case RuntimeType.NetCore: case RuntimeType.SingleFile: - if (_target.OperatingSystem == OSPlatform.Windows) + if (_runtime.Target.OperatingSystem == OSPlatform.Windows) { return RuntimeConfiguration.WindowsCore; } - else if (_target.OperatingSystem == OSPlatform.Linux || _target.OperatingSystem == OSPlatform.OSX) + else if (_runtime.Target.OperatingSystem == OSPlatform.Linux || _runtime.Target.OperatingSystem == OSPlatform.OSX) { return RuntimeConfiguration.UnixCore; } @@ -173,9 +185,20 @@ namespace SOS.Hosting return _runtime.RuntimeModule.ImageSize; } + private void SetRuntimeDirectory( + IntPtr self, + string runtimeModuleDirectory) + { + _runtime.RuntimeModuleDirectory = runtimeModuleDirectory; + } + private string GetRuntimeDirectory( IntPtr self) { + if (_runtime.RuntimeModuleDirectory is not null) + { + return _runtime.RuntimeModuleDirectory; + } return Path.GetDirectoryName(_runtime.RuntimeModule.FileName); } @@ -219,7 +242,7 @@ namespace SOS.Hosting byte* fileVersionBuffer, int fileVersionBufferSizeInBytes) { - IModuleService moduleService = _target.Services.GetService(); + IModuleService moduleService = _services.GetService(); IModule module; try { @@ -282,7 +305,7 @@ namespace SOS.Hosting Trace.TraceError("Failed to obtain DAC CLRDataCreateInstance"); return IntPtr.Zero; } - var dataTarget = new DataTargetWrapper(_target, _runtime); + var dataTarget = new DataTargetWrapper(_services, _runtime); int hr = createInstance(IID_IXCLRDataProcess, dataTarget.IDataTarget, out IntPtr unk); if (hr != 0) { @@ -317,7 +340,7 @@ namespace SOS.Hosting Build = 0, Revision = 0, }; - var dataTarget = new CorDebugDataTargetWrapper(_target, _runtime); + var dataTarget = new CorDebugDataTargetWrapper(_services); ulong clrInstanceId = _runtime.RuntimeModule.ImageBase; int hresult = 0; @@ -451,6 +474,11 @@ namespace SOS.Hosting private delegate ulong GetModuleSizeDelegate( [In] IntPtr self); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate void SetRuntimeDirectoryDelegate( + [In] IntPtr self, + [In, MarshalAs(UnmanagedType.LPStr)] string runtimeModuleDirectory); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.LPStr)] private delegate string GetRuntimeDirectoryDelegate( diff --git a/src/SOS/SOS.Hosting/SOSHost.cs b/src/SOS/SOS.Hosting/SOSHost.cs index de9d7df03..5189d5858 100644 --- a/src/SOS/SOS.Hosting/SOSHost.cs +++ b/src/SOS/SOS.Hosting/SOSHost.cs @@ -10,7 +10,6 @@ using System; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.InteropServices; using System.Text; using Architecture = System.Runtime.InteropServices.Architecture; @@ -18,60 +17,43 @@ using Architecture = System.Runtime.InteropServices.Architecture; namespace SOS.Hosting { /// - /// Helper code to hosting SOS under ClrMD + /// Helper code to hosting the native SOS code /// public sealed class SOSHost : IDisposable { - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate int SOSCommandDelegate( - IntPtr ILLDBServices, - [In, MarshalAs(UnmanagedType.LPStr)] string args); - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate int SOSInitializeDelegate( - IntPtr IHost); - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate void SOSUninitializeDelegate(); - - private const string SOSInitialize = "SOSInitializeByHost"; - private const string SOSUninitialize = "SOSUninitializeByHost"; - // This is what dbgeng/IDebuggerServices returns for non-PE modules that don't have a timestamp internal const uint InvalidTimeStamp = 0xFFFFFFFE; internal const uint InvalidChecksum = 0xFFFFFFFF; + internal readonly IServiceProvider Services; internal readonly ITarget Target; + internal readonly TargetWrapper TargetWrapper; internal readonly IConsoleService ConsoleService; internal readonly IModuleService ModuleService; internal readonly IThreadService ThreadService; internal readonly IMemoryService MemoryService; + private readonly SOSLibrary _sosLibrary; private readonly IntPtr _interface; - private readonly HostWrapper _hostWrapper; private readonly ulong _ignoreAddressBitsMask; - private IntPtr _sosLibrary = IntPtr.Zero; private bool _disposed; /// - /// The native SOS binaries path. Default is OS/architecture (RID) named directory in the same directory as this assembly. + /// Create an instance of the hosting class. Has the lifetime of the target. Depends on the + /// context service for the current thread and runtime. /// - public string SOSPath { get; set; } - - /// - /// Create an instance of the hosting class - /// - /// target instance - public SOSHost(ITarget target) - { - Target = target; - ConsoleService = target.Services.GetService(); - ModuleService = target.Services.GetService(); - ThreadService = target.Services.GetService(); - MemoryService = target.Services.GetService(); + /// service provider + public SOSHost(IServiceProvider services) + { + Services = services; + Target = services.GetService() ?? throw new DiagnosticsException("No target"); + TargetWrapper = new TargetWrapper(services); + Target.DisposeOnDestroy(this); + ConsoleService = services.GetService(); + ModuleService = services.GetService(); + ThreadService = services.GetService(); + MemoryService = services.GetService(); _ignoreAddressBitsMask = MemoryService.SignExtensionMask(); - - string rid = InstallHelper.GetRid(); - SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid); + _sosLibrary = services.GetService(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -83,97 +65,19 @@ namespace SOS.Hosting var lldbServices = new LLDBServices(this); _interface = lldbServices.ILLDBServices; } - _hostWrapper = new HostWrapper(target.Host); - _hostWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(target.Host)); - } - - /// - /// Loads and initializes the SOS module. - /// - public void InitializeSOSHost() - { - if (_sosLibrary == IntPtr.Zero) - { - string sos; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - sos = "sos.dll"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - sos = "libsos.so"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - sos = "libsos.dylib"; - } - else { - throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}"); - } - string sosPath = Path.Combine(SOSPath, sos); - try - { - _sosLibrary = Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.LoadLibrary(sosPath); - } - catch (DllNotFoundException ex) - { - // This is a workaround for the Microsoft SDK docker images. Can fail when LoadLibrary uses libdl.so to load the SOS module. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - throw new DllNotFoundException("Problem loading SOS module. Try installing libc6-dev (apt-get install libc6-dev) to work around this problem.", ex); - } - else - { - throw; - } - } - if (_sosLibrary == IntPtr.Zero) - { - throw new FileNotFoundException($"SOS module {sosPath} not found"); - } - var initializeFunc = GetDelegateFunction(_sosLibrary, SOSInitialize); - if (initializeFunc == null) - { - throw new EntryPointNotFoundException($"Can not find SOS module initialization function: {SOSInitialize}"); - } - Target.DisposeOnClose(Target.Host.OnShutdownEvent.Register(OnShutdownEvent)); - int result = initializeFunc(_hostWrapper.IHost); - if (result != 0) - { - throw new InvalidOperationException($"SOS initialization FAILED 0x{result:X8}"); - } - Trace.TraceInformation("SOS initialized: sosPath '{0}'", sosPath); - } } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - ~SOSHost() - { - Dispose(false); - } - - private void Dispose(bool _) + void IDisposable.Dispose() { + Trace.TraceInformation($"SOSHost.Dispose {_disposed}"); if (!_disposed) { - Trace.TraceInformation("SOSHost.Dispose"); _disposed = true; + TargetWrapper.Release(); COMHelper.Release(_interface); } } - /// - /// Shutdown/clean up the native SOS. - /// - private void OnShutdownEvent() - { - Trace.TraceInformation("SOSHost: OnShutdownEvent"); - var uninitializeFunc = GetDelegateFunction(_sosLibrary, SOSUninitialize); - uninitializeFunc?.Invoke(); - } - /// /// Execute a SOS command. /// @@ -198,19 +102,11 @@ namespace SOS.Hosting /// just the command name /// the command arguments and options public void ExecuteCommand(string command, string arguments) - { - Debug.Assert(_sosLibrary != null); - - var commandFunc = GetDelegateFunction(_sosLibrary, command); - if (commandFunc == null) - { - throw new EntryPointNotFoundException($"Can not find SOS command: {command}"); - } - int result = commandFunc(_interface, arguments ?? ""); - if (result != HResult.S_OK) - { - Trace.TraceError($"SOS command FAILED 0x{result:X8}"); + { + if (_disposed) { + throw new ObjectDisposedException("SOSHost instance disposed"); } + _sosLibrary.ExecuteCommand(_interface, command, arguments); } #region Reverse PInvoke Implementations @@ -681,11 +577,12 @@ namespace SOS.Hosting IntPtr context, uint contextSize) { - if (!ThreadService.CurrentThreadId.HasValue) + IThread thread = Services.GetService(); + if (thread is not null) { - return HResult.E_FAIL; + return GetThreadContextBySystemId(self, thread.ThreadId, 0, contextSize, context); } - return GetThreadContextBySystemId(self, ThreadService.CurrentThreadId.Value, 0, contextSize, context); + return HResult.E_FAIL; } internal int GetThreadContextBySystemId( @@ -757,11 +654,12 @@ namespace SOS.Hosting IntPtr self, out uint id) { - if (!ThreadService.CurrentThreadId.HasValue) { - id = 0; - return HResult.E_FAIL; + IThread thread = Services.GetService(); + if (thread is not null) { + return GetThreadIdBySystemId(self, thread.ThreadId, out id); } - return GetThreadIdBySystemId(self, ThreadService.CurrentThreadId.Value, out id); + id = 0; + return HResult.E_FAIL; } internal int SetCurrentThreadId( @@ -770,7 +668,11 @@ namespace SOS.Hosting { try { - ThreadService.CurrentThreadId = ThreadService.GetThreadFromIndex(unchecked((int)id)).ThreadId; + var contextService = Services.GetService(); + if (contextService is null) { + return HResult.E_FAIL; + } + contextService.SetCurrentThread(ThreadService.GetThreadFromIndex(unchecked((int)id)).ThreadId); } catch (DiagnosticsException) { @@ -783,10 +685,10 @@ namespace SOS.Hosting IntPtr self, out uint sysId) { - uint? id = ThreadService.CurrentThreadId; - if (id.HasValue) - { - sysId = id.Value; + IThread thread = Services.GetService(); + if (thread is not null) + { + sysId = thread.ThreadId; return HResult.S_OK; } sysId = 0; @@ -847,12 +749,12 @@ namespace SOS.Hosting IntPtr self, ulong* offset) { - if (ThreadService.CurrentThreadId.HasValue) - { - uint threadId = ThreadService.CurrentThreadId.Value; + IThread thread = Services.GetService(); + if (thread is not null) + { try { - ulong teb = ThreadService.GetThreadFromId(threadId).GetThreadTeb(); + ulong teb = thread.GetThreadTeb(); Write(offset, teb); return HResult.S_OK; } @@ -946,15 +848,12 @@ namespace SOS.Hosting int index, out ulong value) { - if (ThreadService.CurrentThreadId.HasValue) - { - IThread thread = ThreadService.GetThreadFromId(ThreadService.CurrentThreadId.Value); - if (thread != null) + IThread thread = Services.GetService(); + if (thread is not null) + { + if (thread.TryGetRegisterValue(index, out value)) { - if (thread.TryGetRegisterValue(index, out value)) - { - return HResult.S_OK; - } + return HResult.S_OK; } } value = 0; diff --git a/src/SOS/SOS.Hosting/SOSLibrary.cs b/src/SOS/SOS.Hosting/SOSLibrary.cs new file mode 100644 index 000000000..6d0ce843f --- /dev/null +++ b/src/SOS/SOS.Hosting/SOSLibrary.cs @@ -0,0 +1,176 @@ +// 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.DebugServices; +using Microsoft.Diagnostics.Runtime.Utilities; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace SOS.Hosting +{ + /// + /// Helper code to load and initialize SOS + /// + public sealed class SOSLibrary + { + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate int SOSCommandDelegate( + IntPtr ILLDBServices, + [In, MarshalAs(UnmanagedType.LPStr)] string args); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate int SOSInitializeDelegate( + IntPtr IHost); + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate void SOSUninitializeDelegate(); + + private const string SOSInitialize = "SOSInitializeByHost"; + private const string SOSUninitialize = "SOSUninitializeByHost"; + + private readonly IContextService _contextService; + private readonly HostWrapper _hostWrapper; + private IntPtr _sosLibrary = IntPtr.Zero; + + /// + /// The native SOS binaries path. Default is OS/architecture (RID) named directory in the same directory as this assembly. + /// + public string SOSPath { get; set; } + + public static SOSLibrary Create(IHost host) + { + SOSLibrary sosLibrary = null; + try + { + sosLibrary = new SOSLibrary(host); + sosLibrary.Initialize(); + } + catch + { + sosLibrary.Uninitialize(); + sosLibrary = null; + throw; + } + host.OnShutdownEvent.Register(() => { + sosLibrary.Uninitialize(); + sosLibrary = null; + }); + return sosLibrary; + } + + /// + /// Create an instance of the hosting class + /// + /// target instance + private SOSLibrary(IHost host) + { + _contextService = host.Services.GetService(); + + string rid = InstallHelper.GetRid(); + 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)); + } + + /// + /// Loads and initializes the SOS module. + /// + private void Initialize() + { + if (_sosLibrary == IntPtr.Zero) + { + string sos; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + sos = "sos.dll"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + sos = "libsos.so"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { + sos = "libsos.dylib"; + } + else { + throw new PlatformNotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}"); + } + string sosPath = Path.Combine(SOSPath, sos); + try + { + _sosLibrary = Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.LoadLibrary(sosPath); + } + catch (DllNotFoundException ex) + { + // This is a workaround for the Microsoft SDK docker images. Can fail when LoadLibrary uses libdl.so to load the SOS module. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + throw new DllNotFoundException("Problem loading SOS module. Try installing libc6-dev (apt-get install libc6-dev) to work around this problem.", ex); + } + else + { + throw; + } + } + if (_sosLibrary == IntPtr.Zero) + { + throw new FileNotFoundException($"SOS module {sosPath} not found"); + } + var initializeFunc = SOSHost.GetDelegateFunction(_sosLibrary, SOSInitialize); + if (initializeFunc == null) + { + throw new EntryPointNotFoundException($"Can not find SOS module initialization function: {SOSInitialize}"); + } + int result = initializeFunc(_hostWrapper.IHost); + if (result != 0) + { + throw new InvalidOperationException($"SOS initialization FAILED 0x{result:X8}"); + } + Trace.TraceInformation("SOS initialized: sosPath '{0}'", sosPath); + } + } + + /// + /// Shutdown/clean up the native SOS module. + /// + private void Uninitialize() + { + Trace.TraceInformation("SOSHost: Uninitialize"); + if (_sosLibrary != IntPtr.Zero) + { + var uninitializeFunc = SOSHost.GetDelegateFunction(_sosLibrary, SOSUninitialize); + uninitializeFunc?.Invoke(); + + Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.FreeLibrary(_sosLibrary); + _sosLibrary = IntPtr.Zero; + } + _hostWrapper.Release(); + } + + /// + /// Execute a SOS command. + /// + /// client interface + /// just the command name + /// the command arguments and options + public void ExecuteCommand(IntPtr client, string command, string arguments) + { + Debug.Assert(_sosLibrary != IntPtr.Zero); + + var commandFunc = SOSHost.GetDelegateFunction(_sosLibrary, command); + if (commandFunc == null) + { + throw new EntryPointNotFoundException($"Can not find SOS command: {command}"); + } + int result = commandFunc(client, arguments ?? ""); + if (result != HResult.S_OK) + { + Trace.TraceError($"SOS command FAILED 0x{result:X8}"); + } + } + + private SOSHost GetSOSHost() => _contextService.Services.GetService(); + } +} diff --git a/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs b/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs new file mode 100644 index 000000000..21b4a629b --- /dev/null +++ b/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs @@ -0,0 +1,141 @@ +// 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.DebugServices; +using Microsoft.Diagnostics.Runtime.Utilities; +using Microsoft.FileFormats; +using Microsoft.SymbolStore; +using Microsoft.SymbolStore.KeyGenerators; +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace SOS.Hosting +{ + public static class SymbolServiceExtensions + { + // HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) + const int E_INSUFFICIENT_BUFFER = unchecked((int)0x8007007a); + + /// + /// Metadata locator helper for the DAC. + /// + /// file name and path to module + /// module timestamp + /// module image + /// not used + /// not used + /// not used + /// size of incoming buffer (pMetadata) + /// pointer to buffer + /// size of outgoing metadata + /// HRESULT + public static int GetMetadataLocator( + this ISymbolService symbolService, + string imagePath, + uint imageTimestamp, + uint imageSize, + byte[] mvid, + uint mdRva, + uint flags, + uint bufferSize, + 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 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; + } + + /// + /// Metadata locator helper for the DAC. + /// + /// file name and path to module + /// module timestamp + /// module image + /// output buffer size + /// native pointer to put actual path size + /// native pointer to WCHAR path buffer + /// HRESULT + public static int GetICorDebugMetadataLocator( + this ISymbolService symbolService, + string imagePath, + uint imageTimestamp, + uint imageSize, + uint pathBufferSize, + IntPtr pPathBufferSize, + IntPtr pwszPathBuffer) + { + int hr = HResult.S_OK; + int actualSize = 0; + + Debug.Assert(pwszPathBuffer != IntPtr.Zero); + try + { + if (symbolService.IsSymbolStoreEnabled) + { + SymbolStoreKey key = PEFileKeyGenerator.GetKey(imagePath, imageTimestamp, imageSize); + string localFilePath = symbolService.DownloadFile(key); + localFilePath += "\0"; // null terminate the string + actualSize = localFilePath.Length; + + if (pathBufferSize > actualSize) + { + Trace.TraceInformation($"GetICorDebugMetadataLocator: SUCCEEDED {localFilePath}"); + Marshal.Copy(localFilePath.ToCharArray(), 0, pwszPathBuffer, actualSize); + } + else + { + Trace.TraceError("GetICorDebugMetadataLocator: E_INSUFFICIENT_BUFFER"); + hr = E_INSUFFICIENT_BUFFER; + } + } + else + { + Trace.TraceError($"GetICorDebugMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} symbol store not enabled"); + hr = HResult.E_FAIL; + } + } + catch (Exception ex) when + (ex is UnauthorizedAccessException || + ex is BadImageFormatException || + ex is InvalidVirtualAddressException || + ex is IOException) + { + Trace.TraceError($"GetICorDebugMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} ERROR {ex.Message}"); + hr = HResult.E_FAIL; + } + if (pPathBufferSize != IntPtr.Zero) + { + Marshal.WriteInt32(pPathBufferSize, actualSize); + } + return hr; + } + } +} diff --git a/src/SOS/SOS.Hosting/SymbolServiceWrapper.cs b/src/SOS/SOS.Hosting/SymbolServiceWrapper.cs index 74973a536..a5cee2931 100644 --- a/src/SOS/SOS.Hosting/SymbolServiceWrapper.cs +++ b/src/SOS/SOS.Hosting/SymbolServiceWrapper.cs @@ -73,18 +73,16 @@ namespace SOS.Hosting public static readonly Guid IID_ISymbolService = new Guid("7EE88D46-F8B3-4645-AD3E-01FE7D4F70F1"); - // HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) - const int E_INSUFFICIENT_BUFFER = unchecked((int)0x8007007a); - - private readonly IHost _host; + private readonly Func _getMemoryService; private readonly ISymbolService _symbolService; - private IMemoryService _memoryService; - public SymbolServiceWrapper(IHost host) + public SymbolServiceWrapper(IHost host, Func getMemoryService) { Debug.Assert(host != null); - _host = host; + Debug.Assert(getMemoryService != null); + _getMemoryService = getMemoryService; _symbolService = host.Services.GetService(); + Debug.Assert(_symbolService != null); VTableBuilder builder = AddInterface(IID_ISymbolService, validate: false); builder.AddMethod(new IsSymbolStoreEnabledDelegate((IntPtr self) => _symbolService.IsSymbolStoreEnabled)); @@ -103,6 +101,7 @@ namespace SOS.Hosting builder.AddMethod(new GetMetadataLocatorDelegate(GetMetadataLocator)); builder.AddMethod(new GetICorDebugMetadataLocatorDelegate(GetICorDebugMetadataLocator)); builder.Complete(); + AddRef(); } @@ -695,50 +694,7 @@ namespace SOS.Hosting IntPtr pPathBufferSize, IntPtr pwszPathBuffer) { - int hr = HResult.S_OK; - int actualSize = 0; - - Debug.Assert(pwszPathBuffer != IntPtr.Zero); - try - { - if (_symbolService.IsSymbolStoreEnabled) - { - SymbolStoreKey key = PEFileKeyGenerator.GetKey(imagePath, imageTimestamp, imageSize); - string localFilePath = _symbolService.DownloadFile(key); - localFilePath += "\0"; // null terminate the string - actualSize = localFilePath.Length; - - if (pathBufferSize > actualSize) - { - Trace.TraceInformation($"GetICorDebugMetadataLocator: SUCCEEDED {localFilePath}"); - Marshal.Copy(localFilePath.ToCharArray(), 0, pwszPathBuffer, actualSize); - } - else - { - Trace.TraceError("GetICorDebugMetadataLocator: E_INSUFFICIENT_BUFFER"); - hr = E_INSUFFICIENT_BUFFER; - } - } - else - { - Trace.TraceError($"GetICorDebugMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} symbol store not enabled"); - hr = HResult.E_FAIL; - } - } - catch (Exception ex) when - (ex is UnauthorizedAccessException || - ex is BadImageFormatException || - ex is InvalidVirtualAddressException || - ex is IOException) - { - Trace.TraceError($"GetICorDebugMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} ERROR {ex.Message}"); - hr = HResult.E_FAIL; - } - if (pPathBufferSize != IntPtr.Zero) - { - Marshal.WriteInt32(pPathBufferSize, actualSize); - } - return hr; + return _symbolService.GetICorDebugMetadataLocator(imagePath, imageTimestamp, imageSize, pathBufferSize, pPathBufferSize, pwszPathBuffer); } /// @@ -996,21 +952,7 @@ namespace SOS.Hosting return pathName.Substring(pos + 1); } - private IMemoryService MemoryService - { - get - { - if (_memoryService == null) - { - ITarget target = _host.CurrentTarget; - if (target == null) { - throw new DiagnosticsException("SymbolService: no current target"); - } - _memoryService = target.Services.GetService(); - } - return _memoryService; - } - } + private IMemoryService MemoryService => _getMemoryService() ?? throw new DiagnosticsException("SymbolServiceWrapper: no current target"); #region Symbol service delegates diff --git a/src/SOS/SOS.Hosting/TargetWrapper.cs b/src/SOS/SOS.Hosting/TargetWrapper.cs index 229dca527..1ff3de96a 100644 --- a/src/SOS/SOS.Hosting/TargetWrapper.cs +++ b/src/SOS/SOS.Hosting/TargetWrapper.cs @@ -26,22 +26,21 @@ namespace SOS.Hosting public static readonly Guid IID_ITarget = new Guid("B4640016-6CA0-468E-BA2C-1FFF28DE7B72"); + private readonly IServiceProvider _services; private readonly ITarget _target; private readonly Dictionary _wrappers = new Dictionary(); - public TargetWrapper(ITarget target) + public TargetWrapper(IServiceProvider services) { - Debug.Assert(target != null); - _target = target; + _services = services; + _target = services.GetService() ?? throw new DiagnosticsException("No target"); VTableBuilder builder = AddInterface(IID_ITarget, validate: false); builder.AddMethod(new GetOperatingSystemDelegate(GetOperatingSystem)); builder.AddMethod(new GetTempDirectoryDelegate(GetTempDirectory)); - builder.AddMethod(new GetRuntimeDirectoryDelegate(GetRuntimeDirectory)); builder.AddMethod(new GetRuntimeDelegate(GetRuntime)); builder.AddMethod(new FlushDelegate(Flush)); - builder.AddMethod(new CloseDelegate(Close)); ITarget = builder.Complete(); @@ -79,31 +78,20 @@ namespace SOS.Hosting return _target.GetTempDirectory(); } - private string GetRuntimeDirectory( - IntPtr self) - { - var runtimeService = _target.Services.GetService(); - if (runtimeService == null) - { - return null; - } - return runtimeService.RuntimeModuleDirectory; - } - - private int GetRuntime( + private HResult GetRuntime( IntPtr self, IntPtr* ppRuntime) { if (ppRuntime == null) { return HResult.E_INVALIDARG; } - IRuntime runtime = _target.Services.GetService()?.CurrentRuntime; + IRuntime runtime = _services.GetService(); if (runtime == null) { return HResult.E_NOINTERFACE; } if (!_wrappers.TryGetValue(runtime, out RuntimeWrapper wrapper)) { - wrapper = new RuntimeWrapper(_target, runtime); + wrapper = new RuntimeWrapper(_services, runtime); _wrappers.Add(runtime, wrapper); } *ppRuntime = wrapper.IRuntime; @@ -116,12 +104,6 @@ namespace SOS.Hosting _target.Flush(); } - private void Close( - IntPtr self) - { - _target.Close(); - } - #region ITarget delegates [UnmanagedFunctionPointer(CallingConvention.Winapi)] @@ -134,12 +116,7 @@ namespace SOS.Hosting [In] IntPtr self); [UnmanagedFunctionPointer(CallingConvention.Winapi)] - [return: MarshalAs(UnmanagedType.LPStr)] - private delegate string GetRuntimeDirectoryDelegate( - [In] IntPtr self); - - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate int GetRuntimeDelegate( + private delegate HResult GetRuntimeDelegate( [In] IntPtr self, [Out] IntPtr* ppRuntime); @@ -147,10 +124,6 @@ namespace SOS.Hosting private delegate void FlushDelegate( [In] IntPtr self); - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate void CloseDelegate( - [In] IntPtr self); - #endregion } } diff --git a/src/SOS/Strike/dbgengservices.cpp b/src/SOS/Strike/dbgengservices.cpp index bd11981ae..3b1c47b10 100644 --- a/src/SOS/Strike/dbgengservices.cpp +++ b/src/SOS/Strike/dbgengservices.cpp @@ -502,22 +502,10 @@ HRESULT DbgEngServices::ChangeEngineState( { if (((Argument & DEBUG_STATUS_MASK) == DEBUG_STATUS_BREAK) && ((Argument & DEBUG_STATUS_INSIDE_WAIT) == 0)) { - ULONG processId = 0; - if (FAILED(m_system->GetCurrentProcessSystemId(&processId))) - { - m_control->Output(DEBUG_OUTPUT_NORMAL, "ChangeEngineState: DestroyTarget\n"); - Extensions::GetInstance()->DestroyTarget(); - } - else - { - m_control->Output(DEBUG_OUTPUT_NORMAL, "ChangeEngineState: processId %d\n", processId); + m_control->Output(DEBUG_OUTPUT_NORMAL, "ChangeEngineState\n"); - // Has the process changed since the last commmand? - Extensions::GetInstance()->UpdateTarget(processId); - - // Flush the target when the debugger target breaks - Extensions::GetInstance()->FlushTarget(); - } + // Flush the target when the debugger target breaks + Extensions::GetInstance()->FlushTarget(); } } return DEBUG_STATUS_NO_CHANGE; diff --git a/src/SOS/Strike/platform/runtimeimpl.cpp b/src/SOS/Strike/platform/runtimeimpl.cpp index 7d368657d..dedac177a 100644 --- a/src/SOS/Strike/platform/runtimeimpl.cpp +++ b/src/SOS/Strike/platform/runtimeimpl.cpp @@ -364,33 +364,41 @@ ULONG Runtime::Release() //---------------------------------------------------------------------------- /**********************************************************************\ - * Returns the runtime directory of the target + * Set the runtime module directory to search for DAC/DBI +\**********************************************************************/ +void Runtime::SetRuntimeDirectory(LPCSTR runtimeModuleDirectory) +{ + if (m_runtimeDirectory != nullptr) + { + free((void*)m_runtimeDirectory); + m_runtimeDirectory = nullptr; + } + if (runtimeModuleDirectory != nullptr) + { + m_runtimeDirectory = _strdup(runtimeModuleDirectory); + } +} + +/**********************************************************************\ + * Returns the runtime directory \**********************************************************************/ LPCSTR Runtime::GetRuntimeDirectory() { if (m_runtimeDirectory == nullptr) { - LPCSTR runtimeDirectory = m_target->GetRuntimeDirectory(); - if (runtimeDirectory != nullptr) + if (GetFileAttributesA(m_name) == INVALID_FILE_ATTRIBUTES) { - m_runtimeDirectory = _strdup(runtimeDirectory); + ExtDbgOut("Error: Runtime module %s doesn't exist %08x\n", m_name, HRESULT_FROM_WIN32(GetLastError())); + return nullptr; } - else + // Parse off the file name + char* runtimeDirectory = _strdup(m_name); + char* lastSlash = strrchr(runtimeDirectory, GetTargetDirectorySeparatorW()); + if (lastSlash != nullptr) { - if (GetFileAttributesA(m_name) == INVALID_FILE_ATTRIBUTES) - { - ExtDbgOut("Error: Runtime module %s doesn't exist %08x\n", m_name, HRESULT_FROM_WIN32(GetLastError())); - return nullptr; - } - // Parse off the file name - char* runtimeDirectory = _strdup(m_name); - char* lastSlash = strrchr(runtimeDirectory, GetTargetDirectorySeparatorW()); - if (lastSlash != nullptr) - { - *lastSlash = '\0'; - } - m_runtimeDirectory = runtimeDirectory; + *lastSlash = '\0'; } + m_runtimeDirectory = runtimeDirectory; } return m_runtimeDirectory; } @@ -581,7 +589,7 @@ void Runtime::DisplayStatus() ExtOut(" Runtime module path: %s\n", m_name); } if (m_runtimeDirectory != nullptr) { - ExtOut(" Runtime directory: %s\n", m_runtimeDirectory); + ExtOut(" Runtime module directory: %s\n", m_runtimeDirectory); } if (m_dacFilePath != nullptr) { ExtOut(" DAC file path: %s\n", m_dacFilePath); diff --git a/src/SOS/Strike/platform/runtimeimpl.h b/src/SOS/Strike/platform/runtimeimpl.h index b6cdc83ea..01c85f556 100644 --- a/src/SOS/Strike/platform/runtimeimpl.h +++ b/src/SOS/Strike/platform/runtimeimpl.h @@ -186,6 +186,8 @@ public: ULONG64 STDMETHODCALLTYPE GetModuleSize() const { return m_size; } + void STDMETHODCALLTYPE SetRuntimeDirectory(LPCSTR runtimeModuleDirectory); + LPCSTR STDMETHODCALLTYPE GetRuntimeDirectory(); HRESULT STDMETHODCALLTYPE GetClrDataProcess(IXCLRDataProcess** ppClrDataProcess); diff --git a/src/SOS/Strike/platform/targetimpl.cpp b/src/SOS/Strike/platform/targetimpl.cpp index 3be7d461b..d752da7e5 100644 --- a/src/SOS/Strike/platform/targetimpl.cpp +++ b/src/SOS/Strike/platform/targetimpl.cpp @@ -17,7 +17,6 @@ Target* Target::s_target = nullptr; Target::Target() : m_ref(1), m_tmpPath(nullptr), - m_runtimeModulePath(nullptr), #ifndef FEATURE_PAL m_desktop(nullptr), #endif @@ -27,11 +26,34 @@ Target::Target() : Target::~Target() { - Close(); - if (m_runtimeModulePath != nullptr) + // Clean up the temporary directory files and DAC symlink. + LPCSTR tmpPath = (LPCSTR)InterlockedExchangePointer((PVOID *)&m_tmpPath, nullptr); + if (tmpPath != nullptr) { - free((void*)m_runtimeModulePath); - m_runtimeModulePath = nullptr; + std::string directory(tmpPath); + directory.append("*"); + + WIN32_FIND_DATAA data; + HANDLE findHandle = FindFirstFileA(directory.c_str(), &data); + + if (findHandle != INVALID_HANDLE_VALUE) + { + do + { + if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) + { + std::string file(tmpPath); + file.append(data.cFileName); + DeleteFileA(file.c_str()); + } + } + while (0 != FindNextFileA(findHandle, &data)); + + FindClose(findHandle); + } + + RemoveDirectoryA(tmpPath); + free((void*)tmpPath); } if (m_netcore != nullptr) { @@ -124,17 +146,6 @@ bool Target::SwitchRuntimeInstance(bool desktop) } #endif -/**********************************************************************\ - * Set the runtime directory path -\**********************************************************************/ -void Target::SetRuntimeDirectoryInstance(LPCSTR runtimeModulePath) -{ - if (m_runtimeModulePath != nullptr) { - free((void*)m_runtimeModulePath); - } - m_runtimeModulePath = _strdup(runtimeModulePath); -} - /**********************************************************************\ * Display the internal target and runtime status \**********************************************************************/ @@ -152,9 +163,6 @@ void Target::DisplayStatusInstance() if (m_tmpPath != nullptr) { ExtOut("Temp path: %s\n", m_tmpPath); } - if (m_runtimeModulePath != nullptr) { - ExtOut("Runtime module path: %s\n", m_runtimeModulePath); - } if (m_netcore != nullptr) { m_netcore->DisplayStatus(); } @@ -261,11 +269,6 @@ LPCSTR Target::GetTempDirectory() return m_tmpPath; } -LPCSTR Target::GetRuntimeDirectory() -{ - return m_runtimeModulePath; -} - HRESULT Target::GetRuntime(IRuntime** ppRuntime) { return CreateInstance(ppRuntime); @@ -283,39 +286,6 @@ void Target::Flush() #endif } -void Target::Close() -{ - // Clean up the temporary directory files and DAC symlink. - LPCSTR tmpPath = (LPCSTR)InterlockedExchangePointer((PVOID *)&m_tmpPath, nullptr); - if (tmpPath != nullptr) - { - std::string directory(tmpPath); - directory.append("*"); - - WIN32_FIND_DATAA data; - HANDLE findHandle = FindFirstFileA(directory.c_str(), &data); - - if (findHandle != INVALID_HANDLE_VALUE) - { - do - { - if ((data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) - { - std::string file(tmpPath); - file.append(data.cFileName); - DeleteFileA(file.c_str()); - } - } - while (0 != FindNextFileA(findHandle, &data)); - - FindClose(findHandle); - } - - RemoveDirectoryA(tmpPath); - free((void*)tmpPath); - } -} - bool IsWindowsTarget() { ITarget* target = GetTarget(); diff --git a/src/SOS/Strike/platform/targetimpl.h b/src/SOS/Strike/platform/targetimpl.h index f071cb905..2300810f8 100644 --- a/src/SOS/Strike/platform/targetimpl.h +++ b/src/SOS/Strike/platform/targetimpl.h @@ -17,7 +17,6 @@ class Target : public ITarget private: LONG m_ref; LPCSTR m_tmpPath; - LPCSTR m_runtimeModulePath; #ifndef FEATURE_PAL Runtime* m_desktop; #endif @@ -28,7 +27,6 @@ private: #ifndef FEATURE_PAL bool SwitchRuntimeInstance(bool desktop); #endif - void SetRuntimeDirectoryInstance(LPCSTR runtimeModulePath); void DisplayStatusInstance(); Target(); @@ -39,19 +37,6 @@ public: HRESULT CreateInstance(IRuntime** ppRuntime); - static bool SetRuntimeDirectory(LPCSTR runtimeModulePath) - { - std::string fullPath; - if (!GetAbsolutePath(runtimeModulePath, fullPath)) - { - return false; - } - GetInstance(); - _ASSERTE(s_target != nullptr); - s_target->SetRuntimeDirectoryInstance(fullPath.c_str()); - return true; - } - #ifndef FEATURE_PAL static bool SwitchRuntime(bool desktop) { @@ -97,12 +82,8 @@ public: LPCSTR STDMETHODCALLTYPE GetTempDirectory(); - LPCSTR STDMETHODCALLTYPE GetRuntimeDirectory(); - HRESULT STDMETHODCALLTYPE GetRuntime(IRuntime** pRuntime); void STDMETHODCALLTYPE Flush(); - - void STDMETHODCALLTYPE Close(); }; diff --git a/src/SOS/Strike/sos.def b/src/SOS/Strike/sos.def index 21c522969..81dff1bef 100644 --- a/src/SOS/Strike/sos.def +++ b/src/SOS/Strike/sos.def @@ -7,6 +7,7 @@ EXPORTS AnalyzeOOM analyzeoom=AnalyzeOOM ao=AnalyzeOOM + clrmodules ClrStack clrstack=ClrStack CLRStack=ClrStack @@ -73,6 +74,7 @@ EXPORTS EHInfo ehinfo=EHInfo Ehinfo=EHInfo + ext FinalizeQueue finalizequeue=FinalizeQueue fq=FinalizeQueue @@ -243,7 +245,6 @@ EXPORTS tracetocode=TraceToCode #endif - ext SOSInitializeByHost SOSUninitializeByHost InitializeHostServices diff --git a/src/SOS/Strike/sos_unixexports.src b/src/SOS/Strike/sos_unixexports.src index ce570f7ff..6e5eebe9e 100644 --- a/src/SOS/Strike/sos_unixexports.src +++ b/src/SOS/Strike/sos_unixexports.src @@ -3,6 +3,7 @@ ; See the LICENSE file in the project root for more information. bpmd +clrmodules ClrStack dbgout DumpALC @@ -31,6 +32,7 @@ EEVersion GCWhere EEStack EHInfo +ext FinalizeQueue FindAppDomain GCInfo @@ -62,6 +64,5 @@ Token2EE u VerifyHeap -ext SOSInitializeByHost SOSUninitializeByHost diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index 6821c25f3..203b32dc5 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -16719,10 +16719,12 @@ DECLARE_API(SetClrPath) { std::string command("setclrpath "); command.append(args); - Status = hostServices->DispatchCommand(command.c_str()); + return hostServices->DispatchCommand(command.c_str()); } else { + INIT_API_EE(); + StringHolder runtimeModulePath; CMDValue arg[] = { @@ -16735,22 +16737,20 @@ DECLARE_API(SetClrPath) } if (narg > 0) { - if (!Target::SetRuntimeDirectory(runtimeModulePath.data)) + std::string fullPath; + if (!GetAbsolutePath(runtimeModulePath.data, fullPath)) { - ExtErr("Invalid runtime path %s\n", runtimeModulePath.data); + ExtErr("Invalid runtime directory %s\n", fullPath.c_str()); return E_FAIL; } + g_pRuntime->SetRuntimeDirectory(fullPath.c_str()); } - ITarget* target = GetTarget(); - if (target != nullptr) - { - const char* runtimeDirectory = target->GetRuntimeDirectory(); - if (runtimeDirectory != nullptr) { - ExtOut("Runtime module path: %s\n", runtimeDirectory); - } + const char* runtimeDirectory = g_pRuntime->GetRuntimeDirectory(); + if (runtimeDirectory != nullptr) { + ExtOut("Runtime module directory: %s\n", runtimeDirectory); } } - return Status; + return S_OK; } // @@ -16809,25 +16809,48 @@ DECLARE_API(runtimes) } // -// Enables and disables managed extension logging +// Executes managed extension commands // -DECLARE_API(logging) +HRESULT ExecuteCommand(PCSTR command, PCSTR args) { - INIT_API_NOEE(); - IHostServices* hostServices = GetHostServices(); if (hostServices != nullptr) { - std::string command("logging "); - command.append(args); - Status = hostServices->DispatchCommand(command.c_str()); + std::string commandLine(command); + if (args != nullptr && strlen(args) > 0) + { + commandLine.append(" "); + commandLine.append(args); + } + if (!commandLine.empty()) + { + return hostServices->DispatchCommand(commandLine.c_str()); + } } - else + else { ExtErr("Command not loaded\n"); + return E_FAIL; } + return S_OK; +} - return Status; +// +// Dumps the managed assemblies +// +DECLARE_API(clrmodules) +{ + INIT_API_EXT(); + return ExecuteCommand("clrmodules", args); +} + +// +// Enables and disables managed extension logging +// +DECLARE_API(logging) +{ + INIT_API_EXT(); + return ExecuteCommand("logging", args); } // @@ -16835,25 +16858,8 @@ DECLARE_API(logging) // DECLARE_API(ext) { - INIT_API_NOEE(); - - IHostServices* hostServices = GetHostServices(); - if (hostServices != nullptr) - { - // Just load the managed infrastructure if no command. This is useful in lldb - // where the managed extension commands are not added until a command does - // GetHostServices() like soshelp, logging, sosstatus, setsymbolserver and ext. - if (args != nullptr && strlen(args) > 0) - { - Status = hostServices->DispatchCommand(args); - } - } - else - { - ExtErr("Command not loaded\n"); - } - - return Status; + INIT_API_EXT(); + return ExecuteCommand("", args); } void PrintHelp (__in_z LPCSTR pszCmdName) diff --git a/src/SOS/inc/runtime.h b/src/SOS/inc/runtime.h index 009b71237..c5b914e59 100644 --- a/src/SOS/inc/runtime.h +++ b/src/SOS/inc/runtime.h @@ -55,7 +55,12 @@ public: virtual ULONG64 STDMETHODCALLTYPE GetModuleSize() const = 0; /// - /// Returns the directory of the runtime file + /// Set the runtime module directory to search for DAC/DBI + /// + virtual void STDMETHODCALLTYPE SetRuntimeDirectory(LPCSTR runtimeModuleDirectory) = 0; + + /// + /// Returns the directory of the runtime module /// virtual LPCSTR STDMETHODCALLTYPE GetRuntimeDirectory() = 0; diff --git a/src/SOS/inc/target.h b/src/SOS/inc/target.h index 1cf51e5a7..771a7b276 100644 --- a/src/SOS/inc/target.h +++ b/src/SOS/inc/target.h @@ -43,12 +43,6 @@ public: /// temporary directory string virtual LPCSTR STDMETHODCALLTYPE GetTempDirectory() = 0; - /// - /// Returns the directory of the runtime file - /// - /// runtime directory or null if none set - virtual LPCSTR STDMETHODCALLTYPE GetRuntimeDirectory() = 0; - /// /// Returns the current runtime instance /// @@ -60,11 +54,6 @@ public: /// Flushes any internal caching or state /// virtual void STDMETHODCALLTYPE Flush() = 0; - - /// - /// Cleans up any internal resources - /// - virtual void STDMETHODCALLTYPE Close() = 0; }; #ifdef __cplusplus diff --git a/src/SOS/lldbplugin/services.cpp b/src/SOS/lldbplugin/services.cpp index 28e6ee1fd..f812c0767 100644 --- a/src/SOS/lldbplugin/services.cpp +++ b/src/SOS/lldbplugin/services.cpp @@ -504,6 +504,7 @@ LLDBServices::GetLastEventInformation( { return E_FAIL; } + InitializeThreadInfo(process); *processId = GetProcessId(process); *threadId = GetThreadId(thread); @@ -1381,6 +1382,8 @@ LLDBServices::GetCurrentProcessSystemId( return E_FAIL; } + InitializeThreadInfo(process); + *sysId = GetProcessId(process); return S_OK; } diff --git a/src/SOS/lldbplugin/soscommand.cpp b/src/SOS/lldbplugin/soscommand.cpp index 59d22fa94..27f80ae22 100644 --- a/src/SOS/lldbplugin/soscommand.cpp +++ b/src/SOS/lldbplugin/soscommand.cpp @@ -151,6 +151,7 @@ sosCommandInitialize(lldb::SBDebugger debugger) g_services->AddCommand("sos", new sosCommand(nullptr), "Various .NET Core debugging commands. See 'soshelp' for more details. sos "); g_services->AddCommand("ext", new sosCommand("ext"), "Execute extension command. See 'soshelp' for more details. ext "); g_services->AddCommand("bpmd", new sosCommand("bpmd"), "Creates a breakpoint at the specified managed method in the specified module."); + g_services->AddCommand("clrmodules", new sosCommand("clrmodules"), "Lists the managed modules in the process."); g_services->AddCommand("clrstack", new sosCommand("ClrStack"), "Provides a stack trace of managed code only."); g_services->AddCommand("clrthreads", new sosCommand("Threads"), "List the managed threads running."); g_services->AddCommand("clru", new sosCommand("u"), "Displays an annotated disassembly of a managed method."); diff --git a/src/Tools/dotnet-dump/Analyzer.cs b/src/Tools/dotnet-dump/Analyzer.cs index c2f11aff9..d6b7be75f 100644 --- a/src/Tools/dotnet-dump/Analyzer.cs +++ b/src/Tools/dotnet-dump/Analyzer.cs @@ -26,19 +26,31 @@ namespace Microsoft.Diagnostics.Tools.Dump private readonly ConsoleProvider _consoleProvider; private readonly CommandProcessor _commandProcessor; private readonly SymbolService _symbolService; + private readonly ContextService _contextService; + private int _targetIdFactory; private Target _target; public Analyzer() { + LoggingCommand.Initialize(); + _serviceProvider = new ServiceProvider(); _consoleProvider = new ConsoleProvider(); _commandProcessor = new CommandProcessor(); _symbolService = new SymbolService(this); + _contextService = new ContextService(this); _serviceProvider.AddService(this); _serviceProvider.AddService(_consoleProvider); _serviceProvider.AddService(_commandProcessor); _serviceProvider.AddService(_symbolService); + _serviceProvider.AddService(_contextService); + _serviceProvider.AddServiceFactory(() => SOSLibrary.Create(this)); + + _contextService.ServiceProvider.AddServiceFactory(() => { + ClrRuntime clrRuntime = _contextService.Services.GetService(); + return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null; + }); _commandProcessor.AddCommands(new Assembly[] { typeof(Analyzer).Assembly }); _commandProcessor.AddCommands(new Assembly[] { typeof(ClrMDHelper).Assembly }); @@ -81,13 +93,10 @@ namespace Microsoft.Diagnostics.Tools.Dump if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || dataTarget.DataReader.EnumerateModules().Any((module) => Path.GetExtension(module.FileName) == ".dylib")) { targetPlatform = OSPlatform.OSX; } - _target = new TargetFromDataReader(dataTarget.DataReader, targetPlatform, this, dump_path.FullName); + _target = new TargetFromDataReader(dataTarget.DataReader, targetPlatform, this, _targetIdFactory++, dump_path.FullName); + _contextService.SetCurrentTarget(_target); - _target.ServiceProvider.AddServiceFactory(() => { - var sosHost = new SOSHost(_target); - sosHost.InitializeSOSHost(); - return sosHost; - }); + _target.ServiceProvider.AddServiceFactory(() => new SOSHost(_contextService.Services)); // Automatically enable symbol server support _symbolService.AddSymbolServer(msdl: true, symweb: false, symbolServerPath: null, authToken: null, timeoutInMinutes: 0); @@ -98,8 +107,7 @@ namespace Microsoft.Diagnostics.Tools.Dump { foreach (string cmd in command) { - Parse(cmd); - + _commandProcessor.Execute(cmd, _contextService.Services); if (_consoleProvider.Shutdown) { break; } @@ -112,7 +120,7 @@ namespace Microsoft.Diagnostics.Tools.Dump _consoleProvider.WriteLine("Type 'quit' or 'exit' to exit the session."); _consoleProvider.Start((string commandLine, CancellationToken cancellation) => { - Parse(commandLine); + _commandProcessor.Execute(commandLine, _contextService.Services); }); } } @@ -133,8 +141,7 @@ namespace Microsoft.Diagnostics.Tools.Dump { if (_target != null) { - _target.Close(); - _target = null; + DestroyTarget(_target); } // Persist the current command history try @@ -154,36 +161,6 @@ namespace Microsoft.Diagnostics.Tools.Dump return Task.FromResult(0); } - private void Parse(string commandLine) - { - // If there is no target, then provide just the global services - ServiceProvider services = _serviceProvider; - if (_target != null) - { - // Create a per command invocation service provider. These services may change between each command invocation. - services = new ServiceProvider(_target.Services); - - // Add the current thread if any - services.AddServiceFactory(() => { - IThreadService threadService = _target.Services.GetService(); - if (threadService != null && threadService.CurrentThreadId.HasValue) { - return threadService.GetThreadFromId(threadService.CurrentThreadId.Value); - } - return null; - }); - - // Add the current runtime and related services - var runtimeService = _target.Services.GetService(); - if (runtimeService != null) - { - services.AddServiceFactory(() => runtimeService.CurrentRuntime); - services.AddServiceFactory(() => services.GetService()?.Services.GetService()); - services.AddServiceFactory(() => new ClrMDHelper(services.GetService())); - } - } - _commandProcessor.Execute(commandLine, services); - } - #region IHost public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent(); @@ -194,9 +171,20 @@ namespace Microsoft.Diagnostics.Tools.Dump IEnumerable IHost.EnumerateTargets() => _target != null ? new ITarget[] { _target } : Array.Empty(); - ITarget IHost.CurrentTarget => _target; - - void IHost.SetCurrentTarget(int targetid) => throw new NotImplementedException(); + public void DestroyTarget(ITarget target) + { + if (target == null) { + throw new ArgumentNullException(nameof(target)); + } + if (target == _target) + { + _target = null; + _contextService.ClearCurrentTarget(); + if (target is IDisposable disposable) { + disposable.Dispose(); + } + } + } #endregion }