// 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; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Windows.Forms; using Debugger; using Debugger.AddIn; using Debugger.AddIn.Tooltips; using Debugger.AddIn.TreeModel; using Debugger.Interop.CorPublish; using Debugger.MetaData; using ICSharpCode.Core; using ICSharpCode.Core.WinForms; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.SharpDevelop.Editor.Bookmarks; using ICSharpCode.SharpDevelop.Debugging; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Gui.OptionPanels; using ICSharpCode.SharpDevelop.Project; using Mono.Cecil; 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; internal IDebuggerDecompilerService debuggerDecompilerService; /// 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) { SourcecodeSegment seg = CurrentStackFrame.SetIP(filename, line, column, dryRun); WindowsDebugger.RefreshPads(); JumpToCurrentLine(); return seg != null; } else { return false; } } public void Dispose() { Stop(); } #endregion public event EventHandler Initialize; public void InitializeService() { // get decompiler service debuggerDecompilerService = SD.GetService(); // 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 = null; if (bookmark is DecompiledBreakpointBookmark) { try { if (debuggerDecompilerService == null) { LoggingService.Warn("No IDebuggerDecompilerService found!"); return; } var dbb = (DecompiledBreakpointBookmark)bookmark; MemberReference memberReference = null; string assemblyFile, typeName; if (DecompiledBreakpointBookmark.GetAssemblyAndType(dbb.FileName, out assemblyFile, out typeName)) { memberReference = dbb.GetMemberReference(debuggerDecompilerService.GetAssemblyResolver(assemblyFile)); } int token = memberReference.MetadataToken.ToInt32(); if (!debuggerDecompilerService.CheckMappings(token)) debuggerDecompilerService.DecompileOnDemand(memberReference as TypeDefinition); int[] ilRanges; int methodToken; #warning decompiler // if (debuggerDecompilerService.GetILAndTokenByLineNumber(token, dbb.LineNumber, out ilRanges, out methodToken)) { // CurrentDebugger.AddILBreakpoint(memberReference.FullName, dbb.LineNumber, memberReference.MetadataToken.ToInt32(), methodToken, ilRanges[0], dbb.IsEnabled); // } } catch (System.Exception ex) { LoggingService.Error("Error on DecompiledBreakpointBookmark: " + ex.Message); } } else { breakpoint = CurrentDebugger.AddBreakpoint(bookmark.FileName, bookmark.LineNumber, 0, bookmark.IsEnabled); } if (breakpoint == null) { LoggingService.Warn(string.Format("unable to create breakpoint: {0}", bookmark.ToString())); return; } 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.InvokeAsync(() => MessageService.ShowWarning(errorMessage)).FireAndForget(); 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; if (e.ExceptionThrown != null) { HandleException(e); return; } bool breakpointHit = false; foreach (Breakpoint breakpoint in e.BreakpointsHit) { var bookmark = SD.BookmarkManager.Bookmarks.OfType().First(bm => bm.InternalBreakpointObject == breakpoint); if (string.IsNullOrEmpty(bookmark.Condition)) { breakpointHit = true; } else { if (EvaluateCondition(bookmark.Condition)) { breakpointHit = true; DebuggerService.PrintDebugMessage(string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHitAtBecause}") + "\n", bookmark.LineNumber, bookmark.FileName, bookmark.Condition)); } } } // We can have several, potentially conditional, breakpoints at the same time // ... as well as stepper happening on the same line if (e.Break || breakpointHit) { LoggingService.Info("Jump to current line"); JumpToCurrentLine(); } else { e.Process.AsyncContinue(); } RefreshPads(); } void debuggedProcess_DebuggingResumed(object sender, DebuggerEventArgs e) { OnIsProcessRunningChanged(EventArgs.Empty); DebuggerService.RemoveCurrentLineMarker(); CurrentThread = null; CurrentStackFrame = null; RefreshPads(); } void HandleException(DebuggerPausedEventArgs e) { JumpToCurrentLine(); StringBuilder stacktraceBuilder = new StringBuilder(); if (e.ExceptionThrown.IsUnhandled) { // Need to intercept now so that we can evaluate properties if (e.Thread.InterceptException(e.ExceptionThrown)) { stacktraceBuilder.AppendLine(e.ExceptionThrown.ToString()); string stackTrace; try { stackTrace = e.ExceptionThrown.GetStackTrace(e.Thread, StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.EndOfInnerException}")); } catch (GetValueException) { stackTrace = e.Thread.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.ExceptionThrown.ToString()); stacktraceBuilder.Append(e.Thread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}"))); } } else { stacktraceBuilder.AppendLine(e.ExceptionThrown.ToString()); stacktraceBuilder.Append(e.Thread.GetStackTrace(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.Symbols}"), StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.LineFormat.NoSymbols}"))); } string title = e.ExceptionThrown.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.ExceptionThrown.Type); Bitmap icon = WinFormsResourceService.GetBitmap(e.ExceptionThrown.IsUnhandled ? "Icons.32x32.Error" : "Icons.32x32.Warning"); DebuggeeExceptionForm.Show(e.Process, title, message, stacktraceBuilder.ToString(), icon, e.ExceptionThrown.IsUnhandled, e.ExceptionThrown); RefreshPads(); } public bool BreakAndInterceptHandledException(Debugger.Exception exception) { if (!CurrentThread.InterceptException(exception)) { MessageService.ShowError("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptHandledException}"); return false; } JumpToCurrentLine(); return true; } 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 (CurrentThread == null) return; SD.Workbench.MainWindow.Activate(); // if (debuggedProcess.IsSelectedFrameForced()) { if (CurrentThread != null && CurrentStackFrame.HasSymbols) { JumpToSourceCode(); } else { #warning JumpToDecompiledCode(CurrentStackFrame); } // } else { // var frame = debuggedProcess.SelectedThread.MostRecentStackFrame; // // other pause reasons // if (frame != null && frame.HasSymbols) { // JumpToSourceCode(); // } else { // // use most recent stack frame because we don't have the symbols // JumpToDecompiledCode(debuggedProcess.SelectedThread.MostRecentStackFrame); // } // } } void JumpToSourceCode() { if (CurrentProcess == null || CurrentStackFrame == null) return; SourcecodeSegment nextStatement = CurrentStackFrame.NextStatement; if (nextStatement != null) { DebuggerService.RemoveCurrentLineMarker(); DebuggerService.JumpToCurrentLine(nextStatement.Filename, nextStatement.StartLine, nextStatement.StartColumn, nextStatement.EndLine, nextStatement.EndColumn); } } /* void JumpToDecompiledCode(Debugger.StackFrame frame) { if (frame == null) { LoggingService.Error("No stack frame!"); return; } if (debuggerDecompilerService == null) { LoggingService.Warn("No IDebuggerDecompilerService found!"); return; } // check for options - if these options are enabled, debugging decompiled code should not continue if (!CurrentProcess.Options.DecompileCodeWithoutSymbols) { LoggingService.Info("Decompiled code debugging is disabled!"); return; } DebuggerService.RemoveCurrentLineMarker(); // get external data int typeToken = frame.MethodInfo.DeclaringType.MetadataToken; int methodToken = frame.MethodInfo.MetadataToken; int ilOffset = frame.IP; int[] ilRanges = null; int line = -1; bool isMatch = false; var debugType = (DebugType)frame.MethodInfo.DeclaringType; debuggerDecompilerService.DebugStepInformation = Tuple.Create(methodToken, ilOffset); if (debuggerDecompilerService.GetILAndLineNumber(typeToken, methodToken, ilOffset, out ilRanges, out line, out isMatch)) { // update marker & navigate to line NavigationService.NavigateTo(debugType.DebugModule.FullPath, debugType.FullNameWithoutGenericArguments, IDStringProvider.GetIDString(frame.MethodInfo), line); } else { // no line => do decompilation NavigationService.NavigateTo(debugType.DebugModule.FullPath, debugType.FullNameWithoutGenericArguments, IDStringProvider.GetIDString(frame.MethodInfo)); } } */ 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) { } } } } }