From 8a5b5547d4a087f9cdd99d676b3b3ac15347d2da Mon Sep 17 00:00:00 2001 From: Ronny Klier Date: Mon, 8 Aug 2011 13:45:46 +0200 Subject: [PATCH] Added windows for breakpoints and callstack + fix crash when no MemberReference is found for code mapping --- .../Commands/BreakpointCommand.cs | 12 +- Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj | 10 + .../ILSpy.Debugger/UI/BreakpointPanel.xaml | 73 ++++++ .../ILSpy.Debugger/UI/BreakpointPanel.xaml.cs | 99 ++++++++ .../ILSpy.Debugger/UI/CallStackPanel.xaml | 35 +++ .../ILSpy.Debugger/UI/CallStackPanel.xaml.cs | 215 ++++++++++++++++++ .../AvalonEdit/IconBarMargin.cs | 55 ++++- 7 files changed, 484 insertions(+), 15 deletions(-) create mode 100644 Debugger/ILSpy.Debugger/UI/BreakpointPanel.xaml create mode 100644 Debugger/ILSpy.Debugger/UI/BreakpointPanel.xaml.cs create mode 100644 Debugger/ILSpy.Debugger/UI/CallStackPanel.xaml create mode 100644 Debugger/ILSpy.Debugger/UI/CallStackPanel.xaml.cs diff --git a/Debugger/ILSpy.Debugger/Commands/BreakpointCommand.cs b/Debugger/ILSpy.Debugger/Commands/BreakpointCommand.cs index 52a2a2a24..5d179bb0b 100644 --- a/Debugger/ILSpy.Debugger/Commands/BreakpointCommand.cs +++ b/Debugger/ILSpy.Debugger/Commands/BreakpointCommand.cs @@ -9,6 +9,7 @@ using ICSharpCode.ILSpy.AvalonEdit; using ICSharpCode.ILSpy.Bookmarks; using ICSharpCode.ILSpy.Debugger.Bookmarks; using ICSharpCode.ILSpy.Debugger.Services; +using Mono.Cecil; namespace ICSharpCode.ILSpy.Debugger.Commands { @@ -27,16 +28,21 @@ namespace ICSharpCode.ILSpy.Debugger.Commands // check if the codemappings exists for this line var storage = DebugInformation.CodeMappings; int token = 0; - foreach (var key in storage.Keys) { - var instruction = storage[key].GetInstructionByLineNumber(line, out token); + foreach (var storageEntry in storage) { + var instruction = storageEntry.Value.GetInstructionByLineNumber(line, out token); if (instruction == null) { continue; } // no bookmark on the line: create a new breakpoint + MemberReference memberReference; + if (!DebugInformation.DecompiledMemberReferences.TryGetValue(storageEntry.Key, out memberReference)) { + continue; + } + DebuggerService.ToggleBreakpointAt( - DebugInformation.DecompiledMemberReferences[key], + memberReference, line, instruction.ILInstructionOffset, DebugInformation.Language); diff --git a/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj b/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj index f5feab262..bd7405a7a 100644 --- a/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj +++ b/Debugger/ILSpy.Debugger/ILSpy.Debugger.csproj @@ -109,6 +109,14 @@ AttachToProcessWindow.xaml Code + + BreakpointPanel.xaml + Code + + + CallStackPanel.xaml + Code + DebuggerSettingsPanel.xaml Code @@ -130,6 +138,8 @@ + + diff --git a/Debugger/ILSpy.Debugger/UI/BreakpointPanel.xaml b/Debugger/ILSpy.Debugger/UI/BreakpointPanel.xaml new file mode 100644 index 000000000..8ee9f32c6 --- /dev/null +++ b/Debugger/ILSpy.Debugger/UI/BreakpointPanel.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Debugger/ILSpy.Debugger/UI/BreakpointPanel.xaml.cs b/Debugger/ILSpy.Debugger/UI/BreakpointPanel.xaml.cs new file mode 100644 index 000000000..f5419220c --- /dev/null +++ b/Debugger/ILSpy.Debugger/UI/BreakpointPanel.xaml.cs @@ -0,0 +1,99 @@ +// 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 System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using ICSharpCode.ILSpy; +using ICSharpCode.ILSpy.Bookmarks; + +namespace ILSpyPlugin +{ + /// + /// Interaction logic for BreakpointPanel.xaml + /// + public partial class BreakpointPanel : UserControl, IPane + { + static BreakpointPanel s_instance; + + public static BreakpointPanel Instance + { + get { + if (null == s_instance) + { + App.Current.VerifyAccess(); + s_instance = new BreakpointPanel(); + } + return s_instance; + } + } + + private BreakpointPanel() + { + InitializeComponent(); + view.ItemsSource = BookmarkManager.Bookmarks; + BookmarkManager.Added += new BookmarkEventHandler(OnAdded); + BookmarkManager.Removed += new BookmarkEventHandler(OnRemoved); + } + + public void Show() + { + if (!IsVisible) + MainWindow.Instance.ShowInBottomPane("Breakpoints", this); + } + + private void OnAdded(object sender, BookmarkEventArgs e) + { + view.ItemsSource = null; + view.ItemsSource = BookmarkManager.Bookmarks; + } + private void OnRemoved(object sender, BookmarkEventArgs e) + { + view.ItemsSource = null; + view.ItemsSource = BookmarkManager.Bookmarks; + } + + + public void Closed() + { + } + + void view_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + if (MouseButton.Left != e.ChangedButton) + return; + var selectedItem = view.SelectedItem as BookmarkBase; + if (null == selectedItem) + return; + // TODO: Line should be part of jump target + MainWindow.Instance.JumpToReference(selectedItem.MemberReference); + MainWindow.Instance.TextView.UnfoldAndScroll(selectedItem.LineNumber); + e.Handled = true; + } + + void view_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key != Key.Delete) + return; + var selectedItem = view.SelectedItem as BookmarkBase; + if (null == selectedItem) + return; + BookmarkManager.RemoveMark(selectedItem); + e.Handled = true; + } + } + + [ExportMainMenuCommand(Menu="_Debugger", Header="Show _Breakpoints", MenuCategory="Others", MenuOrder=8)] + public class BookmarkManagerPanelCommand : SimpleCommand + { + public override void Execute(object parameter) + { + BreakpointPanel.Instance.Show(); + } + } +} \ No newline at end of file diff --git a/Debugger/ILSpy.Debugger/UI/CallStackPanel.xaml b/Debugger/ILSpy.Debugger/UI/CallStackPanel.xaml new file mode 100644 index 000000000..7e6665431 --- /dev/null +++ b/Debugger/ILSpy.Debugger/UI/CallStackPanel.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Debugger/ILSpy.Debugger/UI/CallStackPanel.xaml.cs b/Debugger/ILSpy.Debugger/UI/CallStackPanel.xaml.cs new file mode 100644 index 000000000..ab5bc25c2 --- /dev/null +++ b/Debugger/ILSpy.Debugger/UI/CallStackPanel.xaml.cs @@ -0,0 +1,215 @@ +// 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.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; + +using Debugger; +using ICSharpCode.ILSpy; +using ICSharpCode.ILSpy.Debugger.Models.TreeModel; +using ICSharpCode.ILSpy.Debugger.Services; +using ICSharpCode.ILSpy.XmlDoc; +using Mono.Cecil; + +namespace ILSpyPlugin +{ + /// + /// Interaction logic for CallStackPanel.xaml + /// + public partial class CallStackPanel : UserControl + { + static CallStackPanel s_instance; + IDebugger m_currentDebugger; + Process debuggedProcess; + + public static CallStackPanel Instance + { + get { + if (null == s_instance) + { + App.Current.VerifyAccess(); + s_instance = new CallStackPanel(); + } + return s_instance; + } + } + + private CallStackPanel() + { + InitializeComponent(); + + DebuggerService.DebugStarted += new EventHandler(OnDebugStarted); + DebuggerService.DebugStopped += new EventHandler(OnDebugStopped); + if (DebuggerService.IsDebuggerStarted) + OnDebugStarted(null, EventArgs.Empty); + } + + public void Show() + { + if (!IsVisible) + MainWindow.Instance.ShowInBottomPane("Callstack", this); + } + + void OnDebugStarted(object sender, EventArgs args) + { + m_currentDebugger = DebuggerService.CurrentDebugger; + m_currentDebugger.IsProcessRunningChanged += new EventHandler(OnProcessRunningChanged); + debuggedProcess = ((WindowsDebugger)m_currentDebugger).DebuggedProcess; + OnProcessRunningChanged(null, EventArgs.Empty); + } + + void OnDebugStopped(object sender, EventArgs args) + { + m_currentDebugger.IsProcessRunningChanged -= new EventHandler(OnProcessRunningChanged); + m_currentDebugger = null; + debuggedProcess = null; + } + + void OnProcessRunningChanged(object sender, EventArgs args) + { + if (m_currentDebugger.IsProcessRunning) + return; + RefreshPad(); + } + + void RefreshPad() + { + if (debuggedProcess == null || debuggedProcess.IsRunning || debuggedProcess.SelectedThread == null) { + view.ItemsSource = null; + return; + } + + List items = null; + StackFrame activeFrame = null; + try { + Utils.DoEvents(debuggedProcess); + items = CreateItems().ToList(); + activeFrame = debuggedProcess.SelectedThread.SelectedStackFrame; + } catch(AbortedBecauseDebuggeeResumedException) { + } catch(System.Exception) { + if (debuggedProcess == null || debuggedProcess.HasExited) { + // Process unexpectedly exited + } else { + throw; + } + } + view.ItemsSource = items; + view.SelectedItem = items != null ? items.FirstOrDefault(item => object.Equals(activeFrame, item.Frame)) : null; + } + + IEnumerable CreateItems() + { + foreach (StackFrame frame in debuggedProcess.SelectedThread.GetCallstack(100)) { + CallStackItem item; + + // show modules names + string moduleName = frame.MethodInfo.DebugModule.ToString(); + + item = new CallStackItem() { + Name = GetFullName(frame), ModuleName = moduleName + }; + item.Frame = frame; + yield return item; + Utils.DoEvents(debuggedProcess); + } + } + + internal static string GetFullName(StackFrame frame) + { + // disabled by default, my be switched if options / context menu is added + bool showArgumentNames = false; + bool showArgumentValues = false; + + StringBuilder name = new StringBuilder(); + name.Append(frame.MethodInfo.DeclaringType.FullName); + name.Append('.'); + name.Append(frame.MethodInfo.Name); + if (showArgumentNames || showArgumentValues) { + name.Append("("); + for (int i = 0; i < frame.ArgumentCount; i++) { + string parameterName = null; + string argValue = null; + if (showArgumentNames) { + try { + parameterName = frame.MethodInfo.GetParameters()[i].Name; + } catch { } + if (parameterName == "") parameterName = null; + } + if (showArgumentValues) { + try { + argValue = frame.GetArgumentValue(i).AsString(100); + } catch { } + } + if (parameterName != null && argValue != null) { + name.Append(parameterName); + name.Append("="); + name.Append(argValue); + } + if (parameterName != null && argValue == null) { + name.Append(parameterName); + } + if (parameterName == null && argValue != null) { + name.Append(argValue); + } + if (parameterName == null && argValue == null) { + name.Append("Global.NA"); + } + if (i < frame.ArgumentCount - 1) { + name.Append(", "); + } + } + name.Append(")"); + } + + return name.ToString(); + } + + + void view_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + if (MouseButton.Left != e.ChangedButton) + return; + var selectedItem = view.SelectedItem as CallStackItem; + if (null == selectedItem) + return; + var foundAssembly = MainWindow.Instance.CurrentAssemblyList.OpenAssembly(selectedItem.Frame.MethodInfo.DebugModule.FullPath); + if (null == foundAssembly || null == foundAssembly.AssemblyDefinition) + return; + + MemberReference mr = XmlDocKeyProvider.FindMemberByKey(foundAssembly.AssemblyDefinition.MainModule, "M:" + selectedItem.Name); + if (mr == null) + return; + MainWindow.Instance.JumpToReference(mr); + e.Handled = true; + } + } + + public class CallStackItem + { + public string Name { get; set; } + public string Language { get; set; } + public StackFrame Frame { get; set; } + public string Line { get; set; } + public string ModuleName { get; set; } + + public Brush FontColor { + get { return Brushes.Black; } + } + } + + [ExportMainMenuCommand(Menu="_Debugger", Header="Show _Callstack", MenuCategory="Others", MenuOrder=9)] + public class CallstackPanelcommand : SimpleCommand + { + public override void Execute(object parameter) + { + CallStackPanel.Instance.Show(); + } + } +} \ No newline at end of file diff --git a/ILSpy.SharpDevelop.LGPL/AvalonEdit/IconBarMargin.cs b/ILSpy.SharpDevelop.LGPL/AvalonEdit/IconBarMargin.cs index b4e68f10a..74426f437 100644 --- a/ILSpy.SharpDevelop.LGPL/AvalonEdit/IconBarMargin.cs +++ b/ILSpy.SharpDevelop.LGPL/AvalonEdit/IconBarMargin.cs @@ -26,8 +26,8 @@ namespace ICSharpCode.ILSpy.AvalonEdit public IconBarMargin(IconBarManager manager) { - BookmarkManager.Added += delegate { InvalidateVisual(); }; - BookmarkManager.Removed += delegate { InvalidateVisual(); }; + BookmarkManager.Added += new BookmarkEventHandler(OnBookmarkAdded); + BookmarkManager.Removed += new BookmarkEventHandler(OnBookmarkRemoved); this.manager = manager; } @@ -242,6 +242,39 @@ namespace ICSharpCode.ILSpy.AvalonEdit } } + public void OnBookmarkAdded(object sender, BookmarkEventArgs args) + { + var breakpoint = args.Bookmark as BreakpointBookmark; + if (null == breakpoint) + return; + var storage = DebugInformation.CodeMappings; + if (storage == null || storage.Count == 0) + return; + var key = breakpoint.MemberReference.MetadataToken.ToInt32(); + if (storage.ContainsKey(key)) + { + // register to show enabled/disabled state + breakpoint.ImageChanged += delegate { InvalidateVisual(); }; + InvalidateVisual(); + } + } + + public void OnBookmarkRemoved(object sender, BookmarkEventArgs args) + { + var breakpoint = args.Bookmark as BreakpointBookmark; + if (null == breakpoint) + return; + var storage = DebugInformation.CodeMappings; + if (storage == null || storage.Count == 0) + return; + var key = breakpoint.MemberReference.MetadataToken.ToInt32(); + if (storage.ContainsKey(key)) + { + breakpoint.ImageChanged -= delegate { InvalidateVisual(); }; + InvalidateVisual(); + } + } + public void SyncBookmarks() { var storage = DebugInformation.CodeMappings; @@ -249,7 +282,6 @@ namespace ICSharpCode.ILSpy.AvalonEdit return; //remove existing bookmarks and create new ones - List newBookmarks = new List(); for (int i = BookmarkManager.Bookmarks.Count - 1; i >= 0; --i) { var breakpoint = BookmarkManager.Bookmarks[i] as BreakpointBookmark; if (breakpoint == null) @@ -257,7 +289,12 @@ namespace ICSharpCode.ILSpy.AvalonEdit var key = breakpoint.MemberReference.MetadataToken.ToInt32(); if (!storage.ContainsKey(key)) - continue; + { + // in case this was visible before + breakpoint.ImageChanged -= delegate { InvalidateVisual(); }; + continue; + } + breakpoint.ImageChanged += delegate { InvalidateVisual(); }; var member = DebugInformation.DecompiledMemberReferences[key]; @@ -271,17 +308,11 @@ namespace ICSharpCode.ILSpy.AvalonEdit breakpoint.Location = new AstLocation(map.SourceCodeLine, 0); breakpoint.ILRange = map.ILInstructionOffset; - // Why were the breakpoints removed and recreated -// newBookmarks.Add(new BreakpointBookmark( -// member, new AstLocation(map.SourceCodeLine, 0), -// map.ILInstructionOffset, BreakpointAction.Break, DebugInformation.Language)); -// -// BookmarkManager.RemoveMark(breakpoint); + // Why were the breakpoints removed and recreated? + // This prevents enable/disable of breakpoints } } - newBookmarks.ForEach(m => BookmarkManager.AddMark(m)); - SyncCurrentLineBookmark(); }