From 25f53c818b1e0abd2e2d52d517764d20ea51015a Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Sat, 21 Dec 2024 12:19:03 -0800 Subject: [PATCH 1/2] Misc service and implementation cleanup Clean up and add useful functions to IModuleService, IThreadService, IHost, ITarget --- .../AssemblyResolver.cs | 10 +- .../Host.cs | 36 ++++++ .../Module.cs | 2 +- .../ModuleFromAddress.cs | 2 + .../ModuleService.cs | 18 ++- .../ModuleServiceFromDataReader.cs | 2 + .../ServiceEvent.cs | 2 +- .../SymbolService.cs | 31 +++++- .../Target.cs | 42 ------- .../Thread.cs | 32 +++++- .../ThreadService.cs | 105 ++++++++++-------- .../ThreadServiceFromDataReader.cs | 16 +-- .../Utilities.cs | 23 ++++ .../IHost.cs | 5 + .../ILiveTargetFactory.cs | 20 ++++ .../IModuleService.cs | 10 ++ .../ISymbolService.cs | 7 ++ .../ITarget.cs | 5 - .../IThreadService.cs | 9 ++ .../Host}/ReadMemoryCommand.cs | 0 .../TestHost/TestDataWriter.cs | 2 +- .../MemoryServiceFromDebuggerServices.cs | 15 +-- .../ThreadServiceFromDebuggerServices.cs | 10 +- src/SOS/SOS.Hosting/HostWrapper.cs | 12 ++ .../SOS.Hosting/SymbolServiceExtensions.cs | 87 ++++++++++++--- src/SOS/SOS.Hosting/TargetWrapper.cs | 12 -- src/SOS/Strike/platform/runtimeimpl.cpp | 32 ------ src/SOS/inc/target.h | 6 - .../LibraryProviderWrapper.cs | 2 + .../DebugServicesTests.cs | 3 - .../SymbolServiceTests.cs | 2 + 31 files changed, 353 insertions(+), 207 deletions(-) create mode 100644 src/Microsoft.Diagnostics.DebugServices/ILiveTargetFactory.cs rename src/{Tools/dotnet-dump/Commands => Microsoft.Diagnostics.ExtensionCommands/Host}/ReadMemoryCommand.cs (100%) diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs index 626b31082f..146d51f426 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/AssemblyResolver.cs @@ -30,7 +30,7 @@ public static void Enable() private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { - // apply any existing policy + // Apply any existing policy AssemblyName referenceName = new(AppDomain.CurrentDomain.ApplyPolicy(args.Name)); string fileName = referenceName.Name + ".dll"; string assemblyPath; @@ -39,10 +39,10 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven // Look next to the executing assembly probingPath = Path.Combine(_defaultAssembliesPath, fileName); - Debug.WriteLine($"Considering {probingPath} based on ExecutingAssembly"); + Trace.TraceInformation($"Considering {probingPath} based on ExecutingAssembly"); if (Probe(probingPath, referenceName.Version, out assembly)) { - Debug.WriteLine($"Matched {probingPath} based on ExecutingAssembly"); + Trace.TraceInformation($"Matched {probingPath} based on ExecutingAssembly"); return assembly; } @@ -51,10 +51,10 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven if (!string.IsNullOrEmpty(assemblyPath)) { probingPath = Path.Combine(Path.GetDirectoryName(assemblyPath), fileName); - Debug.WriteLine($"Considering {probingPath} based on RequestingAssembly"); + Trace.TraceInformation($"Considering {probingPath} based on RequestingAssembly"); if (Probe(probingPath, referenceName.Version, out assembly)) { - Debug.WriteLine($"Matched {probingPath} based on RequestingAssembly"); + Trace.TraceInformation($"Matched {probingPath} based on RequestingAssembly"); return assembly; } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Host.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Host.cs index d3637d0e40..9190f57ae2 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Host.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Host.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using Microsoft.Diagnostics.DebugServices; @@ -14,6 +15,7 @@ public class Host : IHost private readonly ServiceManager _serviceManager; private ServiceContainer _serviceContainer; private readonly List _targets = new(); + private string _tempDirectory; private int _targetIdFactory; public Host(HostType type) @@ -61,6 +63,7 @@ public void DestoryTargets() target.Destroy(); } _targets.Clear(); + CleanupTempDirectory(); } #region IHost @@ -84,6 +87,39 @@ public int AddTarget(ITarget target) return _targetIdFactory++; } + public string GetTempDirectory() + { + if (_tempDirectory == null) + { + // Use the SOS process's id if can't get the target's + uint processId = (uint)Process.GetCurrentProcess().Id; + + // SOS depends on that the temp directory ends with "/". + _tempDirectory = Path.Combine(Path.GetTempPath(), "sos" + processId.ToString()) + Path.DirectorySeparatorChar; + Directory.CreateDirectory(_tempDirectory); + } + return _tempDirectory; + } + #endregion + + private void CleanupTempDirectory() + { + if (_tempDirectory != null) + { + try + { + foreach (string file in Directory.EnumerateFiles(_tempDirectory)) + { + File.Delete(file); + } + Directory.Delete(_tempDirectory); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + } + _tempDirectory = null; + } + } } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs index ec9d72fe8c..d16bbfe853 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Module.cs @@ -73,7 +73,7 @@ public virtual void Dispose() public virtual uint? IndexTimeStamp { get; protected set; } - public bool IsPEImage + public virtual bool IsPEImage { get { diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleFromAddress.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleFromAddress.cs index 7b0c6d0781..1df12cf6af 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleFromAddress.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleFromAddress.cs @@ -69,6 +69,8 @@ public override string LoadSymbols() #endregion + internal void AddService(T service) => _serviceContainer.AddService(service); + protected override ModuleService ModuleService { get; } } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs index 1a2f617a6b..c8d6f4d46a 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleService.cs @@ -172,6 +172,19 @@ IEnumerable IModuleService.GetModuleFromModuleName(string moduleName) } } + /// + /// Create a module instance + /// + /// artifical index + /// module base address + /// module size + /// module name + /// IModule + IModule IModuleService.CreateModule(int moduleIndex, ulong imageBase, ulong imageSize, string imageName) + { + return new ModuleFromAddress(this, moduleIndex, imageBase, imageSize, imageName); + } + #endregion /// @@ -220,7 +233,7 @@ internal PEFile GetPEInfo(ulong address, ulong size, out IEnumerable pdbs, out Module.Flags flags); - // Continue only if marked as a PE. This bit regardless of the layout if the module has a PE header/signature. + // Continue only if marked as a PE. This bit is set regardless of the layout if the module has a PE header/signature. if ((flags & Module.Flags.IsPEImage) != 0) { if (peFile is null || pdbs.Count == 0) @@ -288,9 +301,6 @@ private PEFile GetPEInfo(bool isVirtual, ulong address, ulong size, out Listbuild id or null internal byte[] GetBuildId(ulong address) { - // This code is called by the image mapping memory service so it needs to use the - // original or raw memory service to prevent recursion so it can't use the ELFFile - // or MachOFile instance that is available from the IModule.Services provider. Stream stream = MemoryService.CreateMemoryStream(); byte[] buildId = null; try diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleServiceFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleServiceFromDataReader.cs index 075636ec9c..0c34b32a17 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleServiceFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ModuleServiceFromDataReader.cs @@ -47,6 +47,8 @@ public ModuleFromDataReader(ModuleServiceFromDataReader moduleService, int modul public override uint? IndexTimeStamp => _moduleInfo.IndexTimeStamp == InvalidTimeStamp ? null : unchecked((uint)_moduleInfo.IndexTimeStamp); + public override bool IsPEImage => _moduleInfo.Kind == ModuleKind.PortableExecutable; + public override ImmutableArray BuildId { get diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs index 3fc3cdeb28..ec67337128 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ServiceEvent.cs @@ -51,7 +51,7 @@ public ServiceEvent() public IDisposable RegisterOneShot(Action callback) => Register(oneshot: true, callback); #pragma warning disable CA1859 - // Use concrete types when possible for improved performance. Explicitly obscure any functionality other than a duspose token. + // Use concrete types when possible for improved performance. Explicitly obscure any functionality other than a dispose token. private IDisposable Register(bool oneshot, Action callback) #pragma warning restore CA1859 { diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs index 1247620b74..0c9b5df74d 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/SymbolService.cs @@ -72,7 +72,7 @@ public SymbolService(IHost host) /// /// The default symbol cache path: /// * dbgeng on Windows uses the dbgeng symbol cache path: %PROGRAMDATA%\dbg\sym - /// * dotnet-dump on Windows uses the VS symbol cache path: %TEMPDIR%\SymbolCache + /// * VS or dotnet-dump on Windows uses the VS symbol cache path: %TEMPDIR%\SymbolCache /// * dotnet-dump/lldb on Linux/MacOS uses: $HOME/.dotnet/symbolcache /// public string DefaultSymbolCache @@ -86,8 +86,7 @@ public string DefaultSymbolCache _defaultSymbolCache = _host.HostType switch { HostType.DbgEng => Path.Combine(Environment.GetEnvironmentVariable("PROGRAMDATA"), "dbg", "sym"), - HostType.DotnetDump => Path.Combine(Path.GetTempPath(), "SymbolCache"), - _ => throw new NotSupportedException($"Host type not supported {_host.HostType}"), + _ => Path.Combine(Path.GetTempPath(), "SymbolCache"), }; } else @@ -461,6 +460,32 @@ public string DownloadSymbolFile(IModule module) /// the full path name of the file public string DownloadFile(string index, string file) => DownloadFile(new SymbolStoreKey(index, file)); + /// + /// Downloads and returns the metadata for the assembly. + /// + /// module assembly + /// metadata bytes + public ImmutableArray GetMetadata(IModule module) + { + try + { + PEReader reader = module.Services.GetService(); + if (reader is not null && reader.HasMetadata) + { + PEMemoryBlock metadataInfo = reader.GetMetadata(); + return metadataInfo.GetContent(); + } + } + catch (Exception ex) when + (ex is InvalidOperationException || + ex is BadImageFormatException || + ex is IOException) + { + Trace.TraceError($"GetMetaData: {ex.Message}"); + } + return ImmutableArray.Empty; + } + /// /// Returns the metadata for the assembly /// diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs index 5d092e9263..c9d6b7b982 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Target.cs @@ -17,7 +17,6 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation public abstract class Target : ITarget { private readonly string _dumpPath; - private string _tempDirectory; private ServiceContainer _serviceContainer; protected readonly ServiceContainerFactory _serviceContainerFactory; @@ -82,23 +81,6 @@ protected void Finished() /// public uint? ProcessId { get; protected set; } - /// - /// Returns the unique temporary directory for this instance of SOS - /// - public string GetTempDirectory() - { - if (_tempDirectory == null) - { - // Use the SOS process's id if can't get the target's - uint processId = ProcessId.GetValueOrDefault((uint)Process.GetCurrentProcess().Id); - - // SOS depends on that the temp directory ends with "/". - _tempDirectory = Path.Combine(Path.GetTempPath(), "sos" + processId.ToString()) + Path.DirectorySeparatorChar; - Directory.CreateDirectory(_tempDirectory); - } - return _tempDirectory; - } - /// /// The per target services. /// @@ -132,7 +114,6 @@ public virtual void Destroy() OnDestroyEvent.Fire(); _serviceContainer.RemoveService(typeof(ITarget)); _serviceContainer.DisposeServices(); - CleanupTempDirectory(); } #endregion @@ -154,25 +135,6 @@ private static Reader CreateReader(IServiceProvider services) return new Reader(new StreamAddressSpace(stream), layoutManager); } - private void CleanupTempDirectory() - { - if (_tempDirectory != null) - { - try - { - foreach (string file in Directory.EnumerateFiles(_tempDirectory)) - { - File.Delete(file); - } - Directory.Delete(_tempDirectory); - } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) - { - } - _tempDirectory = null; - } - } - public override bool Equals(object obj) { return Id == ((ITarget)obj).Id; @@ -192,10 +154,6 @@ public override string ToString() { sb.Append($" {_dumpPath}"); } - if (_tempDirectory != null) - { - sb.Append($" {_tempDirectory}"); - } return sb.ToString(); } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs index 96804254d7..4289685380 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Thread.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Runtime.InteropServices; namespace Microsoft.Diagnostics.DebugServices.Implementation { @@ -23,7 +24,7 @@ public Thread(ThreadService threadService, int index, uint id) _serviceContainer.AddService(this); } - void IDisposable.Dispose() + public void Dispose() { _serviceContainer.RemoveService(typeof(IThread)); _serviceContainer.DisposeServices(); @@ -58,22 +59,47 @@ public ReadOnlySpan GetThreadContext() { if (_threadContext.IsEmpty) { - _threadContext = _threadService.GetThreadContext(this); + byte[] threadContext = new byte[_threadService.ContextSize]; + if (!GetThreadContextInner(_threadService.ContextFlags, threadContext)) + { + throw new DiagnosticsException(); + } + _threadContext = threadContext; } return _threadContext.Span; } + /// + /// Get the thread context + /// + /// Windows context flags + /// Context buffer + /// true succeeded, false failed + protected virtual bool GetThreadContextInner(uint contextFlags, byte[] context) => _threadService.GetThreadContext(ThreadId, contextFlags, context); + public ulong GetThreadTeb() { if (!_teb.HasValue) { - _teb = _threadService.GetThreadTeb(this); + _teb = GetThreadTebInner(); } return _teb.Value; } + /// + /// Returns the Windows TEB pointer for the thread + /// + /// TEB pointer or 0 if not implemented or thread id not found + protected virtual ulong GetThreadTebInner() => _threadService.GetThreadTeb(ThreadId); + #endregion + protected void SetContextFlags(uint contextFlags, Span context) + { + Span threadSpan = context.Slice(_threadService.ContextFlagsOffset, sizeof(uint)); + MemoryMarshal.Write(threadSpan, ref contextFlags); + } + public override bool Equals(object obj) { IThread thread = (IThread)obj; diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs index 668d17e127..9aa784acb0 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadService.cs @@ -16,12 +16,13 @@ namespace Microsoft.Diagnostics.DebugServices.Implementation { /// public abstract class ThreadService : IThreadService, IDisposable { - private readonly int _contextSize; - private readonly uint _contextFlags; private readonly Dictionary _lookupByName; private readonly Dictionary _lookupByIndex; private Dictionary _threads; + protected internal readonly int ContextSize; + protected internal readonly uint ContextFlags; + protected internal readonly int ContextFlagsOffset; protected internal readonly IServiceProvider Services; protected internal readonly ITarget Target; @@ -36,38 +37,38 @@ public ThreadService(IServiceProvider services) { case Architecture.X64: // Dumps generated with newer dbgeng have bigger context buffers and clrmd requires the context size to at least be that size. - _contextSize = Target.OperatingSystem == OSPlatform.Windows ? 0x700 : AMD64Context.Size; - _contextFlags = AMD64Context.ContextControl | AMD64Context.ContextInteger | AMD64Context.ContextSegments | AMD64Context.ContextFloatingPoint; + ContextSize = Target.Host.HostType != HostType.Vs && Target.OperatingSystem == OSPlatform.Windows ? 0x700 : AMD64Context.Size; + ContextFlags = AMD64Context.ContextControl | AMD64Context.ContextInteger | AMD64Context.ContextSegments | AMD64Context.ContextFloatingPoint; contextType = typeof(AMD64Context); break; case Architecture.X86: - _contextSize = X86Context.Size; - _contextFlags = X86Context.ContextControl | X86Context.ContextInteger | X86Context.ContextSegments | X86Context.ContextFloatingPoint; + ContextSize = X86Context.Size; + ContextFlags = X86Context.ContextControl | X86Context.ContextInteger | X86Context.ContextSegments | X86Context.ContextFloatingPoint; contextType = typeof(X86Context); break; case Architecture.Arm64: - _contextSize = Arm64Context.Size; - _contextFlags = Arm64Context.ContextControl | Arm64Context.ContextInteger | Arm64Context.ContextFloatingPoint; + ContextSize = Arm64Context.Size; + ContextFlags = Arm64Context.ContextControl | Arm64Context.ContextInteger | Arm64Context.ContextFloatingPoint; contextType = typeof(Arm64Context); break; case Architecture.Arm: - _contextSize = ArmContext.Size; - _contextFlags = ArmContext.ContextControl | ArmContext.ContextInteger | ArmContext.ContextFloatingPoint; + ContextSize = ArmContext.Size; + ContextFlags = ArmContext.ContextControl | ArmContext.ContextInteger | ArmContext.ContextFloatingPoint; contextType = typeof(ArmContext); break; case (Architecture)6 /* Architecture.LoongArch64 */: - _contextSize = LoongArch64Context.Size; - _contextFlags = LoongArch64Context.ContextControl | LoongArch64Context.ContextInteger | LoongArch64Context.ContextFloatingPoint; + ContextSize = LoongArch64Context.Size; + ContextFlags = LoongArch64Context.ContextControl | LoongArch64Context.ContextInteger | LoongArch64Context.ContextFloatingPoint; contextType = typeof(LoongArch64Context); break; case (Architecture)9 /* Architecture.RiscV64 */: - _contextSize = RiscV64Context.Size; - _contextFlags = RiscV64Context.ContextControl | RiscV64Context.ContextInteger | RiscV64Context.ContextFloatingPoint; + ContextSize = RiscV64Context.Size; + ContextFlags = RiscV64Context.ContextControl | RiscV64Context.ContextInteger | RiscV64Context.ContextFloatingPoint; contextType = typeof(RiscV64Context); break; @@ -81,6 +82,11 @@ public ThreadService(IServiceProvider services) FieldInfo[] fields = contextType.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo field in fields) { + FieldOffsetAttribute offsetAttribute = field.GetCustomAttributes(inherit: false).Single(); + if (field.Name.Equals("contextflags", StringComparison.InvariantCultureIgnoreCase)) + { + ContextFlagsOffset = offsetAttribute.Value; + } RegisterAttribute registerAttribute = field.GetCustomAttributes(inherit: false).SingleOrDefault(); if (registerAttribute is null) { @@ -108,7 +114,6 @@ public ThreadService(IServiceProvider services) { FramePointerIndex = index; } - FieldOffsetAttribute offsetAttribute = field.GetCustomAttributes(inherit: false).Single(); RegisterInfo registerInfo = new(index, offsetAttribute.Value, Marshal.SizeOf(field.FieldType), registerAttribute.Name ?? field.Name.ToLowerInvariant()); registers.Add(registerInfo); index++; @@ -224,6 +229,43 @@ public bool TryGetRegisterValue(ReadOnlySpan context, int registerIndex, o return false; } + /// + /// Change a specific register. Currently only used to set the ContextFlags in certain implementations. + /// + /// writeable context span + /// register index + /// value to write + /// + public bool TrySetRegisterValue(Span context, int registerIndex, ulong value) + { + if (TryGetRegisterInfo(registerIndex, out RegisterInfo info)) + { + Span threadSpan = context.Slice(info.RegisterOffset, info.RegisterSize); + switch (info.RegisterSize) + { + case 1: + byte byteValue = (byte)value; + MemoryMarshal.Write(threadSpan, ref byteValue); + return true; + case 2: + ushort ushortValue = (ushort)value; + MemoryMarshal.Write(threadSpan, ref ushortValue); + return true; + case 4: + uint uintValue = (uint)value; + MemoryMarshal.Write(threadSpan, ref uintValue); + return true; + case 8: + MemoryMarshal.Write(threadSpan, ref value); + return true; + default: + Trace.TraceError($"SetRegisterValue: {info.RegisterName} invalid size {info.RegisterSize}"); + break; + } + } + return false; + } + /// /// Enumerate all the native threads /// @@ -268,32 +310,6 @@ public IThread GetThreadFromId(uint threadId) #endregion - /// - /// Get the thread context - /// - /// thread instance - /// context array - /// invalid thread id - internal byte[] GetThreadContext(Thread thread) - { - byte[] threadContext = new byte[_contextSize]; - if (!GetThreadContext(thread.ThreadId, _contextFlags, (uint)_contextSize, threadContext)) - { - throw new DiagnosticsException(); - } - return threadContext; - } - - /// - /// Get the thread TEB - /// - /// thread instance - /// TEB - internal ulong GetThreadTeb(Thread thread) - { - return GetThreadTeb(thread.ThreadId); - } - /// /// Get/create the thread dictionary. /// @@ -313,17 +329,16 @@ private Dictionary GetThreads() /// /// OS thread id /// Windows context flags - /// Context size /// Context buffer /// true succeeded, false failed /// invalid thread id - protected abstract bool GetThreadContext(uint threadId, uint contextFlags, uint contextSize, byte[] context); + protected internal virtual bool GetThreadContext(uint threadId, uint contextFlags, byte[] context) => throw new NotImplementedException(); /// /// Returns the Windows TEB pointer for the thread /// /// OS thread id - /// TEB pointer or 0 - protected abstract ulong GetThreadTeb(uint threadId); + /// TEB pointer or 0 if not implemented or thread id not found + protected internal virtual ulong GetThreadTeb(uint threadId) => throw new NotImplementedException(); } } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs index 36c8ffe4c4..2cb1c43459 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/ThreadServiceFromDataReader.cs @@ -25,11 +25,16 @@ public ThreadServiceFromDataReader(IServiceProvider services, IDataReader dataRe _threadReader = (IThreadReader)dataReader; } - protected override bool GetThreadContext(uint threadId, uint contextFlags, uint contextSize, byte[] context) + protected override IEnumerable GetThreadsInner() + { + return _threadReader.EnumerateOSThreadIds().Select((uint id, int index) => new Thread(this, index, id)).Cast(); + } + + protected internal override bool GetThreadContext(uint threadId, uint contextFlags, byte[] context) { try { - return _dataReader.GetThreadContext(threadId, contextFlags, new Span(context, 0, unchecked((int)contextSize))); + return _dataReader.GetThreadContext(threadId, contextFlags, new Span(context)); } catch (ClrDiagnosticsException ex) { @@ -38,12 +43,7 @@ protected override bool GetThreadContext(uint threadId, uint contextFlags, uint } } - protected override IEnumerable GetThreadsInner() - { - return _threadReader.EnumerateOSThreadIds().Select((uint id, int index) => new Thread(this, index, id)).Cast(); - } - - protected override ulong GetThreadTeb(uint threadId) + protected internal override ulong GetThreadTeb(uint threadId) { return _threadReader.GetThreadTeb(threadId); } diff --git a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs index 0fb9984ed2..a966eeecc9 100644 --- a/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs +++ b/src/Microsoft.Diagnostics.DebugServices.Implementation/Utilities.cs @@ -28,6 +28,29 @@ public static class Utilities /// public static string ToHex(this ImmutableArray array) => string.Concat(array.Select((b) => b.ToString("x2"))); + /// + /// Returns the pointer size for a given processor type + /// + /// processor type + /// pointer size + /// + public static int GetPointerSizeFromArchitecture(Architecture architecture) + { + switch (architecture) + { + case Architecture.X64: + case Architecture.Arm64: + case (Architecture)6 /* Architecture.LoongArch64 */: + case (Architecture)9 /* Architecture.RiscV64 */: + return 8; + case Architecture.X86: + case Architecture.Arm: + return 4; + default: + throw new NotSupportedException("Architecture not supported"); + } + } + /// /// Combines two hash codes into a single hash code, in an order-dependent manner. /// diff --git a/src/Microsoft.Diagnostics.DebugServices/IHost.cs b/src/Microsoft.Diagnostics.DebugServices/IHost.cs index b489bfd4ab..a8e0250156 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IHost.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IHost.cs @@ -55,5 +55,10 @@ public interface IHost /// target instance /// target id int AddTarget(ITarget target); + + /// + /// Returns the unique temporary directory for this instance of SOS + /// + string GetTempDirectory(); } } diff --git a/src/Microsoft.Diagnostics.DebugServices/ILiveTargetFactory.cs b/src/Microsoft.Diagnostics.DebugServices/ILiveTargetFactory.cs new file mode 100644 index 0000000000..c736533049 --- /dev/null +++ b/src/Microsoft.Diagnostics.DebugServices/ILiveTargetFactory.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DebugServices +{ + /// + /// Attaches to live processes. + /// + public interface ILiveTargetFactory + { + /// + /// Attaches to a live process and suspends it until the target is destroyed/closed. + /// + /// target instance + /// can not construct target instance + ITarget Attach(int processId); + } +} diff --git a/src/Microsoft.Diagnostics.DebugServices/IModuleService.cs b/src/Microsoft.Diagnostics.DebugServices/IModuleService.cs index ad5ad4f856..8eb1981248 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IModuleService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IModuleService.cs @@ -45,5 +45,15 @@ public interface IModuleService /// module name to find /// matching modules IEnumerable GetModuleFromModuleName(string moduleName); + + /// + /// Create a module instance from a stream (memory or file). + /// + /// artifical index + /// module base address + /// module size + /// module name + /// IModule + IModule CreateModule(int moduleIndex, ulong imageBase, ulong imageSize, string imageName); } } diff --git a/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs b/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs index fb3c23bdc4..372ac6fa27 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ISymbolService.cs @@ -129,6 +129,13 @@ public bool AddSymbolServer( /// the full path name of the file string DownloadFile(string index, string file); + /// + /// Downloads and returns the metadata for the assembly. + /// + /// module assembly + /// metadata bytes + ImmutableArray GetMetadata(IModule module); + /// /// Returns the metadata for the assembly /// diff --git a/src/Microsoft.Diagnostics.DebugServices/ITarget.cs b/src/Microsoft.Diagnostics.DebugServices/ITarget.cs index 4ed59e556c..f7be0cf9e4 100644 --- a/src/Microsoft.Diagnostics.DebugServices/ITarget.cs +++ b/src/Microsoft.Diagnostics.DebugServices/ITarget.cs @@ -43,11 +43,6 @@ public interface ITarget /// uint? ProcessId { get; } - /// - /// Returns the unique temporary directory for this instance of SOS - /// - string GetTempDirectory(); - /// /// The per target services. /// diff --git a/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs b/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs index 922ecdac0f..ecbac1b271 100644 --- a/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs +++ b/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs @@ -58,6 +58,15 @@ public interface IThreadService /// true if value found bool TryGetRegisterValue(ReadOnlySpan context, int registerIndex, out ulong value); + /// + /// Change a specific register. Currently only used to set the ContextFlags in certain implementations. + /// + /// writeable context span + /// register index + /// value to write + /// + public bool TrySetRegisterValue(Span context, int registerIndex, ulong value); + /// /// Enumerate all the native threads /// diff --git a/src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/Host/ReadMemoryCommand.cs similarity index 100% rename from src/Tools/dotnet-dump/Commands/ReadMemoryCommand.cs rename to src/Microsoft.Diagnostics.ExtensionCommands/Host/ReadMemoryCommand.cs diff --git a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs index 5ff0b5e4d9..700b028659 100644 --- a/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs +++ b/src/Microsoft.Diagnostics.TestHelpers/TestHost/TestDataWriter.cs @@ -33,7 +33,7 @@ public void Build(IServiceProvider services) { ITarget target = services.GetService(); Debug.Assert(target is not null); - AddMembers(Target, typeof(ITarget), target, nameof(ITarget.Id), nameof(ITarget.GetTempDirectory)); + AddMembers(Target, typeof(ITarget), target, nameof(ITarget.Id)); XElement modulesElement = new("Modules"); Target.Add(modulesElement); diff --git a/src/SOS/SOS.Extensions/MemoryServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/MemoryServiceFromDebuggerServices.cs index e825b9908d..c90104a971 100644 --- a/src/SOS/SOS.Extensions/MemoryServiceFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/MemoryServiceFromDebuggerServices.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Diagnostics.DebugServices; +using Microsoft.Diagnostics.DebugServices.Implementation; using Microsoft.Diagnostics.Runtime.Utilities; namespace SOS.Extensions @@ -26,19 +27,7 @@ internal MemoryServiceFromDebuggerServices(ITarget target, DebuggerServices debu Debug.Assert(target != null); Debug.Assert(debuggerServices != null); _debuggerServices = debuggerServices; - - switch (target.Architecture) - { - case Architecture.X64: - case Architecture.Arm64: - case (Architecture)6 /* Architecture.LoongArch64 */: - PointerSize = 8; - break; - case Architecture.X86: - case Architecture.Arm: - PointerSize = 4; - break; - } + PointerSize = Utilities.GetPointerSizeFromArchitecture(target.Architecture); } #region IMemoryService diff --git a/src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs b/src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs index c257770db5..694f61dac4 100644 --- a/src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs +++ b/src/SOS/SOS.Extensions/ThreadServiceFromDebuggerServices.cs @@ -24,11 +24,6 @@ internal ThreadServiceFromDebuggerServices(IServiceProvider services, DebuggerSe _debuggerServices = debuggerServices; } - protected override bool GetThreadContext(uint threadId, uint contextFlags, uint contextSize, byte[] context) - { - return _debuggerServices.GetThreadContext(threadId, contextFlags, contextSize, context) == HResult.S_OK; - } - protected override IEnumerable GetThreadsInner() { HResult hr = _debuggerServices.GetNumberThreads(out uint number); @@ -55,6 +50,11 @@ protected override IEnumerable GetThreadsInner() } } + protected override bool GetThreadContext(uint threadId, uint contextFlags, byte[] context) + { + return _debuggerServices.GetThreadContext(threadId, contextFlags, (uint)context.Length, context).IsOK; + } + protected override ulong GetThreadTeb(uint threadId) { _debuggerServices.GetThreadTeb(threadId, out ulong teb); diff --git a/src/SOS/SOS.Hosting/HostWrapper.cs b/src/SOS/SOS.Hosting/HostWrapper.cs index 1e18682675..181b6a04d3 100644 --- a/src/SOS/SOS.Hosting/HostWrapper.cs +++ b/src/SOS/SOS.Hosting/HostWrapper.cs @@ -27,6 +27,7 @@ public HostWrapper(IHost host) builder.AddMethod(new GetHostTypeDelegate(GetHostType)); builder.AddMethod(new GetServiceDelegate(ServiceWrapper.GetService)); builder.AddMethod(new GetCurrentTargetDelegate(GetCurrentTarget)); + builder.AddMethod(new GetTempDirectoryDelegate(GetTempDirectory)); IHost = builder.Complete(); AddRef(); @@ -65,6 +66,12 @@ private int GetCurrentTarget(IntPtr self, out IntPtr targetWrapper) return HResult.S_OK; } + private string GetTempDirectory( + IntPtr self) + { + return _host.GetTempDirectory(); + } + #endregion #region IHost delegates @@ -84,6 +91,11 @@ private delegate int GetCurrentTargetDelegate( [In] IntPtr self, [Out] out IntPtr target); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + [return: MarshalAs(UnmanagedType.LPStr)] + private delegate string GetTempDirectoryDelegate( + [In] IntPtr self); + #endregion } } diff --git a/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs b/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs index 6fe61d348c..40db61b555 100644 --- a/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs +++ b/src/SOS/SOS.Hosting/SymbolServiceExtensions.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; using Microsoft.Diagnostics.DebugServices; using Microsoft.Diagnostics.Runtime.Utilities; @@ -70,21 +71,49 @@ public static int GetMetadataLocator( { return HResult.E_INVALIDARG; } - int hr = HResult.S_OK; + int hr = HResult.E_FAIL; int dataSize = 0; - ImmutableArray metadata = symbolService.GetMetadata(imagePath, imageTimestamp, imageSize); - if (!metadata.IsEmpty) + if (symbolService.IsSymbolStoreEnabled) { - dataSize = metadata.Length; - int size = Math.Min((int)bufferSize, dataSize); - Marshal.Copy(metadata.ToArray(), 0, pMetadata, size); + try + { + SymbolStoreKey key = PEFileKeyGenerator.GetKey(imagePath, imageTimestamp, imageSize); + string localFilePath = symbolService.DownloadFile(key.Index, key.FullPathName); + if (!string.IsNullOrWhiteSpace(localFilePath)) + { + Stream peStream = TryOpenFile(localFilePath); + if (peStream != null) + { + using PEReader peReader = new(peStream, PEStreamOptions.Default); + if (peReader.HasMetadata) + { + PEMemoryBlock metadataInfo = peReader.GetMetadata(); + ImmutableArray metadata = metadataInfo.GetContent(); + if (!metadata.IsEmpty) + { + dataSize = metadata.Length; + int size = Math.Min((int)bufferSize, dataSize); + Marshal.Copy(metadata.ToArray(), 0, pMetadata, size); + hr = HResult.S_OK; + } + } + } + } + else + { + Trace.TraceError($"GetMetaDataLocator: file not download {key.Index}"); + } + } + catch (Exception ex) when (ex is InvalidOperationException or BadImageFormatException or IOException) + { + Trace.TraceError($"GetMetaDataLocator: {ex.Message}"); + } } else { - hr = HResult.E_FAIL; + Trace.TraceError($"GetMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} symbol store not enabled"); } - if (pMetadataSize != IntPtr.Zero) { Marshal.WriteInt32(pMetadataSize, dataSize); @@ -115,9 +144,9 @@ public static int GetICorDebugMetadataLocator( int actualSize = 0; Debug.Assert(pwszPathBuffer != IntPtr.Zero); - try + if (symbolService.IsSymbolStoreEnabled) { - if (symbolService.IsSymbolStoreEnabled) + try { SymbolStoreKey key = PEFileKeyGenerator.GetKey(imagePath, imageTimestamp, imageSize); string localFilePath = symbolService.DownloadFile(key.Index, key.FullPathName); @@ -143,19 +172,19 @@ public static int GetICorDebugMetadataLocator( hr = HResult.E_FAIL; } } - else + catch (Exception ex) when + (ex is UnauthorizedAccessException + or BadImageFormatException + or InvalidVirtualAddressException + or IOException) { - Trace.TraceError($"GetICorDebugMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} symbol store not enabled"); + Trace.TraceError($"GetICorDebugMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} ERROR {ex.Message}"); hr = HResult.E_FAIL; } } - catch (Exception ex) when - (ex is UnauthorizedAccessException or - BadImageFormatException or - InvalidVirtualAddressException or - IOException) + else { - Trace.TraceError($"GetICorDebugMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} ERROR {ex.Message}"); + Trace.TraceError($"GetICorDebugMetadataLocator: {imagePath} {imageTimestamp:X8} {imageSize:X8} symbol store not enabled"); hr = HResult.E_FAIL; } if (pPathBufferSize != IntPtr.Zero) @@ -164,5 +193,27 @@ InvalidVirtualAddressException or } return hr; } + + /// + /// Attempt to open a file stream. + /// + /// file path + /// stream or null if doesn't exist or error + public static Stream TryOpenFile(string path) + { + if (path is not null && File.Exists(path)) + { + try + { + return File.OpenRead(path); + } + catch (Exception ex) when (ex is UnauthorizedAccessException or NotSupportedException or IOException) + { + Trace.TraceError($"TryOpenFile: {ex.Message}"); + } + } + + return null; + } } } diff --git a/src/SOS/SOS.Hosting/TargetWrapper.cs b/src/SOS/SOS.Hosting/TargetWrapper.cs index 2313e2833e..9749975363 100644 --- a/src/SOS/SOS.Hosting/TargetWrapper.cs +++ b/src/SOS/SOS.Hosting/TargetWrapper.cs @@ -45,7 +45,6 @@ public TargetWrapper( builder.AddMethod(new GetOperatingSystemDelegate(GetOperatingSystem)); builder.AddMethod(new HostWrapper.GetServiceDelegate(ServiceWrapper.GetService)); - builder.AddMethod(new GetTempDirectoryDelegate(GetTempDirectory)); builder.AddMethod(new GetRuntimeDelegate(GetRuntime)); builder.AddMethod(new FlushDelegate(Flush)); @@ -84,12 +83,6 @@ private OperatingSystem GetOperatingSystem( return OperatingSystem.Unknown; } - private string GetTempDirectory( - IntPtr self) - { - return _target.GetTempDirectory(); - } - private int GetRuntime( IntPtr self, IntPtr* ppRuntime) @@ -133,11 +126,6 @@ private void Flush( private delegate OperatingSystem GetOperatingSystemDelegate( [In] IntPtr self); - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - [return: MarshalAs(UnmanagedType.LPStr)] - private delegate string GetTempDirectoryDelegate( - [In] IntPtr self); - [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate int GetRuntimeDelegate( [In] IntPtr self, diff --git a/src/SOS/Strike/platform/runtimeimpl.cpp b/src/SOS/Strike/platform/runtimeimpl.cpp index 81e1514e12..78fde28d71 100644 --- a/src/SOS/Strike/platform/runtimeimpl.cpp +++ b/src/SOS/Strike/platform/runtimeimpl.cpp @@ -277,38 +277,6 @@ LPCSTR Runtime::GetDacFilePath() if (access(dacModulePath.c_str(), F_OK) == 0) #endif { -#if defined(__linux__) - // We are creating a symlink to the DAC in a temp directory - // where libcoreclrtraceptprovider.so doesn't exist so it - // doesn't get loaded by the DAC causing a LTTng-UST exception. - // - // Issue #https://github.com/dotnet/coreclr/issues/20205 - LPCSTR tmpPath = m_target->GetTempDirectory(); - if (tmpPath != nullptr) - { - std::string dacSymLink(tmpPath); - dacSymLink.append(NETCORE_DAC_DLL_NAME_A); - - // Check if the DAC file already exists in the temp directory because - // of a "loadsymbols" command which downloads everything. - if (access(dacSymLink.c_str(), F_OK) == 0) - { - dacModulePath.assign(dacSymLink); - } - else - { - int error = symlink(dacModulePath.c_str(), dacSymLink.c_str()); - if (error == 0) - { - dacModulePath.assign(dacSymLink); - } - else - { - ExtErr("symlink(%s, %s) FAILED %s\n", dacModulePath.c_str(), dacSymLink.c_str(), strerror(errno)); - } - } - } -#endif m_dacFilePath = _strdup(dacModulePath.c_str()); } } diff --git a/src/SOS/inc/target.h b/src/SOS/inc/target.h index d1a3bea7c6..85fdcabad1 100644 --- a/src/SOS/inc/target.h +++ b/src/SOS/inc/target.h @@ -46,12 +46,6 @@ ITarget : public IUnknown /// S_OK or E_NOINTERFACE virtual HRESULT STDMETHODCALLTYPE GetService(REFIID serviceId, PVOID* service) = 0; - /// - /// Returns the unique temporary directory for this instance of SOS - /// - /// temporary directory string - virtual LPCSTR STDMETHODCALLTYPE GetTempDirectory() = 0; - /// /// Returns the current runtime instance /// diff --git a/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs b/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs index 1746a0f544..8e1560a085 100644 --- a/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs +++ b/src/tests/DbgShim.UnitTests/LibraryProviderWrapper.cs @@ -504,6 +504,8 @@ private ISymbolService SymbolService public int AddTarget(ITarget target) => throw new NotImplementedException(); + public string GetTempDirectory() => throw new NotImplementedException(); + #endregion #region ICLRDebuggingLibraryProvider* delegates diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs index fb7ba82b99..b7c2369b68 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/DebugServicesTests.cs @@ -71,9 +71,6 @@ public void TargetTests(TestHost host) // Check that the ITarget properties match the test data host.TestData.CompareMembers(host.TestData.Target, target); - - // Test temp directory - AssertX.DirectoryExists("Target temporary directory", target.GetTempDirectory(), Output); } [SkippableTheory, MemberData(nameof(GetConfigurations))] diff --git a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs index 4fe716b078..27d279f978 100644 --- a/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs +++ b/src/tests/Microsoft.Diagnostics.DebugServices.UnitTests/SymbolServiceTests.cs @@ -105,6 +105,8 @@ public void SymbolPathTests() public int AddTarget(ITarget target) => throw new NotImplementedException(); + public string GetTempDirectory() => throw new NotImplementedException(); + #endregion } From 7453831dac0c464bd106128a3128f7a4467723cc Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Sat, 28 Dec 2024 12:23:04 -0800 Subject: [PATCH 2/2] Allow REPL to start without dump path --- src/Tools/dotnet-dump/Analyzer.cs | 14 ++++++++------ src/Tools/dotnet-dump/Program.cs | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Tools/dotnet-dump/Analyzer.cs b/src/Tools/dotnet-dump/Analyzer.cs index ac1dadca56..340e090228 100644 --- a/src/Tools/dotnet-dump/Analyzer.cs +++ b/src/Tools/dotnet-dump/Analyzer.cs @@ -38,8 +38,6 @@ public Analyzer() public Task Analyze(FileInfo dump_path, string[] command) { - _fileLoggingConsoleService.WriteLine($"Loading core dump: {dump_path} ..."); - // Attempt to load the persisted command history string historyFileName = null; try @@ -97,13 +95,17 @@ or NotSupportedException try { - ITarget target = dumpTargetFactory.OpenDump(dump_path.FullName); - contextService.SetCurrentTarget(target); - // Automatically enable symbol server support, default cache and search for symbols in the dump directory symbolService.AddSymbolServer(retryCount: 3); symbolService.AddCachePath(symbolService.DefaultSymbolCache); - symbolService.AddDirectoryPath(Path.GetDirectoryName(dump_path.FullName)); + + if (dump_path is not null) + { + _fileLoggingConsoleService.WriteLine($"Loading core dump: {dump_path} ..."); + ITarget target = dumpTargetFactory.OpenDump(dump_path.FullName); + contextService.SetCurrentTarget(target); + symbolService.AddDirectoryPath(Path.GetDirectoryName(dump_path.FullName)); + } // Run the commands from the dotnet-dump command line. Any errors/exceptions from the // command execution will be displayed and dotnet-dump exited. diff --git a/src/Tools/dotnet-dump/Program.cs b/src/Tools/dotnet-dump/Program.cs index 76a6ab7779..e52d30f725 100644 --- a/src/Tools/dotnet-dump/Program.cs +++ b/src/Tools/dotnet-dump/Program.cs @@ -101,7 +101,8 @@ private static Argument DumpPath() => new Argument( name: "dump_path") { - Description = "Name of the dump file to analyze." + Description = "Name of the dump file to analyze.", + Arity = ArgumentArity.ZeroOrOne }.ExistingOnly(); private static Option RunCommand() =>