//
//
//
//
// $Revision: 2274 $
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Debugger.Wrappers.CorDebug;
namespace Debugger
{
public partial class Thread: DebuggerObject, IExpirable
{
Process process;
ICorDebugThread corThread;
internal ExceptionType currentExceptionType;
List steppers = new List();
uint id;
bool lastSuspendedState = false;
ThreadPriority lastPriority = ThreadPriority.Normal;
string lastName = string.Empty;
bool hasBeenLoaded = false;
bool hasExpired = false;
bool nativeThreadExited = false;
Function selectedFunction;
public event EventHandler Expired;
public event EventHandler NativeThreadExited;
public bool HasExpired {
get {
return hasExpired;
}
}
[Debugger.Tests.Ignore]
public Process Process {
get {
return process;
}
}
internal bool HasBeenLoaded {
get {
return hasBeenLoaded;
}
set {
hasBeenLoaded = value;
OnStateChanged();
}
}
[Debugger.Tests.Ignore]
public uint ID {
get{
return id;
}
}
[Debugger.Tests.Ignore]
public ExceptionType CurrentExceptionType {
get {
return currentExceptionType;
}
set {
currentExceptionType = value;
}
}
public bool IsAtSafePoint {
get {
return CorThread.UserState != CorDebugUserState.USER_UNSAFE_POINT;
}
}
internal ICorDebugThread CorThread {
get {
if (nativeThreadExited) {
throw new DebuggerException("Native thread has exited");
}
return corThread;
}
}
internal Thread(Process process, ICorDebugThread corThread)
{
this.process = process;
this.corThread = corThread;
this.id = CorThread.ID;
}
void Expire()
{
Debug.Assert(!this.hasExpired);
process.TraceMessage("Thread " + this.ID + " expired");
this.hasExpired = true;
OnExpired(new ThreadEventArgs(this));
if (process.SelectedThread == this) {
process.SelectedThread = null;
}
}
protected virtual void OnExpired(EventArgs e)
{
if (Expired != null) {
Expired(this, e);
}
}
internal void NotifyNativeThreadExited()
{
if (!this.hasExpired) Expire();
nativeThreadExited = true;
OnNativeThreadExited(new ThreadEventArgs(this));
}
protected virtual void OnNativeThreadExited(ThreadEventArgs e)
{
if (NativeThreadExited != null) {
NativeThreadExited(this, e);
}
}
public bool Suspended {
get {
if (process.IsRunning) return lastSuspendedState;
lastSuspendedState = (CorThread.DebugState == CorDebugThreadState.THREAD_SUSPEND);
return lastSuspendedState;
}
set {
CorThread.SetDebugState((value==true)?CorDebugThreadState.THREAD_SUSPEND:CorDebugThreadState.THREAD_RUN);
}
}
public ThreadPriority Priority {
get {
if (!HasBeenLoaded) return lastPriority;
if (process.IsRunning) return lastPriority;
Value runTimeValue = RuntimeValue;
if (runTimeValue.IsNull) return ThreadPriority.Normal;
lastPriority = (ThreadPriority)(int)runTimeValue.GetMember("m_Priority").PrimitiveValue;
return lastPriority;
}
}
public Value RuntimeValue {
get {
if (!HasBeenLoaded) throw new DebuggerException("Thread has not started jet");
process.AssertPaused();
return new Value(
process,
new IExpirable[] {process.PauseSession},
new IMutable[] {},
delegate { return CorThread.Object;}
);
}
}
public string Name {
get {
if (!HasBeenLoaded) return lastName;
if (process.IsRunning) return lastName;
Value runtimeValue = RuntimeValue;
if (runtimeValue.IsNull) return lastName;
Value runtimeName = runtimeValue.GetMember("m_Name");
if (runtimeName.IsNull) return string.Empty;
lastName = runtimeName.AsString.ToString();
return lastName;
}
}
public bool InterceptCurrentException()
{
if (!CorThread.Is()) return false; // Is the debuggee .NET 2.0?
if (LastFunction == null) return false; // Is frame available? It is not at StackOverflow
try {
CorThread.CastTo().InterceptCurrentException(LastFunction.CorILFrame.CastTo());
return true;
} catch (COMException e) {
// 0x80131C02: Cannot intercept this exception
if ((uint)e.ErrorCode == 0x80131C02) {
return false;
}
throw;
}
}
internal Stepper GetStepper(ICorDebugStepper corStepper)
{
foreach(Stepper stepper in steppers) {
if (stepper.IsCorStepper(corStepper)) {
return stepper;
}
}
throw new DebuggerException("Stepper is not in collection");
}
internal List Steppers {
get {
return steppers;
}
}
public event EventHandler StateChanged;
protected void OnStateChanged()
{
if (StateChanged != null) {
StateChanged(this, new ThreadEventArgs(this));
}
}
public override string ToString()
{
return String.Format("ID = {0,-10} Name = {1,-20} Suspended = {2,-8}", ID, Name, Suspended);
}
public Exception CurrentException {
get {
return new Exception(this);
}
}
public IList Callstack {
get {
return new List(CallstackEnum).AsReadOnly();
}
}
IEnumerable CallstackEnum {
get {
process.AssertPaused();
foreach(ICorDebugChain corChain in CorThread.EnumerateChains().Enumerator) {
if (corChain.IsManaged == 0) continue; // Only managed ones
foreach(ICorDebugFrame corFrame in corChain.EnumerateFrames().Enumerator) {
if (corFrame.Is()) {
Function function;
try {
function = GetFunctionFromCache(new FrameID(corChain.Index, corFrame.Index), corFrame.As());
} catch (COMException) { // TODO
continue;
};
yield return function;
}
}
}
}
}
Dictionary functionCache = new Dictionary();
Function GetFunctionFromCache(FrameID frameID, ICorDebugILFrame corFrame)
{
Function function;
if (functionCache.TryGetValue(frameID, out function) && !function.HasExpired) {
function.CorILFrame = corFrame;
return function;
} else {
function = new Function(this, frameID, corFrame);
functionCache[frameID] = function;
return function;
}
}
internal ICorDebugFrame GetFrameAt(FrameID frameID)
{
process.AssertPaused();
ICorDebugChainEnum corChainEnum = CorThread.EnumerateChains();
if (frameID.ChainIndex >= corChainEnum.Count) throw new ArgumentException("Chain index too big", "chainIndex");
corChainEnum.Skip(corChainEnum.Count - frameID.ChainIndex - 1);
ICorDebugChain corChain = corChainEnum.Next();
if (corChain.IsManaged == 0) throw new ArgumentException("Chain is not managed", "chainIndex");
ICorDebugFrameEnum corFrameEnum = corChain.EnumerateFrames();
if (frameID.FrameIndex >= corFrameEnum.Count) throw new ArgumentException("Frame index too big", "frameIndex");
corFrameEnum.Skip(corFrameEnum.Count - frameID.FrameIndex - 1);
return corFrameEnum.Next();
}
// See docs\Stepping.txt
public void CheckExpiration()
{
try {
ICorDebugChainEnum chainEnum = CorThread.EnumerateChains();
} catch (COMException e) {
// 0x8013132D: The state of the thread is invalid.
// 0x8013134F: Object is in a zombie state
// 0x80131301: Process was terminated.
if ((uint)e.ErrorCode == 0x8013132D ||
(uint)e.ErrorCode == 0x8013134F ||
(uint)e.ErrorCode == 0x80131301) {
this.Expire();
return;
} else throw;
}
if (process.Evaluating) return;
ICorDebugChainEnum corChainEnum = CorThread.EnumerateChains();
int maxChainIndex = (int)corChainEnum.Count - 1;
ICorDebugFrameEnum corFrameEnum = corChainEnum.Next().EnumerateFrames();
// corFrameEnum.Count can return 0 in ExitThread callback
int maxFrameIndex = (int)corFrameEnum.Count - 1;
ICorDebugFrame lastFrame = corFrameEnum.Next();
// Check the token of the current function - function can change if there are multiple handlers for an event
Function function;
if (lastFrame != null &&
functionCache.TryGetValue(new FrameID((uint)maxChainIndex, (uint)maxFrameIndex), out function) &&
function.Token != lastFrame.FunctionToken) {
functionCache.Remove(new FrameID((uint)maxChainIndex, (uint)maxFrameIndex));
function.OnExpired(EventArgs.Empty);
}
// Expire all functions behind the current maximum
// Multiple functions can expire at once (test case: Step out of Button1Click in simple winforms application)
List> toBeRemoved = new List>();
foreach(KeyValuePair kvp in functionCache) {
if ((kvp.Key.ChainIndex > maxChainIndex) ||
(kvp.Key.ChainIndex == maxChainIndex && kvp.Key.FrameIndex > maxFrameIndex)) {
toBeRemoved.Add(kvp);
}
}
foreach(KeyValuePair kvp in toBeRemoved){
functionCache.Remove(kvp.Key);
kvp.Value.OnExpired(EventArgs.Empty);
}
}
[Debugger.Tests.SummaryOnly]
public Function SelectedFunction {
get {
return selectedFunction;
}
set {
if (value != null && !value.HasSymbols) {
throw new DebuggerException("SelectedFunction must have symbols");
}
selectedFunction = value;
}
}
[Debugger.Tests.SummaryOnly]
public Function LastFunctionWithLoadedSymbols {
get {
foreach (Function function in CallstackEnum) {
if (function.HasSymbols) {
return function;
}
}
return null;
}
}
///
/// Returns the most recent function on callstack.
/// Returns null if callstack is empty.
///
[Debugger.Tests.SummaryOnly]
public Function LastFunction {
get {
foreach(Function function in CallstackEnum) {
return function;
}
return null;
}
}
///
/// Returns the first function that was called on thread
///
[Debugger.Tests.SummaryOnly]
public Function FirstFunction {
get {
Function first = null;
foreach(Function function in Callstack) {
first = function;
}
return first;
}
}
public bool IsLastFunctionNative {
get {
process.AssertPaused();
return corThread.ActiveChain.IsManaged == 0;
}
}
}
}