From a69f8fe1c0dc48d010e07a6f7670b8038969025f Mon Sep 17 00:00:00 2001 From: Eusebiu Marcu Date: Sun, 13 Feb 2011 01:30:31 +0200 Subject: [PATCH] Attach/detach to running process --- .../AvalonEdit/IconBarMargin.cs | 1 - .../Bookmarks/BreakpointBookmark.cs | 4 +- Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj | 21 +- .../Models/TreeModel/ArrayRangeNode.cs | 120 +++ .../Models/TreeModel/ChildNodesOfObject.cs | 153 ++++ .../Models/TreeModel/ExpressionNode.cs | 420 ++++++++++ .../Models/TreeModel/ICorDebug.cs | 153 ++++ .../Models/TreeModel/IEnumerableNode.cs | 29 + .../Models/TreeModel/IListNode.cs | 25 + .../Models/TreeModel/ISetText.cs | 13 + .../TreeModel}/ITreeNode.cs | 2 +- .../TreeModel}/IVisualizerCommand.cs | 2 +- .../Models/TreeModel/SavedTreeNode.cs | 26 + .../Models/TreeModel/StackFrameNode.cs | 47 ++ .../Models/TreeModel/TreeNode.cs | 94 +++ .../ILSpy.Debugger/Models/TreeModel/Utils.cs | 66 ++ .../Services/Debugger/DebuggerHelper.cs | 111 +++ .../Services/Debugger/ListHelper.cs | 33 + .../Debugger/RemotingConfigurationHelpper.cs | 81 ++ .../Debugger/TypeResolverExtension.cs | 82 ++ .../Services/Debugger/WindowsDebugger.cs | 727 ++++++++++++++++++ .../Services/ImageService/ImageService.cs | 6 + .../UI/AttachToProcessWindow.xaml.cs | 10 + ILSpy/Commands/RoutedUICommands.cs | 3 + ILSpy/MainWindow.xaml | 10 +- ILSpy/MainWindow.xaml.cs | 14 +- 26 files changed, 2242 insertions(+), 11 deletions(-) create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/ArrayRangeNode.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/ChildNodesOfObject.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/ICorDebug.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/IEnumerableNode.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/IListNode.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/ISetText.cs rename Debugger/ILSpy.Debugger/{Services/Debugger/Tooltips => Models/TreeModel}/ITreeNode.cs (95%) rename Debugger/ILSpy.Debugger/{Services/Debugger/Tooltips => Models/TreeModel}/IVisualizerCommand.cs (93%) create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/SavedTreeNode.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/StackFrameNode.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/TreeNode.cs create mode 100644 Debugger/ILSpy.Debugger/Models/TreeModel/Utils.cs create mode 100644 Debugger/ILSpy.Debugger/Services/Debugger/DebuggerHelper.cs create mode 100644 Debugger/ILSpy.Debugger/Services/Debugger/ListHelper.cs create mode 100644 Debugger/ILSpy.Debugger/Services/Debugger/RemotingConfigurationHelpper.cs create mode 100644 Debugger/ILSpy.Debugger/Services/Debugger/TypeResolverExtension.cs create mode 100644 Debugger/ILSpy.Debugger/Services/Debugger/WindowsDebugger.cs diff --git a/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs b/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs index 4df9182b9..b9118ad9a 100644 --- a/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs +++ b/Debugger/ILSpy.Debugger/AvalonEdit/IconBarMargin.cs @@ -26,7 +26,6 @@ using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Utils; using ILSpy.Debugger.Bookmarks; -using ILSpy.Debugger.Debugging; using ILSpy.Debugger.Services; namespace ILSpy.Debugger.AvalonEdit diff --git a/Debugger/ILSpy.Debugger/Bookmarks/BreakpointBookmark.cs b/Debugger/ILSpy.Debugger/Bookmarks/BreakpointBookmark.cs index 26e93978a..c459748e5 100644 --- a/Debugger/ILSpy.Debugger/Bookmarks/BreakpointBookmark.cs +++ b/Debugger/ILSpy.Debugger/Bookmarks/BreakpointBookmark.cs @@ -28,8 +28,8 @@ namespace ILSpy.Debugger.Bookmarks public enum BreakpointAction { Break, -// Trace, -// Condition + Trace, + Condition } public class BreakpointBookmark : BookmarkBase diff --git a/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj b/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj index d714bc1b7..742048f58 100644 --- a/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj +++ b/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj @@ -63,11 +63,27 @@ + + + + + + + + + + + + + + - - + + + + @@ -80,6 +96,7 @@ + diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/ArrayRangeNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/ArrayRangeNode.cs new file mode 100644 index 000000000..366d0647b --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/ArrayRangeNode.cs @@ -0,0 +1,120 @@ +// 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.Text; + +using Debugger; +using ICSharpCode.NRefactory.Ast; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public partial class Utils + { + public static IEnumerable LazyGetChildNodesOfArray(Expression expression, ArrayDimensions dimensions) + { + if (dimensions.TotalElementCount == 0) + return new TreeNode[] { new TreeNode(null, "(empty)", null, null, null) }; + + return new ArrayRangeNode(expression, dimensions, dimensions).ChildNodes; + } + } + + /// This is a partent node for all elements within a given bounds + public class ArrayRangeNode: TreeNode + { + const int MaxElementCount = 100; + + Expression arrayTarget; + ArrayDimensions bounds; + ArrayDimensions originalBounds; + + public ArrayRangeNode(Expression arrayTarget, ArrayDimensions bounds, ArrayDimensions originalBounds) + { + this.arrayTarget = arrayTarget; + this.bounds = bounds; + this.originalBounds = originalBounds; + + this.Name = GetName(); + this.ChildNodes = LazyGetChildren(); + } + + string GetName() + { + StringBuilder name = new StringBuilder(); + bool isFirst = true; + name.Append("["); + for(int i = 0; i < bounds.Count; i++) { + if (!isFirst) name.Append(", "); + isFirst = false; + ArrayDimension dim = bounds[i]; + ArrayDimension originalDim = originalBounds[i]; + + if (dim.Count == 0) { + throw new DebuggerException("Empty dimension"); + } else if (dim.Count == 1) { + name.Append(dim.LowerBound.ToString()); + } else if (dim.Equals(originalDim)) { + name.Append("*"); + } else { + name.Append(dim.LowerBound); + name.Append(".."); + name.Append(dim.UpperBound); + } + } + name.Append("]"); + return name.ToString(); + } + + static string GetName(int[] indices) + { + StringBuilder sb = new StringBuilder(indices.Length * 4); + sb.Append("["); + bool isFirst = true; + foreach(int index in indices) { + if (!isFirst) sb.Append(", "); + sb.Append(index.ToString()); + isFirst = false; + } + sb.Append("]"); + return sb.ToString(); + } + + IEnumerable LazyGetChildren() + { + // The whole array is small - just add all elements as childs + if (bounds.TotalElementCount <= MaxElementCount) { + foreach(int[] indices in bounds.Indices) { + string imageName; + var image = ExpressionNode.GetImageForArrayIndexer(out imageName); + var expression = new ExpressionNode(image, GetName(indices), arrayTarget.AppendIndexer(indices)); + expression.ImageName = imageName; + yield return expression; + } + yield break; + } + + // Find a dimension of size at least 2 + int splitDimensionIndex = bounds.Count - 1; + for(int i = 0; i < bounds.Count; i++) { + if (bounds[i].Count > 1) { + splitDimensionIndex = i; + break; + } + } + ArrayDimension splitDim = bounds[splitDimensionIndex]; + + // Split the dimension + int elementsPerSegment = 1; + while (splitDim.Count > elementsPerSegment * MaxElementCount) { + elementsPerSegment *= MaxElementCount; + } + for(int i = splitDim.LowerBound; i <= splitDim.UpperBound; i += elementsPerSegment) { + List newDims = new List(bounds); + newDims[splitDimensionIndex] = new ArrayDimension(i, Math.Min(i + elementsPerSegment - 1, splitDim.UpperBound)); + yield return new ArrayRangeNode(arrayTarget, new ArrayDimensions(newDims), originalBounds); + } + yield break; + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/ChildNodesOfObject.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/ChildNodesOfObject.cs new file mode 100644 index 000000000..82959d534 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/ChildNodesOfObject.cs @@ -0,0 +1,153 @@ +// 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.Collections; +using System.Collections.Generic; +using System.Reflection; + +using Debugger; +using Debugger.MetaData; +using ICSharpCode.Core; +using ICSharpCode.NRefactory.Ast; +using ILSpy.Debugger.Services; +using ILSpy.Debugger.Services.Debugger; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public partial class Utils + { + public static IEnumerable LazyGetChildNodesOfObject(Expression targetObject, DebugType shownType) + { + MemberInfo[] publicStatic = shownType.GetFieldsAndNonIndexedProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly); + MemberInfo[] publicInstance = shownType.GetFieldsAndNonIndexedProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + MemberInfo[] nonPublicStatic = shownType.GetFieldsAndNonIndexedProperties(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly); + MemberInfo[] nonPublicInstance = shownType.GetFieldsAndNonIndexedProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + + DebugType baseType = (DebugType)shownType.BaseType; + if (baseType != null) { + yield return new TreeNode( + ImageService.GetImage("Icons.16x16.Class"), + "BaseClass", + baseType.Name, + baseType.FullName, + baseType.FullName == "System.Object" ? null : Utils.LazyGetChildNodesOfObject(targetObject, baseType) + ); + } + + if (nonPublicInstance.Length > 0) { + yield return new TreeNode( + null, + "NonPublicMembers", + string.Empty, + string.Empty, + Utils.LazyGetMembersOfObject(targetObject, nonPublicInstance) + ); + } + + if (publicStatic.Length > 0 || nonPublicStatic.Length > 0) { + IEnumerable childs = Utils.LazyGetMembersOfObject(targetObject, publicStatic); + if (nonPublicStatic.Length > 0) { + TreeNode nonPublicStaticNode = new TreeNode( + null, + "NonPublicStaticMembers", + string.Empty, + string.Empty, + Utils.LazyGetMembersOfObject(targetObject, nonPublicStatic) + ); + childs = Utils.PrependNode(nonPublicStaticNode, childs); + } + yield return new TreeNode( + null, + "StaticMembers", + string.Empty, + string.Empty, + childs + ); + } + + DebugType iListType = (DebugType)shownType.GetInterface(typeof(IList).FullName); + if (iListType != null) { + yield return new IListNode(targetObject); + } else { + DebugType iEnumerableType, itemType; + if (shownType.ResolveIEnumerableImplementation(out iEnumerableType, out itemType)) { + yield return new IEnumerableNode(targetObject, itemType); + } + } + + foreach(TreeNode node in LazyGetMembersOfObject(targetObject, publicInstance)) { + yield return node; + } + } + + public static IEnumerable LazyGetMembersOfObject(Expression expression, MemberInfo[] members) + { + List nodes = new List(); + foreach(MemberInfo memberInfo in members) { + string imageName; + var image = ExpressionNode.GetImageForMember((IDebugMemberInfo)memberInfo, out imageName); + var exp = new ExpressionNode(image, memberInfo.Name, expression.AppendMemberReference((IDebugMemberInfo)memberInfo)); + exp.ImageName = imageName; + nodes.Add(exp); + } + nodes.Sort(); + return nodes; + } + + + public static IEnumerable LazyGetItemsOfIList(Expression targetObject) + { + // This is needed for expanding IEnumerable + targetObject = new CastExpression( + new TypeReference(typeof(IList).FullName), + targetObject, + CastType.Cast + ); + int count = 0; + GetValueException error = null; + try { + count = GetIListCount(targetObject); + } catch (GetValueException e) { + // Cannot yield a value in the body of a catch clause (CS1631) + error = e; + } + if (error != null) { + yield return new TreeNode(null, "(error)", error.Message, null, null); + } else if (count == 0) { + yield return new TreeNode(null, "(empty)", null, null, null); + } else { + for(int i = 0; i < count; i++) { + string imageName; + var image = ExpressionNode.GetImageForArrayIndexer(out imageName); + var expression = new ExpressionNode(image, "[" + i + "]", targetObject.AppendIndexer(i)); + expression.ImageName = imageName; + yield return expression; + } + } + } + + /// + /// Evaluates System.Collections.ICollection.Count property on given object. + /// + /// Evaluating System.Collections.ICollection.Count on targetObject failed. + public static int GetIListCount(Expression targetObject) + { + Value list = targetObject.Evaluate(WindowsDebugger.CurrentProcess); + var iCollectionInterface = list.Type.GetInterface(typeof(ICollection).FullName); + if (iCollectionInterface == null) + throw new GetValueException(targetObject, targetObject.PrettyPrint() + " does not implement System.Collections.ICollection"); + PropertyInfo countProperty = iCollectionInterface.GetProperty("Count"); + // Do not get string representation since it can be printed in hex + return (int)list.GetPropertyValue(countProperty).PrimitiveValue; + } + + public static IEnumerable PrependNode(TreeNode node, IEnumerable rest) + { + yield return node; + if (rest != null) { + foreach(TreeNode absNode in rest) { + yield return absNode; + } + } + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs new file mode 100644 index 000000000..34ba634a9 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/ExpressionNode.cs @@ -0,0 +1,420 @@ +// 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.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Media; + +using Debugger; +using Debugger.Interop.CorDebug; +using Debugger.MetaData; +using ICSharpCode.NRefactory.Ast; +using ILSpy.Debugger.Services; +using ILSpy.Debugger.Services.Debugger; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public class ExpressionNode: TreeNode, ISetText, INotifyPropertyChanged + { + bool evaluated; + + Expression expression; + bool canSetText; + GetValueException error; + string fullText; + + public bool Evaluated { + get { return evaluated; } + set { evaluated = value; } + } + + public Expression Expression { + get { return expression; } + } + + public override bool CanSetText { + get { + if (!evaluated) EvaluateExpression(); + return canSetText; + } + } + + public GetValueException Error { + get { + if (!evaluated) EvaluateExpression(); + return error; + } + } + + public string FullText { + get { return fullText; } + } + + public override string Text { + get { + if (!evaluated) EvaluateExpression(); + return base.Text; + } + set { + if (value != base.Text) { + base.Text = value; + NotifyPropertyChanged("Text"); + } + } + } + + public override string FullName { + get { + if (!evaluated) EvaluateExpression(); + + return this.expression.PrettyPrint() ?? Name.Trim(); + } + } + + public override string Type { + get { + if (!evaluated) EvaluateExpression(); + return base.Type; + } + } + + public override IEnumerable ChildNodes { + get { + if (!evaluated) EvaluateExpression(); + return base.ChildNodes; + } + } + + public override bool HasChildNodes { + get { + if (!evaluated) EvaluateExpression(); + return base.HasChildNodes; + } + } + + /// Used to determine available VisualizerCommands + private DebugType expressionType; + /// Used to determine available VisualizerCommands + private bool valueIsNull = true; + + private IEnumerable visualizerCommands; + public override IEnumerable VisualizerCommands { + get { + if (visualizerCommands == null) { + visualizerCommands = getAvailableVisualizerCommands(); + } + return visualizerCommands; + } + } + + private IEnumerable getAvailableVisualizerCommands() + { + if (!evaluated) EvaluateExpression(); + + if (this.expressionType == null) { + // no visualizers if EvaluateExpression failed + yield break; + } + if (this.valueIsNull) { + // no visualizers if evaluated value is null + yield break; + } + /*if (this.expressionType.IsPrimitive || this.expressionType.IsSystemDotObject() || this.expressionType.IsEnum()) { + // no visualizers for primitive types + yield break; + }*/ + + yield break; +// foreach (var descriptor in VisualizerDescriptors.GetAllDescriptors()) { +// if (descriptor.IsVisualizerAvailable(this.expressionType)) { +// yield return descriptor.CreateVisualizerCommand(this.Expression); +// } +// } + } + + public ExpressionNode(ImageSource image, string name, Expression expression) + { + this.ImageSource = image; + this.Name = name; + this.expression = expression; + } + + void EvaluateExpression() + { + evaluated = true; + + Value val; + try { + val = expression.Evaluate(WindowsDebugger.DebuggedProcess); + } catch (GetValueException e) { + error = e; + this.Text = e.Message; + return; + } + + this.canSetText = val.Type.IsPrimitive; + + this.expressionType = val.Type; + this.Type = val.Type.Name; + this.valueIsNull = val.IsNull; + + // Note that these return enumerators so they are lazy-evaluated + if (val.IsNull) { + } else if (val.Type.IsPrimitive || val.Type.FullName == typeof(string).FullName) { // Must be before IsClass + } else if (val.Type.IsArray) { // Must be before IsClass + if (val.ArrayLength > 0) + this.ChildNodes = Utils.LazyGetChildNodesOfArray(this.Expression, val.ArrayDimensions); + } else if (val.Type.IsClass || val.Type.IsValueType) { + if (val.Type.FullNameWithoutGenericArguments == typeof(List<>).FullName) { + if ((int)val.GetMemberValue("_size").PrimitiveValue > 0) + this.ChildNodes = Utils.LazyGetItemsOfIList(this.expression); + } else { + this.ChildNodes = Utils.LazyGetChildNodesOfObject(this.Expression, val.Type); + } + } else if (val.Type.IsPointer) { + Value deRef = val.Dereference(); + if (deRef != null) { + this.ChildNodes = new ExpressionNode [] { new ExpressionNode(this.ImageSource, "*" + this.Name, this.Expression.AppendDereference()) }; + } + } + + if (true) { + TreeNode info = ICorDebug.GetDebugInfoRoot(val.AppDomain, val.CorValue); + this.ChildNodes = Utils.PrependNode(info, this.ChildNodes); + } + + // Do last since it may expire the object + if (val.Type.IsInteger) { + fullText = FormatInteger(val.PrimitiveValue); + } else if (val.Type.IsPointer) { + fullText = String.Format("0x{0:X}", val.PointerAddress); + } else if ((val.Type.FullName == typeof(string).FullName || + val.Type.FullName == typeof(char).FullName) && !val.IsNull) { + try { + fullText = '"' + Escape(val.InvokeToString()) + '"'; + } catch (GetValueException e) { + error = e; + fullText = e.Message; + return; + } + } else if ((val.Type.IsClass || val.Type.IsValueType) && !val.IsNull) { + try { + fullText = val.InvokeToString(); + } catch (GetValueException e) { + error = e; + fullText = e.Message; + return; + } + } else { + fullText = val.AsString(); + } + + this.Text = (fullText.Length > 256) ? fullText.Substring(0, 256) + "..." : fullText; + } + + string Escape(string source) + { + return source.Replace("\n", "\\n") + .Replace("\t", "\\t") + .Replace("\r", "\\r") + .Replace("\0", "\\0") + .Replace("\b", "\\b") + .Replace("\a", "\\a") + .Replace("\f", "\\f") + .Replace("\v", "\\v") + .Replace("\"", "\\\""); + } + + string FormatInteger(object i) + { + if (true) + return i.ToString(); + + string hex = null; + for(int len = 1;; len *= 2) { + hex = string.Format("{0:X" + len + "}", i); + if (hex.Length == len) + break; + } + + if (true) { + return "0x" + hex; + } else { + if (ShowAsHex(i)) { + return String.Format("{0} (0x{1})", i, hex); + } else { + return i.ToString(); + } + } + } + + bool ShowAsHex(object i) + { + ulong val; + if (i is sbyte || i is short || i is int || i is long) { + unchecked { val = (ulong)Convert.ToInt64(i); } + if (val > (ulong)long.MaxValue) + val = ~val + 1; + } else { + val = Convert.ToUInt64(i); + } + if (val >= 0x10000) + return true; + + int ones = 0; // How many 1s there is + int runs = 0; // How many runs of 1s there is + int size = 0; // Size of the integer in bits + while(val != 0) { // There is at least one 1 + while((val & 1) == 0) { // Skip 0s + val = val >> 1; + size++; + } + while((val & 1) == 1) { // Skip 1s + val = val >> 1; + size++; + ones++; + } + runs++; + } + + return size >= 7 && runs <= (size + 7) / 8; + } + + public override bool SetText(string newText) + { + string fullName = FullName; + + Value val = null; + try { + val = this.Expression.Evaluate(WindowsDebugger.DebuggedProcess); + if (val.Type.IsInteger && newText.StartsWith("0x")) { + try { + val.PrimitiveValue = long.Parse(newText.Substring(2), NumberStyles.HexNumber); + } catch (FormatException) { + throw new NotSupportedException(); + } catch (OverflowException) { + throw new NotSupportedException(); + } + } else { + val.PrimitiveValue = newText; + } + this.Text = newText; + return true; + } catch (NotSupportedException) { + string format = "Can not convert {0} to {1}"; + string msg = string.Format(format, newText, val.Type.PrimitiveType); + System.Windows.MessageBox.Show(msg); + } catch (COMException) { + // COMException (0x80131330): Cannot perfrom SetValue on non-leaf frames. + // Happens if trying to set value after exception is breaked + System.Windows.MessageBox.Show("UnknownError"); + } + return false; + } + + public static ImageSource GetImageForThis(out string imageName) + { + imageName = "Icons.16x16.Parameter"; + return ImageService.GetImage(imageName); + } + + public static ImageSource GetImageForParameter(out string imageName) + { + imageName = "Icons.16x16.Parameter"; + return ImageService.GetImage(imageName); + } + + public static ImageSource GetImageForLocalVariable(out string imageName) + { + imageName = "Icons.16x16.Local"; + return ImageService.GetImage(imageName); + } + + public static ImageSource GetImageForArrayIndexer(out string imageName) + { + imageName = "Icons.16x16.Field"; + return ImageService.GetImage(imageName); + } + + public static ImageSource GetImageForMember(IDebugMemberInfo memberInfo, out string imageName) + { + string name = string.Empty; + if (memberInfo.IsPublic) { + } else if (memberInfo.IsAssembly) { + name += "Internal"; + } else if (memberInfo.IsFamily) { + name += "Protected"; + } else if (memberInfo.IsPrivate) { + name += "Private"; + } + if (memberInfo is FieldInfo) { + name += "Field"; + } else if (memberInfo is PropertyInfo) { + name += "Property"; + } else if (memberInfo is MethodInfo) { + name += "Method"; + } else { + throw new DebuggerException("Unknown member type " + memberInfo.GetType().FullName); + } + + imageName = "Icons.16x16." + name; + return ImageService.GetImage(imageName); + } + +// public ContextMenuStrip GetContextMenu() +// { +// if (this.Error != null) return GetErrorContextMenu(); +// +// ContextMenuStrip menu = new ContextMenuStrip(); +// +// ToolStripMenuItem copyItem; +// copyItem = new ToolStripMenuItem(); +// copyItem.Text = ResourceService.GetString("MainWindow.Windows.Debug.LocalVariables.CopyToClipboard"); +// copyItem.Checked = false; +// copyItem.Click += delegate { +// ClipboardWrapper.SetText(fullText); +// }; + +// ToolStripMenuItem hexView; +// hexView = new ToolStripMenuItem(); +// hexView.Text = ResourceService.GetString("MainWindow.Windows.Debug.LocalVariables.ShowInHexadecimal"); +// hexView.Checked = DebuggingOptions.Instance.ShowValuesInHexadecimal; +// hexView.Click += delegate { +// // refresh all pads that use ValueNode for display +// DebuggingOptions.Instance.ShowValuesInHexadecimal = !DebuggingOptions.Instance.ShowValuesInHexadecimal; +// // always check if instance is null, might be null if pad is not opened +// if (LocalVarPad.Instance != null) +// LocalVarPad.Instance.RefreshPad(); +// if (WatchPad.Instance != null) +// WatchPad.Instance.RefreshPad(); +// }; + +// menu.Items.AddRange(new ToolStripItem[] { +// copyItem, +// //hexView +// }); +// +// return menu; +// } + + public static WindowsDebugger WindowsDebugger { + get { + return (WindowsDebugger)DebuggerService.CurrentDebugger; + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + private void NotifyPropertyChanged(string info) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(info)); + } + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/ICorDebug.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/ICorDebug.cs new file mode 100644 index 000000000..001547d0d --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/ICorDebug.cs @@ -0,0 +1,153 @@ +// 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.Collections.Generic; +using Debugger.Interop.CorDebug; +using Debugger.MetaData; +using Debugger; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public class ICorDebug + { + public class InfoNode: TreeNode + { + List children; + + public InfoNode(string name, string text): this(name, text, null) + { + + } + + public InfoNode(string name, string text, List children) + { + this.Name = name; + this.Text = text; + this.ChildNodes = children; + this.children = children; + } + + public void AddChild(string name, string text) + { + if (children == null) { + children = new List(); + this.ChildNodes = children; + } + children.Add(new InfoNode(name, text)); + } + + public void AddChild(string name, string text, List subChildren) + { + if (children == null) { + children = new List(); + this.ChildNodes = children; + } + children.Add(new InfoNode(name, text, subChildren)); + } + } + + public static InfoNode GetDebugInfoRoot(AppDomain appDomain, ICorDebugValue corValue) + { + return new InfoNode("ICorDebug", "", GetDebugInfo(appDomain, corValue)); + } + + public static List GetDebugInfo(AppDomain appDomain, ICorDebugValue corValue) + { + List items = new List(); + + if (corValue is ICorDebugValue) { + InfoNode info = new InfoNode("ICorDebugValue", ""); + info.AddChild("Address", corValue.GetAddress().ToString("X8")); + info.AddChild("Type", ((CorElementType)corValue.GetTheType()).ToString()); + info.AddChild("Size", corValue.GetSize().ToString()); + items.Add(info); + } + if (corValue is ICorDebugValue2) { + InfoNode info = new InfoNode("ICorDebugValue2", ""); + ICorDebugValue2 corValue2 = (ICorDebugValue2)corValue; + string fullname; + try { + fullname = DebugType.CreateFromCorType(appDomain, corValue2.GetExactType()).FullName; + } catch (DebuggerException e) { + fullname = e.Message; + } + info.AddChild("ExactType", fullname); + items.Add(info); + } + if (corValue is ICorDebugGenericValue) { + InfoNode info = new InfoNode("ICorDebugGenericValue", ""); + try { + byte[] bytes = ((ICorDebugGenericValue)corValue).GetRawValue(); + for(int i = 0; i < bytes.Length; i += 8) { + string val = ""; + for(int j = i; j < bytes.Length && j < i + 8; j++) { + val += bytes[j].ToString("X2") + " "; + } + info.AddChild("Value" + i.ToString("X2"), val); + } + } catch (System.ArgumentException) { + info.AddChild("Value", "N/A"); + } + items.Add(info); + } + if (corValue is ICorDebugReferenceValue) { + InfoNode info = new InfoNode("ICorDebugReferenceValue", ""); + ICorDebugReferenceValue refValue = (ICorDebugReferenceValue)corValue; + info.AddChild("IsNull", (refValue.IsNull() != 0).ToString()); + if (refValue.IsNull() == 0) { + info.AddChild("Value", refValue.GetValue().ToString("X8")); + if (refValue.Dereference() != null) { + info.AddChild("Dereference", "", GetDebugInfo(appDomain, refValue.Dereference())); + } else { + info.AddChild("Dereference", "N/A"); + } + } + items.Add(info); + } + if (corValue is ICorDebugHeapValue) { + InfoNode info = new InfoNode("ICorDebugHeapValue", ""); + items.Add(info); + } + if (corValue is ICorDebugHeapValue2) { + InfoNode info = new InfoNode("ICorDebugHeapValue2", ""); + items.Add(info); + } + if (corValue is ICorDebugObjectValue) { + InfoNode info = new InfoNode("ICorDebugObjectValue", ""); + ICorDebugObjectValue objValue = (ICorDebugObjectValue)corValue; + info.AddChild("Class", objValue.GetClass().GetToken().ToString("X8")); + info.AddChild("IsValueClass", (objValue.IsValueClass() != 0).ToString()); + items.Add(info); + } + if (corValue is ICorDebugObjectValue2) { + InfoNode info = new InfoNode("ICorDebugObjectValue2", ""); + items.Add(info); + } + if (corValue is ICorDebugBoxValue) { + InfoNode info = new InfoNode("ICorDebugBoxValue", ""); + ICorDebugBoxValue boxValue = (ICorDebugBoxValue)corValue; + info.AddChild("Object", "", GetDebugInfo(appDomain, boxValue.GetObject())); + items.Add(info); + } + if (corValue is ICorDebugStringValue) { + InfoNode info = new InfoNode("ICorDebugStringValue", ""); + ICorDebugStringValue stringValue = (ICorDebugStringValue)corValue; + info.AddChild("Length", stringValue.GetLength().ToString()); + info.AddChild("String", stringValue.GetString()); + items.Add(info); + } + if (corValue is ICorDebugArrayValue) { + InfoNode info = new InfoNode("ICorDebugArrayValue", ""); + info.AddChild("...", "..."); + items.Add(info); + } + if (corValue is ICorDebugHandleValue) { + InfoNode info = new InfoNode("ICorDebugHandleValue", ""); + ICorDebugHandleValue handleValue = (ICorDebugHandleValue)corValue; + info.AddChild("HandleType", handleValue.GetHandleType().ToString()); + items.Add(info); + } + + return items; + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/IEnumerableNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/IEnumerableNode.cs new file mode 100644 index 000000000..5f8cc96df --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/IEnumerableNode.cs @@ -0,0 +1,29 @@ +// 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 Debugger.MetaData; +using ICSharpCode.NRefactory.Ast; +using ILSpy.Debugger.Services.Debugger; + +namespace ILSpy.Debugger.Models.TreeModel +{ + /// + /// IEnumerable node in the variable tree. + /// + public class IEnumerableNode : TreeNode + { + Expression targetObject; + Expression debugListExpression; + + public IEnumerableNode(Expression targetObject, DebugType itemType) + { + this.targetObject = targetObject; + + this.Name = "IEnumerable"; + this.Text = "Expanding will enumerate the IEnumerable"; + DebugType debugListType; + this.debugListExpression = DebuggerHelpers.CreateDebugListExpression(targetObject, itemType, out debugListType); + this.ChildNodes = Utils.LazyGetItemsOfIList(this.debugListExpression); + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/IListNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/IListNode.cs new file mode 100644 index 000000000..670467011 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/IListNode.cs @@ -0,0 +1,25 @@ +// 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 ICSharpCode.NRefactory.Ast; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public class IListNode : TreeNode + { + Expression targetObject; + int count; + + public IListNode(Expression targetObject) + { + this.targetObject = targetObject; + + this.Name = "IList"; + this.count = Utils.GetIListCount(this.targetObject); + this.ChildNodes = Utils.LazyGetItemsOfIList(this.targetObject); + } + + public override bool HasChildNodes { + get { return this.count > 0; } + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/ISetText.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/ISetText.cs new file mode 100644 index 000000000..ec52873d2 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/ISetText.cs @@ -0,0 +1,13 @@ +// 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; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public interface ISetText + { + bool CanSetText { get; } + + bool SetText(string text); + } +} diff --git a/Debugger/ILSpy.Debugger/Services/Debugger/Tooltips/ITreeNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/ITreeNode.cs similarity index 95% rename from Debugger/ILSpy.Debugger/Services/Debugger/Tooltips/ITreeNode.cs rename to Debugger/ILSpy.Debugger/Models/TreeModel/ITreeNode.cs index 93e164b09..270dcb0b5 100644 --- a/Debugger/ILSpy.Debugger/Services/Debugger/Tooltips/ITreeNode.cs +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/ITreeNode.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Windows.Media; -namespace ILSpy.Debugger.Debugging +namespace ILSpy.Debugger.Models.TreeModel { /// /// Node that can be bound to . diff --git a/Debugger/ILSpy.Debugger/Services/Debugger/Tooltips/IVisualizerCommand.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/IVisualizerCommand.cs similarity index 93% rename from Debugger/ILSpy.Debugger/Services/Debugger/Tooltips/IVisualizerCommand.cs rename to Debugger/ILSpy.Debugger/Models/TreeModel/IVisualizerCommand.cs index df25c392f..f1ab16f33 100644 --- a/Debugger/ILSpy.Debugger/Services/Debugger/Tooltips/IVisualizerCommand.cs +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/IVisualizerCommand.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace ILSpy.Debugger.Debugging +namespace ILSpy.Debugger.Models.TreeModel { /// /// Command called from . diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/SavedTreeNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/SavedTreeNode.cs new file mode 100644 index 000000000..818b7335f --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/SavedTreeNode.cs @@ -0,0 +1,26 @@ +// 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.Windows.Media; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public class SavedTreeNode : TreeNode + { + public override bool CanSetText { + get { return true; } + } + + public SavedTreeNode(ImageSource image, string fullname, string text) + { + base.ImageSource = image; + FullName = fullname; + Text = text; + } + + public override bool SetText(string newValue) { + Text = newValue; + return false; + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/StackFrameNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/StackFrameNode.cs new file mode 100644 index 000000000..3c74db041 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/StackFrameNode.cs @@ -0,0 +1,47 @@ +// 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.Collections.Generic; +using Debugger; +using Debugger.MetaData; +using ICSharpCode.NRefactory.Ast; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public class StackFrameNode: TreeNode + { + StackFrame stackFrame; + + public StackFrame StackFrame { + get { return stackFrame; } + } + + public StackFrameNode(StackFrame stackFrame) + { + this.stackFrame = stackFrame; + + this.Name = stackFrame.MethodInfo.Name; + this.ChildNodes = LazyGetChildNodes(); + } + + IEnumerable LazyGetChildNodes() + { + foreach(DebugParameterInfo par in stackFrame.MethodInfo.GetParameters()) { + string imageName; + var image = ExpressionNode.GetImageForParameter(out imageName); + var expression = new ExpressionNode(image, par.Name, par.GetExpression()); + expression.ImageName = imageName; + yield return expression; + } + foreach(DebugLocalVariableInfo locVar in stackFrame.MethodInfo.GetLocalVariables(this.StackFrame.IP)) { + string imageName; + var image = ExpressionNode.GetImageForLocalVariable(out imageName); + var expression = new ExpressionNode(image, locVar.Name, locVar.GetExpression()); + expression.ImageName = imageName; + yield return expression; + } + if (stackFrame.Thread.CurrentException != null) { + yield return new ExpressionNode(null, "__exception", new IdentifierExpression("__exception")); + } + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/TreeNode.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/TreeNode.cs new file mode 100644 index 000000000..fd264110f --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/TreeNode.cs @@ -0,0 +1,94 @@ +// 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.ComponentModel; +using System.Linq; +using System.Windows.Media; + +namespace ILSpy.Debugger.Models.TreeModel +{ + /// + /// A node in the variable tree. + /// The node is imutable. + /// + public class TreeNode : ITreeNode + { + string text = string.Empty; + + IEnumerable childNodes = null; + + public ImageSource ImageSource { get; protected set; } + + public string Name { get; set; } + + public string ImageName { get; set; } + + public virtual string FullName { + get { return Name; } + set { Name = value; } + } + + public virtual string Text + { + get { return text; } + set { text = value; } + } + + public virtual string Type { get; protected set; } + + public virtual IEnumerable ChildNodes { + get { return childNodes; } + protected set { childNodes = value; } + } + + IEnumerable ITreeNode.ChildNodes { + get { return childNodes; } + } + + public virtual bool HasChildNodes { + get { return childNodes != null; } + } + + public virtual bool CanSetText { + get { return false; } + } + + public virtual IEnumerable VisualizerCommands { + get { + return null; + } + } + + public virtual bool HasVisualizerCommands { + get { + return (VisualizerCommands != null) && (VisualizerCommands.Count() > 0); + } + } + + public bool IsPinned { get; set; } + + public TreeNode() + { + } + + public TreeNode(ImageSource iconImage, string name, string text, string type, IEnumerable childNodes) + { + this.ImageSource = iconImage; + this.Name = name; + this.text = text; + this.Type = type; + this.childNodes = childNodes; + } + + public int CompareTo(ITreeNode other) + { + return this.FullName.CompareTo(other.FullName); + } + + public virtual bool SetText(string newValue) { + return false; + } + } +} diff --git a/Debugger/ILSpy.Debugger/Models/TreeModel/Utils.cs b/Debugger/ILSpy.Debugger/Models/TreeModel/Utils.cs new file mode 100644 index 000000000..167f4573b --- /dev/null +++ b/Debugger/ILSpy.Debugger/Models/TreeModel/Utils.cs @@ -0,0 +1,66 @@ +// 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.Windows.Threading; + +using Debugger; + +namespace ILSpy.Debugger.Models.TreeModel +{ + public static partial class Utils + { + /// Process on which to track debuggee state + public static void DoEvents(Process process) + { + if (process == null) return; + DebuggeeState oldState = process.DebuggeeState; + WpfDoEvents(); + DebuggeeState newState = process.DebuggeeState; + if (oldState != newState) { + //LoggingService.Info("Aborted because debuggee resumed"); + throw new AbortedBecauseDebuggeeResumedException(); + } + } + + public static void WpfDoEvents() + { + DispatcherFrame frame = new DispatcherFrame(); + Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => frame.Continue = false)); + Dispatcher.PushFrame(frame); + } + } + + public class AbortedBecauseDebuggeeResumedException: System.Exception + { + public AbortedBecauseDebuggeeResumedException(): base() + { + + } + } + + public class PrintTimes: PrintTime + { + public PrintTimes(string text): base(text + " - end") + { + //LoggingService.InfoFormatted("{0} - start", text); + } + } + + public class PrintTime: IDisposable + { + string text; + System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch(); + + public PrintTime(string text) + { + this.text = text; + stopwatch.Start(); + } + + public void Dispose() + { + stopwatch.Stop(); + //LoggingService.InfoFormatted("{0} ({1} ms)", text, stopwatch.ElapsedMilliseconds); + } + } +} diff --git a/Debugger/ILSpy.Debugger/Services/Debugger/DebuggerHelper.cs b/Debugger/ILSpy.Debugger/Services/Debugger/DebuggerHelper.cs new file mode 100644 index 000000000..21c55ac79 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Services/Debugger/DebuggerHelper.cs @@ -0,0 +1,111 @@ +// 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 Debugger.Interop.CorDebug; +using System; +using System.Collections.Generic; +using System.Reflection; +using Debugger; +using Debugger.MetaData; +using ICSharpCode.NRefactory.Ast; + +namespace ILSpy.Debugger.Services.Debugger +{ + public static class DebuggerHelpers + { + /// + /// Creates an expression which, when evaluated, creates a List<T> in the debugee + /// filled with contents of IEnumerable<T> from the debugee. + /// + /// Expression for IEnumerable variable in the debugee. + /// + /// The generic argument of IEnumerable<T> that implements. + public static Expression CreateDebugListExpression(Expression iEnumerableVariable, DebugType itemType, out DebugType listType) + { + // is using itemType.AppDomain ok? + listType = DebugType.CreateFromType(itemType.AppDomain, typeof(System.Collections.Generic.List<>), itemType); + var iEnumerableType = DebugType.CreateFromType(itemType.AppDomain, typeof(IEnumerable<>), itemType); + // explicitely cast the variable to IEnumerable, where T is itemType + Expression iEnumerableVariableExplicitCast = new CastExpression(iEnumerableType.GetTypeReference() , iEnumerableVariable, CastType.Cast); + return new ObjectCreateExpression(listType.GetTypeReference(), iEnumerableVariableExplicitCast.ToList()); + } + + /// + /// Gets underlying address of object in the debuggee. + /// + public static ulong GetObjectAddress(this Value val) + { + if (val.IsNull) return 0; + ICorDebugReferenceValue refVal = val.CorReferenceValue; + return refVal.GetValue(); + } + + /// + /// Returns true if this type is enum. + /// + public static bool IsEnum(this DebugType type) + { + return (type.BaseType != null) && (type.BaseType.FullName == "System.Enum"); + } + + /// + /// Returns true is this type is just System.Object. + /// + public static bool IsSystemDotObject(this DebugType type) + { + return type.FullName == "System.Object"; + } + + /// + /// Evaluates expression and gets underlying address of object in the debuggee. + /// + public static ulong GetObjectAddress(this Expression expr) + { + return expr.Evaluate(WindowsDebugger.CurrentProcess).GetObjectAddress(); + } + + /// + /// System.Runtime.CompilerServices.GetHashCode method, for obtaining non-overriden hash codes from debuggee. + /// + private static DebugMethodInfo hashCodeMethod; + + /// + /// Invokes RuntimeHelpers.GetHashCode on given value, that is a default hashCode ignoring user overrides. + /// + /// Valid value. + /// Hash code of the object in the debugee. + public static int InvokeDefaultGetHashCode(this Value value) + { + if (hashCodeMethod == null || hashCodeMethod.Process.HasExited) { + DebugType typeRuntimeHelpers = DebugType.CreateFromType(value.AppDomain, typeof(System.Runtime.CompilerServices.RuntimeHelpers)); + hashCodeMethod = (DebugMethodInfo)typeRuntimeHelpers.GetMethod("GetHashCode", BindingFlags.Public | BindingFlags.Static); + if (hashCodeMethod == null) { + throw new DebuggerException("Cannot obtain method System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode"); + } + } + // David: I had hard time finding out how to invoke static method. + // value.InvokeMethod is nice for instance methods. + // what about MethodInfo.Invoke() ? + // also, there could be an overload that takes 1 parameter instead of array + Value defaultHashCode = Eval.InvokeMethod(DebuggerHelpers.hashCodeMethod, null, new Value[]{value}); + + //MethodInfo method = value.Type.GetMember("GetHashCode", BindingFlags.Method | BindingFlags.IncludeSuperType) as MethodInfo; + //string hashCode = value.InvokeMethod(method, null).AsString; + return (int)defaultHashCode.PrimitiveValue; + } + + public static Value EvalPermanentReference(this Expression expr) + { + return expr.Evaluate(WindowsDebugger.CurrentProcess).GetPermanentReference(); + } + + public static bool IsNull(this Expression expr) + { + return expr.Evaluate(WindowsDebugger.CurrentProcess).IsNull; + } + + public static DebugType GetType(this Expression expr) + { + return expr.Evaluate(WindowsDebugger.CurrentProcess).Type; + } + } +} diff --git a/Debugger/ILSpy.Debugger/Services/Debugger/ListHelper.cs b/Debugger/ILSpy.Debugger/Services/Debugger/ListHelper.cs new file mode 100644 index 000000000..f4540c104 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Services/Debugger/ListHelper.cs @@ -0,0 +1,33 @@ +// 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; + +namespace ILSpy.Debugger.Services.Debugger +{ + /// + /// ListHelper wraps System.Collection.Generic.List methods to return the original list, + /// instead of returning 'void', so we can write eg. list.Sorted().First() + /// + public static class ListHelper + { + public static List Sorted(this List list, IComparer comparer) + { + list.Sort(comparer); + return list; + } + + public static List Sorted(this List list) + { + list.Sort(); + return list; + } + + public static List ToList(this T singleItem) + { + var newList = new List(); + newList.Add(singleItem); + return newList; + } + } +} diff --git a/Debugger/ILSpy.Debugger/Services/Debugger/RemotingConfigurationHelpper.cs b/Debugger/ILSpy.Debugger/Services/Debugger/RemotingConfigurationHelpper.cs new file mode 100644 index 000000000..88fafe46e --- /dev/null +++ b/Debugger/ILSpy.Debugger/Services/Debugger/RemotingConfigurationHelpper.cs @@ -0,0 +1,81 @@ +// 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.IO; +using System.Reflection; +using System.Runtime.Remoting; +using System.Security.Policy; + +namespace ILSpy.Debugger.Services.Debugger +{ + [Serializable] + class RemotingConfigurationHelpper + { + public string path; + + public RemotingConfigurationHelpper(string path) + { + this.path = path; + } + + public static string GetLoadedAssemblyPath(string assemblyName) + { + string path = null; + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { + try { + string fullFilename = assembly.Location; + if (Path.GetFileName(fullFilename).Equals(assemblyName, StringComparison.OrdinalIgnoreCase)) { + path = Path.GetDirectoryName(fullFilename); + break; + } + } catch (NotSupportedException) { + // assembly.Location throws NotSupportedException for assemblies emitted using + // Reflection.Emit by custom controls used in the forms designer + } + } + if (path == null) { + throw new Exception("Assembly " + assemblyName + " is not loaded"); + } + return path; + } + + public void Configure() + { + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + + RemotingConfiguration.Configure(Path.Combine(path, "Client.config"), false); + + string baseDir = Directory.GetDirectoryRoot(AppDomain.CurrentDomain.BaseDirectory); + string relDirs = AppDomain.CurrentDomain.BaseDirectory + ";" + path; + AppDomain serverAppDomain = AppDomain.CreateDomain("Debugging server", + new Evidence(AppDomain.CurrentDomain.Evidence), + baseDir, + relDirs, + AppDomain.CurrentDomain.ShadowCopyFiles); + serverAppDomain.DoCallBack(new CrossAppDomainDelegate(ConfigureServer)); + } + + private void ConfigureServer() + { + AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; + RemotingConfiguration.Configure(Path.Combine(path, "Server.config"), false); + } + + Assembly AssemblyResolve(object sender, ResolveEventArgs args) + { + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { + try { + string fullFilename = assembly.Location; + if (Path.GetFileNameWithoutExtension(fullFilename).Equals(args.Name, StringComparison.OrdinalIgnoreCase) || + assembly.FullName == args.Name) { + return assembly; + } + } catch (NotSupportedException) { + // assembly.Location throws NotSupportedException for assemblies emitted using + // Reflection.Emit by custom controls used in the forms designer + } + } + return null; + } + } +} diff --git a/Debugger/ILSpy.Debugger/Services/Debugger/TypeResolverExtension.cs b/Debugger/ILSpy.Debugger/Services/Debugger/TypeResolverExtension.cs new file mode 100644 index 000000000..b913e07d1 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Services/Debugger/TypeResolverExtension.cs @@ -0,0 +1,82 @@ +// 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; +using Debugger.MetaData; +using System.Collections.Generic; +using System.Linq; + +namespace ILSpy.Debugger.Services.Debugger +{ + /// + /// Helper for obtaining information about DebugType. + /// + public static class TypeResolverExtension + { + /// + /// Gets generic interface this type implements. + /// The generic argument of the interface does not have to be specified. + /// If you know the generic argument, use DebugType.GetInterface. + /// + /// Eg. "System.Collections.Generic.IList" + public static DebugType GetGenericInterface(this DebugType type, string fullNamePrefix) + { + foreach(DebugType inter in type.GetInterfaces()) { + if (inter.FullName.StartsWith(fullNamePrefix) && inter.GetGenericArguments().Length > 0) { + return inter; + } + } + // not found, search BaseType + return type.BaseType == null ? null : (DebugType)type.BaseType.GetInterface(fullNamePrefix); + } + + /// + /// Resolves implementation of System.Collections.Generic.IList on this type. + /// + /// Result found implementation of System.Collections.Generic.IList. + /// The only generic argument of + /// True if found, false otherwise. + public static bool ResolveIListImplementation(this DebugType type, out DebugType iListType, out DebugType itemType) + { + return type.ResolveGenericInterfaceImplementation( + "System.Collections.Generic.IList", out iListType, out itemType); + } + + /// + /// Resolves implementation of System.Collections.Generic.IEnumerable on this type. + /// + /// Result found implementation of System.Collections.Generic.IEnumerable. + /// The only generic argument of + /// True if found, false otherwise. + public static bool ResolveIEnumerableImplementation(this DebugType type, out DebugType iEnumerableType, out DebugType itemType) + { + return type.ResolveGenericInterfaceImplementation( + "System.Collections.Generic.IEnumerable", out iEnumerableType, out itemType); + } + + /// + /// Resolves implementation of a single-generic-argument interface on this type. + /// + /// Interface name to search for (eg. "System.Collections.Generic.IList") + /// Result found implementation. + /// The only generic argument of + /// True if found, false otherwise. + public static bool ResolveGenericInterfaceImplementation(this DebugType type, string fullNamePrefix, out DebugType implementation, out DebugType itemType) + { + if (type == null) + throw new ArgumentNullException("type"); + + implementation = null; + itemType = null; + + implementation = type.GetGenericInterface(fullNamePrefix); + if (implementation != null) { + if (implementation.GetGenericArguments().Length == 1) { + itemType = (DebugType)implementation.GetGenericArguments()[0]; + return true; + } + } + return false; + } + } +} diff --git a/Debugger/ILSpy.Debugger/Services/Debugger/WindowsDebugger.cs b/Debugger/ILSpy.Debugger/Services/Debugger/WindowsDebugger.cs new file mode 100644 index 000000000..3a3f85139 --- /dev/null +++ b/Debugger/ILSpy.Debugger/Services/Debugger/WindowsDebugger.cs @@ -0,0 +1,727 @@ +// 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.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows; +using System.Windows.Media; + +using Debugger; +using Debugger.Interop.CorPublish; +using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.Ast; +using ICSharpCode.NRefactory.Visitors; +using ILSpy.Debugger.Bookmarks; +using ILSpy.Debugger.Models.TreeModel; +using ILSpy.Debugger.Services.Debugger; +using CorDbg = Debugger; +using Process = Debugger.Process; + +namespace ILSpy.Debugger.Services +{ + public class WindowsDebugger : IDebugger + { + enum StopAttachedProcessDialogResult { + Detach = 0, + Terminate = 1, + Cancel = 2 + } + + bool useRemotingForThreadInterop = false; + bool attached; + + NDebugger debugger; + + ICorPublish corPublish; + + Process debuggedProcess; + + //DynamicTreeDebuggerRow currentTooltipRow; + //Expression currentTooltipExpression; + + public event EventHandler ProcessSelected; + + public NDebugger DebuggerCore { + get { + return debugger; + } + } + + public Process DebuggedProcess { + get { + return debuggedProcess; + } + } + + public static Process CurrentProcess { + get { + WindowsDebugger dbgr = DebuggerService.CurrentDebugger as WindowsDebugger; + if (dbgr != null && dbgr.DebuggedProcess != null) { + return dbgr.DebuggedProcess; + } else { + return null; + } + } + } + + /// + public bool BreakAtBeginning { + get; + set; + } + + protected virtual void OnProcessSelected(ProcessEventArgs e) + { + if (ProcessSelected != null) { + ProcessSelected(this, e); + } + } + + public bool ServiceInitialized { + get { + return debugger != null; + } + } + + public WindowsDebugger() + { + + } + + #region IDebugger Members + + string errorDebugging = "Error.Debugging"; + string errorNotDebugging = "Error.NotDebugging"; + string errorProcessRunning = "Error.ProcessRunning"; + string errorProcessPaused = "Error.ProcessPaused"; + string errorCannotStepNoActiveFunction = "Threads.CannotStepNoActiveFunction"; + + public bool IsDebugging { + get { + return ServiceInitialized && debuggedProcess != null; + } + } + + public bool IsAttached { + get { + return ServiceInitialized && attached; + } + } + + public bool IsProcessRunning { + get { + return IsDebugging && debuggedProcess.IsRunning; + } + } + + public void Start(ProcessStartInfo processStartInfo) + { + if (IsDebugging) { + MessageBox.Show(errorDebugging); + return; + } + if (!ServiceInitialized) { + InitializeService(); + } + + string version = debugger.GetProgramVersion(processStartInfo.FileName); + + if (version.StartsWith("v1.0")) { + MessageBox.Show("Net10NotSupported"); + } else if (version.StartsWith("v1.1")) { + MessageBox.Show("Net1.1NotSupported"); +// } else if (string.IsNullOrEmpty(version)) { +// // Not a managed assembly +// MessageBox.Show(".Error.BadAssembly}"); + } else if (debugger.IsKernelDebuggerEnabled) { + MessageBox.Show("KernelDebuggerEnabled"); + } else { + attached = false; + if (DebugStarting != null) + DebugStarting(this, EventArgs.Empty); + + try { + Process process = debugger.Start(processStartInfo.FileName, + processStartInfo.WorkingDirectory, + processStartInfo.Arguments); + SelectProcess(process); + } catch (System.Exception e) { + // COMException: The request is not supported. (Exception from HRESULT: 0x80070032) + // COMException: The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log for more detail. (Exception from HRESULT: 0x800736B1) + // COMException: The requested operation requires elevation. (Exception from HRESULT: 0x800702E4) + // COMException: The directory name is invalid. (Exception from HRESULT: 0x8007010B) + // BadImageFormatException: is not a valid Win32 application. (Exception from HRESULT: 0x800700C1) + // UnauthorizedAccessException: Отказано в доступе. (Исключение из HRESULT: 0x80070005 (E_ACCESSDENIED)) + if (e is COMException || e is BadImageFormatException || e is UnauthorizedAccessException) { + string msg = "CannotStartProcess"; + msg += " " + e.Message; + // TODO: Remove + if (e is COMException && ((uint)((COMException)e).ErrorCode == 0x80070032)) { + msg += Environment.NewLine + Environment.NewLine; + msg += "64-bit debugging is not supported. Please set Project -> Project Options... -> Compiling -> Target CPU to 32bit."; + } + MessageBox.Show(msg); + + if (DebugStopped != null) + DebugStopped(this, EventArgs.Empty); + } else { + throw; + } + } + } + } + + public void Attach(System.Diagnostics.Process existingProcess) + { + if (existingProcess == null) + return; + + if (IsDebugging) { + MessageBox.Show(errorDebugging); + return; + } + if (!ServiceInitialized) { + InitializeService(); + } + + string version = debugger.GetProgramVersion(existingProcess.MainModule.FileName); + if (version.StartsWith("v1.0")) { + MessageBox.Show("Net10NotSupported"); + } else { + if (DebugStarting != null) + DebugStarting(this, EventArgs.Empty); + + try { + Process process = debugger.Attach(existingProcess); + attached = true; + SelectProcess(process); + } catch (System.Exception e) { + // CORDBG_E_DEBUGGER_ALREADY_ATTACHED + if (e is COMException || e is UnauthorizedAccessException) { + string msg = "CannotAttachToProcess"; + MessageBox.Show(msg + " " + e.Message); + + if (DebugStopped != null) + DebugStopped(this, EventArgs.Empty); + } else { + throw; + } + } + } + } + + public void Detach() + { + if (debuggedProcess == null) + return; + + debugger.Detach(); + } + + public void StartWithoutDebugging(ProcessStartInfo processStartInfo) + { + System.Diagnostics.Process.Start(processStartInfo); + } + + public void Stop() + { + if (!IsDebugging) { + MessageBox.Show(errorNotDebugging, "Stop"); + return; + } + if (IsAttached) { + Detach(); + } else { + debuggedProcess.Terminate(); + } + } + + // ExecutionControl: + + public void Break() + { + if (!IsDebugging) { + MessageBox.Show(errorNotDebugging, "Break"); + return; + } + if (!IsProcessRunning) { + MessageBox.Show(errorProcessPaused, "Break"); + return; + } + debuggedProcess.Break(); + } + + public void Continue() + { + if (!IsDebugging) { + MessageBox.Show(errorNotDebugging, "Continue"); + return; + } + if (IsProcessRunning) { + MessageBox.Show(errorProcessRunning, "Continue"); + return; + } + debuggedProcess.AsyncContinue(); + } + + // Stepping: + + public void StepInto() + { + if (!IsDebugging) { + MessageBox.Show(errorNotDebugging, "StepInto"); + return; + } + if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { + MessageBox.Show(errorCannotStepNoActiveFunction, "StepInto"); + } else { + debuggedProcess.SelectedStackFrame.AsyncStepInto(); + } + } + + public void StepOver() + { + if (!IsDebugging) { + MessageBox.Show(errorNotDebugging, "StepOver"); + return; + } + if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { + MessageBox.Show(errorCannotStepNoActiveFunction, "StepOver"); + } else { + debuggedProcess.SelectedStackFrame.AsyncStepOver(); + } + } + + public void StepOut() + { + if (!IsDebugging) { + MessageBox.Show(errorNotDebugging, "StepOut"); + return; + } + if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { + MessageBox.Show(errorCannotStepNoActiveFunction, "StepOut"); + } else { + debuggedProcess.SelectedStackFrame.AsyncStepOut(); + } + } + + public event EventHandler DebugStarting; + public event EventHandler DebugStarted; + public event EventHandler DebugStopped; + public event EventHandler IsProcessRunningChanged; + + protected virtual void OnIsProcessRunningChanged(EventArgs e) + { + if (IsProcessRunningChanged != null) { + IsProcessRunningChanged(this, e); + } + } + + /// + /// Gets variable of given name. + /// Returns null if unsuccessful. Can throw GetValueException. + /// Thrown when evaluation fails. Exception message explains reason. + /// + public Value GetValueFromName(string variableName) + { + if (!CanEvaluate) { + return null; + } + return ExpressionEvaluator.Evaluate(variableName, SupportedLanguage.CSharp, debuggedProcess.SelectedStackFrame); + } + + /// + /// Gets Expression for given variable. Can throw GetValueException. + /// Thrown when getting expression fails. Exception message explains reason. + /// + public ICSharpCode.NRefactory.Ast.Expression GetExpression(string variableName) + { + if (!CanEvaluate) { + throw new GetValueException("Cannot evaluate now - debugged process is either null or running or has no selected stack frame"); + } + return ExpressionEvaluator.ParseExpression(variableName, SupportedLanguage.CSharp); + } + + public bool IsManaged(int processId) + { + corPublish = new CorpubPublishClass(); + CorDbg.Interop.TrackedComObjects.Track(corPublish); + + ICorPublishProcess process = corPublish.GetProcess((uint)processId); + if (process != null) { + return process.IsManaged() != 0; + } + return false; + } + + /// + /// Gets the current value of the variable as string that can be displayed in tooltips. + /// Returns null if unsuccessful. + /// + public string GetValueAsString(string variableName) + { + try { + Value val = GetValueFromName(variableName); + if (val == null) return null; + return val.AsString(); + } catch (GetValueException) { + return null; + } + } + + bool CanEvaluate + { + get { + return debuggedProcess != null && !debuggedProcess.IsRunning && debuggedProcess.SelectedStackFrame != null; + } + } + + /// + /// Gets the tooltip control that shows the value of given variable. + /// Return null if no tooltip is available. + /// + public object GetTooltipControl(Location logicalPosition, string variableName) + { + try { + var tooltipExpression = GetExpression(variableName); + string imageName; + var image = ExpressionNode.GetImageForLocalVariable(out imageName); + ExpressionNode expressionNode = new ExpressionNode(image, variableName, tooltipExpression); + expressionNode.ImageName = imageName; + return null; + // return new DebuggerTooltipControl(logicalPosition, expressionNode); + } catch (GetValueException) { + return null; + } + } + + public ITreeNode GetNode(string variable, string currentImageName = null) + { + try { + var expression = GetExpression(variable); + string imageName; + ImageSource image; + if (string.IsNullOrEmpty(currentImageName)) { + image = ExpressionNode.GetImageForLocalVariable(out imageName); + } + else { + image = ImageService.GetImage(currentImageName); + imageName = currentImageName; + } + ExpressionNode expressionNode = new ExpressionNode(image, variable, expression); + expressionNode.ImageName = imageName; + return expressionNode; + } catch (GetValueException) { + return null; + } + } + + public bool CanSetInstructionPointer(string filename, int line, int column) + { + if (debuggedProcess != null && debuggedProcess.IsPaused && debuggedProcess.SelectedStackFrame != null) { + SourcecodeSegment seg = debuggedProcess.SelectedStackFrame.CanSetIP(filename, line, column); + return seg != null; + } else { + return false; + } + } + + public bool SetInstructionPointer(string filename, int line, int column) + { + if (CanSetInstructionPointer(filename, line, column)) { + SourcecodeSegment seg = debuggedProcess.SelectedStackFrame.SetIP(filename, line, column); + return seg != null; + } else { + return false; + } + } + + public void Dispose() + { + Stop(); + } + + #endregion + + public event EventHandler Initialize; + + public void InitializeService() + { + if (useRemotingForThreadInterop) { + // This needs to be called before instance of NDebugger is created + string path = RemotingConfigurationHelpper.GetLoadedAssemblyPath("Debugger.Core.dll"); + new RemotingConfigurationHelpper(path).Configure(); + } + + debugger = new NDebugger(); + + //debugger.Options = DebuggingOptions.Instance; + + debugger.DebuggerTraceMessage += debugger_TraceMessage; + debugger.Processes.Added += debugger_ProcessStarted; + debugger.Processes.Removed += debugger_ProcessExited; + + DebuggerService.BreakPointAdded += delegate (object sender, BreakpointBookmarkEventArgs e) { + AddBreakpoint(e.BreakpointBookmark); + }; + + foreach (BreakpointBookmark b in DebuggerService.Breakpoints) { + AddBreakpoint(b); + } + + if (Initialize != null) { + Initialize(this, null); + } + } + + bool Compare(byte[] a, byte[] b) + { + if (a.Length != b.Length) return false; + for(int i = 0; i < a.Length; i++) { + if (a[i] != b[i]) return false; + } + return true; + } + + void AddBreakpoint(BreakpointBookmark bookmark) + { + Breakpoint breakpoint = debugger.Breakpoints.Add(bookmark.TypeName, null, bookmark.LineNumber, 0, bookmark.IsEnabled); +// Action setBookmarkColor = delegate { +// if (debugger.Processes.Count == 0) { +// bookmark.IsHealthy = true; +// bookmark.Tooltip = null; +// } else if (!breakpoint.IsSet) { +// bookmark.IsHealthy = false; +// bookmark.Tooltip = "Breakpoint was not found in any loaded modules"; +// } else if (breakpoint.OriginalLocation.CheckSum == null) { +// bookmark.IsHealthy = true; +// bookmark.Tooltip = null; +// } else { +// byte[] fileMD5; +// IEditable file = FileService.GetOpenFile(bookmark.FileName) as IEditable; +// if (file != null) { +// byte[] fileContent = Encoding.UTF8.GetBytesWithPreamble(file.Text); +// fileMD5 = new MD5CryptoServiceProvider().ComputeHash(fileContent); +// } else { +// fileMD5 = new MD5CryptoServiceProvider().ComputeHash(File.ReadAllBytes(bookmark.FileName)); +// } +// if (Compare(fileMD5, breakpoint.OriginalLocation.CheckSum)) { +// bookmark.IsHealthy = true; +// bookmark.Tooltip = null; +// } else { +// bookmark.IsHealthy = false; +// bookmark.Tooltip = "Check sum or file does not match to the original"; +// } +// } +// }; + + // event handlers on bookmark and breakpoint don't need deregistration + bookmark.IsEnabledChanged += delegate { + breakpoint.Enabled = bookmark.IsEnabled; + }; + breakpoint.Set += delegate { + //setBookmarkColor(); + }; + + //setBookmarkColor(); + + EventHandler> bp_debugger_ProcessStarted = (sender, e) => { + //setBookmarkColor(); + // User can change line number by inserting or deleting lines + breakpoint.Line = bookmark.LineNumber; + }; + EventHandler> bp_debugger_ProcessExited = (sender, e) => { + //setBookmarkColor(); + }; + + EventHandler bp_debugger_BreakpointHit = + new EventHandler( + delegate(object sender, BreakpointEventArgs e) + { + //LoggingService.Debug(bookmark.Action + " " + bookmark.ScriptLanguage + " " + bookmark.Condition); + + switch (bookmark.Action) { + case BreakpointAction.Break: + break; + case BreakpointAction.Condition: +// if (Evaluate(bookmark.Condition, bookmark.ScriptLanguage)) +// DebuggerService.PrintDebugMessage(string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHitAtBecause}") + "\n", bookmark.LineNumber, bookmark.FileName, bookmark.Condition)); +// else +// this.debuggedProcess.AsyncContinue(); + break; + case BreakpointAction.Trace: + //DebuggerService.PrintDebugMessage(string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHitAt}") + "\n", bookmark.LineNumber, bookmark.FileName)); + break; + } + }); + + BookmarkEventHandler bp_bookmarkManager_Removed = null; + bp_bookmarkManager_Removed = (sender, e) => { + if (bookmark == e.Bookmark) { + debugger.Breakpoints.Remove(breakpoint); + + // unregister the events + debugger.Processes.Added -= bp_debugger_ProcessStarted; + debugger.Processes.Removed -= bp_debugger_ProcessExited; + breakpoint.Hit -= bp_debugger_BreakpointHit; + BookmarkManager.Removed -= bp_bookmarkManager_Removed; + } + }; + // register the events + debugger.Processes.Added += bp_debugger_ProcessStarted; + debugger.Processes.Removed += bp_debugger_ProcessExited; + breakpoint.Hit += bp_debugger_BreakpointHit; + BookmarkManager.Removed += bp_bookmarkManager_Removed; + } + + bool Evaluate(string code, string language) + { + try { + SupportedLanguage supportedLanguage = (SupportedLanguage)Enum.Parse(typeof(SupportedLanguage), language, true); + Value val = ExpressionEvaluator.Evaluate(code, supportedLanguage, debuggedProcess.SelectedStackFrame); + + if (val != null && val.Type.IsPrimitive && val.PrimitiveValue is bool) + return (bool)val.PrimitiveValue; + else + return false; + } catch (GetValueException e) { + string errorMessage = "Error while evaluating breakpoint condition " + code + ":\n" + e.Message + "\n"; + //DebuggerService.PrintDebugMessage(errorMessage); + //WorkbenchSingleton.SafeThreadAsyncCall(MessageService.ShowWarning, errorMessage); + return true; + } + } + + void LogMessage(object sender, MessageEventArgs e) + { + //DebuggerService.PrintDebugMessage(e.Message); + } + + void debugger_TraceMessage(object sender, MessageEventArgs e) + { + //LoggingService.Debug("Debugger: " + e.Message); + } + + void debugger_ProcessStarted(object sender, CollectionItemEventArgs e) + { + if (debugger.Processes.Count == 1) { + if (DebugStarted != null) { + DebugStarted(this, EventArgs.Empty); + } + } + e.Item.LogMessage += LogMessage; + } + + void debugger_ProcessExited(object sender, CollectionItemEventArgs e) + { + if (debugger.Processes.Count == 0) { + if (DebugStopped != null) { + DebugStopped(this, e); + } + SelectProcess(null); + } else { + SelectProcess(debugger.Processes[0]); + } + } + + public void SelectProcess(Process process) + { + if (debuggedProcess != null) { + debuggedProcess.Paused -= debuggedProcess_DebuggingPaused; + debuggedProcess.ExceptionThrown -= debuggedProcess_ExceptionThrown; + debuggedProcess.Resumed -= debuggedProcess_DebuggingResumed; + } + debuggedProcess = process; + if (debuggedProcess != null) { + debuggedProcess.Paused += debuggedProcess_DebuggingPaused; + debuggedProcess.ExceptionThrown += debuggedProcess_ExceptionThrown; + debuggedProcess.Resumed += debuggedProcess_DebuggingResumed; + + debuggedProcess.BreakAtBeginning = BreakAtBeginning; + } + // reset + BreakAtBeginning = false; + + JumpToCurrentLine(); + OnProcessSelected(new ProcessEventArgs(process)); + } + + void debuggedProcess_DebuggingPaused(object sender, ProcessEventArgs e) + { + OnIsProcessRunningChanged(EventArgs.Empty); + + //using(new PrintTimes("Jump to current line")) { + JumpToCurrentLine(); + //} + // TODO update tooltip + /*if (currentTooltipRow != null && currentTooltipRow.IsShown) { + using(new PrintTimes("Update tooltip")) { + try { + Utils.DoEvents(debuggedProcess); + AbstractNode updatedNode = ValueNode.Create(currentTooltipExpression); + currentTooltipRow.SetContentRecursive(updatedNode); + } catch (AbortedBecauseDebuggeeResumedException) { + } + } + }*/ + } + + void debuggedProcess_DebuggingResumed(object sender, CorDbg.ProcessEventArgs e) + { + OnIsProcessRunningChanged(EventArgs.Empty); + DebuggerService.RemoveCurrentLineMarker(); + } + + void debuggedProcess_ExceptionThrown(object sender, CorDbg.ExceptionEventArgs e) + { + if (!e.IsUnhandled) { + // Ignore the exception + e.Process.AsyncContinue(); + return; + } + + JumpToCurrentLine(); + + StringBuilder stacktraceBuilder = new StringBuilder(); + + // Need to intercept now so that we can evaluate properties + if (e.Process.SelectedThread.InterceptCurrentException()) { + stacktraceBuilder.AppendLine(e.Exception.ToString()); + string stackTrace; + try { + stackTrace = e.Exception.GetStackTrace("--- End of inner exception stack trace ---"); + } catch (GetValueException) { + stackTrace = e.Process.SelectedThread.GetStackTrace("at {0} in {1}:line {2}", "at {0}"); + } + stacktraceBuilder.Append(stackTrace); + } else { + // For example, happens on stack overflow + stacktraceBuilder.AppendLine("CannotInterceptException"); + stacktraceBuilder.AppendLine(e.Exception.ToString()); + stacktraceBuilder.Append(e.Process.SelectedThread.GetStackTrace("at {0} in {1}:line {2}", "at {0}")); + } + + string title = e.IsUnhandled ? "Unhandled" : "Handled"; + string message = string.Format("Message {0} {1}", e.Exception.Type, e.Exception.Message); + + MessageBox.Show(message + stacktraceBuilder.ToString(), title); + } + + public void JumpToCurrentLine() + { + DebuggerService.RemoveCurrentLineMarker(); + if (debuggedProcess != null) { + SourcecodeSegment nextStatement = debuggedProcess.NextStatement; + if (nextStatement != null) { + DebuggerService.JumpToCurrentLine(nextStatement.Filename, nextStatement.StartLine, nextStatement.StartColumn, nextStatement.EndLine, nextStatement.EndColumn); + } + } + } + + public void ShowAttachDialog() + { + throw new NotImplementedException(); + } + } +} diff --git a/Debugger/ILSpy.Debugger/Services/ImageService/ImageService.cs b/Debugger/ILSpy.Debugger/Services/ImageService/ImageService.cs index 585e3f4b4..cb755c3f1 100644 --- a/Debugger/ILSpy.Debugger/Services/ImageService/ImageService.cs +++ b/Debugger/ILSpy.Debugger/Services/ImageService/ImageService.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Windows.Media; using System.Windows.Media.Imaging; namespace ILSpy.Debugger.Services @@ -32,5 +33,10 @@ namespace ILSpy.Debugger.Services public static readonly BitmapImage Breakpoint = LoadBitmap("Breakpoint"); public static readonly BitmapImage CurrentLine = LoadBitmap("CurrentLine"); + + public static ImageSource GetImage(string imageName) + { + return LoadBitmap(imageName); + } } } diff --git a/Debugger/ILSpy.Debugger/UI/AttachToProcessWindow.xaml.cs b/Debugger/ILSpy.Debugger/UI/AttachToProcessWindow.xaml.cs index 2e23d2f32..d7c3d8cc5 100644 --- a/Debugger/ILSpy.Debugger/UI/AttachToProcessWindow.xaml.cs +++ b/Debugger/ILSpy.Debugger/UI/AttachToProcessWindow.xaml.cs @@ -25,6 +25,7 @@ using System.Linq; using System.Windows; using ILSpy.Debugger.Models; +using ILSpy.Debugger.Services; namespace ILSpy.Debugger.UI { @@ -33,6 +34,13 @@ namespace ILSpy.Debugger.UI /// public partial class AttachToProcessWindow : Window { + public static IDebugger Debugger { get; private set; } + + static AttachToProcessWindow() + { + Debugger = new WindowsDebugger(); + } + public AttachToProcessWindow() { InitializeComponent(); @@ -89,6 +97,8 @@ namespace ILSpy.Debugger.UI // start attaching var process = ((RunningProcess)this.RunningProcesses.SelectedItem).Process; + Debugger.Attach(process); + this.DialogResult = true; } void CancelButton_Click(object sender, RoutedEventArgs e) diff --git a/ILSpy/Commands/RoutedUICommands.cs b/ILSpy/Commands/RoutedUICommands.cs index 38886eb0b..91b431c09 100644 --- a/ILSpy/Commands/RoutedUICommands.cs +++ b/ILSpy/Commands/RoutedUICommands.cs @@ -26,8 +26,11 @@ namespace ICSharpCode.ILSpy.Commands static RoutedUICommands() { AttachToProcess = new RoutedUICommand("Attach to running process...", "AttachToProcess", typeof(RoutedUICommands)); + DetachFromProcess = new RoutedUICommand("Detach from process...", "DetachFromProcess", typeof(RoutedUICommands)); } public static RoutedUICommand AttachToProcess { get; private set; } + + public static RoutedUICommand DetachFromProcess { get; private set; } } } diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index be979225a..ed4fcfe40 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -24,6 +24,9 @@ + @@ -63,11 +66,12 @@ - + + @@ -104,9 +108,9 @@ ItemsSource="{x:Static local:Languages.AllLanguages}" SelectedItem="{Binding FilterSettings.Language}" /> - + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 4c0e03106..2445e675c 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -334,7 +334,19 @@ namespace ICSharpCode.ILSpy { var window = new AttachToProcessWindow(); window.Owner = this; - window.ShowDialog(); + if (window.ShowDialog() == true) + { + AttachMenuItem.IsEnabled = AttachButton.IsEnabled = false; + DetachMenuItem.IsEnabled = true; + } + } + + void DetachFromProcessExecuted(object sender, ExecutedRoutedEventArgs e) + { + AttachToProcessWindow.Debugger.Detach(); + + AttachMenuItem.IsEnabled = AttachButton.IsEnabled = true; + DetachMenuItem.IsEnabled = false; } void ExitClick(object sender, RoutedEventArgs e)