diff options
author | David Karlaš <david.karlas@xamarin.com> | 2015-01-14 22:31:49 +0300 |
---|---|---|
committer | David Karlaš <david.karlas@xamarin.com> | 2015-01-14 22:32:04 +0300 |
commit | af2ecb3305d213b204c229856b6000e45ffc2ffe (patch) | |
tree | 26ac3ede906e10a2f09fab5b69af07b75581b139 /main/src/addins/MonoDevelop.Debugger | |
parent | 8750ddd3794d7817bc41e479951f00755dc9db9f (diff) |
Bug 2852 - Debugger cannot remap source paths
Diffstat (limited to 'main/src/addins/MonoDevelop.Debugger')
4 files changed, 325 insertions, 18 deletions
diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj index 01ef9011cf..8f2307d023 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj @@ -148,6 +148,7 @@ <Compile Include="MonoDevelop.Debugger\DebuggerEngineBackend.cs" /> <Compile Include="MonoDevelop.Debugger\BreakpointPropertiesDialog.cs" /> <Compile Include="MonoDevelop.Debugger\TextEntryWithCodeCompletion.cs" /> + <Compile Include="MonoDevelop.Debugger\SourceCodeLookup.cs" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="MonoDevelop.Debugger.addin.xml"> diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DisassemblyView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DisassemblyView.cs index 19a1df9ded..90973bb76f 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DisassemblyView.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DisassemblyView.cs @@ -39,6 +39,12 @@ using TextEditor = Mono.TextEditor.TextEditor; using Mono.TextEditor; using Mono.Debugging.Client; using Mono.TextEditor.Highlighting; +using Gtk; +using MonoDevelop.Ide.Gui.Dialogs; +using MonoDevelop.Ide; +using System.Security.Cryptography; +using Gdk; +using MonoDevelop.Components; namespace MonoDevelop.Debugger { @@ -87,6 +93,68 @@ namespace MonoDevelop.Debugger DebuggingService.StoppedEvent += OnStop; } + + OverlayMessageWindow messageOverlayWindow; + + void ShowLoadSourceFile (StackFrame sf) + { + if (messageOverlayWindow != null) { + messageOverlayWindow.Destroy (); + messageOverlayWindow = null; + } + messageOverlayWindow = new OverlayMessageWindow (); + + var hbox = new HBox (); + hbox.Spacing = 8; + var label = new Label (string.Format ("{0} not found. Find source file at alternative location.", Path.GetFileName (sf.SourceLocation.FileName))); + hbox.TooltipText = sf.SourceLocation.FileName; + var color = (HslColor)editor.ColorStyle.NotificationText.Foreground; + label.ModifyFg (StateType.Normal, color); + + int w, h; + label.Layout.GetPixelSize (out w, out h); + + hbox.PackStart (label, true, true, 0); + var openButton = new Button (Gtk.Stock.Open); + openButton.WidthRequest = 60; + hbox.PackEnd (openButton, false, false, 0); + + var container = new HBox (); + const int containerPadding = 8; + container.PackStart (hbox, true, true, containerPadding); + messageOverlayWindow.Child = container; + messageOverlayWindow.ShowOverlay (editor); + + messageOverlayWindow.SizeFunc = () => openButton.SizeRequest ().Width + w + hbox.Spacing * 5 + containerPadding * 2; + openButton.Clicked += delegate { + var dlg = new OpenFileDialog (GettextCatalog.GetString ("File to Open"), Gtk.FileChooserAction.Open) { + TransientFor = IdeApp.Workbench.RootWindow, + ShowEncodingSelector = true, + ShowViewerSelector = true + }; + if (!dlg.Run ()) + return; + var newFilePath = dlg.SelectedFile; + try { + if (File.Exists (newFilePath)) { + if (SourceCodeLookup.CheckFileMd5 (newFilePath, sf.SourceLocation.FileHash)) { + SourceCodeLookup.AddLoadedFile (newFilePath, sf.SourceLocation.FileName); + sf.UpdateSourceFile (newFilePath); + if (IdeApp.Workbench.OpenDocument (newFilePath, null, sf.SourceLocation.Line, 1, OpenDocumentOptions.Debugger) != null) { + this.WorkbenchWindow.CloseWindow (false); + } + } else { + MessageService.ShowWarning ("File checksum doesn't match."); + } + } else { + MessageService.ShowWarning ("File not found."); + } + } catch (Exception) { + MessageService.ShowWarning ("Error opening file"); + } + }; + } + public override string TabPageLabel { get { return GettextCatalog.GetString ("Disassembly"); @@ -116,13 +184,24 @@ namespace MonoDevelop.Debugger editor.Document.RemoveMarker (currentDebugLineMarker); if (DebuggingService.CurrentFrame == null) { + if (messageOverlayWindow != null) { + messageOverlayWindow.Destroy (); + messageOverlayWindow = null; + } sw.Sensitive = false; return; } sw.Sensitive = true; - - StackFrame sf = DebuggingService.CurrentFrame; + var sf = DebuggingService.CurrentFrame; + if (!string.IsNullOrWhiteSpace (sf.SourceLocation.FileName) && sf.SourceLocation.Line != -1 && sf.SourceLocation.FileHash != null) { + ShowLoadSourceFile (sf); + } else { + if (messageOverlayWindow != null) { + messageOverlayWindow.Destroy (); + messageOverlayWindow = null; + } + } if (!string.IsNullOrEmpty (sf.SourceLocation.FileName) && File.Exists (sf.SourceLocation.FileName)) FillWithSource (); else @@ -324,6 +403,10 @@ namespace MonoDevelop.Debugger { addressLines.Clear (); currentFile = null; + if (messageOverlayWindow != null) { + messageOverlayWindow.Destroy (); + messageOverlayWindow = null; + } sw.Sensitive = false; autoRefill = false; editor.Document.Text = string.Empty; @@ -420,4 +503,97 @@ namespace MonoDevelop.Debugger return st; } } + + //Copy pasted from SourceEditor + class OverlayMessageWindow : Gtk.EventBox + { + const int border = 8; + + public Func<int> SizeFunc; + + TextEditor textEditor; + + public OverlayMessageWindow () + { + AppPaintable = true; + } + + public void ShowOverlay (TextEditor textEditor) + { + this.textEditor = textEditor; + this.ShowAll (); + textEditor.AddTopLevelWidget (this, 0, 0); + textEditor.SizeAllocated += HandleSizeAllocated; + var child = (TextEditor.EditorContainerChild)textEditor [this]; + child.FixedPosition = true; + } + + protected override void OnDestroyed () + { + base.OnDestroyed (); + if (textEditor != null) { + textEditor.SizeAllocated -= HandleSizeAllocated; + textEditor = null; + } + } + + protected override void OnSizeRequested (ref Requisition requisition) + { + base.OnSizeRequested (ref requisition); + + if (wRequest > 0) { + requisition.Width = wRequest; + } + } + + protected override void OnSizeAllocated (Gdk.Rectangle allocation) + { + base.OnSizeAllocated (allocation); + Resize (allocation); + } + + int wRequest = -1; + + void HandleSizeAllocated (object o, Gtk.SizeAllocatedArgs args) + { + if (SizeFunc != null) { + var req = Math.Min (SizeFunc (), textEditor.Allocation.Width - border * 2); + if (req != wRequest) { + wRequest = req; + QueueResize (); + } + } else { + if (Allocation.Width > textEditor.Allocation.Width - border * 2) { + if (textEditor.Allocation.Width - border * 2 > 0) { + QueueResize (); + } + } + } + Resize (Allocation); + } + + void Resize (Gdk.Rectangle alloc) + { + textEditor.MoveTopLevelWidget (this, (textEditor.Allocation.Width - alloc.Width) / 2, textEditor.Allocation.Height - alloc.Height - 8); + } + + protected override bool OnExposeEvent (Gdk.EventExpose evnt) + { + using (var cr = CairoHelper.Create (evnt.Window)) { + cr.LineWidth = 1; + cr.Rectangle (0, 0, Allocation.Width, Allocation.Height); + cr.SetSourceColor (textEditor.ColorStyle.NotificationText.Background); + cr.Fill (); + cr.RoundedRectangle (0, 0, Allocation.Width, Allocation.Height, 3); + cr.SetSourceColor (textEditor.ColorStyle.NotificationText.Background); + cr.FillPreserve (); + + cr.SetSourceColor (textEditor.ColorStyle.NotificationBorder.Color); + cr.Stroke (); + } + + return base.OnExposeEvent (evnt); + } + + } } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Initializer.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Initializer.cs index ec225d84e7..bf7644f869 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Initializer.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Initializer.cs @@ -64,7 +64,7 @@ namespace MonoDevelop.Debugger void OnFrameChanged (object s, EventArgs a) { - if (disassemblyDoc != null && DebuggingService.IsFeatureSupported (DebuggerFeatures.Disassembly)) + if (disassemblyDoc != null && IdeApp.Workbench.ActiveDocument == disassemblyDoc && DebuggingService.IsFeatureSupported (DebuggerFeatures.Disassembly)) disassemblyView.Update (); var frame = DebuggingService.CurrentFrame; @@ -73,11 +73,19 @@ namespace MonoDevelop.Debugger FilePath file = frame.SourceLocation.FileName; int line = frame.SourceLocation.Line; - - if (!file.IsNullOrEmpty && System.IO.File.Exists (file) && line != -1) { - Document doc = IdeApp.Workbench.OpenDocument (file, line, 1, OpenDocumentOptions.Debugger); - if (doc != null) - return; + if (line != -1) { + if (!file.IsNullOrEmpty && System.IO.File.Exists (file)) { + if (IdeApp.Workbench.OpenDocument (file, null, line, 1, OpenDocumentOptions.Debugger) != null) + return; + } + if (frame.SourceLocation.FileHash != null) { + var newFilePath = SourceCodeLookup.FindSourceFile (file, frame.SourceLocation.FileHash); + if (newFilePath != null) { + frame.UpdateSourceFile (newFilePath); + if (IdeApp.Workbench.OpenDocument (newFilePath, null, line, 1, OpenDocumentOptions.Debugger) != null) + return; + } + } } // If we don't have an address space, we can't disassemble @@ -115,16 +123,10 @@ namespace MonoDevelop.Debugger if (bt != null) { for (int n=0; n<bt.FrameCount; n++) { StackFrame sf = bt.GetFrame (n); - if (!sf.IsExternalCode && sf.SourceLocation.Line != -1) { - bool found = !string.IsNullOrEmpty (sf.SourceLocation.FileName) - && System.IO.File.Exists (sf.SourceLocation.FileName); - if (found) { - if (n != DebuggingService.CurrentFrameIndex) - DebuggingService.CurrentFrameIndex = n; - break; - } else { - LoggingService.LogWarning ("Debugger could not find file '{0}'", sf.SourceLocation.FileName); - } + if (sf.SourceLocation.Line != -1) { + if (n != DebuggingService.CurrentFrameIndex) + DebuggingService.CurrentFrameIndex = n; + break; } } } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/SourceCodeLookup.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/SourceCodeLookup.cs new file mode 100644 index 0000000000..c6655cdef5 --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/SourceCodeLookup.cs @@ -0,0 +1,128 @@ +// +// SourceCodeLookup.cs +// +// Author: +// David Karlaš <david.karlas@xamarin.com> +// +// Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using MonoDevelop.Core; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Linq; +using MonoDevelop.Ide; + +namespace MonoDevelop.Debugger +{ + static class SourceCodeLookup + { + readonly static List<Tuple<FilePath,FilePath>> possiblePaths = new List<Tuple<FilePath, FilePath>> (); + readonly static Dictionary<FilePath,FilePath> directMapping = new Dictionary<FilePath, FilePath> (); + + /// <summary> + /// Finds the source file. + /// </summary> + /// <returns>The source file.</returns> + /// <param name="originalFile">File from .mdb/.pdb.</param> + /// <param name="hash">Hash of original file stored in .mdb/.pdb.</param> + public static FilePath FindSourceFile (FilePath originalFile, byte[] hash) + { + if (directMapping.ContainsKey (originalFile)) + return directMapping [originalFile]; + foreach (var folder in possiblePaths) { + //file = /tmp/ci_build/mono/System/Net/Http/HttpClient.cs + var relativePath = originalFile.ToRelative (folder.Item1); + //relativePath = System/Net/Http/HttpClient.cs + var newFile = folder.Item2.Combine (relativePath); + //newPossiblePath = C:\GIT\mono_source\System\Net\Http\HttpClient.cs + if (CheckFileMd5 (newFile, hash)) { + directMapping.Add (originalFile, newFile); + return newFile; + } + } + foreach (var document in IdeApp.Workbench.Documents.Where((d) => d.FileName.FileName == originalFile.FileName)) { + //Check if it's already added to avoid MD5 checking + if (!directMapping.ContainsKey (originalFile)) { + if (CheckFileMd5 (document.FileName, hash)) { + AddLoadedFile (document.FileName, originalFile); + return document.FileName; + } + } + } + foreach (var bp in DebuggingService.Breakpoints.GetBreakpoints().Where((bp) => Path.GetFileName(bp.FileName) == originalFile.FileName)) { + //Check if it's already added to avoid MD5 checking + if (!directMapping.ContainsKey (originalFile)) { + if (CheckFileMd5 (bp.FileName, hash)) { + AddLoadedFile (bp.FileName, originalFile); + return bp.FileName; + } + } + } + return FilePath.Null; + } + + public static bool CheckFileMd5 (FilePath file, byte[] hash) + { + if (File.Exists (file)) { + using (var fs = File.OpenRead (file)) { + using (var md5 = MD5.Create ()) { + if (md5.ComputeHash (fs).SequenceEqual (hash)) { + return true; + } + } + } + } + return false; + } + + /// <summary> + /// Call this method when user succesfully opens file so we can reuse this path + /// for other files from same project. + /// Notice that it's caller job to verify hash matches for performance reasons. + /// </summary> + /// <param name="file">File path which user picked.</param> + /// <param name = "originalFile">Original file path from .pdb/.mdb.</param> + public static void AddLoadedFile (FilePath file, FilePath originalFile) + { + if (directMapping.ContainsKey (originalFile)) + return; + directMapping.Add (originalFile, file); + //file = C:\GIT\mono_source\System\Text\UTF8Encoding.cs + //originalFile = /tmp/ci_build/mono/System/Text/UTF8Encoding.cs + var fileParent = file.ParentDirectory; + var originalParent = originalFile.ParentDirectory; + if (fileParent == originalParent) { + //This can happen if file was renamed + possiblePaths.Add (new Tuple<FilePath, FilePath> (originalParent, fileParent)); + } else { + while (fileParent.FileName == originalParent.FileName) { + fileParent = fileParent.ParentDirectory; + originalParent = originalParent.ParentDirectory; + } + //fileParent = C:\GIT\mono_source\ + //originalParent = /tmp/ci_build/mono/ + possiblePaths.Add (new Tuple<FilePath, FilePath> (originalParent, fileParent)); + } + } + } +} + |