// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Threading; using Debugger.Interop.CorDebug; namespace Debugger { public class Thread: DebuggerObject { // AppDomain for thread can be changing Process process; uint id; ICorDebugThread corThread; bool hasExited = false; Stepper currentStepIn; StackFrame selectedStackFrame; Exception currentException; DebuggeeState currentException_DebuggeeState; ExceptionType currentExceptionType; bool currentExceptionIsUnhandled; public event EventHandler NameChanged; public event EventHandler Exited; public Process Process { get { return process; } } [Debugger.Tests.Ignore] public uint ID { get{ return id; } } public bool HasExited { get { return hasExited; } private set { hasExited = value; } } internal Stepper CurrentStepIn { get { return currentStepIn; } set { currentStepIn = value; } } /// From time to time the thread may be in invalid state. public bool IsInValidState { get { try { CorThread.GetUserState().ToString(); CorThread.EnumerateChains(); return true; } catch (COMException e) { // The state of the thread is invalid. if ((uint)e.ErrorCode == 0x8013132D) { return false; } throw; } } } internal ICorDebugThread CorThread { get { if (hasExited) { throw new DebuggerException("Thread has exited"); } return corThread; } } internal Thread(Process process, ICorDebugThread corThread) { this.process = process; this.corThread = corThread; this.id = CorThread.GetID(); } internal void NotifyExited() { if (this.HasExited) throw new DebuggerException("Already exited"); process.TraceMessage("Thread " + this.ID + " exited"); if (process.SelectedThread == this) { process.SelectedThread = null; } this.HasExited = true; OnExited(new ThreadEventArgs(this)); process.Threads.Remove(this); } protected virtual void OnExited(ThreadEventArgs e) { if (Exited != null) { Exited(this, e); } } /// If the thread is not at safe point, it is not posible to evaluate /// on it /// Returns false if the thread is in invalid state public bool IsAtSafePoint { get { process.AssertPaused(); if (!IsInValidState) return false; return CorThread.GetUserState() != CorDebugUserState.USER_UNSAFE_POINT; } } public bool Suspended { get; set; } /// Returns Normal if the thread is in invalid state public ThreadPriority Priority { get { process.AssertPaused(); if (!IsInValidState) return ThreadPriority.Normal; Value runTimeValue = RuntimeValue; if (runTimeValue.IsNull) return ThreadPriority.Normal; return (ThreadPriority)(int)runTimeValue.GetMemberValue("m_Priority").PrimitiveValue; } } /// Returns value representing the System.Threading.Thread object /// The value is null while the thread is being created (the CreateThread callback) and /// it may stay null during the run of the program. (probaly until the debuggee accesses /// the System.Threading.Thread object which forces the creation) public Value RuntimeValue { get { process.AssertPaused(); ICorDebugValue corValue = this.CorThread.GetObject(); return new Value(process.AppDomains[this.CorThread.GetAppDomain()], corValue); } } /// Returns empty string if the thread is in invalid state public string Name { get { process.AssertPaused(); if (!IsInValidState) return string.Empty; Value runtimeValue = RuntimeValue; if (runtimeValue.IsNull) return string.Empty; Value runtimeName = runtimeValue.GetMemberValue("m_Name"); if (runtimeName.IsNull) return string.Empty; return runtimeName.AsString(100); } } protected virtual void OnNameChanged(ThreadEventArgs e) { if (NameChanged != null) { NameChanged(this, e); } } internal void NotifyNameChanged() { OnNameChanged(new ThreadEventArgs(this)); } public Exception CurrentException { get { if (currentException_DebuggeeState == process.DebuggeeState) { return currentException; } else { return null; } } internal set { currentException = value; } } internal DebuggeeState CurrentException_DebuggeeState { get { return currentException_DebuggeeState; } set { currentException_DebuggeeState = value; } } public ExceptionType CurrentExceptionType { get { return currentExceptionType; } internal set { currentExceptionType = value; } } public bool CurrentExceptionIsUnhandled { get { return currentExceptionIsUnhandled; } internal set { currentExceptionIsUnhandled = value; } } /// Tryies to intercept the current exception. /// The intercepted expression stays available through the CurrentException property. /// False, if the exception was already intercepted or /// if it can not be intercepted. public bool InterceptCurrentException() { if (!(this.CorThread is ICorDebugThread2)) return false; // Is the debuggee .NET 2.0? if (this.CorThread.GetCurrentException() == null) return false; // Is there any exception if (this.MostRecentStackFrame == null) return false; // Is frame available? It is not at StackOverflow try { // Interception will expire the CorValue so keep permanent reference currentException.MakeValuePermanent(); ((ICorDebugThread2)this.CorThread).InterceptCurrentException(this.MostRecentStackFrame.CorILFrame); } catch (COMException e) { // 0x80131C02: Cannot intercept this exception if ((uint)e.ErrorCode == 0x80131C02) return false; // 0x80131C33: Interception of the current exception is not legal if ((uint)e.ErrorCode == 0x80131C33) return false; // 0x80004005: Error HRESULT E_FAIL has been returned from a call to a COM component. // Use this to reproduce: new FileIOPermission(PermissionState.Unrestricted).Deny(); if ((uint)e.ErrorCode == 0x80004005) return false; throw; } catch (ArgumentException) { // May happen in release code with does not have any symbols return false; } process.AsyncContinue(DebuggeeStateAction.Keep, new Thread[] { this /* needed */ }, null); process.WaitForPause(); return true; } public override string ToString() { return String.Format("Thread Name = {1} Suspended = {2}", ID, Name, Suspended); } /// Gets the whole callstack of the Thread. /// If the thread is in invalid state returns empty array [Debugger.Tests.Ignore] public StackFrame[] GetCallstack() { return new List(Callstack).ToArray(); } /// Get given number of frames from the callstack public StackFrame[] GetCallstack(int maxFrames) { List frames = new List(); foreach(StackFrame frame in Callstack) { frames.Add(frame); if (frames.Count == maxFrames) break; } return frames.ToArray(); } public IEnumerable Callstack { get { process.AssertPaused(); if (!IsInValidState) { yield break; } uint corChainIndex = CorThread.EnumerateChains().GetCount(); foreach(ICorDebugChain corChain in CorThread.EnumerateChains().GetEnumerator()) { corChainIndex--; if (corChain.IsManaged() == 0) continue; // Only managed ones uint corFrameIndex = corChain.EnumerateFrames().GetCount(); foreach(ICorDebugFrame corFrame in corChain.EnumerateFrames().GetEnumerator()) { corFrameIndex--; if (!(corFrame is ICorDebugILFrame)) continue; // Only IL frames StackFrame stackFrame; try { stackFrame = new StackFrame(this, (ICorDebugILFrame)corFrame, corChainIndex, corFrameIndex); } catch (COMException) { // TODO: Remove continue; }; yield return stackFrame; } } } } internal StackFrame GetStackFrameAt(uint chainIndex, uint frameIndex) { process.AssertPaused(); ICorDebugChainEnum corChainEnum = CorThread.EnumerateChains(); if (chainIndex >= corChainEnum.GetCount()) throw new DebuggerException("The requested chain index is too big"); corChainEnum.Skip(corChainEnum.GetCount() - chainIndex - 1); ICorDebugChain corChain = corChainEnum.Next(); if (corChain.IsManaged() == 0) throw new DebuggerException("The requested chain is not managed"); ICorDebugFrameEnum corFrameEnum = corChain.EnumerateFrames(); if (frameIndex >= corFrameEnum.GetCount()) throw new DebuggerException("The requested frame index is too big"); corFrameEnum.Skip(corFrameEnum.GetCount() - frameIndex - 1); ICorDebugFrame corFrame = corFrameEnum.Next(); if (!(corFrame is ICorDebugILFrame)) throw new DebuggerException("The rquested frame is not IL frame"); StackFrame stackFrame = new StackFrame(this, (ICorDebugILFrame)corFrame, chainIndex, frameIndex); return stackFrame; } [Debugger.Tests.Ignore] public string GetStackTrace() { return GetStackTrace("at {0} in {1}:line {2}", "at {0}"); } public string GetStackTrace(string formatSymbols, string formatNoSymbols) { StringBuilder stackTrace = new StringBuilder(); foreach(StackFrame stackFrame in this.GetCallstack(100)) { SourcecodeSegment loc = stackFrame.NextStatement; stackTrace.Append(" "); if (loc != null) { stackTrace.AppendFormat(formatSymbols, stackFrame.MethodInfo.FullName, loc.Filename, loc.StartLine); } else { stackTrace.AppendFormat(formatNoSymbols, stackFrame.MethodInfo.FullName); } stackTrace.AppendLine(); } return stackTrace.ToString(); } public StackFrame SelectedStackFrame { get { if (selectedStackFrame != null && selectedStackFrame.IsInvalid) return null; if (process.IsRunning) return null; return selectedStackFrame; } set { if (value != null && !value.HasSymbols) { throw new DebuggerException("SelectedFunction must have symbols"); } selectedStackFrame = value; } } /// /// Returns the most recent stack frame (the one that is currently executing). /// Returns null if callstack is empty. /// public StackFrame MostRecentStackFrame { get { foreach(StackFrame stackFrame in Callstack) { return stackFrame; } return null; } } public bool IsInNativeCode { get { process.AssertPaused(); if (this.IsInValidState) { return corThread.GetActiveChain().IsManaged() == 0; } else { return false; } } } } [Serializable] public class ThreadEventArgs : ProcessEventArgs { Thread thread; public Thread Thread { get { return thread; } } public ThreadEventArgs(Thread thread): base(thread.Process) { this.thread = thread; } } }