From c1d5d98e0eb776ba5a2795719f2e51880eb90dbc Mon Sep 17 00:00:00 2001 From: Eusebiu Marcu Date: Thu, 30 Dec 2010 15:31:56 +0200 Subject: [PATCH] Break at first line of execution --- .../Debugger.AddIn/Service/WindowsDebugger.cs | 1495 +++++++++-------- .../Debugger/Debugger.Core/Breakpoint.cs | 359 ++-- src/AddIns/Debugger/Debugger.Core/Process.cs | 1294 +++++++------- .../Project/ICSharpCode.SharpDevelop.addin | 54 +- .../Project/Src/Commands/DebugCommands.cs | 14 +- .../Src/Services/Debugger/DefaultDebugger.cs | 5 + .../Src/Services/Debugger/IDebugger.cs | 7 + 7 files changed, 1654 insertions(+), 1574 deletions(-) diff --git a/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs b/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs index d9a03c0ff7..127ffaf641 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs @@ -1,742 +1,753 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt) - -using System; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Text; -using System.Windows.Forms; - -using Debugger; -using Debugger.AddIn.Tooltips; -using Debugger.AddIn.TreeModel; -using Debugger.Interop; -using Debugger.Interop.CorPublish; -using ICSharpCode.Core; -using ICSharpCode.Core.WinForms; -using ICSharpCode.NRefactory; -using ICSharpCode.NRefactory.Ast; -using ICSharpCode.NRefactory.Visitors; -using ICSharpCode.SharpDevelop.Bookmarks; -using ICSharpCode.SharpDevelop.Debugging; -using ICSharpCode.SharpDevelop.Gui; -using ICSharpCode.SharpDevelop.Project; -using Process = Debugger.Process; - -namespace ICSharpCode.SharpDevelop.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; - } - } - } - - 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 = "${res:XML.MainMenu.DebugMenu.Error.Debugging}"; - string errorNotDebugging = "${res:XML.MainMenu.DebugMenu.Error.NotDebugging}"; - string errorProcessRunning = "${res:XML.MainMenu.DebugMenu.Error.ProcessRunning}"; - string errorProcessPaused = "${res:XML.MainMenu.DebugMenu.Error.ProcessPaused}"; - string errorCannotStepNoActiveFunction = "${res:MainWindow.Windows.Debug.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 bool CanDebug(IProject project) - { - return true; - } - - public void Start(ProcessStartInfo processStartInfo) - { - if (IsDebugging) { - MessageService.ShowMessage(errorDebugging); - return; - } - if (!ServiceInitialized) { - InitializeService(); - } - string version = debugger.GetProgramVersion(processStartInfo.FileName); - if (version.StartsWith("v1.0")) { - MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.Net10NotSupported}"); - } else if (version.StartsWith("v1.1")) { - MessageService.ShowMessage(StringParser.Parse("${res:XML.MainMenu.DebugMenu.Error.Net10NotSupported}").Replace("1.0", "1.1")); -// } else if (string.IsNullOrEmpty(version)) { -// // Not a managed assembly -// MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.BadAssembly}"); - } else if (debugger.IsKernelDebuggerEnabled) { - MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.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 = StringParser.Parse("${res:XML.MainMenu.DebugMenu.Error.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."; - } - MessageService.ShowMessage(msg); - - if (DebugStopped != null) - DebugStopped(this, EventArgs.Empty); - } else { - throw; - } - } - } - } - - public void ShowAttachDialog() - { - using (AttachToProcessForm attachForm = new AttachToProcessForm()) { - if (attachForm.ShowDialog(WorkbenchSingleton.MainWin32Window) == DialogResult.OK) { - Attach(attachForm.Process); - } - } - } - - public void Attach(System.Diagnostics.Process existingProcess) - { - if (IsDebugging) { - MessageService.ShowMessage(errorDebugging); - return; - } - if (!ServiceInitialized) { - InitializeService(); - } - - string version = debugger.GetProgramVersion(existingProcess.MainModule.FileName); - if (version.StartsWith("v1.0")) { - MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.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 = StringParser.Parse("${res:XML.MainMenu.DebugMenu.Error.CannotAttachToProcess}"); - MessageService.ShowMessage(msg + " " + e.Message); - - if (DebugStopped != null) - DebugStopped(this, EventArgs.Empty); - } else { - throw; - } - } - } - } - - public void Detach() - { - debugger.Detach(); - } - - public void StartWithoutDebugging(ProcessStartInfo processStartInfo) - { - System.Diagnostics.Process.Start(processStartInfo); - } - - public void Stop() - { - if (!IsDebugging) { - MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.Stop}"); - return; - } - if (IsAttached) { - StopAttachedProcessDialogResult result = ShowStopAttachedProcessDialog(); - switch (result) { - case StopAttachedProcessDialogResult.Terminate: - debuggedProcess.Terminate(); - attached = false; - break; - case StopAttachedProcessDialogResult.Detach: - Detach(); - attached = false; - break; - } - } else { - debuggedProcess.Terminate(); - } - } - - // ExecutionControl: - - public void Break() - { - if (!IsDebugging) { - MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.Break}"); - return; - } - if (!IsProcessRunning) { - MessageService.ShowMessage(errorProcessPaused, "${res:XML.MainMenu.DebugMenu.Break}"); - return; - } - debuggedProcess.Break(); - } - - public void Continue() - { - if (!IsDebugging) { - MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.Continue}"); - return; - } - if (IsProcessRunning) { - MessageService.ShowMessage(errorProcessRunning, "${res:XML.MainMenu.DebugMenu.Continue}"); - return; - } - debuggedProcess.AsyncContinue(); - } - - // Stepping: - - public void StepInto() - { - if (!IsDebugging) { - MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.StepInto}"); - return; - } - if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { - MessageService.ShowMessage(errorCannotStepNoActiveFunction, "${res:XML.MainMenu.DebugMenu.StepInto}"); - } else { - debuggedProcess.SelectedStackFrame.AsyncStepInto(); - } - } - - public void StepOver() - { - if (!IsDebugging) { - MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.StepOver}"); - return; - } - if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { - MessageService.ShowMessage(errorCannotStepNoActiveFunction, "${res:XML.MainMenu.DebugMenu.StepOver}"); - } else { - debuggedProcess.SelectedStackFrame.AsyncStepOver(); - } - } - - public void StepOut() - { - if (!IsDebugging) { - MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.StepOut}"); - return; - } - if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { - MessageService.ShowMessage(errorCannotStepNoActiveFunction, "${res:XML.MainMenu.DebugMenu.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 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(); - Debugger.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 new DebuggerTooltipControl(logicalPosition, expressionNode); - } catch (GetValueException) { - return null; - } - } - - public ITreeNode GetNode(string variable, string currentImageName = null) - { - try { - var expression = GetExpression(variable); - string imageName; - IImage image; - if (string.IsNullOrEmpty(currentImageName)) { - image = ExpressionNode.GetImageForLocalVariable(out imageName); - } - else { - image = new ResourceServiceImage(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.FileName, null, bookmark.LineNumber, 0, bookmark.IsEnabled); - MethodInvoker 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; - } - 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, ProcessEventArgs e) - { - OnIsProcessRunningChanged(EventArgs.Empty); - DebuggerService.RemoveCurrentLineMarker(); - } - - void debuggedProcess_ExceptionThrown(object sender, 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(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.EndOfInnerException}")); - } catch (GetValueException) { - stackTrace = e.Process.SelectedThread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}")); - } - stacktraceBuilder.Append(stackTrace); - } else { - // For example, happens on stack overflow - stacktraceBuilder.AppendLine(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptException}")); - stacktraceBuilder.AppendLine(e.Exception.ToString()); - stacktraceBuilder.Append(e.Process.SelectedThread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}"))); - } - - string title = e.IsUnhandled ? StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Unhandled}") : StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Handled}"); - string message = string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Message}"), e.Exception.Type, e.Exception.Message); - Bitmap icon = WinFormsResourceService.GetBitmap(e.IsUnhandled ? "Icons.32x32.Error" : "Icons.32x32.Warning"); - - DebuggeeExceptionForm.Show(debuggedProcess, title, message, stacktraceBuilder.ToString(), icon); - } - - public void JumpToCurrentLine() - { - WorkbenchSingleton.MainWindow.Activate(); - DebuggerService.RemoveCurrentLineMarker(); - if (debuggedProcess != null) { - SourcecodeSegment nextStatement = debuggedProcess.NextStatement; - if (nextStatement != null) { - DebuggerService.JumpToCurrentLine(nextStatement.Filename, nextStatement.StartLine, nextStatement.StartColumn, nextStatement.EndLine, nextStatement.EndColumn); - } - } - } - - StopAttachedProcessDialogResult ShowStopAttachedProcessDialog() - { - string caption = StringParser.Parse("${res:XML.MainMenu.DebugMenu.Stop}"); - string message = StringParser.Parse("${res:MainWindow.Windows.Debug.StopProcessDialog.Message}"); - string[] buttonLabels = new string[] { StringParser.Parse("${res:XML.MainMenu.DebugMenu.Detach}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Terminate}"), StringParser.Parse("${res:Global.CancelButtonText}") }; - return (StopAttachedProcessDialogResult)MessageService.ShowCustomDialog(caption, message, (int)StopAttachedProcessDialogResult.Detach, (int)StopAttachedProcessDialogResult.Cancel, buttonLabels); - } - } -} +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt) + +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Windows.Forms; + +using Debugger; +using Debugger.AddIn.Tooltips; +using Debugger.AddIn.TreeModel; +using Debugger.Interop; +using Debugger.Interop.CorPublish; +using ICSharpCode.Core; +using ICSharpCode.Core.WinForms; +using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.Ast; +using ICSharpCode.NRefactory.Visitors; +using ICSharpCode.SharpDevelop.Bookmarks; +using ICSharpCode.SharpDevelop.Debugging; +using ICSharpCode.SharpDevelop.Gui; +using ICSharpCode.SharpDevelop.Project; +using Process = Debugger.Process; + +namespace ICSharpCode.SharpDevelop.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 BreakAtBegining { + 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 = "${res:XML.MainMenu.DebugMenu.Error.Debugging}"; + string errorNotDebugging = "${res:XML.MainMenu.DebugMenu.Error.NotDebugging}"; + string errorProcessRunning = "${res:XML.MainMenu.DebugMenu.Error.ProcessRunning}"; + string errorProcessPaused = "${res:XML.MainMenu.DebugMenu.Error.ProcessPaused}"; + string errorCannotStepNoActiveFunction = "${res:MainWindow.Windows.Debug.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 bool CanDebug(IProject project) + { + return true; + } + + public void Start(ProcessStartInfo processStartInfo) + { + if (IsDebugging) { + MessageService.ShowMessage(errorDebugging); + return; + } + if (!ServiceInitialized) { + InitializeService(); + } + string version = debugger.GetProgramVersion(processStartInfo.FileName); + if (version.StartsWith("v1.0")) { + MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.Net10NotSupported}"); + } else if (version.StartsWith("v1.1")) { + MessageService.ShowMessage(StringParser.Parse("${res:XML.MainMenu.DebugMenu.Error.Net10NotSupported}").Replace("1.0", "1.1")); +// } else if (string.IsNullOrEmpty(version)) { +// // Not a managed assembly +// MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.BadAssembly}"); + } else if (debugger.IsKernelDebuggerEnabled) { + MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.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 = StringParser.Parse("${res:XML.MainMenu.DebugMenu.Error.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."; + } + MessageService.ShowMessage(msg); + + if (DebugStopped != null) + DebugStopped(this, EventArgs.Empty); + } else { + throw; + } + } + } + } + + public void ShowAttachDialog() + { + using (AttachToProcessForm attachForm = new AttachToProcessForm()) { + if (attachForm.ShowDialog(WorkbenchSingleton.MainWin32Window) == DialogResult.OK) { + Attach(attachForm.Process); + } + } + } + + public void Attach(System.Diagnostics.Process existingProcess) + { + if (IsDebugging) { + MessageService.ShowMessage(errorDebugging); + return; + } + if (!ServiceInitialized) { + InitializeService(); + } + + string version = debugger.GetProgramVersion(existingProcess.MainModule.FileName); + if (version.StartsWith("v1.0")) { + MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.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 = StringParser.Parse("${res:XML.MainMenu.DebugMenu.Error.CannotAttachToProcess}"); + MessageService.ShowMessage(msg + " " + e.Message); + + if (DebugStopped != null) + DebugStopped(this, EventArgs.Empty); + } else { + throw; + } + } + } + } + + public void Detach() + { + debugger.Detach(); + } + + public void StartWithoutDebugging(ProcessStartInfo processStartInfo) + { + System.Diagnostics.Process.Start(processStartInfo); + } + + public void Stop() + { + if (!IsDebugging) { + MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.Stop}"); + return; + } + if (IsAttached) { + StopAttachedProcessDialogResult result = ShowStopAttachedProcessDialog(); + switch (result) { + case StopAttachedProcessDialogResult.Terminate: + debuggedProcess.Terminate(); + attached = false; + break; + case StopAttachedProcessDialogResult.Detach: + Detach(); + attached = false; + break; + } + } else { + debuggedProcess.Terminate(); + } + } + + // ExecutionControl: + + public void Break() + { + if (!IsDebugging) { + MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.Break}"); + return; + } + if (!IsProcessRunning) { + MessageService.ShowMessage(errorProcessPaused, "${res:XML.MainMenu.DebugMenu.Break}"); + return; + } + debuggedProcess.Break(); + } + + public void Continue() + { + if (!IsDebugging) { + MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.Continue}"); + return; + } + if (IsProcessRunning) { + MessageService.ShowMessage(errorProcessRunning, "${res:XML.MainMenu.DebugMenu.Continue}"); + return; + } + debuggedProcess.AsyncContinue(); + } + + // Stepping: + + public void StepInto() + { + if (!IsDebugging) { + MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.StepInto}"); + return; + } + if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { + MessageService.ShowMessage(errorCannotStepNoActiveFunction, "${res:XML.MainMenu.DebugMenu.StepInto}"); + } else { + debuggedProcess.SelectedStackFrame.AsyncStepInto(); + } + } + + public void StepOver() + { + if (!IsDebugging) { + MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.StepOver}"); + return; + } + if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { + MessageService.ShowMessage(errorCannotStepNoActiveFunction, "${res:XML.MainMenu.DebugMenu.StepOver}"); + } else { + debuggedProcess.SelectedStackFrame.AsyncStepOver(); + } + } + + public void StepOut() + { + if (!IsDebugging) { + MessageService.ShowMessage(errorNotDebugging, "${res:XML.MainMenu.DebugMenu.StepOut}"); + return; + } + if (debuggedProcess.SelectedStackFrame == null || debuggedProcess.IsRunning) { + MessageService.ShowMessage(errorCannotStepNoActiveFunction, "${res:XML.MainMenu.DebugMenu.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 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(); + Debugger.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 new DebuggerTooltipControl(logicalPosition, expressionNode); + } catch (GetValueException) { + return null; + } + } + + public ITreeNode GetNode(string variable, string currentImageName = null) + { + try { + var expression = GetExpression(variable); + string imageName; + IImage image; + if (string.IsNullOrEmpty(currentImageName)) { + image = ExpressionNode.GetImageForLocalVariable(out imageName); + } + else { + image = new ResourceServiceImage(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.FileName, null, bookmark.LineNumber, 0, bookmark.IsEnabled); + MethodInvoker 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.BreakAtBegining = BreakAtBegining; + } + // reset + BreakAtBegining = 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, ProcessEventArgs e) + { + OnIsProcessRunningChanged(EventArgs.Empty); + DebuggerService.RemoveCurrentLineMarker(); + } + + void debuggedProcess_ExceptionThrown(object sender, 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(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.EndOfInnerException}")); + } catch (GetValueException) { + stackTrace = e.Process.SelectedThread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}")); + } + stacktraceBuilder.Append(stackTrace); + } else { + // For example, happens on stack overflow + stacktraceBuilder.AppendLine(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptException}")); + stacktraceBuilder.AppendLine(e.Exception.ToString()); + stacktraceBuilder.Append(e.Process.SelectedThread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}"))); + } + + string title = e.IsUnhandled ? StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Unhandled}") : StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Handled}"); + string message = string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Message}"), e.Exception.Type, e.Exception.Message); + Bitmap icon = WinFormsResourceService.GetBitmap(e.IsUnhandled ? "Icons.32x32.Error" : "Icons.32x32.Warning"); + + DebuggeeExceptionForm.Show(debuggedProcess, title, message, stacktraceBuilder.ToString(), icon); + } + + public void JumpToCurrentLine() + { + WorkbenchSingleton.MainWindow.Activate(); + DebuggerService.RemoveCurrentLineMarker(); + if (debuggedProcess != null) { + SourcecodeSegment nextStatement = debuggedProcess.NextStatement; + if (nextStatement != null) { + DebuggerService.JumpToCurrentLine(nextStatement.Filename, nextStatement.StartLine, nextStatement.StartColumn, nextStatement.EndLine, nextStatement.EndColumn); + } + } + } + + StopAttachedProcessDialogResult ShowStopAttachedProcessDialog() + { + string caption = StringParser.Parse("${res:XML.MainMenu.DebugMenu.Stop}"); + string message = StringParser.Parse("${res:MainWindow.Windows.Debug.StopProcessDialog.Message}"); + string[] buttonLabels = new string[] { StringParser.Parse("${res:XML.MainMenu.DebugMenu.Detach}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Terminate}"), StringParser.Parse("${res:Global.CancelButtonText}") }; + return (StopAttachedProcessDialogResult)MessageService.ShowCustomDialog(caption, message, (int)StopAttachedProcessDialogResult.Detach, (int)StopAttachedProcessDialogResult.Cancel, buttonLabels); + } + } +} diff --git a/src/AddIns/Debugger/Debugger.Core/Breakpoint.cs b/src/AddIns/Debugger/Debugger.Core/Breakpoint.cs index 1ce4cd0413..a9d2278dce 100644 --- a/src/AddIns/Debugger/Debugger.Core/Breakpoint.cs +++ b/src/AddIns/Debugger/Debugger.Core/Breakpoint.cs @@ -1,175 +1,184 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -using Debugger.Interop.CorDebug; - -namespace Debugger -{ - public class Breakpoint: DebuggerObject - { - NDebugger debugger; - - string fileName; - byte[] checkSum; - int line; - int column; - bool enabled; - - SourcecodeSegment originalLocation; - - List corBreakpoints = new List(); - - public event EventHandler Hit; - public event EventHandler Set; - - [Debugger.Tests.Ignore] - public NDebugger Debugger { - get { return debugger; } - } - - public string FileName { - get { return fileName; } - } - - public byte[] CheckSum { - get { return checkSum; } - } - - public int Line { - get { return line; } - set { line = value; } - } - - public int Column { - get { return column; } - } - - public bool Enabled { - get { return enabled; } - set { - enabled = value; - foreach(ICorDebugFunctionBreakpoint corBreakpoint in corBreakpoints) { - corBreakpoint.Activate(enabled ? 1 : 0); - } - } - } - - public SourcecodeSegment OriginalLocation { - get { return originalLocation; } - } - - public bool IsSet { - get { - return corBreakpoints.Count > 0; - } - } - - protected virtual void OnHit(BreakpointEventArgs e) - { - if (Hit != null) { - Hit(this, e); - } - } - - internal void NotifyHit() - { - OnHit(new BreakpointEventArgs(this)); - debugger.Breakpoints.OnHit(this); - } - - protected virtual void OnSet(BreakpointEventArgs e) - { - if (Set != null) { - Set(this, e); - } - } - - public Breakpoint(NDebugger debugger, string fileName, byte[] checkSum, int line, int column, bool enabled) - { - this.debugger = debugger; - this.fileName = fileName; - this.checkSum = checkSum; - this.line = line; - this.column = column; - this.enabled = enabled; - } - - internal bool IsOwnerOf(ICorDebugBreakpoint breakpoint) - { - foreach(ICorDebugFunctionBreakpoint corFunBreakpoint in corBreakpoints) { - if (((ICorDebugBreakpoint)corFunBreakpoint).Equals(breakpoint)) return true; - } - return false; - } - - internal void Deactivate() - { - foreach(ICorDebugFunctionBreakpoint corBreakpoint in corBreakpoints) { - #if DEBUG - // Get repro - corBreakpoint.Activate(0); - #else - try { - corBreakpoint.Activate(0); - } catch(COMException e) { - // Sometimes happens, but we had not repro yet. - // 0x80131301: Process was terminated. - if ((uint)e.ErrorCode == 0x80131301) - continue; - throw; - } - #endif - } - corBreakpoints.Clear(); - } - - internal void MarkAsDeactivated() - { - corBreakpoints.Clear(); - } - - internal bool SetBreakpoint(Module module) - { - SourcecodeSegment segment = SourcecodeSegment.Resolve(module, FileName, CheckSum, Line, Column); - if (segment == null) return false; - - originalLocation = segment; - - ICorDebugFunctionBreakpoint corBreakpoint = segment.CorFunction.GetILCode().CreateBreakpoint((uint)segment.ILStart); - corBreakpoint.Activate(enabled ? 1 : 0); - - corBreakpoints.Add(corBreakpoint); - - OnSet(new BreakpointEventArgs(this)); - - return true; - } - - /// Remove this breakpoint - public void Remove() - { - debugger.Breakpoints.Remove(this); - } - } - - [Serializable] - public class BreakpointEventArgs : DebuggerEventArgs - { - Breakpoint breakpoint; - - public Breakpoint Breakpoint { - get { - return breakpoint; - } - } - - public BreakpointEventArgs(Breakpoint breakpoint): base(breakpoint.Debugger) - { - this.breakpoint = breakpoint; - } - } -} +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using Debugger.Interop.CorDebug; + +namespace Debugger +{ + public class Breakpoint: DebuggerObject + { + NDebugger debugger; + + string fileName; + byte[] checkSum; + int line; + int column; + bool enabled; + + SourcecodeSegment originalLocation; + + List corBreakpoints = new List(); + + public event EventHandler Hit; + public event EventHandler Set; + + [Debugger.Tests.Ignore] + public NDebugger Debugger { + get { return debugger; } + } + + public string FileName { + get { return fileName; } + } + + public byte[] CheckSum { + get { return checkSum; } + } + + public int Line { + get { return line; } + set { line = value; } + } + + public int Column { + get { return column; } + } + + public bool Enabled { + get { return enabled; } + set { + enabled = value; + foreach(ICorDebugFunctionBreakpoint corBreakpoint in corBreakpoints) { + corBreakpoint.Activate(enabled ? 1 : 0); + } + } + } + + public SourcecodeSegment OriginalLocation { + get { return originalLocation; } + } + + public bool IsSet { + get { + return corBreakpoints.Count > 0; + } + } + + protected virtual void OnHit(BreakpointEventArgs e) + { + if (Hit != null) { + Hit(this, e); + } + } + + internal void NotifyHit() + { + OnHit(new BreakpointEventArgs(this)); + debugger.Breakpoints.OnHit(this); + } + + protected virtual void OnSet(BreakpointEventArgs e) + { + if (Set != null) { + Set(this, e); + } + } + + public Breakpoint(NDebugger debugger, ICorDebugFunctionBreakpoint corBreakpoint) + { + this.debugger = debugger; + this.corBreakpoints.Add(corBreakpoint); + } + + public Breakpoint(NDebugger debugger, string fileName, byte[] checkSum, int line, int column, bool enabled) + { + this.debugger = debugger; + this.fileName = fileName; + this.checkSum = checkSum; + this.line = line; + this.column = column; + this.enabled = enabled; + } + + internal bool IsOwnerOf(ICorDebugBreakpoint breakpoint) + { + foreach(ICorDebugFunctionBreakpoint corFunBreakpoint in corBreakpoints) { + if (((ICorDebugBreakpoint)corFunBreakpoint).Equals(breakpoint)) return true; + } + return false; + } + + internal void Deactivate() + { + foreach(ICorDebugFunctionBreakpoint corBreakpoint in corBreakpoints) { + #if DEBUG + // Get repro + corBreakpoint.Activate(0); + #else + try { + corBreakpoint.Activate(0); + } catch(COMException e) { + // Sometimes happens, but we had not repro yet. + // 0x80131301: Process was terminated. + if ((uint)e.ErrorCode == 0x80131301) + continue; + throw; + } + #endif + } + corBreakpoints.Clear(); + } + + internal void MarkAsDeactivated() + { + corBreakpoints.Clear(); + } + + internal bool SetBreakpoint(Module module) + { + if (this.fileName == null) + return false; + + SourcecodeSegment segment = SourcecodeSegment.Resolve(module, FileName, CheckSum, Line, Column); + if (segment == null) return false; + + originalLocation = segment; + + ICorDebugFunctionBreakpoint corBreakpoint = segment.CorFunction.GetILCode().CreateBreakpoint((uint)segment.ILStart); + corBreakpoint.Activate(enabled ? 1 : 0); + + corBreakpoints.Add(corBreakpoint); + + OnSet(new BreakpointEventArgs(this)); + + return true; + } + + /// Remove this breakpoint + public void Remove() + { + debugger.Breakpoints.Remove(this); + } + } + + [Serializable] + public class BreakpointEventArgs : DebuggerEventArgs + { + Breakpoint breakpoint; + + public Breakpoint Breakpoint { + get { + return breakpoint; + } + } + + public BreakpointEventArgs(Breakpoint breakpoint): base(breakpoint.Debugger) + { + this.breakpoint = breakpoint; + } + } +} diff --git a/src/AddIns/Debugger/Debugger.Core/Process.cs b/src/AddIns/Debugger/Debugger.Core/Process.cs index 8616e618e1..c2856a471a 100644 --- a/src/AddIns/Debugger/Debugger.Core/Process.cs +++ b/src/AddIns/Debugger/Debugger.Core/Process.cs @@ -1,631 +1,663 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -using Debugger.Interop; -using Debugger.Interop.CorDebug; -using ICSharpCode.NRefactory.Ast; -using ICSharpCode.NRefactory.Visitors; - -namespace Debugger -{ - internal enum DebuggeeStateAction { Keep, Clear } - - public class Process: DebuggerObject - { - NDebugger debugger; - - ICorDebugProcess corProcess; - ManagedCallback callbackInterface; - - EvalCollection activeEvals; - ModuleCollection modules; - ThreadCollection threads; - AppDomainCollection appDomains; - - string workingDirectory; - - public NDebugger Debugger { - get { return debugger; } - } - - internal ICorDebugProcess CorProcess { - get { return corProcess; } - } - - public Options Options { - get { return debugger.Options; } - } - - public string DebuggeeVersion { - get { return debugger.DebuggeeVersion; } - } - - internal ManagedCallback CallbackInterface { - get { return callbackInterface; } - } - - public EvalCollection ActiveEvals { - get { return activeEvals; } - } - - internal bool Evaluating { - get { return activeEvals.Count > 0; } - } - - public ModuleCollection Modules { - get { return modules; } - } - - public ThreadCollection Threads { - get { return threads; } - } - - public Thread SelectedThread { - get { return this.Threads.Selected; } - set { this.Threads.Selected = value; } - } - - public StackFrame SelectedStackFrame { - get { - if (SelectedThread == null) { - return null; - } else { - return SelectedThread.SelectedStackFrame; - } - } - } - - public SourcecodeSegment NextStatement { - get { - if (SelectedStackFrame == null || IsRunning) { - return null; - } else { - return SelectedStackFrame.NextStatement; - } - } - } - - public AppDomainCollection AppDomains { - get { return appDomains; } - } - - List steppers = new List(); - - internal List Steppers { - get { return steppers; } - } - - public string WorkingDirectory { - get { return workingDirectory; } - } - - internal Process(NDebugger debugger, ICorDebugProcess corProcess, string workingDirectory) - { - this.debugger = debugger; - this.corProcess = corProcess; - this.workingDirectory = workingDirectory; - - this.callbackInterface = new ManagedCallback(this); - - activeEvals = new EvalCollection(debugger); - modules = new ModuleCollection(debugger); - threads = new ThreadCollection(debugger); - appDomains = new AppDomainCollection(debugger); - } - - static unsafe public Process CreateProcess(NDebugger debugger, string filename, string workingDirectory, string arguments) - { - debugger.TraceMessage("Executing " + filename + " " + arguments); - - uint[] processStartupInfo = new uint[17]; - processStartupInfo[0] = sizeof(uint) * 17; - uint[] processInfo = new uint[4]; - - ICorDebugProcess outProcess; - - if (workingDirectory == null || workingDirectory == "") { - workingDirectory = System.IO.Path.GetDirectoryName(filename); - } - - _SECURITY_ATTRIBUTES secAttr = new _SECURITY_ATTRIBUTES(); - secAttr.bInheritHandle = 0; - secAttr.lpSecurityDescriptor = IntPtr.Zero; - secAttr.nLength = (uint)sizeof(_SECURITY_ATTRIBUTES); - - fixed (uint* pprocessStartupInfo = processStartupInfo) - fixed (uint* pprocessInfo = processInfo) - outProcess = - debugger.CorDebug.CreateProcess( - filename, // lpApplicationName - // If we do not prepend " ", the first argument migh just get lost - " " + arguments, // lpCommandLine - ref secAttr, // lpProcessAttributes - ref secAttr, // lpThreadAttributes - 1,//TRUE // bInheritHandles - 0x00000010 /*CREATE_NEW_CONSOLE*/, // dwCreationFlags - IntPtr.Zero, // lpEnvironment - workingDirectory, // lpCurrentDirectory - (uint)pprocessStartupInfo, // lpStartupInfo - (uint)pprocessInfo, // lpProcessInformation, - CorDebugCreateProcessFlags.DEBUG_NO_SPECIAL_OPTIONS // debuggingFlags - ); - - return new Process(debugger, outProcess, workingDirectory); - } - - /// Fired when System.Diagnostics.Trace.WriteLine() is called in debuged process - public event EventHandler LogMessage; - - protected internal virtual void OnLogMessage(MessageEventArgs arg) - { - TraceMessage ("Debugger event: OnLogMessage"); - if (LogMessage != null) { - LogMessage(this, arg); - } - } - - public void TraceMessage(string message, params object[] args) - { - if (args.Length > 0) - message = string.Format(message, args); - System.Diagnostics.Debug.WriteLine("Debugger:" + message); - debugger.OnDebuggerTraceMessage(new MessageEventArgs(this, message)); - } - - /// Read the specified amount of memory at the given memory address - /// The content of the memory. The amount of the read memory may be less then requested. - public unsafe byte[] ReadMemory(ulong address, int size) - { - byte[] buffer = new byte[size]; - int readCount; - fixed(byte* pBuffer = buffer) { - readCount = (int)corProcess.ReadMemory(address, (uint)size, new IntPtr(pBuffer)); - } - if (readCount != size) Array.Resize(ref buffer, readCount); - return buffer; - } - - /// Writes the given buffer at the specified memory address - /// The number of bytes written - public unsafe int WriteMemory(ulong address, byte[] buffer) - { - if (buffer.Length == 0) return 0; - int written; - fixed(byte* pBuffer = buffer) { - written = (int)corProcess.WriteMemory(address, (uint)buffer.Length, new IntPtr(pBuffer)); - } - return written; - } - - #region Exceptions - - bool pauseOnHandledException = false; - - public event EventHandler ExceptionThrown; - - public bool PauseOnHandledException { - get { return pauseOnHandledException; } - set { pauseOnHandledException = value; } - } - - protected internal virtual void OnExceptionThrown(ExceptionEventArgs e) - { - TraceMessage ("Debugger event: OnExceptionThrown()"); - if (ExceptionThrown != null) { - ExceptionThrown(this, e); - } - } - - #endregion - - // State control for the process - - internal bool TerminateCommandIssued = false; - internal Queue BreakpointHitEventQueue = new Queue(); - internal Dictionary ExpressionsCache = new Dictionary(); - - #region Events - - public event EventHandler Paused; - public event EventHandler Resumed; - - // HACK: public - public virtual void OnPaused() - { - AssertPaused(); - // No real purpose - just additional check - if (callbackInterface.IsInCallback) throw new DebuggerException("Can not raise event within callback."); - TraceMessage ("Debugger event: OnPaused()"); - if (Paused != null) { - foreach(Delegate d in Paused.GetInvocationList()) { - if (IsRunning) { - TraceMessage ("Skipping OnPaused delegate because process has resumed"); - break; - } - if (this.TerminateCommandIssued || this.HasExited) { - TraceMessage ("Skipping OnPaused delegate because process has exited"); - break; - } - d.DynamicInvoke(this, new ProcessEventArgs(this)); - } - } - } - - protected virtual void OnResumed() - { - AssertRunning(); - if (callbackInterface.IsInCallback) - throw new DebuggerException("Can not raise event within callback."); - TraceMessage ("Debugger event: OnResumed()"); - if (Resumed != null) { - Resumed(this, new ProcessEventArgs(this)); - } - } - - - #endregion - - #region PauseSession & DebugeeState - - PauseSession pauseSession; - DebuggeeState debuggeeState; - - /// - /// Indentification of the current debugger session. This value changes whenever debugger is continued - /// - public PauseSession PauseSession { - get { return pauseSession; } - } - - /// - /// Indentification of the state of the debugee. This value changes whenever the state of the debugee significatntly changes - /// - public DebuggeeState DebuggeeState { - get { return debuggeeState; } - } - - /// Puts the process into a paused state - internal void NotifyPaused(PausedReason pauseReason) - { - AssertRunning(); - pauseSession = new PauseSession(this, pauseReason); - if (debuggeeState == null) { - debuggeeState = new DebuggeeState(this); - } - } - - /// Puts the process into a resumed state - internal void NotifyResumed(DebuggeeStateAction action) - { - AssertPaused(); - pauseSession = null; - if (action == DebuggeeStateAction.Clear) { - if (debuggeeState == null) throw new DebuggerException("Debugee state already cleared"); - debuggeeState = null; - this.ExpressionsCache.Clear(); - } - } - - /// Sets up the eviroment and raises user events - internal void RaisePausedEvents() - { - AssertPaused(); - DisableAllSteppers(); - CheckSelectedStackFrames(); - SelectMostRecentStackFrameWithLoadedSymbols(); - - if (this.PauseSession.PausedReason == PausedReason.Exception) { - ExceptionEventArgs args = new ExceptionEventArgs(this, this.SelectedThread.CurrentException, this.SelectedThread.CurrentExceptionType, this.SelectedThread.CurrentExceptionIsUnhandled); - OnExceptionThrown(args); - // The event could have resumed or killed the process - if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; - } - - while(BreakpointHitEventQueue.Count > 0) { - Breakpoint breakpoint = BreakpointHitEventQueue.Dequeue(); - breakpoint.NotifyHit(); - // The event could have resumed or killed the process - if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; - } - - OnPaused(); - // The event could have resumed the process - if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; - } - - #endregion - - internal void AssertPaused() - { - if (IsRunning) { - throw new DebuggerException("Process is not paused."); - } - } - - internal void AssertRunning() - { - if (IsPaused) { - throw new DebuggerException("Process is not running."); - } - } - - public bool IsRunning { - get { return pauseSession == null; } - } - - public bool IsPaused { - get { return !IsRunning; } - } - - bool hasExited = false; - - public event EventHandler Exited; - - public bool HasExited { - get { - return hasExited; - } - } - - internal void NotifyHasExited() - { - if(!hasExited) { - hasExited = true; - if (Exited != null) { - Exited(this, new ProcessEventArgs(this)); - } - // Expire pause seesion first - if (IsPaused) { - NotifyResumed(DebuggeeStateAction.Clear); - } - debugger.Processes.Remove(this); - } - } - - public void Break() - { - AssertRunning(); - - corProcess.Stop(uint.MaxValue); // Infinite; ignored anyway - - NotifyPaused(PausedReason.ForcedBreak); - RaisePausedEvents(); - } - - public void Detach() - { - if (IsRunning) { - corProcess.Stop(uint.MaxValue); - NotifyPaused(PausedReason.ForcedBreak); - } - - // This is necessary for detach - foreach(Stepper s in this.Steppers) { - if (s.CorStepper.IsActive() == 1) { - s.CorStepper.Deactivate(); - } - } - this.Steppers.Clear(); - - corProcess.Detach(); - - // modules - foreach(Module m in this.Modules) - { - m.Dispose(); - } - - this.modules.Clear(); - - // threads - this.threads.Clear(); - - NotifyHasExited(); - } - - public void Continue() - { - AsyncContinue(); - WaitForPause(); - } - - internal Thread[] UnsuspendedThreads { - get { - List unsuspendedThreads = new List(this.Threads.Count); - foreach(Thread t in this.Threads) { - if (!t.Suspended) - unsuspendedThreads.Add(t); - } - return unsuspendedThreads.ToArray(); - } - } - - /// - /// Resume execution and run all threads not marked by the user as susspended. - /// - public void AsyncContinue() - { - AsyncContinue(DebuggeeStateAction.Clear, this.UnsuspendedThreads, CorDebugThreadState.THREAD_RUN); - } - - internal CorDebugThreadState NewThreadState = CorDebugThreadState.THREAD_RUN; - - /// Null to keep current setting - /// What happens to created threads. Null to keep current setting - internal void AsyncContinue(DebuggeeStateAction action, Thread[] threadsToRun, CorDebugThreadState? newThreadState) - { - AssertPaused(); - - if (threadsToRun != null) { -// corProcess.SetAllThreadsDebugState(CorDebugThreadState.THREAD_SUSPEND, null); -// Note: There is unreported thread, stopping it prevents the debugee from exiting -// It is not corProcess.GetHelperThreadID -// ICorDebugThread[] ts = new ICorDebugThread[corProcess.EnumerateThreads().GetCount()]; -// corProcess.EnumerateThreads().Next((uint)ts.Length, ts); - foreach(Thread t in this.Threads) { - CorDebugThreadState state = Array.IndexOf(threadsToRun, t) == -1 ? CorDebugThreadState.THREAD_SUSPEND : CorDebugThreadState.THREAD_RUN; - try { - t.CorThread.SetDebugState(state); - } catch (COMException e) { - // The state of the thread is invalid. (Exception from HRESULT: 0x8013132D) - // It can happen for example when thread has not started yet - if ((uint)e.ErrorCode == 0x8013132D) { - // TraceMessage("Can not suspend thread - The state of the thread is invalid. Thread ID = " + t.CorThread.GetID()); - } else { - throw; - } - } - } - } - - if (newThreadState != null) { - this.NewThreadState = newThreadState.Value; - } - - NotifyResumed(action); - corProcess.Continue(0); - if (this.Options.Verbose) { - this.TraceMessage("Continue"); - } - - if (action == DebuggeeStateAction.Clear) { - OnResumed(); - } - } - - /// Terminates the execution of the process - public void Terminate() - { - AsyncTerminate(); - // Wait until ExitProcess callback is received - WaitForExit(); - } - - /// Terminates the execution of the process - public void AsyncTerminate() - { - // Resume stoped tread - if (this.IsPaused) { - // We might get more callbacks so we should maintain consistent sate - //AsyncContinue(); // Continue the process to get remaining callbacks - } - - // Expose race condition - drain callback queue - System.Threading.Thread.Sleep(0); - - // Stop&terminate - both must be called - corProcess.Stop(uint.MaxValue); - corProcess.Terminate(0); - this.TerminateCommandIssued = true; - - // Do not mark the process as exited - // This is done once ExitProcess callback is received - } - - void SelectSomeThread() - { - if (this.SelectedThread != null && !this.SelectedThread.IsInValidState) { - this.SelectedThread = null; - } - if (this.SelectedThread == null) { - foreach(Thread thread in this.Threads) { - if (thread.IsInValidState) { - this.SelectedThread = thread; - break; - } - } - } - } - - internal void CheckSelectedStackFrames() - { - foreach(Thread thread in this.Threads) { - if (thread.IsInValidState) { - if (thread.SelectedStackFrame != null && thread.SelectedStackFrame.IsInvalid) { - thread.SelectedStackFrame = null; - } - } else { - thread.SelectedStackFrame = null; - } - } - } - - internal void SelectMostRecentStackFrameWithLoadedSymbols() - { - SelectSomeThread(); - if (this.SelectedThread != null) { - this.SelectedThread.SelectedStackFrame = null; - foreach (StackFrame stackFrame in this.SelectedThread.Callstack) { - if (stackFrame.HasSymbols) { - if (this.Options.StepOverDebuggerAttributes && stackFrame.MethodInfo.IsNonUserCode) - continue; - this.SelectedThread.SelectedStackFrame = stackFrame; - break; - } - } - } - } - - internal Stepper GetStepper(ICorDebugStepper corStepper) - { - foreach(Stepper stepper in this.Steppers) { - if (stepper.IsCorStepper(corStepper)) { - return stepper; - } - } - throw new DebuggerException("Stepper is not in collection"); - } - - internal void DisableAllSteppers() - { - foreach(Thread thread in this.Threads) { - thread.CurrentStepIn = null; - } - foreach(Stepper stepper in this.Steppers) { - stepper.Ignore = true; - } - } - - /// - /// Waits until the debugger pauses unless it is already paused. - /// Use PausedReason to find out why it paused. - /// - public void WaitForPause() - { - while(this.IsRunning && !this.HasExited) { - debugger.MTA2STA.WaitForCall(); - debugger.MTA2STA.PerformAllCalls(); - } - if (this.HasExited) throw new ProcessExitedException(); - } - - public void WaitForPause(TimeSpan timeout) - { - System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); - watch.Start(); - while(this.IsRunning && !this.HasExited) { - TimeSpan timeLeft = timeout - watch.Elapsed; - if (timeLeft <= TimeSpan.FromMilliseconds(10)) break; - //this.TraceMessage("Time left: " + timeLeft.TotalMilliseconds); - debugger.MTA2STA.WaitForCall(timeLeft); - debugger.MTA2STA.PerformAllCalls(); - } - if (this.HasExited) throw new ProcessExitedException(); - } - - /// - /// Waits until the precesses exits. - /// - public void WaitForExit() - { - while(!this.HasExited) { - debugger.MTA2STA.WaitForCall(); - debugger.MTA2STA.PerformAllCalls(); - } - } - } -} +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using Debugger.Interop.CorDebug; +using Debugger.Interop.CorSym; +using ICSharpCode.NRefactory.Ast; +using ICSharpCode.NRefactory.Visitors; + +namespace Debugger +{ + internal enum DebuggeeStateAction { Keep, Clear } + + public class Process: DebuggerObject + { + NDebugger debugger; + + ICorDebugProcess corProcess; + ManagedCallback callbackInterface; + + EvalCollection activeEvals; + ModuleCollection modules; + ThreadCollection threads; + AppDomainCollection appDomains; + + string workingDirectory; + + public NDebugger Debugger { + get { return debugger; } + } + + internal ICorDebugProcess CorProcess { + get { return corProcess; } + } + + public Options Options { + get { return debugger.Options; } + } + + public string DebuggeeVersion { + get { return debugger.DebuggeeVersion; } + } + + internal ManagedCallback CallbackInterface { + get { return callbackInterface; } + } + + public EvalCollection ActiveEvals { + get { return activeEvals; } + } + + internal bool Evaluating { + get { return activeEvals.Count > 0; } + } + + public ModuleCollection Modules { + get { return modules; } + } + + public ThreadCollection Threads { + get { return threads; } + } + + public Thread SelectedThread { + get { return this.Threads.Selected; } + set { this.Threads.Selected = value; } + } + + public StackFrame SelectedStackFrame { + get { + if (SelectedThread == null) { + return null; + } else { + return SelectedThread.SelectedStackFrame; + } + } + } + + public SourcecodeSegment NextStatement { + get { + if (SelectedStackFrame == null || IsRunning) { + return null; + } else { + return SelectedStackFrame.NextStatement; + } + } + } + + public bool BreakAtBegining { + get; + set; + } + + public AppDomainCollection AppDomains { + get { return appDomains; } + } + + List steppers = new List(); + + internal List Steppers { + get { return steppers; } + } + + public string WorkingDirectory { + get { return workingDirectory; } + } + + internal Process(NDebugger debugger, ICorDebugProcess corProcess, string workingDirectory) + { + this.debugger = debugger; + this.corProcess = corProcess; + this.workingDirectory = workingDirectory; + + this.callbackInterface = new ManagedCallback(this); + + activeEvals = new EvalCollection(debugger); + modules = new ModuleCollection(debugger); + modules.Added += OnModulesAdded; + threads = new ThreadCollection(debugger); + appDomains = new AppDomainCollection(debugger); + } + + static unsafe public Process CreateProcess(NDebugger debugger, string filename, string workingDirectory, string arguments) + { + debugger.TraceMessage("Executing " + filename + " " + arguments); + + uint[] processStartupInfo = new uint[17]; + processStartupInfo[0] = sizeof(uint) * 17; + uint[] processInfo = new uint[4]; + + ICorDebugProcess outProcess; + + if (workingDirectory == null || workingDirectory == "") { + workingDirectory = System.IO.Path.GetDirectoryName(filename); + } + + _SECURITY_ATTRIBUTES secAttr = new _SECURITY_ATTRIBUTES(); + secAttr.bInheritHandle = 0; + secAttr.lpSecurityDescriptor = IntPtr.Zero; + secAttr.nLength = (uint)sizeof(_SECURITY_ATTRIBUTES); + + fixed (uint* pprocessStartupInfo = processStartupInfo) + fixed (uint* pprocessInfo = processInfo) + outProcess = + debugger.CorDebug.CreateProcess( + filename, // lpApplicationName + // If we do not prepend " ", the first argument migh just get lost + " " + arguments, // lpCommandLine + ref secAttr, // lpProcessAttributes + ref secAttr, // lpThreadAttributes + 1,//TRUE // bInheritHandles + 0x00000010 /*CREATE_NEW_CONSOLE*/, // dwCreationFlags + IntPtr.Zero, // lpEnvironment + workingDirectory, // lpCurrentDirectory + (uint)pprocessStartupInfo, // lpStartupInfo + (uint)pprocessInfo, // lpProcessInformation, + CorDebugCreateProcessFlags.DEBUG_NO_SPECIAL_OPTIONS // debuggingFlags + ); + + return new Process(debugger, outProcess, workingDirectory); + } + + /// Fired when System.Diagnostics.Trace.WriteLine() is called in debuged process + public event EventHandler LogMessage; + + protected internal virtual void OnLogMessage(MessageEventArgs arg) + { + TraceMessage ("Debugger event: OnLogMessage"); + if (LogMessage != null) { + LogMessage(this, arg); + } + } + + public void TraceMessage(string message, params object[] args) + { + if (args.Length > 0) + message = string.Format(message, args); + System.Diagnostics.Debug.WriteLine("Debugger:" + message); + debugger.OnDebuggerTraceMessage(new MessageEventArgs(this, message)); + } + + /// Read the specified amount of memory at the given memory address + /// The content of the memory. The amount of the read memory may be less then requested. + public unsafe byte[] ReadMemory(ulong address, int size) + { + byte[] buffer = new byte[size]; + int readCount; + fixed(byte* pBuffer = buffer) { + readCount = (int)corProcess.ReadMemory(address, (uint)size, new IntPtr(pBuffer)); + } + if (readCount != size) Array.Resize(ref buffer, readCount); + return buffer; + } + + /// Writes the given buffer at the specified memory address + /// The number of bytes written + public unsafe int WriteMemory(ulong address, byte[] buffer) + { + if (buffer.Length == 0) return 0; + int written; + fixed(byte* pBuffer = buffer) { + written = (int)corProcess.WriteMemory(address, (uint)buffer.Length, new IntPtr(pBuffer)); + } + return written; + } + + #region Exceptions + + bool pauseOnHandledException = false; + + public event EventHandler ExceptionThrown; + + public bool PauseOnHandledException { + get { return pauseOnHandledException; } + set { pauseOnHandledException = value; } + } + + protected internal virtual void OnExceptionThrown(ExceptionEventArgs e) + { + TraceMessage ("Debugger event: OnExceptionThrown()"); + if (ExceptionThrown != null) { + ExceptionThrown(this, e); + } + } + + #endregion + + // State control for the process + + internal bool TerminateCommandIssued = false; + internal Queue BreakpointHitEventQueue = new Queue(); + internal Dictionary ExpressionsCache = new Dictionary(); + + #region Events + + public event EventHandler Paused; + public event EventHandler Resumed; + + // HACK: public + public virtual void OnPaused() + { + AssertPaused(); + // No real purpose - just additional check + if (callbackInterface.IsInCallback) throw new DebuggerException("Can not raise event within callback."); + TraceMessage ("Debugger event: OnPaused()"); + if (Paused != null) { + foreach(Delegate d in Paused.GetInvocationList()) { + if (IsRunning) { + TraceMessage ("Skipping OnPaused delegate because process has resumed"); + break; + } + if (this.TerminateCommandIssued || this.HasExited) { + TraceMessage ("Skipping OnPaused delegate because process has exited"); + break; + } + d.DynamicInvoke(this, new ProcessEventArgs(this)); + } + } + } + + protected virtual void OnResumed() + { + AssertRunning(); + if (callbackInterface.IsInCallback) + throw new DebuggerException("Can not raise event within callback."); + TraceMessage ("Debugger event: OnResumed()"); + if (Resumed != null) { + Resumed(this, new ProcessEventArgs(this)); + } + } + + #endregion + + #region PauseSession & DebugeeState + + PauseSession pauseSession; + DebuggeeState debuggeeState; + + /// + /// Indentification of the current debugger session. This value changes whenever debugger is continued + /// + public PauseSession PauseSession { + get { return pauseSession; } + } + + /// + /// Indentification of the state of the debugee. This value changes whenever the state of the debugee significatntly changes + /// + public DebuggeeState DebuggeeState { + get { return debuggeeState; } + } + + /// Puts the process into a paused state + internal void NotifyPaused(PausedReason pauseReason) + { + AssertRunning(); + pauseSession = new PauseSession(this, pauseReason); + if (debuggeeState == null) { + debuggeeState = new DebuggeeState(this); + } + } + + /// Puts the process into a resumed state + internal void NotifyResumed(DebuggeeStateAction action) + { + AssertPaused(); + pauseSession = null; + if (action == DebuggeeStateAction.Clear) { + if (debuggeeState == null) throw new DebuggerException("Debugee state already cleared"); + debuggeeState = null; + this.ExpressionsCache.Clear(); + } + } + + /// Sets up the eviroment and raises user events + internal void RaisePausedEvents() + { + AssertPaused(); + DisableAllSteppers(); + CheckSelectedStackFrames(); + SelectMostRecentStackFrameWithLoadedSymbols(); + + if (this.PauseSession.PausedReason == PausedReason.Exception) { + ExceptionEventArgs args = new ExceptionEventArgs(this, this.SelectedThread.CurrentException, this.SelectedThread.CurrentExceptionType, this.SelectedThread.CurrentExceptionIsUnhandled); + OnExceptionThrown(args); + // The event could have resumed or killed the process + if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; + } + + while(BreakpointHitEventQueue.Count > 0) { + Breakpoint breakpoint = BreakpointHitEventQueue.Dequeue(); + breakpoint.NotifyHit(); + // The event could have resumed or killed the process + if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; + } + + OnPaused(); + // The event could have resumed the process + if (this.IsRunning || this.TerminateCommandIssued || this.HasExited) return; + } + + #endregion + + internal void AssertPaused() + { + if (IsRunning) { + throw new DebuggerException("Process is not paused."); + } + } + + internal void AssertRunning() + { + if (IsPaused) { + throw new DebuggerException("Process is not running."); + } + } + + public bool IsRunning { + get { return pauseSession == null; } + } + + public bool IsPaused { + get { return !IsRunning; } + } + + bool hasExited = false; + + public event EventHandler Exited; + + public bool HasExited { + get { + return hasExited; + } + } + + internal void NotifyHasExited() + { + if(!hasExited) { + hasExited = true; + if (Exited != null) { + Exited(this, new ProcessEventArgs(this)); + } + // Expire pause seesion first + if (IsPaused) { + NotifyResumed(DebuggeeStateAction.Clear); + } + debugger.Processes.Remove(this); + } + } + + public void Break() + { + AssertRunning(); + + corProcess.Stop(uint.MaxValue); // Infinite; ignored anyway + + NotifyPaused(PausedReason.ForcedBreak); + RaisePausedEvents(); + } + + public void Detach() + { + if (IsRunning) { + corProcess.Stop(uint.MaxValue); + NotifyPaused(PausedReason.ForcedBreak); + } + + // This is necessary for detach + foreach(Stepper s in this.Steppers) { + if (s.CorStepper.IsActive() == 1) { + s.CorStepper.Deactivate(); + } + } + this.Steppers.Clear(); + + corProcess.Detach(); + + // modules + foreach(Module m in this.Modules) + { + m.Dispose(); + } + + this.modules.Clear(); + + // threads + this.threads.Clear(); + + NotifyHasExited(); + } + + public void Continue() + { + AsyncContinue(); + WaitForPause(); + } + + internal Thread[] UnsuspendedThreads { + get { + List unsuspendedThreads = new List(this.Threads.Count); + foreach(Thread t in this.Threads) { + if (!t.Suspended) + unsuspendedThreads.Add(t); + } + return unsuspendedThreads.ToArray(); + } + } + + /// + /// Resume execution and run all threads not marked by the user as susspended. + /// + public void AsyncContinue() + { + AsyncContinue(DebuggeeStateAction.Clear, this.UnsuspendedThreads, CorDebugThreadState.THREAD_RUN); + } + + internal CorDebugThreadState NewThreadState = CorDebugThreadState.THREAD_RUN; + + /// Null to keep current setting + /// What happens to created threads. Null to keep current setting + internal void AsyncContinue(DebuggeeStateAction action, Thread[] threadsToRun, CorDebugThreadState? newThreadState) + { + AssertPaused(); + + if (threadsToRun != null) { +// corProcess.SetAllThreadsDebugState(CorDebugThreadState.THREAD_SUSPEND, null); +// Note: There is unreported thread, stopping it prevents the debugee from exiting +// It is not corProcess.GetHelperThreadID +// ICorDebugThread[] ts = new ICorDebugThread[corProcess.EnumerateThreads().GetCount()]; +// corProcess.EnumerateThreads().Next((uint)ts.Length, ts); + foreach(Thread t in this.Threads) { + CorDebugThreadState state = Array.IndexOf(threadsToRun, t) == -1 ? CorDebugThreadState.THREAD_SUSPEND : CorDebugThreadState.THREAD_RUN; + try { + t.CorThread.SetDebugState(state); + } catch (COMException e) { + // The state of the thread is invalid. (Exception from HRESULT: 0x8013132D) + // It can happen for example when thread has not started yet + if ((uint)e.ErrorCode == 0x8013132D) { + // TraceMessage("Can not suspend thread - The state of the thread is invalid. Thread ID = " + t.CorThread.GetID()); + } else { + throw; + } + } + } + } + + if (newThreadState != null) { + this.NewThreadState = newThreadState.Value; + } + + NotifyResumed(action); + corProcess.Continue(0); + if (this.Options.Verbose) { + this.TraceMessage("Continue"); + } + + if (action == DebuggeeStateAction.Clear) { + OnResumed(); + } + } + + /// Terminates the execution of the process + public void Terminate() + { + AsyncTerminate(); + // Wait until ExitProcess callback is received + WaitForExit(); + } + + /// Terminates the execution of the process + public void AsyncTerminate() + { + // Resume stoped tread + if (this.IsPaused) { + // We might get more callbacks so we should maintain consistent sate + //AsyncContinue(); // Continue the process to get remaining callbacks + } + + // Expose race condition - drain callback queue + System.Threading.Thread.Sleep(0); + + // Stop&terminate - both must be called + corProcess.Stop(uint.MaxValue); + corProcess.Terminate(0); + this.TerminateCommandIssued = true; + + // Do not mark the process as exited + // This is done once ExitProcess callback is received + } + + void SelectSomeThread() + { + if (this.SelectedThread != null && !this.SelectedThread.IsInValidState) { + this.SelectedThread = null; + } + if (this.SelectedThread == null) { + foreach(Thread thread in this.Threads) { + if (thread.IsInValidState) { + this.SelectedThread = thread; + break; + } + } + } + } + + internal void CheckSelectedStackFrames() + { + foreach(Thread thread in this.Threads) { + if (thread.IsInValidState) { + if (thread.SelectedStackFrame != null && thread.SelectedStackFrame.IsInvalid) { + thread.SelectedStackFrame = null; + } + } else { + thread.SelectedStackFrame = null; + } + } + } + + internal void SelectMostRecentStackFrameWithLoadedSymbols() + { + SelectSomeThread(); + if (this.SelectedThread != null) { + this.SelectedThread.SelectedStackFrame = null; + foreach (StackFrame stackFrame in this.SelectedThread.Callstack) { + if (stackFrame.HasSymbols) { + if (this.Options.StepOverDebuggerAttributes && stackFrame.MethodInfo.IsNonUserCode) + continue; + this.SelectedThread.SelectedStackFrame = stackFrame; + break; + } + } + } + } + + internal Stepper GetStepper(ICorDebugStepper corStepper) + { + foreach(Stepper stepper in this.Steppers) { + if (stepper.IsCorStepper(corStepper)) { + return stepper; + } + } + throw new DebuggerException("Stepper is not in collection"); + } + + internal void DisableAllSteppers() + { + foreach(Thread thread in this.Threads) { + thread.CurrentStepIn = null; + } + foreach(Stepper stepper in this.Steppers) { + stepper.Ignore = true; + } + } + + /// + /// Waits until the debugger pauses unless it is already paused. + /// Use PausedReason to find out why it paused. + /// + public void WaitForPause() + { + while(this.IsRunning && !this.HasExited) { + debugger.MTA2STA.WaitForCall(); + debugger.MTA2STA.PerformAllCalls(); + } + if (this.HasExited) throw new ProcessExitedException(); + } + + public void WaitForPause(TimeSpan timeout) + { + System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); + watch.Start(); + while(this.IsRunning && !this.HasExited) { + TimeSpan timeLeft = timeout - watch.Elapsed; + if (timeLeft <= TimeSpan.FromMilliseconds(10)) break; + //this.TraceMessage("Time left: " + timeLeft.TotalMilliseconds); + debugger.MTA2STA.WaitForCall(timeLeft); + debugger.MTA2STA.PerformAllCalls(); + } + if (this.HasExited) throw new ProcessExitedException(); + } + + /// + /// Waits until the precesses exits. + /// + public void WaitForExit() + { + while(!this.HasExited) { + debugger.MTA2STA.WaitForCall(); + debugger.MTA2STA.PerformAllCalls(); + } + } + + #region Break at begining + + private void OnModulesAdded(object sender, CollectionItemEventArgs e) + { + if (BreakAtBegining) { + if (e.Item.SymReader == null) return; // No symbols + // create a BP at entry point + uint entryPoint = e.Item.SymReader.GetUserEntryPoint(); + if (entryPoint == 0) return; // no EP + var mainFunction = e.Item.CorModule.GetFunctionFromToken(entryPoint); + var corBreakpoint = mainFunction.CreateBreakpoint(); + corBreakpoint.Activate(1); + + // create a SD BP + var breakpoint = new Breakpoint(this.debugger, corBreakpoint); + this.debugger.Breakpoints.Add(breakpoint); + breakpoint.Hit += delegate { + if (breakpoint != null) + breakpoint.Remove(); + breakpoint = null; + }; + BreakAtBegining = false; + } + } + + #endregion + } +} diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin b/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin index 4f18bf1662..adc724bd04 100755 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin @@ -928,9 +928,9 @@ - - - + + + - + + + @@ -1548,23 +1550,27 @@ - - - - - + + + + + + + + + diff --git a/src/Main/Base/Project/Src/Commands/DebugCommands.cs b/src/Main/Base/Project/Src/Commands/DebugCommands.cs index f06e420d1f..90fbac7ee8 100644 --- a/src/Main/Base/Project/Src/Commands/DebugCommands.cs +++ b/src/Main/Base/Project/Src/Commands/DebugCommands.cs @@ -83,7 +83,12 @@ namespace ICSharpCode.SharpDevelop.Project.Commands public override void Run() { LoggingService.Info("Debugger Command: StepOver"); - DebuggerService.CurrentDebugger.StepOver(); + if (!DebuggerService.CurrentDebugger.IsDebugging) { + DebuggerService.CurrentDebugger.BreakAtBegining = true; + new Execute().Run(); + } else { + DebuggerService.CurrentDebugger.StepOver(); + } } } @@ -92,7 +97,12 @@ namespace ICSharpCode.SharpDevelop.Project.Commands public override void Run() { LoggingService.Info("Debugger Command: StepInto"); - DebuggerService.CurrentDebugger.StepInto(); + if (!DebuggerService.CurrentDebugger.IsDebugging) { + DebuggerService.CurrentDebugger.BreakAtBegining = true; + new Execute().Run(); + } else { + DebuggerService.CurrentDebugger.StepOver(); + } } } diff --git a/src/Main/Base/Project/Src/Services/Debugger/DefaultDebugger.cs b/src/Main/Base/Project/Src/Services/Debugger/DefaultDebugger.cs index 448d77676b..5d5a7e5359 100644 --- a/src/Main/Base/Project/Src/Services/Debugger/DefaultDebugger.cs +++ b/src/Main/Base/Project/Src/Services/Debugger/DefaultDebugger.cs @@ -25,6 +25,11 @@ namespace ICSharpCode.SharpDevelop.Debugging } } + /// + public bool BreakAtBegining { + get; set; + } + public bool CanDebug(IProject project) { return true; diff --git a/src/Main/Base/Project/Src/Services/Debugger/IDebugger.cs b/src/Main/Base/Project/Src/Services/Debugger/IDebugger.cs index 734b355f4b..65286c70d1 100644 --- a/src/Main/Base/Project/Src/Services/Debugger/IDebugger.cs +++ b/src/Main/Base/Project/Src/Services/Debugger/IDebugger.cs @@ -25,6 +25,13 @@ namespace ICSharpCode.SharpDevelop.Debugging get; } + /// + /// Gets or sets whether the debugger should break at the first line of execution. + /// + bool BreakAtBegining { + get; set; + } + bool CanDebug(IProject project); ///