Add thread service (#1236)
authorMike McLaughlin <mikem@microsoft.com>
Wed, 17 Jun 2020 21:54:07 +0000 (14:54 -0700)
committerGitHub <noreply@github.com>
Wed, 17 Jun 2020 21:54:07 +0000 (14:54 -0700)
Renames the RegisterService to ThreadService and adds methods to implement these issues:

https://github.com/dotnet/diagnostics/issues/558 "setthread should support different types of thread ids"
https://github.com/dotnet/diagnostics/issues/559 "Sort threads by thread id"

15 files changed:
src/Microsoft.Diagnostics.DebugServices/AnalyzeContext.cs
src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/IThreadService.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/RegisterInfo.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/RegisterService.cs [deleted file]
src/Microsoft.Diagnostics.DebugServices/ThreadInfo.cs [new file with mode: 0644]
src/Microsoft.Diagnostics.DebugServices/ThreadService.cs [new file with mode: 0644]
src/SOS/SOS.Hosting/LLDBServices.cs
src/SOS/SOS.Hosting/SOSHost.cs
src/SOS/SOS.UnitTests/Scripts/OtherCommands.script
src/SOS/SOS.UnitTests/Scripts/WebApp.script
src/Tools/dotnet-dump/Analyzer.cs
src/Tools/dotnet-dump/Commands/RegistersCommand.cs
src/Tools/dotnet-dump/Commands/SOSCommand.cs
src/Tools/dotnet-dump/Commands/SetThreadCommand.cs

index 80f7b15bf267bc676276e08de864fce196e7036e..bc17b757749a6825f25c8d84406d435f578c21e7 100644 (file)
@@ -18,7 +18,7 @@ namespace Microsoft.Diagnostics.DebugServices
         /// <summary>
         /// Current OS thread Id
         /// </summary>
-        public int CurrentThreadId { get; set; }
+        public uint? CurrentThreadId { get; set; }
 
         /// <summary>
         /// Cancellation token for current command
diff --git a/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs b/src/Microsoft.Diagnostics.DebugServices/DiagnosticsException.cs
new file mode 100644 (file)
index 0000000..41f0173
--- /dev/null
@@ -0,0 +1,29 @@
+// 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
+{
+    /// <summary>
+    /// Diagnostics exception
+    /// </summary>
+    public class DiagnosticsException : Exception
+    {
+        public DiagnosticsException()
+            : base()
+        {
+        }
+
+        public DiagnosticsException(string message)
+            : base(message)
+        {
+        }
+
+        public DiagnosticsException(string message, Exception innerException)
+            : base(message, innerException)
+        {
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs b/src/Microsoft.Diagnostics.DebugServices/IThreadService.cs
new file mode 100644 (file)
index 0000000..40ab1ab
--- /dev/null
@@ -0,0 +1,92 @@
+// 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.Collections.Generic;
+using System;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// Provides thread and register info and values
+    /// </summary>
+    public interface IThreadService
+    {
+        /// <summary>
+        /// Details on all the supported registers
+        /// </summary>
+        IEnumerable<RegisterInfo> Registers { get; }
+
+        /// <summary>
+        /// The instruction pointer register index
+        /// </summary>
+        int InstructionPointerIndex { get; }
+
+        /// <summary>
+        /// The frame pointer register index
+        /// </summary>
+        int FramePointerIndex { get; }
+
+        /// <summary>
+        /// The stack pointer register index
+        /// </summary>
+        int StackPointerIndex { get; }
+
+        /// <summary>
+        /// Return the register index for the register name
+        /// </summary>
+        /// <param name="name">register name</param>
+        /// <param name="registerIndex">returns register index or -1</param>
+        /// <returns>true if name found</returns>
+        bool GetRegisterIndexByName(string name, out int registerIndex);
+
+        /// <summary>
+        /// Returns the register info (name, offset, size, etc).
+        /// </summary>
+        /// <param name="registerIndex">register index</param>
+        /// <param name="info">RegisterInfo</param>
+        /// <returns>true if index found</returns>
+        bool GetRegisterInfo(int registerIndex, out RegisterInfo info);
+
+        /// <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
+        /// clrmd data targets don't return any floating point or larger registers.
+        /// </summary>
+        /// <param name="threadId">thread id</param>
+        /// <param name="registerIndex">register index</param>
+        /// <param name="value">value returned</param>
+        /// <returns>true if value found</returns>
+        bool GetRegisterValue(uint threadId, int registerIndex, out ulong value);
+
+        /// <summary>
+        /// Returns the raw context buffer bytes for the specified thread.
+        /// </summary>
+        /// <param name="threadId">thread id</param>
+        /// <returns>register context</returns>
+        /// <exception cref="DiagnosticsException">invalid thread id</exception>
+        byte[] GetThreadContext(uint threadId);
+
+        /// <summary>
+        /// Enumerate all the native threads
+        /// </summary>
+        /// <returns>ThreadInfos for all the threads</returns>
+        IEnumerable<ThreadInfo> EnumerateThreads();
+
+        /// <summary>
+        /// Get the thread info from the thread index
+        /// </summary>
+        /// <param name="threadIndex">index</param>
+        /// <returns>thread info</returns>
+        /// <exception cref="DiagnosticsException">invalid thread index</exception>
+        ThreadInfo GetThreadInfoFromIndex(int threadIndex);
+
+        /// <summary>
+        /// Get the thread info from the OS thread id
+        /// </summary>
+        /// <param name="threadId">os id</param>
+        /// <returns>thread info</returns>
+        /// <exception cref="DiagnosticsException">invalid thread id</exception>
+        ThreadInfo GetThreadInfoFromId(uint threadId);
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/RegisterInfo.cs b/src/Microsoft.Diagnostics.DebugServices/RegisterInfo.cs
new file mode 100644 (file)
index 0000000..a97b92c
--- /dev/null
@@ -0,0 +1,25 @@
+// 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
+{
+       /// <summary>
+       /// Details about a register
+       /// </summary>
+    public struct RegisterInfo
+    {
+        public readonly int RegisterIndex;
+        public readonly int RegisterOffset;
+        public readonly int RegisterSize;
+        public readonly string RegisterName;
+
+        public RegisterInfo(int registerIndex, int registerOffset, int registerSize, string registerName)
+        {
+            RegisterIndex = registerIndex;
+            RegisterOffset = registerOffset;
+            RegisterSize = registerSize;
+            RegisterName = registerName;
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/RegisterService.cs b/src/Microsoft.Diagnostics.DebugServices/RegisterService.cs
deleted file mode 100644 (file)
index a7fea16..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.Diagnostics.Runtime;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using Architecture = Microsoft.Diagnostics.Runtime.Architecture;
-
-namespace Microsoft.Diagnostics.DebugServices
-{
-    /// <summary>
-    /// Provides register info and values
-    /// </summary>
-    public class RegisterService
-    {
-        public struct RegisterInfo
-        {
-            public readonly int RegisterIndex;
-            public readonly int RegisterOffset;
-            public readonly int RegisterSize;
-            public readonly string RegisterName;
-
-            internal RegisterInfo(int registerIndex, int registerOffset, int registerSize, string registerName)
-            {
-                RegisterIndex = registerIndex;
-                RegisterOffset = registerOffset;
-                RegisterSize = registerSize;
-                RegisterName = registerName;
-            }
-        }
-
-        private readonly DataTarget _target;
-        private readonly int _contextSize;
-        private readonly uint _contextFlags;
-        private readonly Dictionary<string, RegisterInfo> _lookupByName;
-        private readonly Dictionary<int, RegisterInfo> _lookupByIndex;
-        private readonly Dictionary<uint, byte[]> _threadContextCache = new Dictionary<uint, byte[]>();
-
-        public IEnumerable<RegisterInfo> Registers { get; }
-
-        public int InstructionPointerIndex { get; }
-
-        public int FramePointerIndex { get; }
-
-        public int StackPointerIndex { get; }
-
-        public RegisterService(DataTarget target)
-        {
-            _target = target;
-
-            Type contextType;
-            switch (target.Architecture)
-            {
-                case Architecture.Amd64:
-                    // Dumps generated with newer dbgeng have bigger context buffers and clrmd requires the context size to at least be that size.
-                    _contextSize = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 0x700 : AMD64Context.Size;
-                    _contextFlags = AMD64Context.ContextControl | AMD64Context.ContextInteger | AMD64Context.ContextSegments;
-                    contextType = typeof(AMD64Context);
-                    break;
-
-                case Architecture.X86:
-                    _contextSize = X86Context.Size;
-                    _contextFlags = X86Context.ContextControl | X86Context.ContextInteger | X86Context.ContextSegments;
-                    contextType = typeof(X86Context);
-                    break;
-
-                case Architecture.Arm64:
-                    _contextSize = Arm64Context.Size;
-                    _contextFlags = Arm64Context.ContextControl | Arm64Context.ContextInteger;
-                    contextType = typeof(Arm64Context);
-                    break;
-
-                case Architecture.Arm:
-                    _contextSize = ArmContext.Size;
-                    _contextFlags = ArmContext.ContextControl | ArmContext.ContextInteger;
-                    contextType = typeof(ArmContext);
-                    break;
-
-                default:
-                    throw new PlatformNotSupportedException($"Unsupported architecture: {target.Architecture}");
-            }
-
-            var registers = new List<RegisterInfo>();
-            int index = 0;
-
-            FieldInfo[] fields = contextType.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic);
-            foreach (FieldInfo field in fields) {
-                RegisterAttribute registerAttribute = field.GetCustomAttributes<RegisterAttribute>(inherit: false).SingleOrDefault();
-                if (registerAttribute == null) {
-                    continue;
-                }
-                RegisterType registerType = registerAttribute.RegisterType & RegisterType.TypeMask;
-                switch (registerType)
-                {
-                    case RegisterType.Control:
-                    case RegisterType.General:
-                    case RegisterType.Segments:
-                        break;
-                    default:
-                        continue;
-                }
-                if ((registerAttribute.RegisterType & RegisterType.ProgramCounter) != 0) {
-                    InstructionPointerIndex = index;
-                }
-                if ((registerAttribute.RegisterType & RegisterType.StackPointer) != 0) {
-                    StackPointerIndex = index;
-                }
-                if ((registerAttribute.RegisterType & RegisterType.FramePointer) != 0) {
-                    FramePointerIndex = index;
-                }
-                FieldOffsetAttribute offsetAttribute = field.GetCustomAttributes<FieldOffsetAttribute>(inherit: false).Single();
-                var registerInfo = new RegisterInfo(index, offsetAttribute.Value, Marshal.SizeOf(field.FieldType), registerAttribute.Name ?? field.Name.ToLower());
-                registers.Add(registerInfo);
-                index++;
-            }
-
-            _lookupByName = registers.ToDictionary((info) => info.RegisterName);
-            _lookupByIndex = registers.ToDictionary((info) => info.RegisterIndex);
-
-            Registers = registers;
-        }
-
-        /// <summary>
-        /// Return the register index for the register name
-        /// </summary>
-        /// <param name="name">register name</param>
-        /// <param name="index">returns register index or -1</param>
-        /// <returns>true if name found</returns>
-        public bool GetRegisterIndexByName(string name, out int index)
-        {
-            if (_lookupByName.TryGetValue(name, out RegisterInfo info))
-            {
-                index = info.RegisterIndex;
-                return true;
-            }
-            index = int.MaxValue;
-            return false;
-        }
-
-        /// <summary>
-        /// Returns the register info (name, offset, size, etc).
-        /// </summary>
-        /// <param name="index">register index</param>
-        /// <param name="info">RegisterInfo</param>
-        /// <returns>true if index found</returns>
-        public bool GetRegisterInfo(int index, out RegisterInfo info)
-        {
-            return _lookupByIndex.TryGetValue(index, out info);
-        }
-
-        /// <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
-        /// clrmd data targets don't return any floating point or larger registers.
-        /// </summary>
-        /// <param name="threadId">thread id</param>
-        /// <param name="index">register index</param>
-        /// <param name="value">value returned</param>
-        /// <returns>true if value found</returns>
-        public bool GetRegisterValue(uint threadId, int index, out ulong value)
-        {
-            value = 0;
-
-            if (_lookupByIndex.TryGetValue(index, out RegisterInfo info))
-            {
-                byte[] threadContext = GetThreadContext(threadId);
-                if (threadContext != null)
-                {
-                    unsafe
-                    {
-                        fixed (byte* ptr = threadContext)
-                        {
-                            switch (info.RegisterSize)
-                            {
-                                case 1:
-                                    value = *((byte*)(ptr + info.RegisterOffset));
-                                    return true;
-                                case 2:
-                                    value = *((ushort*)(ptr + info.RegisterOffset));
-                                    return true;
-                                case 4:
-                                    value = *((uint*)(ptr + info.RegisterOffset));
-                                    return true;
-                                case 8:
-                                    value = *((ulong*)(ptr + info.RegisterOffset));
-                                    return true;
-                            }
-                        }
-                    }
-                }
-            }
-            return false;
-        }
-
-        /// <summary>
-        /// Returns the raw context buffer bytes for the specified thread.
-        /// </summary>
-        /// <param name="threadId">thread id</param>
-        /// <returns>register context or null if error</returns>
-        public byte[] GetThreadContext(uint threadId)
-        {
-            if (_threadContextCache.TryGetValue(threadId, out byte[] threadContext))
-            {
-                return threadContext;
-            }
-            else
-            {
-                unsafe
-                {
-                    threadContext = new byte[_contextSize];
-                    fixed (byte* ptr = threadContext)
-                    {
-                        try
-                        {
-                            if (_target.DataReader.GetThreadContext(threadId, _contextFlags, (uint)_contextSize, new IntPtr(ptr)))
-                            {
-                                _threadContextCache.Add(threadId, threadContext);
-                                return threadContext;
-                            }
-                        }
-                        catch (ClrDiagnosticsException ex)
-                        {
-                            Trace.TraceError(ex.ToString());
-                        }
-                    }
-                }
-            }
-            return null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostics.DebugServices/ThreadInfo.cs b/src/Microsoft.Diagnostics.DebugServices/ThreadInfo.cs
new file mode 100644 (file)
index 0000000..40692f4
--- /dev/null
@@ -0,0 +1,23 @@
+// 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
+{
+    /// <summary>
+    /// Details about a native thread
+    /// </summary>
+    public struct ThreadInfo
+    {
+        public readonly int ThreadIndex;
+        public readonly uint ThreadId;
+        public readonly ulong ThreadTeb;
+
+        public ThreadInfo(int index, uint id, ulong teb)
+        {
+            ThreadIndex = index;
+            ThreadId = id;
+            ThreadTeb = teb;
+        }
+    }
+}
diff --git a/src/Microsoft.Diagnostics.DebugServices/ThreadService.cs b/src/Microsoft.Diagnostics.DebugServices/ThreadService.cs
new file mode 100644 (file)
index 0000000..0978e82
--- /dev/null
@@ -0,0 +1,311 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Runtime;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using Architecture = Microsoft.Diagnostics.Runtime.Architecture;
+
+namespace Microsoft.Diagnostics.DebugServices
+{
+    /// <summary>
+    /// Provides thread and register info and values for the clrmd IDataReader
+    /// </summary>
+    public class ThreadService : IThreadService
+    {
+        private readonly IDataReader _dataReader;
+        private readonly int _contextSize;
+        private readonly uint _contextFlags;
+        private readonly int _instructionPointerIndex;
+        private readonly int _framePointerIndex;
+        private readonly int _stackPointerIndex;
+        private readonly Dictionary<string, RegisterInfo> _lookupByName;
+        private readonly Dictionary<int, RegisterInfo> _lookupByIndex;
+        private readonly IEnumerable<RegisterInfo> _registers;
+        private readonly Dictionary<uint, byte[]> _threadContextCache = new Dictionary<uint, byte[]>();
+        private IEnumerable<ThreadInfo> _threadInfos;
+
+        public ThreadService(IDataReader dataReader)
+        {
+            _dataReader = dataReader;
+
+            Type contextType;
+            switch (dataReader.GetArchitecture())
+            {
+                case Architecture.Amd64:
+                    // Dumps generated with newer dbgeng have bigger context buffers and clrmd requires the context size to at least be that size.
+                    _contextSize = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 0x700 : AMD64Context.Size;
+                    _contextFlags = AMD64Context.ContextControl | AMD64Context.ContextInteger | AMD64Context.ContextSegments;
+                    contextType = typeof(AMD64Context);
+                    break;
+
+                case Architecture.X86:
+                    _contextSize = X86Context.Size;
+                    _contextFlags = X86Context.ContextControl | X86Context.ContextInteger | X86Context.ContextSegments;
+                    contextType = typeof(X86Context);
+                    break;
+
+                case Architecture.Arm64:
+                    _contextSize = Arm64Context.Size;
+                    _contextFlags = Arm64Context.ContextControl | Arm64Context.ContextInteger;
+                    contextType = typeof(Arm64Context);
+                    break;
+
+                case Architecture.Arm:
+                    _contextSize = ArmContext.Size;
+                    _contextFlags = ArmContext.ContextControl | ArmContext.ContextInteger;
+                    contextType = typeof(ArmContext);
+                    break;
+
+                default:
+                    throw new PlatformNotSupportedException($"Unsupported architecture: {dataReader.GetArchitecture()}");
+            }
+
+            var registers = new List<RegisterInfo>();
+            int index = 0;
+
+            FieldInfo[] fields = contextType.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic);
+            foreach (FieldInfo field in fields) {
+                RegisterAttribute registerAttribute = field.GetCustomAttributes<RegisterAttribute>(inherit: false).SingleOrDefault();
+                if (registerAttribute == null) {
+                    continue;
+                }
+                RegisterType registerType = registerAttribute.RegisterType & RegisterType.TypeMask;
+                switch (registerType)
+                {
+                    case RegisterType.Control:
+                    case RegisterType.General:
+                    case RegisterType.Segments:
+                        break;
+                    default:
+                        continue;
+                }
+                if ((registerAttribute.RegisterType & RegisterType.ProgramCounter) != 0) {
+                    _instructionPointerIndex = index;
+                }
+                if ((registerAttribute.RegisterType & RegisterType.StackPointer) != 0) {
+                    _stackPointerIndex = index;
+                }
+                if ((registerAttribute.RegisterType & RegisterType.FramePointer) != 0) {
+                    _framePointerIndex = index;
+                }
+                FieldOffsetAttribute offsetAttribute = field.GetCustomAttributes<FieldOffsetAttribute>(inherit: false).Single();
+                var registerInfo = new RegisterInfo(index, offsetAttribute.Value, Marshal.SizeOf(field.FieldType), registerAttribute.Name ?? field.Name.ToLower());
+                registers.Add(registerInfo);
+                index++;
+            }
+
+            _lookupByName = registers.ToDictionary((info) => info.RegisterName);
+            _lookupByIndex = registers.ToDictionary((info) => info.RegisterIndex);
+            _registers = registers;
+        }
+
+        /// <summary>
+        /// Flush the register service
+        /// </summary>
+        public void Flush()
+        {
+            _threadContextCache.Clear();
+        }
+
+        /// <summary>
+        /// Details on all the supported registers
+        /// </summary>
+        IEnumerable<RegisterInfo> IThreadService.Registers { get { return _registers; } }
+
+        /// <summary>
+        /// The instruction pointer register index
+        /// </summary>
+        int IThreadService.InstructionPointerIndex { get { return _instructionPointerIndex; } }
+
+        /// <summary>
+        /// The frame pointer register index
+        /// </summary>
+        int IThreadService.FramePointerIndex { get { return _framePointerIndex; } }
+
+        /// <summary>
+        /// The stack pointer register index
+        /// </summary>
+        int IThreadService.StackPointerIndex { get { return _stackPointerIndex; } }
+
+        /// <summary>
+        /// Return the register index for the register name
+        /// </summary>
+        /// <param name="name">register name</param>
+        /// <param name="index">returns register index or -1</param>
+        /// <returns>true if name found</returns>
+        bool IThreadService.GetRegisterIndexByName(string name, out int index)
+        {
+            if (_lookupByName.TryGetValue(name, out RegisterInfo info))
+            {
+                index = info.RegisterIndex;
+                return true;
+            }
+            index = int.MaxValue;
+            return false;
+        }
+
+        /// <summary>
+        /// Returns the register info (name, offset, size, etc).
+        /// </summary>
+        /// <param name="index">register index</param>
+        /// <param name="info">RegisterInfo</param>
+        /// <returns>true if index found</returns>
+        bool IThreadService.GetRegisterInfo(int index, out RegisterInfo info)
+        {
+            return _lookupByIndex.TryGetValue(index, out info);
+        }
+
+        /// <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
+        /// clrmd data targets don't return any floating point or larger registers.
+        /// </summary>
+        /// <param name="threadId">thread id</param>
+        /// <param name="index">register index</param>
+        /// <param name="value">value returned</param>
+        /// <returns>true if value found</returns>
+        bool IThreadService.GetRegisterValue(uint threadId, int index, out ulong value)
+        {
+            value = 0;
+
+            if (_lookupByIndex.TryGetValue(index, out RegisterInfo info))
+            {
+                try 
+                { 
+                    byte[] threadContext = ((IThreadService)this).GetThreadContext(threadId);
+                    unsafe
+                    {
+                        fixed (byte* ptr = threadContext)
+                        {
+                            switch (info.RegisterSize)
+                            {
+                                case 1:
+                                    value = *((byte*)(ptr + info.RegisterOffset));
+                                    return true;
+                                case 2:
+                                    value = *((ushort*)(ptr + info.RegisterOffset));
+                                    return true;
+                                case 4:
+                                    value = *((uint*)(ptr + info.RegisterOffset));
+                                    return true;
+                                case 8:
+                                    value = *((ulong*)(ptr + info.RegisterOffset));
+                                    return true;
+                            }
+                        }
+                    }
+                }
+                catch (DiagnosticsException)
+                {
+                }
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns the raw context buffer bytes for the specified thread.
+        /// </summary>
+        /// <param name="threadId">thread id</param>
+        /// <returns>register context</returns>
+        /// <exception cref="DiagnosticsException">invalid thread id</exception>
+        byte[] IThreadService.GetThreadContext(uint threadId)
+        {
+            if (_threadContextCache.TryGetValue(threadId, out byte[] threadContext))
+            {
+                return threadContext;
+            }
+            else
+            {
+                unsafe
+                {
+                    threadContext = new byte[_contextSize];
+                    fixed (byte* ptr = threadContext)
+                    {
+                        try
+                        {
+                            if (_dataReader.GetThreadContext(threadId, _contextFlags, (uint)_contextSize, new IntPtr(ptr)))
+                            {
+                                _threadContextCache.Add(threadId, threadContext);
+                                return threadContext;
+                            }
+                        }
+                        catch (ClrDiagnosticsException ex)
+                        {
+                            throw new DiagnosticsException(ex.Message, ex);
+                        }
+                    }
+                }
+            }
+            throw new DiagnosticsException();
+        }
+
+        /// <summary>
+        /// Enumerate all the native threads
+        /// </summary>
+        /// <returns>ThreadInfos for all the threads</returns>
+        IEnumerable<ThreadInfo> IThreadService.EnumerateThreads()
+        {
+            if (_threadInfos == null)
+            {
+                _threadInfos = _dataReader.EnumerateAllThreads()
+                    .OrderBy((uint threadId) => threadId)
+                    .Select((uint threadId, int threadIndex) => {
+                        ulong teb = 0;
+                        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                        {
+                            try
+                            {
+                                teb = _dataReader.GetThreadTeb(threadId);
+                            }
+                            catch (NotImplementedException)
+                            {
+                            }
+                        }
+                        return new ThreadInfo(threadIndex, threadId, teb); 
+                    });
+            }
+            return _threadInfos;
+        }
+
+        /// <summary>
+        /// Get the thread info from the thread index
+        /// </summary>
+        /// <param name="threadIndex">index</param>
+        /// <returns>thread info</returns>
+        /// <exception cref="DiagnosticsException">invalid thread index</exception>
+        ThreadInfo IThreadService.GetThreadInfoFromIndex(int threadIndex)
+        {
+            try
+            {
+                return ((IThreadService)this).EnumerateThreads().ElementAt(threadIndex);
+            }
+            catch (ArgumentOutOfRangeException ex)
+            {
+                throw new DiagnosticsException($"Invalid thread index: {threadIndex}", ex);
+            }
+        }
+
+        /// <summary>
+        /// Get the thread info from the OS thread id
+        /// </summary>
+        /// <param name="threadId">os id</param>
+        /// <returns>thread info</returns>
+        /// <exception cref="DiagnosticsException">invalid thread id</exception>
+        ThreadInfo IThreadService.GetThreadInfoFromId(uint threadId)
+        {
+            try
+            {
+                return ((IThreadService)this).EnumerateThreads().First((ThreadInfo info) => info.ThreadId == threadId);
+            }
+            catch (InvalidOperationException ex)
+            {
+                throw new DiagnosticsException($"Invalid thread id: {threadId}", ex);
+            }
+        }
+    }
+}
index bce58d3c1f4772bf1019b79f0050fbcdacc244cc..9199cf947b78277f716168d05560833f28ea4848 100644 (file)
@@ -68,7 +68,7 @@ namespace SOS
             builder.AddMethod(new SetCurrentThreadIdDelegate(soshost.SetCurrentThreadId));
             builder.AddMethod(new GetCurrentThreadSystemIdDelegate(soshost.GetCurrentThreadSystemId));
             builder.AddMethod(new GetThreadIdBySystemIdDelegate(soshost.GetThreadIdBySystemId));
-            builder.AddMethod(new GetThreadContextByIdDelegate(GetThreadContextById));
+            builder.AddMethod(new GetThreadContextByIdDelegate(soshost.GetThreadContextById));
 
             builder.AddMethod(new GetValueByNameDelegate(GetValueByName));
             builder.AddMethod(new GetInstructionOffsetDelegate(soshost.GetInstructionOffset));
@@ -143,19 +143,6 @@ namespace SOS
             return S_OK;
         }
 
-        int GetThreadContextById(
-            IntPtr self,
-            uint threadId,
-            uint contextFlags,
-            uint contextSize,
-            IntPtr context)
-        {
-            if (_soshost.DataReader.GetThreadContext(threadId, contextFlags, contextSize, context)) {
-                return S_OK;
-            }
-            return E_FAIL;
-        }
-
         int GetValueByName(
             IntPtr self,
             string name,
index 2c1783eea701e07ba6f6a9679d3506370bb6bc54..44ac24826a01ae73f9aace3910d93879afc358ca 100644 (file)
@@ -170,7 +170,7 @@ namespace SOS
         internal readonly IDataReader DataReader;
         internal readonly AnalyzeContext AnalyzeContext;
 
-        private readonly RegisterService _registerService;
+        private readonly IThreadService _threadService;
         private readonly MemoryService _memoryService;
         private readonly IConsoleService _console;
         private readonly COMCallableIUnknown _ccw;  
@@ -203,7 +203,7 @@ namespace SOS
             _console = serviceProvider.GetService<IConsoleService>();
             AnalyzeContext = serviceProvider.GetService<AnalyzeContext>();
             _memoryService = serviceProvider.GetService<MemoryService>();
-            _registerService = serviceProvider.GetService<RegisterService>();
+            _threadService = serviceProvider.GetService<IThreadService>();
             _versionCache = new ReadVirtualCache(_memoryService);
 
             string rid = InstallHelper.GetRid();
@@ -891,9 +891,27 @@ namespace SOS
             IntPtr context,
             uint contextSize)
         {
-            uint threadId = (uint)AnalyzeContext.CurrentThreadId;
-            byte[] registerContext = _registerService.GetThreadContext(threadId);
-            if (registerContext == null) {
+            if (!AnalyzeContext.CurrentThreadId.HasValue)
+            {
+                return E_FAIL;
+            }
+            return GetThreadContextById(self, AnalyzeContext.CurrentThreadId.Value, 0, contextSize, context);
+        }
+
+        internal int GetThreadContextById(
+            IntPtr self,
+            uint threadId,
+            uint contextFlags,
+            uint contextSize,
+            IntPtr context)
+        {
+            byte[] registerContext;
+            try
+            {
+                registerContext = _threadService.GetThreadContext(threadId);
+            }
+            catch (DiagnosticsException)
+            {
                 return E_FAIL;
             }
             try
@@ -919,7 +937,7 @@ namespace SOS
             IntPtr self,
             out uint number)
         {
-            number = (uint)DataReader.EnumerateAllThreads().Count();
+            number = (uint)_threadService.EnumerateThreads().Count();
             return DebugClient.S_OK;
         }
 
@@ -928,7 +946,7 @@ namespace SOS
             out uint total,
             out uint largestProcess)
         {
-            total = (uint)DataReader.EnumerateAllThreads().Count();
+            total = (uint)_threadService.EnumerateThreads().Count();
             largestProcess = total;
             return DebugClient.S_OK;
         }
@@ -948,7 +966,12 @@ namespace SOS
             IntPtr self,
             out uint id)
         {
-            return GetThreadIdBySystemId(self, (uint)AnalyzeContext.CurrentThreadId, out id);
+            if (!AnalyzeContext.CurrentThreadId.HasValue)
+            {
+                id = 0;
+                return E_FAIL;
+            }
+            return GetThreadIdBySystemId(self, AnalyzeContext.CurrentThreadId.Value, out id);
         }
 
         internal int SetCurrentThreadId(
@@ -957,11 +980,10 @@ namespace SOS
         {
             try
             {
-                unchecked {
-                    AnalyzeContext.CurrentThreadId = (int)DataReader.EnumerateAllThreads().ElementAt((int)id);
-                }
+                ThreadInfo threadInfo = _threadService.GetThreadInfoFromIndex(unchecked((int)id));
+                AnalyzeContext.CurrentThreadId = threadInfo.ThreadId;
             }
-            catch (ArgumentOutOfRangeException)
+            catch (InvalidOperationException)
             {
                 return E_FAIL;
             }
@@ -972,7 +994,12 @@ namespace SOS
             IntPtr self,
             out uint sysId)
         {
-            sysId = (uint)AnalyzeContext.CurrentThreadId;
+            if (!AnalyzeContext.CurrentThreadId.HasValue)
+            {
+                sysId = 0;
+                return E_FAIL;
+            }
+            sysId = AnalyzeContext.CurrentThreadId.Value;
             return S_OK;
         }
 
@@ -983,11 +1010,11 @@ namespace SOS
             uint* ids,
             uint* sysIds)
         {
-            uint id = 0;
             int index = 0;
-            foreach (uint s in DataReader.EnumerateAllThreads())
+            foreach (ThreadInfo threadInfo in _threadService.EnumerateThreads())
             {
-                if (id >= count) {
+                uint id = (uint)threadInfo.ThreadIndex;
+                if (index >= count) {
                     break;
                 }
                 if (id >= start)
@@ -996,11 +1023,10 @@ namespace SOS
                         ids[index] = id;
                     }
                     if (sysIds != null) {
-                        sysIds[index] = s;
+                        sysIds[index] = threadInfo.ThreadId;
                     }
                     index++;
                 }
-                id++;
             }
             return DebugClient.S_OK;
         }
@@ -1010,17 +1036,19 @@ namespace SOS
             uint sysId,
             out uint id)
         {
-            id = 0;
             if (sysId != 0)
             {
-                foreach (uint s in DataReader.EnumerateAllThreads())
+                try
+                {
+                    ThreadInfo threadInfo = _threadService.GetThreadInfoFromId(sysId);
+                    id = (uint)threadInfo.ThreadIndex;
+                    return S_OK;
+                }
+                catch (InvalidOperationException)
                 {
-                    if (s == sysId) {
-                        return S_OK;
-                    }
-                    id++;
                 }
             }
+            id = 0;
             return E_FAIL;
         }
 
@@ -1028,31 +1056,42 @@ namespace SOS
             IntPtr self,
             ulong* offset)
         {
-            uint threadId = (uint)AnalyzeContext.CurrentThreadId;
-            ulong teb = DataReader.GetThreadTeb(threadId);
-            Write(offset, teb);
-            return S_OK;
+            if (!AnalyzeContext.CurrentThreadId.HasValue)
+            {
+                return E_FAIL;
+            }
+            uint threadId = AnalyzeContext.CurrentThreadId.Value;
+            try
+            {
+                ulong teb = _threadService.GetThreadInfoFromId(threadId).ThreadTeb;
+                Write(offset, teb);
+                return S_OK;
+            }
+            catch (InvalidOperationException)
+            {
+                return E_FAIL;
+            }
         }
 
         internal int GetInstructionOffset(
             IntPtr self,
             out ulong offset)
         {
-            return GetRegister(_registerService.InstructionPointerIndex, out offset);
+            return GetRegister(_threadService.InstructionPointerIndex, out offset);
         }
 
         internal int GetStackOffset(
             IntPtr self,
             out ulong offset)
         {
-            return GetRegister(_registerService.StackPointerIndex, out offset);
+            return GetRegister(_threadService.StackPointerIndex, out offset);
         }
 
         internal int GetFrameOffset(
             IntPtr self,
             out ulong offset)
         {
-            return GetRegister(_registerService.FramePointerIndex, out offset);
+            return GetRegister(_threadService.FramePointerIndex, out offset);
         }
 
         internal int GetIndexByName(
@@ -1060,7 +1099,7 @@ namespace SOS
             string name,
             out uint index)
         {
-            if (_registerService.GetRegisterIndexByName(name, out int value)) {
+            if (_threadService.GetRegisterIndexByName(name, out int value)) {
                 index = 0;
                 return E_INVALIDARG;
             }
@@ -1106,7 +1145,7 @@ namespace SOS
             out ulong value)
         {
             value = 0;
-            if (!_registerService.GetRegisterIndexByName(register, out int index)) {
+            if (!_threadService.GetRegisterIndexByName(register, out int index)) {
                 return E_INVALIDARG;
             }
             return GetRegister(index, out value);
@@ -1116,8 +1155,13 @@ namespace SOS
             int index, 
             out ulong value)
         {
-            uint threadId = (uint)AnalyzeContext.CurrentThreadId;
-            if (!_registerService.GetRegisterValue(threadId, index, out value)) {
+            if (!AnalyzeContext.CurrentThreadId.HasValue)
+            {
+                value = 0;
+                return E_FAIL;
+            }
+            uint threadId = AnalyzeContext.CurrentThreadId.Value;
+            if (!_threadService.GetRegisterValue(threadId, index, out value)) {
                 return E_FAIL;
             }
             return S_OK;
index f3f5a29e6e0d6b4f0b092f9bf81ed19c6c8efa91..f133fa0d933e809101e0ea1dffe7d066fdb04ed3 100644 (file)
@@ -70,7 +70,7 @@ VERIFY:\s*<HEXVAL>\s+<HEXVAL>.*
 COMMAND:threads
 VERIFY:\s*<DECVAL>\s+0x<HEXVAL>\s+\(<DECVAL>\)\s+
 COMMAND:registers
-VERIFY:\s*([r|e]ip|pc) = <HEXVAL>\s+
+VERIFY:\s*([r|e]ip|pc) = 0x<HEXVAL>\s+
 ENDIF:DOTNETDUMP
 
 # Issue: https://github.com/dotnet/diagnostics/issues/503
index 3f48029970910f59621ecffa340dcd3c9f33b0bb..5ed1ea937fe784213cd4c2a551bd19cbd322d876 100644 (file)
@@ -22,7 +22,7 @@ VERIFY:\s*<HEXVAL>\s+<HEXVAL>.*
 COMMAND:threads
 VERIFY:\s*<DECVAL>\s+0x<HEXVAL>\s+\(<DECVAL>\)\s+
 COMMAND:registers
-VERIFY:\s*([r|e]ip|pc) = <HEXVAL>\s+
+VERIFY:\s*([r|e]ip|pc) = 0x<HEXVAL>\s+
 ENDIF:DOTNETDUMP
 
 # Verify that ClrStack with no options works
index cc586a8575e6d4d05c647cc1947f23f7e39eb8c5..9c3c11dafe25c67d5bdf1b8a3cc9f85dd7f9fbbf 100644 (file)
@@ -143,13 +143,13 @@ namespace Microsoft.Diagnostics.Tools.Dump
 
             // Create common analyze context for commands
             var analyzeContext = new AnalyzeContext() {
-                CurrentThreadId = unchecked((int)target.DataReader.EnumerateAllThreads().FirstOrDefault())
+                CurrentThreadId = target.DataReader.EnumerateAllThreads().FirstOrDefault()
             };
             _serviceProvider.AddService(analyzeContext);
 
-            // Add the register, memory, SOSHost and ClrRuntime services
-            var registerService = new RegisterService(target);
-            _serviceProvider.AddService(registerService);
+            // Add the thread, memory, SOSHost and ClrRuntime services
+            var threadService = new ThreadService(target.DataReader);
+            _serviceProvider.AddService<IThreadService>(threadService);
 
             var memoryService = new MemoryService(target.DataReader);
             _serviceProvider.AddService(memoryService);
index 441613ba479f34a7c98a4c421b9d6ec02fda2aef..fdcd2ecd7945be03a4be0bc756bcc7caaccb3a57 100644 (file)
@@ -4,60 +4,48 @@
 
 using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.Repl;
-using Microsoft.Diagnostics.Runtime;
-using System;
-using System.Collections.Generic;
-using System.CommandLine;
-using System.Linq;
 
 namespace Microsoft.Diagnostics.Tools.Dump
 {
     [Command(Name = "registers", Help = "Displays the thread's registers.")]
+    [CommandAlias(Name = "r")]
     public class RegistersCommand : CommandBase
     {
         [Argument(Help = "The thread index to display, otherwise use the current thread.")]
         public int? ThreadIndex { get; set; } = null;
 
-        public DataTarget DataTarget { get; set; }
-
         public AnalyzeContext AnalyzeContext { get; set; }
 
-        public RegisterService RegisterService { get; set; }
+        public IThreadService ThreadService { get; set; }
 
         public override void Invoke()
         {
-            IEnumerable<uint> threads = DataTarget.DataReader.EnumerateAllThreads();
             uint threadId;
-
             if (ThreadIndex.HasValue)
             {
-                if (ThreadIndex.Value >= threads.Count()) {
-                    throw new InvalidOperationException($"Invalid thread index {ThreadIndex.Value}");
-                }
-                threadId = threads.ElementAt(ThreadIndex.Value);
+                threadId = ThreadService.GetThreadInfoFromIndex(ThreadIndex.Value).ThreadId;
             }
             else
             {
-                threadId = (uint)AnalyzeContext.CurrentThreadId;
+                threadId = AnalyzeContext.CurrentThreadId.Value;
             }
-
-            foreach (RegisterService.RegisterInfo register in RegisterService.Registers)
+            foreach (RegisterInfo register in ThreadService.Registers)
             {
-                if (RegisterService.GetRegisterValue(threadId, register.RegisterIndex, out ulong value))
+                if (ThreadService.GetRegisterValue(threadId, register.RegisterIndex, out ulong value))
                 {
                     switch (register.RegisterSize)
                     {
                         case 1:
-                            WriteLine("{0} = {1:X1}", register.RegisterName, value);
+                            WriteLine("{0} = 0x{1:X1}", register.RegisterName, value);
                             break;
                         case 2:
-                            WriteLine("{0} = {1:X4}", register.RegisterName, value);
+                            WriteLine("{0} = 0x{1:X4}", register.RegisterName, value);
                             break;
                         case 4:
-                            WriteLine("{0} = {1:X8}", register.RegisterName, value);
+                            WriteLine("{0} = 0x{1:X8}", register.RegisterName, value);
                             break;
                         case 8:
-                            WriteLine("{0} = {1:X16}", register.RegisterName, value);
+                            WriteLine("{0} = 0x{1:X16}", register.RegisterName, value);
                             break;
                     }
                 }
index 97eba4fbc6b01d07bf76d9a3d49d600310ebb5e7..b088aeca1dd658472d5b689df97e4bd94b0a34bf 100644 (file)
@@ -2,11 +2,9 @@
 // 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.Repl;
 using SOS;
 using System;
-using System.CommandLine;
 using System.IO;
 using System.Linq;
 
@@ -28,6 +26,7 @@ namespace Microsoft.Diagnostics.Tools.Dump
     [Command(Name = "dumpmodule",       AliasExpansion = "DumpModule",          Help = "Displays information about a EE module structure at the specified address.")]
     [Command(Name = "dumpmt",           AliasExpansion = "DumpMT",              Help = "Displays information about a method table at the specified address.")]
     [Command(Name = "dumpobj",          AliasExpansion = "DumpObj",             Help = "Displays info about an object at the specified address.")]
+    [CommandAlias(Name = "do")]
     [Command(Name = "dumpvc",           AliasExpansion = "DumpVC",              Help = "Displays info about the fields of a value class.")]
     [Command(Name = "dumpstackobjects", AliasExpansion = "DumpStackObjects",    Help = "Displays all managed objects found within the bounds of the current stack.")]
     [CommandAlias(Name = "dso")]
index c13ac1cb6966d43675da9b738a998b97a1e58663..4411b5597fbbdbf31ea44baae79ef7f27443e0da 100644 (file)
@@ -4,11 +4,6 @@
 
 using Microsoft.Diagnostics.DebugServices;
 using Microsoft.Diagnostics.Repl;
-using Microsoft.Diagnostics.Runtime;
-using System;
-using System.Collections.Generic;
-using System.CommandLine;
-using System.Linq;
 
 namespace Microsoft.Diagnostics.Tools.Dump
 {
@@ -16,30 +11,37 @@ namespace Microsoft.Diagnostics.Tools.Dump
     [CommandAlias(Name = "threads")]
     public class SetThreadCommand : CommandBase
     {
-        [Argument(Help = "The thread index to set, otherwise displays the list of threads.")]
-        public int? ThreadIndex { get; set; } = null;
+        [Argument(Help = "The thread index or id to set, otherwise displays the list of threads.")]
+        public int? Thread { get; set; } = null;
 
-        public DataTarget DataTarget { get; set; }
+        [Option(Name = "--tid", Help = "<thread> is an OS thread id.")]
+        [OptionAlias(Name = "-t")]
+        public bool ThreadId { get; set; }
 
         public AnalyzeContext AnalyzeContext { get; set; }
 
+        public IThreadService ThreadService { get; set; }
+
         public override void Invoke()
         {
-            if (ThreadIndex.HasValue)
+            if (Thread.HasValue)
             {
-                IEnumerable<uint> threads = DataTarget.DataReader.EnumerateAllThreads();
-                if (ThreadIndex.Value >= threads.Count()) {
-                    throw new InvalidOperationException($"Invalid thread index {ThreadIndex.Value}");
+                ThreadInfo threadInfo;
+                if (ThreadId)
+                {
+                    threadInfo = ThreadService.GetThreadInfoFromId((uint)Thread.Value);
+                }
+                else
+                {
+                    threadInfo = ThreadService.GetThreadInfoFromIndex(Thread.Value);
                 }
-                AnalyzeContext.CurrentThreadId = unchecked((int)threads.ElementAt(ThreadIndex.Value));
+                AnalyzeContext.CurrentThreadId = threadInfo.ThreadId;
             }
             else
             {
-                int index = 0;
-                foreach (uint threadId in DataTarget.DataReader.EnumerateAllThreads())
+                foreach (ThreadInfo thread in ThreadService.EnumerateThreads())
                 {
-                    WriteLine("{0}{1} 0x{2:X4} ({2})", threadId == AnalyzeContext.CurrentThreadId ? "*" : " ", index, threadId);
-                    index++;
+                    WriteLine("{0}{1} 0x{2:X4} ({2})", thread.ThreadId == AnalyzeContext.CurrentThreadId.Value ? "*" : " ", thread.ThreadIndex, thread.ThreadId);
                 }
             }
         }