//
//
//
// $Revision$
//
#region License
//
// Copyright (c) 2007, ic#code
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the ic#code nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
#endregion
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
using Debugger;
using Debugger.AddIn;
using Debugger.AddIn.TreeModel;
using Debugger.Core.Wrappers.CorPub;
using Debugger.Expressions;
using ICSharpCode.Core;
using ICSharpCode.Core.WinForms;
using ICSharpCode.NRefactory;
using ICSharpCode.SharpDevelop.Debugging;
using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project;
using Bitmap = System.Drawing.Bitmap;
using BM = ICSharpCode.SharpDevelop.Bookmarks;
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;
Debugger.Process debuggedProcess;
DynamicTreeDebuggerRow currentTooltipRow;
Expression currentTooltipExpression;
public event EventHandler ProcessSelected;
public NDebugger DebuggerCore {
get {
return debugger;
}
}
public Debugger.Process DebuggedProcess {
get {
return debuggedProcess;
}
}
public static Debugger.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);
Debugger.Process process = debugger.Start(processStartInfo.FileName,
processStartInfo.WorkingDirectory,
processStartInfo.Arguments);
SelectProcess(process);
}
}
public void ShowAttachDialog()
{
using (AttachToProcessForm attachForm = new AttachToProcessForm()) {
if (attachForm.ShowDialog() == 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);
Debugger.Process process = debugger.Attach(existingProcess);
attached = true;
SelectProcess(process);
}
}
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.
///
public Value GetValueFromName(string variableName)
{
if (debuggedProcess == null || debuggedProcess.IsRunning || debuggedProcess.SelectedStackFrame == null) {
return null;
} else {
return AstEvaluator.Evaluate(variableName, SupportedLanguage.CSharp, debuggedProcess.SelectedStackFrame);
}
}
public bool IsManaged(int processId)
{
if (corPublish == null) {
corPublish = new ICorPublish();
}
ICorPublishProcess process = corPublish.GetProcess(processId);
if (process != null) {
return process.IsManaged;
}
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;
}
}
///
/// Gets the tooltip control that shows the value of given variable.
/// Return null if no tooltip is available.
///
public DebuggerGridControl GetTooltipControl(string variableName)
{
ValueNode valueNode;
try {
Value val = GetValueFromName(variableName);
if (val == null) return null;
valueNode = new ValueNode(val);
} catch (GetValueException) {
return null;
}
try {
currentTooltipRow = new DynamicTreeDebuggerRow(DebuggedProcess, valueNode);
} catch (AbortedBecauseDebuggeeResumedException) {
return null;
}
currentTooltipExpression = valueNode.Expression;
return new DebuggerGridControl(currentTooltipRow);
}
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 System.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.ProcessStarted += debugger_ProcessStarted;
debugger.ProcessExited += 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.AddBreakpoint(bookmark.FileName, null, bookmark.LineNumber + 1, 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;
TextEditorDisplayBindingWrapper file = FileService.GetOpenFile(bookmark.FileName) as TextEditorDisplayBindingWrapper;
if (file != null) {
byte[] fileContent = Encoding.UTF8.GetBytes(file.Text);
// Insert UTF-8 BOM
byte[] fileContent2 = new byte[fileContent.Length + 3];
Array.Copy(fileContent, 0, fileContent2, 3, fileContent.Length);
fileContent2[0] = 0xEF;
fileContent2[1] = 0xBB;
fileContent2[2] = 0xBF;
fileMD5 = new MD5CryptoServiceProvider().ComputeHash(fileContent2);
} 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 + 1;
};
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.Script);
switch (bookmark.Action) {
case BreakpointAction.Ask:
Bitmap icon = WinFormsResourceService.GetBitmap(false ? "Icons.32x32.Error" : "Icons.32x32.Warning");
DebuggerEventForm.Result result =
DebuggerEventForm.Show(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHit}"),
string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHitAt}"), bookmark.LineNumber, bookmark.FileName), icon, true);
switch (result) {
case DebuggerEventForm.Result.Break:
break;
case DebuggerEventForm.Result.Continue:
this.debuggedProcess.AsyncContinue();
break;
case DebuggerEventForm.Result.Terminate:
this.debuggedProcess.AsyncTerminate();
break;
}
break;
case BreakpointAction.Break:
break;
case BreakpointAction.Continue:
this.debuggedProcess.AsyncContinue();
break;
case BreakpointAction.Script:
if (Evaluate(bookmark.Script, bookmark.ScriptLanguage))
DebuggerService.PrintDebugMessage(string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHitAtBecause}") + "\n", bookmark.LineNumber + 1, bookmark.FileName, bookmark.Script));
else
this.debuggedProcess.AsyncContinue();
break;
case BreakpointAction.Terminate:
this.debuggedProcess.AsyncTerminate();
break;
case BreakpointAction.Trace:
DebuggerService.PrintDebugMessage(string.Format(StringParser.Parse("${res:MainWindow.Windows.Debug.Conditional.Breakpoints.BreakpointHitAt}") + "\n", bookmark.LineNumber + 1, bookmark.FileName));
break;
}
});
BM.BookmarkEventHandler bp_bookmarkManager_Removed = null;
bp_bookmarkManager_Removed = (sender, e) => {
if (bookmark == e.Bookmark) {
debugger.RemoveBreakpoint(breakpoint);
// unregister the events
debugger.ProcessStarted -= bp_debugger_ProcessStarted;
debugger.ProcessExited -= bp_debugger_ProcessExited;
breakpoint.Hit -= bp_debugger_BreakpointHit;
BM.BookmarkManager.Removed -= bp_bookmarkManager_Removed;
}
};
// register the events
debugger.ProcessStarted += bp_debugger_ProcessStarted;
debugger.ProcessExited += bp_debugger_ProcessExited;
breakpoint.Hit += bp_debugger_BreakpointHit;
BM.BookmarkManager.Removed += bp_bookmarkManager_Removed;
}
bool Evaluate(string code, string language)
{
try {
SupportedLanguage supportedLanguage = (SupportedLanguage)Enum.Parse(typeof(SupportedLanguage), language.Replace("#", "Sharp"), true);
Value val = AstEvaluator.Evaluate(code, supportedLanguage, debuggedProcess.SelectedStackFrame);
if (val.PrimitiveValue is bool)
return (bool)val.PrimitiveValue;
else
return false;
} catch (GetValueException e) {
WorkbenchSingleton.SafeThreadAsyncCall(MessageService.ShowError, e);
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, ProcessEventArgs e)
{
if (debugger.Processes.Count == 1) {
if (DebugStarted != null) {
DebugStarted(this, EventArgs.Empty);
}
}
e.Process.LogMessage += LogMessage;
}
void debugger_ProcessExited(object sender, ProcessEventArgs e)
{
if (debugger.Processes.Count == 0) {
if (DebugStopped != null) {
DebugStopped(this, e);
}
SelectProcess(null);
} else {
SelectProcess(debugger.Processes[0]);
}
}
public void SelectProcess(Debugger.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();
}
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 msg = new StringBuilder();
// Need to intercept now so that we can evaluate properties
if (e.Process.SelectedThread.InterceptCurrentException()) {
msg.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}"));
}
msg.Append(stackTrace);
} else {
// For example, happens on stack overflow
msg.AppendLine(StringParser.Parse("${res:MainWindow.Windows.Debug.ExceptionForm.Error.CannotInterceptException}"));
msg.AppendLine(e.Exception.ToString());
msg.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 = msg.ToString();
Bitmap icon = WinFormsResourceService.GetBitmap(e.IsUnhandled ? "Icons.32x32.Error" : "Icons.32x32.Warning");
bool canContinue = !e.IsUnhandled;
//DebuggerEventForm.Result result = DebuggerEventForm.Show(title, message, icon, canContinue);
DebuggerEventForm.Result result = DebugeeExceptionForm.Show(debuggedProcess, title, message, icon, canContinue);
// If the process was killed while the exception form is still being displayed
if (e.Process.HasExited) return;
switch (result) {
case DebuggerEventForm.Result.Break:
break;
case DebuggerEventForm.Result.Continue:
e.Process.AsyncContinue();
break;
case DebuggerEventForm.Result.Terminate:
e.Process.Terminate();
break;
}
}
public void JumpToCurrentLine()
{
WorkbenchSingleton.MainForm.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);
}
}
}