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.
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Manages the current target, thread and runtime contexts
+ /// </summary>
+ 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<IServiceProvider>[] {
+ // 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<ITarget>(GetCurrentTarget);
+ ServiceProvider.AddServiceFactoryWithNoCaching<IThread>(GetCurrentThread);
+ ServiceProvider.AddServiceFactoryWithNoCaching<IRuntime>(GetCurrentRuntime);
+ }
+
+ #region IContextService
+
+ /// <summary>
+ /// Current context service provider. Contains the current ITarget, IThread
+ /// and IRuntime instances along with all per target and global services.
+ /// </summary>
+ public IServiceProvider Services => ServiceProvider;
+
+ /// <summary>
+ /// Fires anytime the current context changes.
+ /// </summary>
+ public IServiceEvent OnContextChange { get; } = new ServiceEvent();
+
+ /// <summary>
+ /// Sets the current target.
+ /// </summary>
+ /// <param name="targetId">target id</param>
+ /// <exception cref="DiagnosticsException">invalid target id</exception>
+ 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);
+ }
+
+ /// <summary>
+ /// Clears (nulls) the current target
+ /// </summary>
+ public void ClearCurrentTarget() => SetCurrentTarget(null);
+
+ /// <summary>
+ /// Set the current thread.
+ /// </summary>
+ /// <param name="threadId">thread id</param>
+ /// <exception cref="DiagnosticsException">invalid thread id</exception>
+ public void SetCurrentThread(uint threadId) => SetCurrentThread(ThreadService?.GetThreadFromId(threadId));
+
+ /// <summary>
+ /// Clears (nulls) the current thread
+ /// </summary>
+ public void ClearCurrentThread() => SetCurrentThread(null);
+
+ /// <summary>
+ /// Set the current runtime
+ /// </summary>
+ /// <param name="runtimeId">runtime id</param>
+ /// <exception cref="DiagnosticsException">invalid runtime id</exception>
+ 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);
+ }
+
+ /// <summary>
+ /// Clears (nulls) the current runtime
+ /// </summary>
+ public void ClearCurrentRuntime() => SetCurrentRuntime(null);
+
+ #endregion
+
+ /// <summary>
+ /// Returns the current target.
+ /// </summary>
+ public ITarget GetCurrentTarget() => _currentTarget ??= Host.EnumerateTargets().FirstOrDefault();
+
+ /// <summary>
+ /// Allows hosts to set the initial current target
+ /// </summary>
+ /// <param name="target"></param>
+ public void SetCurrentTarget(ITarget target)
+ {
+ if (!IsTargetEqual(target, _currentTarget))
+ {
+ _currentTarget = target;
+ _currentThread = null;
+ _currentRuntime = null;
+ ServiceProvider.FlushServices();
+ OnContextChange.Fire();
+ }
+ }
+
+ /// <summary>
+ /// Returns the current thread.
+ /// </summary>
+ public virtual IThread GetCurrentThread() => _currentThread ??= ThreadService?.EnumerateThreads().FirstOrDefault();
+
+ /// <summary>
+ /// Allows hosts to set the initial current thread
+ /// </summary>
+ /// <param name="thread"></param>
+ public virtual void SetCurrentThread(IThread thread)
+ {
+ if (!IsThreadEqual(thread, _currentThread))
+ {
+ _currentThread = thread;
+ ServiceProvider.FlushServices();
+ OnContextChange.Fire();
+ }
+ }
+
+ /// <summary>
+ /// Find the current runtime.
+ /// </summary>
+ public IRuntime GetCurrentRuntime()
+ {
+ if (_currentRuntime is null)
+ {
+ IEnumerable<IRuntime> 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;
+ }
+
+ /// <summary>
+ /// Allows hosts to set the initial current runtime
+ /// </summary>
+ 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<IThreadService>();
+
+ protected IRuntimeService RuntimeService => GetCurrentTarget()?.Services.GetService<IRuntimeService>();
+ }
+}
_memoryCache = new MemoryCache(ReadMemoryFromModule);
_recursionProtection = new HashSet<ulong>();
target.OnFlushEvent.Register(_memoryCache.FlushCache);
- target.DisposeOnClose(target.Services.GetService<ISymbolService>()?.OnChangeEvent.Register(_memoryCache.FlushCache));
+ target.DisposeOnDestroy(target.Services.GetService<ISymbolService>()?.OnChangeEvent.Register(_memoryCache.FlushCache));
}
#region IMemoryService
_target = target;
_memoryService = memoryService;
target.OnFlushEvent.Register(Flush);
- target.DisposeOnClose(SymbolService?.OnChangeEvent.Register(Flush));
+ target.DisposeOnDestroy(SymbolService?.OnChangeEvent.Register(Flush));
}
/// <summary>
#region IModule
+ public ITarget Target => ModuleService.Target;
+
public IServiceProvider Services => ServiceProvider;
public abstract int ModuleIndex { get; }
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 ?? ""}";
// 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;
{
foreach (IModule module in _modules.Values)
{
- if (module is IDisposable disposable)
- {
+ if (module is IDisposable disposable) {
disposable.Dispose();
}
}
/// </summary>
public class Runtime : IRuntime
{
- private readonly ITarget _target;
- private readonly IRuntimeService _runtimeService;
private readonly ClrInfo _clrInfo;
private ISymbolService _symbolService;
private ClrRuntime _clrRuntime;
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) {
ServiceProvider.AddService<ClrInfo>(clrInfo);
ServiceProvider.AddServiceFactoryWithNoCaching<ClrRuntime>(() => 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)
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
{
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";
}
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
{
private string DownloadFile(string fileName)
{
- OSPlatform platform = _target.OperatingSystem;
+ OSPlatform platform = Target.OperatingSystem;
string filePath = null;
if (SymbolService.IsSymbolStoreEnabled)
return filePath;
}
- private ISymbolService SymbolService => _symbolService ??= _target.Services.GetService<ISymbolService>();
+ private ISymbolService SymbolService => _symbolService ??= Target.Services.GetService<ISymbolService>();
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 = {
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}");
}
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<Runtime> _runtimes;
- private Runtime _currentRuntime;
+ private IContextService _contextService;
private IModuleService _moduleService;
private IThreadService _threadService;
private IMemoryService _memoryService;
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)
{
// 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
- /// <summary>
- /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.)
- /// </summary>
- public string RuntimeModuleDirectory
- {
- get { return _runtimeModuleDirectory; }
- set
- {
- _runtimeModuleDirectory = value;
- _runtimes = null;
- _currentRuntime = null;
- }
- }
-
/// <summary>
/// Returns the list of runtimes in the target
/// </summary>
- public IEnumerable<IRuntime> EnumerateRuntimes() => BuildRuntimes();
-
- /// <summary>
- /// Returns the current runtime
- /// </summary>
- public IRuntime CurrentRuntime
+ public IEnumerable<IRuntime> EnumerateRuntimes()
{
- get
+ if (_runtimes is null)
{
- if (_currentRuntime is null) {
- _currentRuntime = FindRuntime();
+ _runtimes = new List<Runtime>();
+ 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;
- }
- }
-
- /// <summary>
- /// Set the current runtime
- /// </summary>
- /// <param name="runtimeId">runtime id</param>
- 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
int IDataReader.ProcessId => unchecked((int)_target.ProcessId.GetValueOrDefault());
- IEnumerable<ModuleInfo> IDataReader.EnumerateModules() =>
+ IEnumerable<ModuleInfo> 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<byte>());
return false;
}
- void IDataReader.FlushCachedData() => _target.Flush();
+ void IDataReader.FlushCachedData()
+ {
+ }
#endregion
bool IExportReader.TryGetSymbolAddress(ulong baseAddress, string name, out ulong offset)
{
- try
+ if (_exportReaderEnabled)
{
- IExportSymbols exportSymbols = ModuleService.GetModuleFromBaseAddress(baseAddress).Services.GetService<IExportSymbols>();
- if (exportSymbols is not null)
+ try
+ {
+ IExportSymbols exportSymbols = ModuleService.GetModuleFromBaseAddress(baseAddress).Services.GetService<IExportSymbols>();
+ 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;
#endregion
- /// <summary>
- /// Find the runtime
- /// </summary>
- private Runtime FindRuntime()
- {
- IEnumerable<Runtime> runtimes = BuildRuntimes();
- Runtime runtime = null;
+ private IRuntime CurrentRuntime => ContextService.Services.GetService<IRuntime>();
- // 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<Runtime> BuildRuntimes()
- {
- if (_runtimes is null)
- {
- _runtimes = new List<Runtime>();
- 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<IContextService>();
private IModuleService ModuleService => _moduleService ??= _target.Services.GetService<IModuleService>();
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());
}
{
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()
{
}
- 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;
}
{
public class ServiceProvider : IServiceProvider
{
- readonly IServiceProvider _parent;
- readonly Dictionary<Type, Func<object>> _factories;
- readonly Dictionary<Type, object> _services;
+ private readonly Func<IServiceProvider>[] _parents;
+ private readonly Dictionary<Type, Func<object>> _factories;
+ private readonly Dictionary<Type, object> _services;
/// <summary>
/// Create a service provider instance
/// </summary>
public ServiceProvider()
- : this(null, null)
+ : this(Array.Empty<Func<IServiceProvider>>())
{
}
/// </summary>
/// <param name="parent">search this provider if service isn't found in this instance</param>
public ServiceProvider(IServiceProvider parent)
- : this(parent, null)
+ : this(new Func<IServiceProvider>[] { () => parent })
{
}
/// <summary>
/// Create a service provider with parent provider and service factories
/// </summary>
- /// <param name="parent">search this provider if service isn't found in this instance or null</param>
- /// <param name="factories">a dictionary of the factories to create the services</param>
- public ServiceProvider(IServiceProvider parent, Dictionary<Type, Func<object>> factories)
+ /// <param name="parents">an array of functions to return the next provider to search if service isn't found in this instance</param>
+ public ServiceProvider(Func<IServiceProvider>[] parents)
{
- _parent = parent;
- _factories = factories ?? new Dictionary<Type, Func<object>>();
+ _parents = parents;
+ _factories = new Dictionary<Type, Func<object>>();
_services = new Dictionary<Type, object>();
}
public void AddService(Type type, object service) => _services.Add(type, service);
/// <summary>
- /// 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.
/// </summary>
/// <param name="type">service type</param>
public void RemoveService(Type type) => _services.Remove(type);
+ /// <summary>
+ /// Flushes all the cached instances of the services. Does not remove any of the service factories registered.
+ /// </summary>
+ public void FlushServices() => _services.Clear();
+
/// <summary>
/// Returns the instance of the service or returns null if service doesn't exist
/// </summary>
service = factory();
}
}
- if (service == null && _parent != null)
+ if (service == null)
{
- service = _parent.GetService(type);
+ foreach (Func<IServiceProvider> parent in _parents)
+ {
+ service = parent()?.GetService(type);
+ if (service != null)
+ {
+ break;
+ }
+ }
}
return service;
}
/// <summary>
/// ITarget base implementation
/// </summary>
- public abstract class Target : ITarget
+ public abstract class Target : ITarget, IDisposable
{
- private static int _targetIdFactory;
private readonly string _dumpPath;
- private readonly List<IDisposable> _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<IDisposable>();
OnFlushEvent = new ServiceEvent();
+ OnDestroyEvent = new ServiceEvent();
// Initialize the per-target services
ServiceProvider = new ServiceProvider(host.Services);
/// </summary>
public IHost Host { get; }
- /// <summary>
- /// Invoked when this target is flushed (via the Flush() call).
- /// </summary>
- public IServiceEvent OnFlushEvent { get; }
-
/// <summary>
/// The target id
/// </summary>
- public int Id { get; } = _targetIdFactory++;
+ public int Id { get; }
/// <summary>
/// Returns the target OS (which may be different from the OS this is running on)
/// </summary>
public IServiceProvider Services => ServiceProvider;
+ /// <summary>
+ /// Invoked when this target is flushed (via the Flush() call).
+ /// </summary>
+ public IServiceEvent OnFlushEvent { get; }
+
/// <summary>
/// Flushes any cached state in the target.
/// </summary>
}
/// <summary>
- /// Registers an object to be disposed when ITarget.Close() is called.
+ /// Invoked when the target is closed.
/// </summary>
- /// <param name="disposable">object to be disposed on Close() or null</param>
- public void DisposeOnClose(IDisposable disposable)
- {
- if (disposable != null)
- {
- _disposables.Add(disposable);
- }
- }
+ public IServiceEvent OnDestroyEvent { get; }
+
+ #endregion
/// <summary>
/// Releases the target and the target's resources.
/// </summary>
- 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)
public override bool Equals(object obj)
{
- return Id == ((Target)obj).Id;
+ return Id == ((ITarget)obj).Id;
}
public override int GetHashCode()
/// <param name="dataReader">IDataReader</param>
/// <param name="targetOS">target operating system</param>
/// <param name="host">the host instance</param>
+ /// <param name="id">target id</param>
/// <param name="dumpPath">path of dump for this target</param>
- 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;
#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;
#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}";
/// </summary>
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<string, RegisterInfo> _lookupByName;
return _lookupByIndex.TryGetValue(index, out info);
}
- /// <summary>
- /// Current OS thread Id
- /// </summary>
- public virtual uint? CurrentThreadId { get; set; }
-
/// <summary>
/// Enumerate all the native threads
/// </summary>
{
_dataReader = dataReader;
_threadReader = (IThreadReader)dataReader;
-
- if (dataReader is IThreadReader threadReader)
- {
- // Initialize the current thread
- IEnumerable<uint> 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)
+++ /dev/null
-// 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
-{
- /// <summary>
- /// Helper class that implements unregister callback
- /// </summary>
- 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();
- }
- }
- }
-}
--- /dev/null
+// 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
+ {
+ /// <summary>
+ /// Combines two hash codes into a single hash code, in an order-dependent manner.
+ /// </summary>
+ /// <remarks>
+ /// 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!
+ /// </remarks>
+ 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));
+ }
+ }
+ }
+}
--- /dev/null
+// 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
+ {
+ /// <summary>
+ /// Returns the current target
+ /// </summary>
+ public static ITarget GetCurrentTarget(this IContextService contextService)
+ {
+ return contextService.Services.GetService<ITarget>();
+ }
+
+ /// <summary>
+ /// Returns the current thread
+ /// </summary>
+ public static IThread GetCurrentThread(this IContextService contextService)
+ {
+ return contextService.Services.GetService<IThread>();
+ }
+
+ /// <summary>
+ /// Returns the current runtime
+ /// </summary>
+ public static IRuntime GetCurrentRuntime(this IContextService contextService)
+ {
+ return contextService.Services.GetService<IRuntime>();
+ }
+ }
+}
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Console output service
+ /// </summary>
+ public interface IContextService
+ {
+ /// <summary>
+ /// Current context service provider. Contains the current ITarget, IThread
+ /// and IRuntime instances along with all per target and global services.
+ /// </summary>
+ IServiceProvider Services { get; }
+
+ /// <summary>
+ /// Fires anytime the current context changes.
+ /// </summary>
+ IServiceEvent OnContextChange { get; }
+
+ /// <summary>
+ /// Sets the current target.
+ /// </summary>
+ /// <param name="targetId">target id</param>
+ void SetCurrentTarget(int targetId);
+
+ /// <summary>
+ /// Clears (nulls) the current target
+ /// </summary>
+ void ClearCurrentTarget();
+
+ /// <summary>
+ /// Set the current thread.
+ /// </summary>
+ /// <param name="threadId">thread id</param>
+ void SetCurrentThread(uint threadId);
+
+ /// <summary>
+ /// Clears (nulls) the current thread.
+ /// </summary>
+ void ClearCurrentThread();
+
+ /// <summary>
+ /// Set the current runtime
+ /// </summary>
+ /// <param name="runtimeId">runtime id</param>
+ void SetCurrentRuntime(int runtimeId);
+
+ /// <summary>
+ /// Clears (nulls) the current runtime
+ /// </summary>
+ void ClearCurrentRuntime();
+ }
+}
IEnumerable<ITarget> EnumerateTargets();
/// <summary>
- /// Current target instances or null
+ /// Destroys/closes the specified target instance
/// </summary>
- ITarget CurrentTarget { get; }
-
- /// <summary>
- /// Sets the current target.
- /// </summary>
- /// <param name="targetid">target id</param>
- void SetCurrentTarget(int targetid);
+ /// <param name="target">target instance</param>
+ void DestroyTarget(ITarget target);
}
}
/// </summary>
public interface IModule
{
+ /// <summary>
+ /// Target for this module.
+ /// </summary>
+ ITarget Target { get; }
+
/// <summary>
/// The per module services like an optional clrmd's PEImage or PEReader instances if a PE module.
/// </summary>
public interface IRuntime
{
/// <summary>
- /// The per target services like clrmd's ClrInfo and ClrRuntime.
+ /// Runtime id
/// </summary>
- IServiceProvider Services { get; }
+ int Id { get; }
/// <summary>
- /// Runtime id
+ /// The target for this runtime.
/// </summary>
- int Id { get; }
+ ITarget Target { get; }
+
+ /// <summary>
+ /// The per target services like clrmd's ClrInfo and ClrRuntime.
+ /// </summary>
+ IServiceProvider Services { get; }
/// <summary>
/// Returns the runtime OS and type
/// </summary>
IModule RuntimeModule { get; }
+ /// <summary>
+ /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.)
+ /// </summary>
+ string RuntimeModuleDirectory { get; set; }
+
/// <summary>
/// Returns the DAC file path
/// </summary>
/// </summary>
public interface IRuntimeService
{
- /// <summary>
- /// Directory of the runtime module (coreclr.dll, libcoreclr.so, etc.)
- /// </summary>
- string RuntimeModuleDirectory { get; set; }
-
/// <summary>
/// Returns the list of runtimes in the target
/// </summary>
IEnumerable<IRuntime> EnumerateRuntimes();
-
- /// <summary>
- /// Returns the current runtime or null if no runtime was found
- /// </summary>
- IRuntime CurrentRuntime { get; }
-
- /// <summary>
- /// Set the current runtime
- /// </summary>
- /// <param name="runtimeId">runtime id</param>
- void SetCurrentRuntime(int runtimeId);
}
}
/// <returns>An opaque IDisposable that will unregister the callback when disposed</returns>
IDisposable Register(Action callback);
+ /// <summary>
+ /// Register for the event callback. Puts the new callback at the end of the list. Automatically
+ /// removed from the event list when fired.
+ /// </summary>
+ /// <param name="callback">callback delegate</param>
+ /// <returns>An opaque IDisposable that will unregister the callback when disposed</returns>
+ IDisposable RegisterOneShot(Action callback);
+
/// <summary>
/// Fires the event
/// </summary>
/// </summary>
public interface ITarget
{
- /// <summary>
- /// Invoked when this target is flushed (via the Flush() call).
- /// </summary>
- IServiceEvent OnFlushEvent { get; }
-
/// <summary>
/// Returns the host interface instance
/// </summary>
IServiceProvider Services { get; }
/// <summary>
- /// Flushes any cached state in the target.
+ /// Invoked when this target is flushed (via the Flush() call).
/// </summary>
- void Flush();
+ IServiceEvent OnFlushEvent { get; }
/// <summary>
- /// Registers an object to be disposed when ITarget.Close() is called.
+ /// Flushes any cached state in the target.
/// </summary>
- /// <param name="disposable">object to be disposed on Close() or null</param>
- void DisposeOnClose(IDisposable disposable);
+ void Flush();
/// <summary>
- /// Releases the target and the target's resources.
+ /// Invoked when the target is destroyed.
/// </summary>
- void Close();
+ IServiceEvent OnDestroyEvent { get; }
}
}
/// </summary>
public interface IThread
{
- /// <summary>
- /// The per thread services.
- /// </summary>
- IServiceProvider Services { get; }
-
/// <summary>
/// Debugger specific thread index.
/// </summary>
/// </summary>
uint ThreadId { get; }
+ /// <summary>
+ /// The target for this target.
+ /// </summary>
+ ITarget Target { get; }
+
+ /// <summary>
+ /// The per thread services.
+ /// </summary>
+ IServiceProvider Services { get; }
+
/// <summary>
/// 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
/// <returns>true if index found</returns>
bool TryGetRegisterInfo(int registerIndex, out RegisterInfo info);
- /// <summary>
- /// Current OS thread Id
- /// </summary>
- uint? CurrentThreadId { get; set; }
-
/// <summary>
/// Enumerate all the native threads
/// </summary>
IEnumerable<IThread> EnumerateThreads();
/// <summary>
- /// Get the thread info from the thread index
+ /// Get the thread from the thread index
/// </summary>
/// <param name="threadIndex">index</param>
/// <returns>thread info</returns>
IThread GetThreadFromIndex(int threadIndex);
/// <summary>
- /// Get the thread info from the OS thread id
+ /// Get the thread from the OS thread id
/// </summary>
/// <param name="threadId">os id</param>
/// <returns>thread info</returns>
/// <returns>platform module name</returns>
public static string GetPlatformModuleName(this ITarget target, string moduleName)
{
- if (target.OperatingSystem == OSPlatform.Windows)
+ if (target.OperatingSystem == OSPlatform.Windows)
{
return moduleName + ".dll";
}
{
return "lib" + moduleName + ".dylib";
}
- else throw new PlatformNotSupportedException(target.OperatingSystem.ToString());
+ throw new PlatformNotSupportedException(target.OperatingSystem.ToString());
+ }
+
+ /// <summary>
+ /// Registers an object to be disposed when target is destroyed.
+ /// </summary>
+ /// <param name="target">target instance</param>
+ /// <param name="disposable">object to be disposed or null</param>
+ /// <returns>IDisposable to unregister this event or null</returns>
+ public static IDisposable DisposeOnDestroy(this ITarget target, IDisposable disposable)
+ {
+ return disposable != null ? target.OnDestroyEvent.Register(() => disposable.Dispose()) : null;
}
}
}
using Microsoft.Diagnostics.Runtime;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Threading.Tasks;
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;
}
public ClrRuntime Runtime { get; set; }
- public override void Invoke()
+ public override void ExtensionInvoke()
{
if (string.IsNullOrEmpty(Address))
{
public ClrRuntime Runtime { get; set; }
- public override void Invoke()
+ public override void ExtensionInvoke()
{
if (string.IsNullOrEmpty(Address))
{
[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)
public abstract class ExtensionCommandBase : CommandBase
{
/// <summary>
- /// 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.
/// </summary>
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()
{
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()
{
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; }
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;
}
foreach (IRuntime runtime in RuntimeService.EnumerateRuntimes())
{
- string current = displayStar ? (runtime == RuntimeService.CurrentRuntime ? "*" : " ") : "";
+ string current = displayStar ? (runtime == ContextService.GetCurrentRuntime() ? "*" : " ") : "";
Write(current);
Write(runtime.ToString());
}
[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; }
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 ?? "<none>");
+ WriteLine("Load path for DAC/DBI: '{0}'", Runtime.RuntimeModuleDirectory ?? "<none>");
}
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);
}
}
}
[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)
{
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);
[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)
}
WriteLine($"==> {ps.ThreadIds.Count} threads with {ps.Stacks.Count} roots{Environment.NewLine}");
-
}
protected override string GetDetailedHelp()
[Option(Name = "--value", Aliases = new string[] { "-v" }, Help = "<value> 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)
{
[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<string, WorkInfo>();
int workItemCount = 0;
[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
{
{
WriteLine(x.Message);
}
-
}
static string GetTimerString(TimerInfo timer)
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Provides the context services on native debuggers
+ /// </summary>
+ 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);
+ }
+ }
+}
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;
/// <summary>
/// Enable the assembly resolver to get the right versions in the same directory as this assembly.
if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework")) {
AssemblyResolver.Enable();
}
+ LoggingCommand.Initialize();
}
/// <summary>
_serviceProvider.AddService<ICommandService>(_commandProcessor);
_serviceProvider.AddService<ISymbolService>(_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));
protected override void Destroy()
{
Trace.TraceInformation("HostServices.Destroy");
+ _hostWrapper.RemoveServiceWrapper(IID_IHostServices);
+ _hostWrapper.Release();
}
#region IHost
IEnumerable<ITarget> IHost.EnumerateTargets() => _target != null ? new ITarget[] { _target } : Array.Empty<ITarget>();
- 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
IntPtr self,
IntPtr iunk)
{
+ Trace.TraceInformation("HostServices.RegisterDebuggerServices");
if (iunk == IntPtr.Zero || DebuggerServices != null) {
return HResult.E_FAIL;
}
{
var consoleService = new ConsoleServiceFromDebuggerServices(DebuggerServices);
_serviceProvider.AddService<IConsoleService>(consoleService);
+
+ _contextService = new ContextServiceFromDebuggerServices(this, DebuggerServices);
+ _serviceProvider.AddService<IContextService>(_contextService);
_serviceProvider.AddServiceFactory<IThreadUnwindService>(() => new ThreadUnwindServiceFromDebuggerServices(DebuggerServices));
- Trace.TraceInformation("HostServices.RegisterDebuggerServices");
+
+ _contextService.ServiceProvider.AddServiceFactory<ClrMDHelper>(() => {
+ ClrRuntime clrRuntime = _contextService.Services.GetService<ClrRuntime>();
+ return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null;
+ });
// Add each extension command to the native debugger
foreach ((string name, string help, IEnumerable<string> aliases) in _commandProcessor.Commands)
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<ITarget>(_target);
+ _target = new TargetFromDebuggerServices(DebuggerServices, this, _targetIdFactory++);
+ _contextService.SetCurrentTarget(_target);
+ _targetWrapper = new TargetWrapper(_contextService.Services);
+ _memoryService = _contextService.Services.GetService<IMemoryService>();
}
catch (Exception ex)
{
IntPtr self,
uint processId)
{
- Trace.TraceInformation($"HostServices.UpdateTarget {processId}");
+ Trace.TraceInformation("HostServices.UpdateTarget {0} #{1}", processId, _target != null ? _target.Id : "<none>");
if (_target == null)
{
return CreateTarget(self);
private void DestroyTarget(
IntPtr self)
{
- Trace.TraceInformation("HostServices.DestroyTarget {0}", _target != null ? _target.Id : "<none>");
- _hostWrapper.DestroyTarget();
+ Trace.TraceInformation("HostServices.DestroyTarget #{0}", _target != null ? _target.Id : "<none>");
if (_target != null)
{
- _serviceProvider.RemoveService(typeof(ITarget));
- _target.Close();
- _target = null;
+ DestroyTarget(_target);
}
}
}
try
{
- return _commandProcessor.Execute(commandLine, GetServices());
+ return _commandProcessor.Execute(commandLine, _contextService.Services);
}
catch (Exception ex)
{
{
try
{
- if (!_commandProcessor.DisplayHelp(command, GetServices()))
+ if (!_commandProcessor.DisplayHelp(command, _contextService.Services))
{
return HResult.E_INVALIDARG;
}
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<IThread>(() =>
- {
- IThreadService threadService = _target.Services.GetService<IThreadService>();
- 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<IRuntimeService>();
- if (runtimeService != null)
- {
- services.AddServiceFactory<IRuntime>(() => runtimeService.CurrentRuntime);
- services.AddServiceFactory<ClrRuntime>(() => services.GetService<IRuntime>()?.Services.GetService<ClrRuntime>());
- services.AddServiceFactory<ClrMDHelper>(() => new ClrMDHelper(services.GetService<ClrRuntime>()));
- }
- }
- return services;
- }
#region IHostServices delegates
/// <summary>
/// Create a target instance from IDataReader
/// </summary>
- 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);
_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;
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<IMemoryService>();
- _threadService = target.Services.GetService<IThreadService>();
- _threadUnwindService = target.Services.GetService<IThreadUnwindService>();
- _symbolServiceWrapper = new SymbolServiceWrapper(target.Host);
+ Debug.Assert(services != null);
+ _target = services.GetService<ITarget>();
+ _symbolService = services.GetService<ISymbolService>();
+ _memoryService = services.GetService<IMemoryService>();
+ _threadService = services.GetService<IThreadService>();
+ _threadUnwindService = services.GetService<IThreadUnwindService>();
_ignoreAddressBitsMask = _memoryService.SignExtensionMask();
VTableBuilder builder = AddInterface(IID_ICorDebugDataTarget, validate: false);
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
// 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<IMemoryService>();
- _threadService = target.Services.GetService<IThreadService>();
- _threadUnwindService = target.Services.GetService<IThreadUnwindService>();
- _moduleService = target.Services.GetService<IModuleService>();
- _remoteMemoryService = target.Services.GetService<IRemoteMemoryService>();
- _symbolServiceWrapper = new SymbolServiceWrapper(target.Host);
+ _symbolService = services.GetService<ISymbolService>();
+ _memoryService = services.GetService<IMemoryService>();
+ _threadService = services.GetService<IThreadService>();
+ _threadUnwindService = services.GetService<IThreadUnwindService>();
+ _moduleService = services.GetService<IModuleService>();
+ _remoteMemoryService = services.GetService<IRemoteMemoryService>();
_ignoreAddressBitsMask = _memoryService.SignExtensionMask();
VTableBuilder builder = AddInterface(IID_ICLRDataTarget, false);
IntPtr self,
out IMAGE_FILE_MACHINE machineType)
{
- machineType = _target.Architecture switch
+ ITarget target = _services.GetService<ITarget>();
+ 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,
IntPtr self,
out uint threadId)
{
- uint? id = _threadService.CurrentThreadId;
+ uint? id = _services.GetService<IThread>()?.ThreadId;
if (id.HasValue)
{
threadId = id.Value;
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
#endregion
}
-}
\ No newline at end of file
+}
private static readonly Guid IID_IHost = new Guid("E0CD8534-A88B-40D7-91BA-1B4C925761E9");
private readonly IHost _host;
+ private readonly Func<TargetWrapper> _getTarget;
private readonly Dictionary<Guid, Func<COMCallableIUnknown>> _factories = new Dictionary<Guid, Func<COMCallableIUnknown>>();
private readonly Dictionary<Guid, COMCallableIUnknown> _wrappers = new Dictionary<Guid, COMCallableIUnknown>();
- private TargetWrapper _targetWrapper;
-
public IntPtr IHost { get; }
- public HostWrapper(IHost host)
+ public HostWrapper(IHost host, Func<TargetWrapper> getTarget)
{
_host = host;
+ _getTarget = getTarget;
VTableBuilder builder = AddInterface(IID_IHost, validate: false);
builder.AddMethod(new GetHostTypeDelegate(GetHostType));
protected override void Destroy()
{
Trace.TraceInformation("HostWrapper.Destroy");
+ foreach (var wrapper in _wrappers.Values)
+ {
+ wrapper.Release();
+ }
+ _wrappers.Clear();
}
/// <summary>
_wrappers.Add(serviceId, service);
}
+ /// <summary>
+ /// Remove the service instance
+ /// </summary>
+ /// <param name="serviceId">guid</param>
+ public void RemoveServiceWrapper(in Guid serviceId)
+ {
+ _factories.Remove(serviceId);
+ _wrappers.Remove(serviceId);
+ }
+
/// <summary>
/// Returns the wrapper instance for the guid
/// </summary>
return service;
}
- public void DestroyTarget()
- {
- if (_targetWrapper != null)
- {
- _targetWrapper.Release();
- _targetWrapper = null;
- }
- }
-
#region IHost
/// <summary>
ptr = IntPtr.Zero;
COMCallableIUnknown wrapper = GetServiceWrapper(guid);
- if (wrapper == null)
- {
+ if (wrapper == null) {
return HResult.E_NOINTERFACE;
}
wrapper.AddRef();
/// <summary>
/// Returns the current target wrapper or null
/// </summary>
- /// <param name="target">target wrapper address returned</param>
+ /// <param name="targetWrapper">target wrapper address returned</param>
/// <returns>S_OK</returns>
- 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;
}
string GetCoreClrDirectory(
IntPtr self)
{
- IRuntime currentRuntime = _soshost.Target.Services.GetService<IRuntimeService>()?.CurrentRuntime;
- if (currentRuntime != null)
- {
+ IRuntime currentRuntime = _soshost.Services.GetService<IRuntime>();
+ if (currentRuntime is not null) {
return Path.GetDirectoryName(currentRuntime.RuntimeModule.FileName);
}
return null;
/// </summary>
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");
#endregion
- private readonly ITarget _target;
+ private readonly IServiceProvider _services;
private readonly IRuntime _runtime;
private readonly IDisposable _onFlushEvent;
private IntPtr _clrDataProcess = IntPtr.Zero;
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));
{
Trace.TraceInformation("RuntimeWrapper.Destroy");
_onFlushEvent.Dispose();
+ Flush();
if (_dacHandle != IntPtr.Zero)
{
DataTarget.PlatformFunctions.FreeLibrary(_dacHandle);
}
}
+ 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(
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;
}
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);
}
byte* fileVersionBuffer,
int fileVersionBufferSizeInBytes)
{
- IModuleService moduleService = _target.Services.GetService<IModuleService>();
+ IModuleService moduleService = _services.GetService<IModuleService>();
IModule module;
try
{
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)
{
Build = 0,
Revision = 0,
};
- var dataTarget = new CorDebugDataTargetWrapper(_target, _runtime);
+ var dataTarget = new CorDebugDataTargetWrapper(_services);
ulong clrInstanceId = _runtime.RuntimeModule.ImageBase;
int hresult = 0;
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(
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;
namespace SOS.Hosting
{
/// <summary>
- /// Helper code to hosting SOS under ClrMD
+ /// Helper code to hosting the native SOS code
/// </summary>
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;
/// <summary>
- /// 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.
/// </summary>
- public string SOSPath { get; set; }
-
- /// <summary>
- /// Create an instance of the hosting class
- /// </summary>
- /// <param name="target">target instance</param>
- public SOSHost(ITarget target)
- {
- Target = target;
- ConsoleService = target.Services.GetService<IConsoleService>();
- ModuleService = target.Services.GetService<IModuleService>();
- ThreadService = target.Services.GetService<IThreadService>();
- MemoryService = target.Services.GetService<IMemoryService>();
+ /// <param name="services">service provider</param>
+ public SOSHost(IServiceProvider services)
+ {
+ Services = services;
+ Target = services.GetService<ITarget>() ?? throw new DiagnosticsException("No target");
+ TargetWrapper = new TargetWrapper(services);
+ Target.DisposeOnDestroy(this);
+ ConsoleService = services.GetService<IConsoleService>();
+ ModuleService = services.GetService<IModuleService>();
+ ThreadService = services.GetService<IThreadService>();
+ MemoryService = services.GetService<IMemoryService>();
_ignoreAddressBitsMask = MemoryService.SignExtensionMask();
-
- string rid = InstallHelper.GetRid();
- SOSPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), rid);
+ _sosLibrary = services.GetService<SOSLibrary>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var lldbServices = new LLDBServices(this);
_interface = lldbServices.ILLDBServices;
}
- _hostWrapper = new HostWrapper(target.Host);
- _hostWrapper.AddServiceWrapper(SymbolServiceWrapper.IID_ISymbolService, () => new SymbolServiceWrapper(target.Host));
- }
-
- /// <summary>
- /// Loads and initializes the SOS module.
- /// </summary>
- 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<SOSInitializeDelegate>(_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);
}
}
- /// <summary>
- /// Shutdown/clean up the native SOS.
- /// </summary>
- private void OnShutdownEvent()
- {
- Trace.TraceInformation("SOSHost: OnShutdownEvent");
- var uninitializeFunc = GetDelegateFunction<SOSUninitializeDelegate>(_sosLibrary, SOSUninitialize);
- uninitializeFunc?.Invoke();
- }
-
/// <summary>
/// Execute a SOS command.
/// </summary>
/// <param name="command">just the command name</param>
/// <param name="arguments">the command arguments and options</param>
public void ExecuteCommand(string command, string arguments)
- {
- Debug.Assert(_sosLibrary != null);
-
- var commandFunc = GetDelegateFunction<SOSCommandDelegate>(_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
IntPtr context,
uint contextSize)
{
- if (!ThreadService.CurrentThreadId.HasValue)
+ IThread thread = Services.GetService<IThread>();
+ 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(
IntPtr self,
out uint id)
{
- if (!ThreadService.CurrentThreadId.HasValue) {
- id = 0;
- return HResult.E_FAIL;
+ IThread thread = Services.GetService<IThread>();
+ 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(
{
try
{
- ThreadService.CurrentThreadId = ThreadService.GetThreadFromIndex(unchecked((int)id)).ThreadId;
+ var contextService = Services.GetService<IContextService>();
+ if (contextService is null) {
+ return HResult.E_FAIL;
+ }
+ contextService.SetCurrentThread(ThreadService.GetThreadFromIndex(unchecked((int)id)).ThreadId);
}
catch (DiagnosticsException)
{
IntPtr self,
out uint sysId)
{
- uint? id = ThreadService.CurrentThreadId;
- if (id.HasValue)
- {
- sysId = id.Value;
+ IThread thread = Services.GetService<IThread>();
+ if (thread is not null)
+ {
+ sysId = thread.ThreadId;
return HResult.S_OK;
}
sysId = 0;
IntPtr self,
ulong* offset)
{
- if (ThreadService.CurrentThreadId.HasValue)
- {
- uint threadId = ThreadService.CurrentThreadId.Value;
+ IThread thread = Services.GetService<IThread>();
+ if (thread is not null)
+ {
try
{
- ulong teb = ThreadService.GetThreadFromId(threadId).GetThreadTeb();
+ ulong teb = thread.GetThreadTeb();
Write(offset, teb);
return HResult.S_OK;
}
int index,
out ulong value)
{
- if (ThreadService.CurrentThreadId.HasValue)
- {
- IThread thread = ThreadService.GetThreadFromId(ThreadService.CurrentThreadId.Value);
- if (thread != null)
+ IThread thread = Services.GetService<IThread>();
+ 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;
--- /dev/null
+// 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
+{
+ /// <summary>
+ /// Helper code to load and initialize SOS
+ /// </summary>
+ 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;
+
+ /// <summary>
+ /// The native SOS binaries path. Default is OS/architecture (RID) named directory in the same directory as this assembly.
+ /// </summary>
+ 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;
+ }
+
+ /// <summary>
+ /// Create an instance of the hosting class
+ /// </summary>
+ /// <param name="target">target instance</param>
+ private SOSLibrary(IHost host)
+ {
+ _contextService = host.Services.GetService<IContextService>();
+
+ 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));
+ }
+
+ /// <summary>
+ /// Loads and initializes the SOS module.
+ /// </summary>
+ 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<SOSInitializeDelegate>(_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);
+ }
+ }
+
+ /// <summary>
+ /// Shutdown/clean up the native SOS module.
+ /// </summary>
+ private void Uninitialize()
+ {
+ Trace.TraceInformation("SOSHost: Uninitialize");
+ if (_sosLibrary != IntPtr.Zero)
+ {
+ var uninitializeFunc = SOSHost.GetDelegateFunction<SOSUninitializeDelegate>(_sosLibrary, SOSUninitialize);
+ uninitializeFunc?.Invoke();
+
+ Microsoft.Diagnostics.Runtime.DataTarget.PlatformFunctions.FreeLibrary(_sosLibrary);
+ _sosLibrary = IntPtr.Zero;
+ }
+ _hostWrapper.Release();
+ }
+
+ /// <summary>
+ /// Execute a SOS command.
+ /// </summary>
+ /// <param name="client">client interface</param>
+ /// <param name="command">just the command name</param>
+ /// <param name="arguments">the command arguments and options</param>
+ public void ExecuteCommand(IntPtr client, string command, string arguments)
+ {
+ Debug.Assert(_sosLibrary != IntPtr.Zero);
+
+ var commandFunc = SOSHost.GetDelegateFunction<SOSCommandDelegate>(_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<SOSHost>();
+ }
+}
--- /dev/null
+// 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);
+
+ /// <summary>
+ /// Metadata locator helper for the DAC.
+ /// </summary>
+ /// <param name="imagePath">file name and path to module</param>
+ /// <param name="imageTimestamp">module timestamp</param>
+ /// <param name="imageSize">module image</param>
+ /// <param name="mvid">not used</param>
+ /// <param name="mdRva">not used</param>
+ /// <param name="flags">not used</param>
+ /// <param name="bufferSize">size of incoming buffer (pMetadata)</param>
+ /// <param name="pMetadata">pointer to buffer</param>
+ /// <param name="pMetadataSize">size of outgoing metadata</param>
+ /// <returns>HRESULT</returns>
+ 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<byte> metadata = symbolService.GetMetadata(imagePath, imageTimestamp, imageSize);
+ if (!metadata.IsEmpty)
+ {
+ dataSize = metadata.Length;
+ int size = Math.Min((int)bufferSize, dataSize);
+ Marshal.Copy(metadata.ToArray(), 0, pMetadata, size);
+ }
+ else
+ {
+ hr = HResult.E_FAIL;
+ }
+
+ if (pMetadataSize != IntPtr.Zero) {
+ Marshal.WriteInt32(pMetadataSize, dataSize);
+ }
+ return hr;
+ }
+
+ /// <summary>
+ /// Metadata locator helper for the DAC.
+ /// </summary>
+ /// <param name="imagePath">file name and path to module</param>
+ /// <param name="imageTimestamp">module timestamp</param>
+ /// <param name="imageSize">module image</param>
+ /// <param name="pathBufferSize">output buffer size</param>
+ /// <param name="pPathBufferSize">native pointer to put actual path size</param>
+ /// <param name="pwszPathBuffer">native pointer to WCHAR path buffer</param>
+ /// <returns>HRESULT</returns>
+ 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;
+ }
+ }
+}
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<IMemoryService> _getMemoryService;
private readonly ISymbolService _symbolService;
- private IMemoryService _memoryService;
- public SymbolServiceWrapper(IHost host)
+ public SymbolServiceWrapper(IHost host, Func<IMemoryService> getMemoryService)
{
Debug.Assert(host != null);
- _host = host;
+ Debug.Assert(getMemoryService != null);
+ _getMemoryService = getMemoryService;
_symbolService = host.Services.GetService<ISymbolService>();
+ Debug.Assert(_symbolService != null);
VTableBuilder builder = AddInterface(IID_ISymbolService, validate: false);
builder.AddMethod(new IsSymbolStoreEnabledDelegate((IntPtr self) => _symbolService.IsSymbolStoreEnabled));
builder.AddMethod(new GetMetadataLocatorDelegate(GetMetadataLocator));
builder.AddMethod(new GetICorDebugMetadataLocatorDelegate(GetICorDebugMetadataLocator));
builder.Complete();
+
AddRef();
}
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);
}
/// <summary>
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<IMemoryService>();
- }
- return _memoryService;
- }
- }
+ private IMemoryService MemoryService => _getMemoryService() ?? throw new DiagnosticsException("SymbolServiceWrapper: no current target");
#region Symbol service delegates
public static readonly Guid IID_ITarget = new Guid("B4640016-6CA0-468E-BA2C-1FFF28DE7B72");
+ private readonly IServiceProvider _services;
private readonly ITarget _target;
private readonly Dictionary<IRuntime, RuntimeWrapper> _wrappers = new Dictionary<IRuntime, RuntimeWrapper>();
- public TargetWrapper(ITarget target)
+ public TargetWrapper(IServiceProvider services)
{
- Debug.Assert(target != null);
- _target = target;
+ _services = services;
+ _target = services.GetService<ITarget>() ?? 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();
return _target.GetTempDirectory();
}
- private string GetRuntimeDirectory(
- IntPtr self)
- {
- var runtimeService = _target.Services.GetService<IRuntimeService>();
- 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<IRuntimeService>()?.CurrentRuntime;
+ IRuntime runtime = _services.GetService<IRuntime>();
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;
_target.Flush();
}
- private void Close(
- IntPtr self)
- {
- _target.Close();
- }
-
#region ITarget delegates
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
[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);
private delegate void FlushDelegate(
[In] IntPtr self);
- [UnmanagedFunctionPointer(CallingConvention.Winapi)]
- private delegate void CloseDelegate(
- [In] IntPtr self);
-
#endregion
}
}
{
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;
//----------------------------------------------------------------------------
/**********************************************************************\
- * 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;
}
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);
ULONG64 STDMETHODCALLTYPE GetModuleSize() const { return m_size; }
+ void STDMETHODCALLTYPE SetRuntimeDirectory(LPCSTR runtimeModuleDirectory);
+
LPCSTR STDMETHODCALLTYPE GetRuntimeDirectory();
HRESULT STDMETHODCALLTYPE GetClrDataProcess(IXCLRDataProcess** ppClrDataProcess);
Target::Target() :
m_ref(1),
m_tmpPath(nullptr),
- m_runtimeModulePath(nullptr),
#ifndef FEATURE_PAL
m_desktop(nullptr),
#endif
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)
{
}
#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
\**********************************************************************/
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();
}
return m_tmpPath;
}
-LPCSTR Target::GetRuntimeDirectory()
-{
- return m_runtimeModulePath;
-}
-
HRESULT Target::GetRuntime(IRuntime** ppRuntime)
{
return CreateInstance(ppRuntime);
#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();
private:
LONG m_ref;
LPCSTR m_tmpPath;
- LPCSTR m_runtimeModulePath;
#ifndef FEATURE_PAL
Runtime* m_desktop;
#endif
#ifndef FEATURE_PAL
bool SwitchRuntimeInstance(bool desktop);
#endif
- void SetRuntimeDirectoryInstance(LPCSTR runtimeModulePath);
void DisplayStatusInstance();
Target();
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)
{
LPCSTR STDMETHODCALLTYPE GetTempDirectory();
- LPCSTR STDMETHODCALLTYPE GetRuntimeDirectory();
-
HRESULT STDMETHODCALLTYPE GetRuntime(IRuntime** pRuntime);
void STDMETHODCALLTYPE Flush();
-
- void STDMETHODCALLTYPE Close();
};
AnalyzeOOM
analyzeoom=AnalyzeOOM
ao=AnalyzeOOM
+ clrmodules
ClrStack
clrstack=ClrStack
CLRStack=ClrStack
EHInfo
ehinfo=EHInfo
Ehinfo=EHInfo
+ ext
FinalizeQueue
finalizequeue=FinalizeQueue
fq=FinalizeQueue
tracetocode=TraceToCode
#endif
- ext
SOSInitializeByHost
SOSUninitializeByHost
InitializeHostServices
; See the LICENSE file in the project root for more information.
bpmd
+clrmodules
ClrStack
dbgout
DumpALC
GCWhere
EEStack
EHInfo
+ext
FinalizeQueue
FindAppDomain
GCInfo
u
VerifyHeap
-ext
SOSInitializeByHost
SOSUninitializeByHost
{
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[] =
{
}
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;
}
//
}
//
-// 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);
}
//
//
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)
virtual ULONG64 STDMETHODCALLTYPE GetModuleSize() const = 0;
/// <summary>
- /// Returns the directory of the runtime file
+ /// Set the runtime module directory to search for DAC/DBI
+ /// </summary>
+ virtual void STDMETHODCALLTYPE SetRuntimeDirectory(LPCSTR runtimeModuleDirectory) = 0;
+
+ /// <summary>
+ /// Returns the directory of the runtime module
/// </summary>
virtual LPCSTR STDMETHODCALLTYPE GetRuntimeDirectory() = 0;
/// <returns>temporary directory string</returns>
virtual LPCSTR STDMETHODCALLTYPE GetTempDirectory() = 0;
- /// <summary>
- /// Returns the directory of the runtime file
- /// </summary>
- /// <returns>runtime directory or null if none set</returns>
- virtual LPCSTR STDMETHODCALLTYPE GetRuntimeDirectory() = 0;
-
/// <summary>
/// Returns the current runtime instance
/// </summary>
/// Flushes any internal caching or state
/// </summary>
virtual void STDMETHODCALLTYPE Flush() = 0;
-
- /// <summary>
- /// Cleans up any internal resources
- /// </summary>
- virtual void STDMETHODCALLTYPE Close() = 0;
};
#ifdef __cplusplus
{
return E_FAIL;
}
+ InitializeThreadInfo(process);
*processId = GetProcessId(process);
*threadId = GetThreadId(thread);
return E_FAIL;
}
+ InitializeThreadInfo(process);
+
*sysId = GetProcessId(process);
return S_OK;
}
g_services->AddCommand("sos", new sosCommand(nullptr), "Various .NET Core debugging commands. See 'soshelp' for more details. sos <command-name> <args>");
g_services->AddCommand("ext", new sosCommand("ext"), "Execute extension command. See 'soshelp' for more details. ext <command-name> <args>");
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.");
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<IHost>(this);
_serviceProvider.AddService<IConsoleService>(_consoleProvider);
_serviceProvider.AddService<ICommandService>(_commandProcessor);
_serviceProvider.AddService<ISymbolService>(_symbolService);
+ _serviceProvider.AddService<IContextService>(_contextService);
+ _serviceProvider.AddServiceFactory<SOSLibrary>(() => SOSLibrary.Create(this));
+
+ _contextService.ServiceProvider.AddServiceFactory<ClrMDHelper>(() => {
+ ClrRuntime clrRuntime = _contextService.Services.GetService<ClrRuntime>();
+ return clrRuntime != null ? new ClrMDHelper(clrRuntime) : null;
+ });
_commandProcessor.AddCommands(new Assembly[] { typeof(Analyzer).Assembly });
_commandProcessor.AddCommands(new Assembly[] { typeof(ClrMDHelper).Assembly });
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<SOSHost>(() => {
- var sosHost = new SOSHost(_target);
- sosHost.InitializeSOSHost();
- return sosHost;
- });
+ _target.ServiceProvider.AddServiceFactory<SOSHost>(() => new SOSHost(_contextService.Services));
// Automatically enable symbol server support
_symbolService.AddSymbolServer(msdl: true, symweb: false, symbolServerPath: null, authToken: null, timeoutInMinutes: 0);
{
foreach (string cmd in command)
{
- Parse(cmd);
-
+ _commandProcessor.Execute(cmd, _contextService.Services);
if (_consoleProvider.Shutdown) {
break;
}
_consoleProvider.WriteLine("Type 'quit' or 'exit' to exit the session.");
_consoleProvider.Start((string commandLine, CancellationToken cancellation) => {
- Parse(commandLine);
+ _commandProcessor.Execute(commandLine, _contextService.Services);
});
}
}
{
if (_target != null)
{
- _target.Close();
- _target = null;
+ DestroyTarget(_target);
}
// Persist the current command history
try
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<IThread>(() => {
- IThreadService threadService = _target.Services.GetService<IThreadService>();
- 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<IRuntimeService>();
- if (runtimeService != null)
- {
- services.AddServiceFactory<IRuntime>(() => runtimeService.CurrentRuntime);
- services.AddServiceFactory<ClrRuntime>(() => services.GetService<IRuntime>()?.Services.GetService<ClrRuntime>());
- services.AddServiceFactory<ClrMDHelper>(() => new ClrMDHelper(services.GetService<ClrRuntime>()));
- }
- }
- _commandProcessor.Execute(commandLine, services);
- }
-
#region IHost
public IServiceEvent OnShutdownEvent { get; } = new ServiceEvent();
IEnumerable<ITarget> IHost.EnumerateTargets() => _target != null ? new ITarget[] { _target } : Array.Empty<ITarget>();
- 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
}