// 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.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Windows.Forms; using Debugger; using Debugger.AddIn; using Debugger.AddIn.Tooltips; using Debugger.AddIn.TreeModel; using Debugger.Interop.CorPublish; using ICSharpCode.Core; using ICSharpCode.Core.WinForms; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.SharpDevelop.Debugging; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Project; using Process = Debugger.Process; using StackFrame = Debugger.StackFrame; using TreeNode = Debugger.AddIn.TreeModel.TreeNode; namespace ICSharpCode.SharpDevelop.Services { public class WindowsDebugger : IDebugger { public static WindowsDebugger Instance { get; set; } public static NDebugger CurrentDebugger { get; private set; } public static Process CurrentProcess { get; private set; } public static Thread CurrentThread { get; set; } public static StackFrame CurrentStackFrame { get; set; } public static Action RefreshingPads; public static void RefreshPads() { if (RefreshingPads != null) { RefreshingPads(); } } /// /// Gets the thread which should be used for all evaluations. /// For the time being, it is the selected thread, but we might /// want to have a dedicated evaluation thread in the future. /// /// /// This exists for two reasons: /// 1) So that the addin has explicit control over evaluations rather than the core /// 2) The need to pass this to calls is a reminder that they might do evaluation /// public static Thread EvalThread { get { if (CurrentProcess == null) throw new GetValueException("Debugger is not running"); if (CurrentProcess.IsRunning) throw new GetValueException("Process is not paused"); if (CurrentThread == null) throw new GetValueException("No thread selected"); return CurrentThread; } } enum StopAttachedProcessDialogResult { Detach = 0, Terminate = 1, Cancel = 2 } bool attached; ICorPublish corPublish; List symbolSources = new List(); /// public bool BreakAtBeginning { get; set; } public bool ServiceInitialized { get { return CurrentDebugger != null; } } public WindowsDebugger() { Instance = this; } #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 && CurrentProcess != null; } } public bool IsAttached { get { return ServiceInitialized && attached; } } public bool IsProcessRunning { get { return IsDebugging && CurrentProcess.IsRunning; } } public bool CanDebug(IProject project) { return true; } public void Start(ProcessStartInfo processStartInfo) { if (IsDebugging) { MessageService.ShowMessage(errorDebugging); return; } if (!ServiceInitialized) { InitializeService(); } string version = CurrentDebugger.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 (CurrentDebugger.IsKernelDebuggerEnabled) { MessageService.ShowMessage("${res:XML.MainMenu.DebugMenu.Error.KernelDebuggerEnabled}"); } else { attached = false; if (DebugStarting != null) DebugStarting(this, EventArgs.Empty); UpdateBreakpointLines(); try { // set the JIT flag for evaluating optimized code Process.DebugMode = DebugModeFlag.Debug; CurrentProcess = CurrentDebugger.Start(processStartInfo.FileName, processStartInfo.WorkingDirectory, processStartInfo.Arguments, this.BreakAtBeginning); debugger_ProcessStarted(); } 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; 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(SD.WinForms.MainWin32Window) == DialogResult.OK) { Attach(attachForm.Process); } } } public void Attach(System.Diagnostics.Process existingProcess) { if (existingProcess == null) return; if (IsDebugging) { MessageService.ShowMessage(errorDebugging); return; } if (!ServiceInitialized) { InitializeService(); } string version = CurrentDebugger.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); UpdateBreakpointLines(); try { // set the JIT flag for evaluating optimized code Process.DebugMode = DebugModeFlag.Debug; CurrentProcess = CurrentDebugger.Attach(existingProcess); debugger_ProcessStarted(); attached = true; CurrentProcess.ModuleLoaded += process_Modules_Added; } 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() { CurrentDebugger.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: CurrentProcess.Terminate(); attached = false; break; case StopAttachedProcessDialogResult.Detach: Detach(); attached = false; break; } } else { CurrentProcess.Terminate(); } } public void Break() { if (CurrentProcess != null && CurrentProcess.IsRunning) { CurrentProcess.Break(); } } public void Continue() { if (CurrentProcess != null && CurrentProcess.IsPaused) { CurrentProcess.AsyncContinue(); } } public void StepInto() { if (CurrentStackFrame != null) { CurrentStackFrame.AsyncStepInto(); } } public void StepOver() { if (CurrentStackFrame != null) { CurrentStackFrame.AsyncStepOver(); } } public void StepOut() { if (CurrentStackFrame != null) { CurrentStackFrame.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); } } 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; } public bool SetInstructionPointer(string filename, int line, int column, bool dryRun) { if (CurrentStackFrame != null) { if (CurrentStackFrame.SetIP(filename, line, column, dryRun)) { WindowsDebugger.RefreshPads(); JumpToCurrentLine(); } } return false; } public void Dispose() { Stop(); } #endregion public event EventHandler Initialize; public void InitializeService() { symbolSources.Add(new PdbSymbolSource()); symbolSources.AddRange(AddInTree.BuildItems("/SharpDevelop/Services/DebuggerService/SymbolSource", null, false)); // init NDebugger CurrentDebugger = new NDebugger(); CurrentDebugger.Options = DebuggingOptions.Instance; foreach (BreakpointBookmark b in SD.BookmarkManager.Bookmarks.OfType()) { AddBreakpoint(b); } SD.BookmarkManager.BookmarkAdded += (sender, e) => { BreakpointBookmark bm = e.Bookmark as BreakpointBookmark; if (bm != null) { AddBreakpoint(bm); } }; SD.BookmarkManager.BookmarkRemoved += (sender, e) => { BreakpointBookmark bm = e.Bookmark as BreakpointBookmark; if (bm != null) { Breakpoint bp = bm.InternalBreakpointObject as Breakpoint; CurrentDebugger.RemoveBreakpoint(bp); } }; if (Initialize != null) { Initialize(this, null); } } void UpdateBreakpointLines() { foreach (BreakpointBookmark bookmark in SD.BookmarkManager.Bookmarks.OfType()) { Breakpoint breakpoint = bookmark.InternalBreakpointObject as Breakpoint; breakpoint.Line = bookmark.LineNumber; breakpoint.Column = bookmark.ColumnNumber; } } void UpdateBreakpointIcons() { foreach (BreakpointBookmark bookmark in SD.BookmarkManager.Bookmarks.OfType()) { Breakpoint breakpoint = bookmark.InternalBreakpointObject as Breakpoint; bookmark.IsHealthy = (CurrentProcess == null) || breakpoint.IsSet; } } void AddBreakpoint(BreakpointBookmark bookmark) { Breakpoint breakpoint = CurrentDebugger.AddBreakpoint(bookmark.FileName, bookmark.LineNumber, 0, bookmark.IsEnabled); bookmark.InternalBreakpointObject = breakpoint; bookmark.IsHealthy = (CurrentProcess == null) || breakpoint.IsSet; bookmark.IsEnabledChanged += delegate { breakpoint.IsEnabled = bookmark.IsEnabled; }; } bool EvaluateCondition(string code) { try { if (CurrentStackFrame == null || CurrentStackFrame.NextStatement == null) return false; var val = Evaluate(code); if (val != null && val.Type.IsPrimitiveType() && 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); SD.MainThread.InvokeAsyncAndForget(() => MessageService.ShowWarning(errorMessage)); return true; } } void LogMessage(object sender, MessageEventArgs e) { DebuggerService.PrintDebugMessage(e.Message); } void debugger_ProcessStarted() { if (DebugStarted != null) { DebugStarted(this, EventArgs.Empty); } CurrentProcess.ModuleLoaded += (s, e) => UpdateBreakpointIcons(); CurrentProcess.ModuleLoaded += (s, e) => RefreshPads(); CurrentProcess.ModuleUnloaded += (s, e) => RefreshPads(); CurrentProcess.LogMessage += LogMessage; CurrentProcess.Paused += debuggedProcess_DebuggingPaused; CurrentProcess.Resumed += debuggedProcess_DebuggingResumed; CurrentProcess.Exited += (s, e) => debugger_ProcessExited(); UpdateBreakpointIcons(); } void debugger_ProcessExited() { if (DebugStopped != null) { DebugStopped(this, EventArgs.Empty); } CurrentProcess = null; CurrentThread = null; CurrentStackFrame = null; UpdateBreakpointIcons(); RefreshPads(); } void debuggedProcess_DebuggingPaused(object sender, DebuggerPausedEventArgs e) { OnIsProcessRunningChanged(EventArgs.Empty); CurrentProcess = e.Process; CurrentThread = e.Thread; CurrentStackFrame = CurrentThread != null ? CurrentThread.MostRecentUserStackFrame : null; // Select the symbol source var symbolSource = symbolSources.FirstOrDefault(s => CurrentStackFrame == null || s.HasSymbols(CurrentStackFrame.MethodInfo)); CurrentProcess.SymbolSource = symbolSource ?? symbolSources.First(); // We can have several events happening at the same time bool breakProcess = e.Break; // Handle thrown exceptions foreach(Thread exceptionThread in e.ExceptionsThrown) { JumpToCurrentLine(); Thread evalThread = exceptionThread; bool isUnhandled = (exceptionThread.CurrentExceptionType == ExceptionType.Unhandled); Value exception = exceptionThread.CurrentException.GetPermanentReferenceOfHeapValue(); List innerExceptions = new List(); for(Value innerException = exception; !innerException.IsNull; innerException = innerException.GetFieldValue("_innerException")) { innerExceptions.Add(innerException.GetPermanentReferenceOfHeapValue()); } // Get the exception description string stacktrace = string.Empty; for(int i = 0; i < innerExceptions.Count; i++) { if (i > 0) { stacktrace += " ---> "; } stacktrace += innerExceptions[i].Type.FullName; Value messageValue = innerExceptions[i].GetFieldValue("_message"); if (!messageValue.IsNull) { stacktrace += ": " + messageValue.AsString(); } } stacktrace += Environment.NewLine + Environment.NewLine; // Get the stacktrace string formatSymbols = StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"); string formatNoSymbols = StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}"); if (isUnhandled) { // Need to intercept now so that we can evaluate properties // Intercept may fail (eg StackOverflow) if (exceptionThread.InterceptException()) { try { // Try to evaluate the StackTrace property to get the .NET formated stacktrace for(int i = innerExceptions.Count - 1; i >= 0; i--) { Value stackTraceValue = innerExceptions[i].GetPropertyValue(evalThread, "StackTrace"); if (!stackTraceValue.IsNull) { stacktrace += stackTraceValue.AsString() + Environment.NewLine; } if (i > 0) { stacktrace += " " + StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.EndOfInnerException}") + Environment.NewLine; } } } catch (GetValueException) { stacktrace += exceptionThread.GetStackTrace(formatSymbols, formatNoSymbols); } } else { stacktrace += StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptException}") + Environment.NewLine + Environment.NewLine; stacktrace += exceptionThread.GetStackTrace(formatSymbols, formatNoSymbols); } } else { // Do not intercept handled expetions stacktrace += exceptionThread.GetStackTrace(formatSymbols, formatNoSymbols); } string title = isUnhandled ? StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Unhandled}") : StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Title.Handled}"); string type = string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Message}"), exception.Type); Bitmap icon = WinFormsResourceService.GetBitmap(isUnhandled ? "Icons.32x32.Error" : "Icons.32x32.Warning"); if (DebuggeeExceptionForm.Show(e.Process, title, type, stacktrace, icon, isUnhandled)) { breakProcess = true; // The dialog box is allowed to kill the process if (e.Process.HasExited) { return; } // Intercept handled exception *after* the user decided to break if (!isUnhandled) { if (!exceptionThread.InterceptException()) { MessageService.ShowError("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptHandledException}"); } } } } // Handle breakpoints foreach (Breakpoint breakpoint in e.BreakpointsHit) { var bookmark = SD.BookmarkManager.Bookmarks.OfType().First(bm => bm.InternalBreakpointObject == breakpoint); if (string.IsNullOrEmpty(bookmark.Condition)) { breakProcess = true; } else { if (EvaluateCondition(bookmark.Condition)) { breakProcess = true; DebuggerService.PrintDebugMessage(string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHitAtBecause}") + "\n", bookmark.LineNumber, bookmark.FileName, bookmark.Condition)); } } } if (breakProcess) { JumpToCurrentLine(); RefreshPads(); } else { e.Process.AsyncContinue(); } } void debuggedProcess_DebuggingResumed(object sender, DebuggerEventArgs e) { OnIsProcessRunningChanged(EventArgs.Empty); DebuggerService.RemoveCurrentLineMarker(); CurrentThread = null; CurrentStackFrame = null; RefreshPads(); } public static Value Evaluate(string code) { if (CurrentStackFrame == null || CurrentStackFrame.NextStatement == null) throw new GetValueException("no stackframe available!"); var location = CurrentStackFrame.NextStatement; var fileName = new FileName(location.Filename); var rr = SD.ParserService.ResolveSnippet(fileName, new TextLocation(location.StartLine, location.StartColumn), new ParseableFileContentFinder().Create(fileName), code, null, System.Threading.CancellationToken.None); return new ExpressionEvaluationVisitor(CurrentStackFrame, EvalThread, CurrentStackFrame.AppDomain.Compilation).Convert(rr); } public void JumpToCurrentLine() { if (CurrentStackFrame == null) return; SD.Workbench.MainWindow.Activate(); if (CurrentStackFrame.HasSymbols) { SequencePoint nextStatement = CurrentStackFrame.NextStatement; if (nextStatement != null) { DebuggerService.RemoveCurrentLineMarker(); 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); } void process_Modules_Added(object sender, ModuleEventArgs e) { if (ProjectService.OpenSolution == null) return; ProjectService.OpenSolution.Projects .Where(p => e.Module.Name.IndexOf(p.Name) >= 0) .ForEach(p => e.Module.LoadSymbolsFromDisk(new []{ Path.GetDirectoryName(p.OutputAssemblyFullPath) })); } public void HandleToolTipRequest(ToolTipRequestEventArgs e) { if (!(IsDebugging && CurrentProcess.IsPaused)) return; var resolveResult = e.ResolveResult; if (resolveResult == null) return; if (resolveResult is LocalResolveResult || resolveResult is MemberResolveResult || resolveResult is InvocationResolveResult) { string text = string.Empty; try { text = new ResolveResultPrettyPrinter().Print(resolveResult); } catch (NotImplementedException) { } Func getValue = delegate { ExpressionEvaluationVisitor eval = new ExpressionEvaluationVisitor(CurrentStackFrame, EvalThread, CurrentStackFrame.AppDomain.Compilation); return eval.Convert(resolveResult); }; try { var rootNode = new ValueNode(ClassBrowserIconService.LocalVariable, text, getValue); e.SetToolTip(new DebuggerTooltipControl(rootNode)); } catch (InvalidOperationException) { } } } } }