Chromium Code Reviews| Index: visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs |
| diff --git a/visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs b/visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4edca89ec9bce72e3a5f9304ad2950db921f5146 |
| --- /dev/null |
| +++ b/visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs |
| @@ -0,0 +1,412 @@ |
| +using EnvDTE; |
| +using EnvDTE80; |
| +using System; |
| +using System.Collections.Generic; |
| +using System.IO; |
| +using System.Linq; |
| +using System.Text; |
| +using System.Resources; |
| +using System.Windows.Forms; |
| + |
| +using Microsoft.VisualStudio.Shell; |
| +using Microsoft.VisualStudio.VCProject; |
| +using Microsoft.VisualStudio.VCProjectEngine; |
| + |
| +namespace NativeClientVSAddIn |
| +{ |
| + /// <summary> |
| + /// This class contains functions and utilities which are run when the user |
| + /// "presses F5" or otherwise starts debugging from within Visual Studio. |
| + /// </summary> |
| + class PluginDebuggerHelper |
| + { |
| + /// <summary> |
| + /// Constructs the PluginDebuggerHelper. Object is not useable until InitializeFromProjectSettings() is called |
|
noelallen1
2012/07/11 22:16:46
80 Char
|
| + /// </summary> |
| + /// <param name="dte">Automation object from Visual Studio</param> |
| + public PluginDebuggerHelper(DTE2 dte) |
| + { |
| + if (dte == null) |
| + { |
| + throw new ArgumentNullException("dte"); |
| + } |
|
Petr Hosek
2012/07/10 05:37:21
Pre/post-checks are much better done with Code Con
tysand
2012/07/11 05:23:46
We examined this, and it seems that this would req
|
| + |
| + _dte = dte; |
| + _isDebuggingNow = false; |
|
Petr Hosek
2012/07/10 05:37:21
I would rather do the initialization for simple va
tysand
2012/07/11 05:23:46
Done.
|
| + _isProperlyInitialized = false; |
| + _webServer = null; |
| + _webServerOutputPane = null; |
| + |
| + // Every second, check for a new instance of the plug-in to attach to |
|
noelallen1
2012/07/11 22:16:46
80
|
| + _pluginFinderTimer = new Timer(); |
| + _pluginFinderTimer.Tick += new EventHandler(FindAndAttachToPlugin); |
| + _pluginFinderForbiddenPids = new List<uint>(); |
| + _processSearcher = new ProcessSearcher(); |
| + } |
| + |
| + /// <summary> |
| + /// Initializes the PluginDebuggerHelper with the current project settings |
| + /// If project settings are unsupported for NaCl/Pepper debugging then |
| + /// the object is not initialized and we return false |
| + /// </summary> |
| + /// <returns>True if the object is successfully initialized, false otherwise</returns> |
| + public bool InitializeFromProjectSettings() |
| + { |
| + _isProperlyInitialized = false; |
| + |
| + // We require that there is only a single start-up project |
|
noelallen1
2012/07/11 22:16:46
Comments should have ending '.'
|
| + Array startupProjects = _dte.Solution.SolutionBuild.StartupProjects as Array; |
| + if (startupProjects == null || startupProjects.Length == 0) |
| + { |
| + throw new ArgumentOutOfRangeException("startupProjects.Length"); |
| + } |
| + else if (startupProjects.Length > 1) |
| + { |
| + WebServerWriteLine(Strings.WebServerMultiStartProjectWarning); |
| + } |
| + |
| + // Get the start-up project object |
| + List<Project> projList = _dte.Solution.Projects.OfType<Project>().ToList(); |
| + Project startProject = projList.Find(proj => proj.UniqueName == (startupProjects.GetValue(0) as string)); |
| + |
| + // Get the current platform type |
| + if (String.Compare(startProject.ConfigurationManager.ActiveConfiguration.PlatformName, |
| + Strings.PepperPlatformName, true) == 0) |
| + { |
| + _projectPlatformType = ProjectPlatformType.Pepper; |
| + _debuggerType = DebuggerType.VS; |
| + PluginFoundEvent += new EventHandler<PluginFoundEventArgs>(AttachVSDebugger); |
| + } |
| + else if (String.Compare(startProject.ConfigurationManager.ActiveConfiguration.PlatformName, |
| + Strings.NaClPlatformName, true) == 0) |
| + { |
| + _projectPlatformType = ProjectPlatformType.NaCl; |
| + _debuggerType = DebuggerType.GDB; |
| + PluginFoundEvent += new EventHandler<PluginFoundEventArgs>(AttachNaClGDB); |
| + } |
| + else |
| + { |
| + _projectPlatformType = ProjectPlatformType.Other; |
| + return false; |
| + } |
| + |
| + // We only support certain project types (e.g. C/C++ projects) |
| + if (Utility.IsVisualCProject(startProject)) |
| + { |
| + VCConfiguration config = Utility.GetActiveVCConfiguration(startProject); |
| + VCProject vcproj = (VCProject)startProject.Object; |
| + VCLinkerTool linker = config.Tools.Item("VCLinkerTool"); |
| + _pluginProjectDirectory = vcproj.ProjectDirectory; // MSDN ensures us this cannot have macros |
| + _pluginAssembly = config.Evaluate(linker.OutputFile); |
| + _pluginOutputDirectory = config.Evaluate(config.OutputDirectory); |
| + _isProperlyInitialized = true; |
| + } |
| + |
| + int webServerPort = 5103; |
| + _sdkRootDirectory = Environment.GetEnvironmentVariable(Strings.SDKPathEnvironmentVariable); |
| + _sdkRootDirectory = _sdkRootDirectory.TrimEnd("/\\".ToArray<char>()); // Ensure no trailing directory / |
| + _webServerExecutable = "python.exe"; |
| + _webServerArguments = String.Format("{0}\\examples\\httpd.py {1}", _sdkRootDirectory, webServerPort); |
| + _gdbPath = _sdkRootDirectory + "customGDB\\gdb.exe"; |
| + _irtPath = _sdkRootDirectory + "\\tools\\irt_x86_64.nexe"; |
| + return _isProperlyInitialized; |
| + } |
| + |
| + /// <summary> |
| + /// This function should be called to start the PluginDebuggerHelper functionality |
| + /// </summary> |
| + public void StartDebugging() |
| + { |
| + if (!_isProperlyInitialized) |
| + { |
| + return; |
| + } |
|
Petr Hosek
2012/07/10 05:37:21
Make sure you are using brackets consistently.
tysand
2012/07/11 05:23:46
Done.
|
| + |
| + StartWebServer(); |
| + _pluginFinderTimer.Interval = 1000; // Start at 1 second, move to 5 seconds after plugin found |
| + _pluginFinderTimer.Start(); |
| + _isDebuggingNow = true; |
| + } |
| + |
| + /// <summary> |
| + /// This function should be called to stop the PluginDebuggerHelper functionality |
| + /// </summary> |
| + public void StopDebugging() |
| + { |
| + _isDebuggingNow = false; |
| + _isProperlyInitialized = false; |
| + _pluginFinderTimer.Stop(); |
| + _pluginFinderForbiddenPids.Clear(); |
| + |
| + // Remove all event handlers from the plug-in found event |
| + if (PluginFoundEvent != null) |
| + { |
| + foreach (Delegate del in PluginFoundEvent.GetInvocationList()) |
| + { |
| + PluginFoundEvent -= (EventHandler<PluginFoundEventArgs>)del; |
| + } |
| + } |
| + |
| + if (_webServer != null) |
| + { |
| + _webServer.Kill(); |
| + _webServer = null; |
| + } |
| + |
| + KillGDBProcess(); |
| + } |
| + |
| + /// <summary> |
| + /// Cleans up the started GDB process |
| + /// </summary> |
| + private void KillGDBProcess() |
| + { |
| + if (_gdbProcess != null) |
| + { |
| + if (!_gdbProcess.HasExited) |
| + _gdbProcess.Kill(); |
| + if (!String.IsNullOrEmpty(_gdbInitFileName) && File.Exists(_gdbInitFileName)) |
| + File.Delete(_gdbInitFileName); |
| + _gdbProcess = null; |
| + } |
| + } |
| + |
| + /// <summary> |
| + /// Called periodically by the Visual Studio UI thread to look for our plug-in process |
| + /// and attach the debugger to it. The call is triggered by the _pluginFinderTimer object |
| + /// </summary> |
| + /// <param name="unused">Unused</param> |
| + /// <param name="unused1">Unused</param> |
| + private void FindAndAttachToPlugin(Object unused, EventArgs unused1) |
| + { |
| + string processNameTarget; |
| + string typeFlagTarget; |
| + string identifierFlagTarget; |
| + switch(_projectPlatformType) |
| + { |
| + case ProjectPlatformType.Pepper: |
| + processNameTarget = Strings.PepperProcessName; |
| + typeFlagTarget = Strings.PepperProcessTypeFlag; |
| + identifierFlagTarget = String.Format(Strings.PepperProcessPluginFlagFormat, _pluginAssembly); |
| + break; |
| + case ProjectPlatformType.NaCl: |
| + processNameTarget = Strings.NaClProcessName; |
| + typeFlagTarget = Strings.NaClProcessTypeFlag; |
| + identifierFlagTarget = Strings.NaClDebugFlag; |
| + break; |
| + default: |
| + return; |
| + } |
| + |
| + /* To identify our target plug-in we look for: it's process name (e.g. chrome.exe), |
|
noelallen1
2012/07/11 22:16:46
Avoid mixing // and /* */ in the same file.
|
| + * identifying command line arguments (e.g. --type=renderer), not already be attached to by us, and |
| + * must be a descendant process of this instance of Visual Studio (debugging started by this VS) */ |
| + List<ProcessInfo> results = _processSearcher.GetResultsByName(processNameTarget); |
| + foreach (ProcessInfo process in results) |
| + { |
| + if (!_pluginFinderForbiddenPids.Contains(process.ID) && |
| + !String.IsNullOrEmpty(process.CommandLine) && |
| + process.CommandLine.IndexOf(typeFlagTarget, StringComparison.InvariantCultureIgnoreCase) != -1 && |
|
Petr Hosek
2012/07/10 05:37:21
I would rather introduce an extension method like
tysand
2012/07/11 05:23:46
Done.
|
| + process.CommandLine.IndexOf(identifierFlagTarget, StringComparison.InvariantCultureIgnoreCase) != -1 && |
| + Utility.IsDescendantOfProcess(_processSearcher, process.ID, (uint)System.Diagnostics.Process.GetCurrentProcess().Id)) |
| + { |
| + _pluginFinderForbiddenPids.Add(process.ID); |
| + PluginFoundEvent.Invoke(this, new PluginFoundEventArgs(process.ID)); |
| + |
| + // Slow down the frequency of checks for new plugins to 5 seconds |
| + _pluginFinderTimer.Interval = 5000; |
| + } |
| + } |
| + } |
| + |
| + /// <summary> |
| + /// Attaches the visual studio debugger to a given process ID |
| + /// </summary> |
| + /// <param name="src">Object the event came from</param> |
| + /// <param name="args">Contains the process ID to attach to</param> |
| + private void AttachVSDebugger(object src, PluginFoundEventArgs args) |
| + { |
| + foreach (EnvDTE.Process proc in _dte.Debugger.LocalProcesses) |
| + if (proc.ProcessID == args.ProcessID) |
| + proc.Attach(); |
| + } |
| + |
| + /// <summary> |
| + /// Will do magic to attach GDB to NaCl process properly, load symbols, and load breakpoints |
| + /// </summary> |
| + /// <param name="src">Object the event came from</param> |
| + /// <param name="args">Contains the process ID to attach to, unused since debug stub already attached</param> |
| + private void AttachNaClGDB(object src, PluginFoundEventArgs args) |
| + { |
| + // Clean up any pre-existing GDB process (can happen if user reloads page) |
| + KillGDBProcess(); |
| + // NOTE: The settings listed here are a placeholder until nacl-gdb is ready |
| + _gdbInitFileName = Path.GetTempFileName(); |
| + List<string> contents = new List<string>(); |
| + contents.Add(String.Format("target remote localhost:{0}", 4014)); |
| + contents.Add("set architecture i386:x86-64"); |
| + contents.Add(String.Format("cd {0}", _pluginProjectDirectory.TrimEnd('\\'))); |
| + contents.Add(String.Format("add-symbol-file {0} 0xC00020080", _pluginAssembly.Replace("\\", "\\\\"))); |
| + contents.Add(String.Format("add-symbol-file {0} 0xC0fc00080", _irtPath.Replace("\\", "\\\\"))); |
|
Petr Hosek
2012/07/10 05:37:21
I would rather consider using StringBuilder class
tysand
2012/07/11 05:23:46
Done.
|
| + |
| + foreach (Breakpoint bp in _dte.Debugger.Breakpoints) |
| + { |
| + if (bp.Enabled && bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTypeFile) |
| + contents.Add(String.Format("b {0}:{1}", Path.GetFileName(bp.File), bp.FileLine)); |
| + else if (bp.Enabled && bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTypeFunction) |
| + contents.Add(String.Format("b {0}", bp.FunctionName)); |
| + } |
| + |
| + contents.Add("continue"); |
| + |
| + File.WriteAllLines(_gdbInitFileName, contents); |
| + |
| + try |
| + { |
| + _gdbProcess = new System.Diagnostics.Process(); |
| + _gdbProcess.StartInfo.UseShellExecute = true; |
| + _gdbProcess.StartInfo.FileName = _gdbPath; |
| + _gdbProcess.StartInfo.Arguments = String.Format("-x {0}", _gdbInitFileName); |
| + _gdbProcess.StartInfo.WorkingDirectory = _pluginProjectDirectory; |
| + _gdbProcess.Start(); |
| + } |
| + catch(Exception e) |
| + { |
| + System.Windows.Forms.MessageBox.Show(e.Message); |
| + } |
| + } |
| + |
| + /// <summary> |
| + /// Spins up the web server process to host our plugin |
| + /// </summary> |
| + private void StartWebServer() |
| + { |
| + // Add a panel to the output window which is used to capture output from the web server hosting the plugin |
| + if (_webServerOutputPane == null) |
| + { |
| + _webServerOutputPane = _dte.ToolWindows.OutputWindow.OutputWindowPanes.Add(Strings.WebServerOutputWindowTitle); |
| + } |
| + |
| + _webServerOutputPane.Clear(); |
| + WebServerWriteLine(Strings.WebServerStartMessage); |
| + |
| + try |
| + { |
| + _webServer = new System.Diagnostics.Process(); |
| + _webServer.StartInfo.CreateNoWindow = true; |
| + _webServer.StartInfo.UseShellExecute = false; |
| + _webServer.StartInfo.RedirectStandardOutput = true; |
| + _webServer.StartInfo.RedirectStandardError = true; |
| + _webServer.StartInfo.FileName = _webServerExecutable; |
| + _webServer.StartInfo.Arguments = _webServerArguments; |
| + _webServer.StartInfo.WorkingDirectory = _pluginProjectDirectory; |
| + _webServer.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(WebServerMessageReceive); |
| + _webServer.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(WebServerMessageReceive); |
| + _webServer.Start(); |
| + _webServer.BeginOutputReadLine(); |
| + _webServer.BeginErrorReadLine(); |
| + } |
| + catch(Exception e) |
| + { |
| + WebServerWriteLine(Strings.WebServerStartFail); |
| + WebServerWriteLine("Exception: " + e.Message); |
| + } |
| + } |
| + |
| + /// <summary> |
| + /// Receives output from the web server process to display in the Visual Studio UI |
| + /// </summary> |
| + /// <param name="sender">Unused</param> |
| + /// <param name="e">Contains the data to display</param> |
| + private void WebServerMessageReceive(object sender, System.Diagnostics.DataReceivedEventArgs e) |
| + { |
| + WebServerWriteLine(e.Data); |
| + } |
| + |
| + /// <summary> |
| + /// Helper function to write data to the Web Server Output Pane |
| + /// </summary> |
| + /// <param name="message">Message to write</param> |
| + private void WebServerWriteLine(string message) |
| + { |
| + _webServerOutputPane.OutputString(message + "\n"); |
| + _webServerOutputPane.Activate(); |
| + } |
| + |
| + /// <summary> |
| + /// Specifies the debugger to use when debugging |
| + /// </summary> |
| + private enum DebuggerType |
| + { |
| + GDB, |
| + VS, |
| + WinGDB |
| + } |
| + |
| + /// <summary> |
| + /// Specifies the type of plug-in being run in this debug session |
| + /// </summary> |
| + private enum ProjectPlatformType |
| + { |
| + Other, |
| + NaCl, |
| + Pepper |
| + } |
| + |
| + private DTE2 _dte; |
| + private bool _isProperlyInitialized; |
| + private bool _isDebuggingNow; |
| + private string _pluginProjectDirectory; |
| + private string _pluginOutputDirectory; |
| + private string _pluginAssembly; |
| + |
| + private string _irtPath; |
| + private string _sdkRootDirectory; |
| + |
| + private System.Diagnostics.Process _gdbProcess; |
| + private string _gdbPath; |
| + private string _gdbInitFileName; |
| + |
| + private DebuggerType _debuggerType; |
| + private ProjectPlatformType _projectPlatformType; |
| + |
| + private System.Diagnostics.Process _webServer; |
| + private OutputWindowPane _webServerOutputPane; |
| + private string _webServerExecutable; |
| + private string _webServerArguments; |
| + |
| + private Timer _pluginFinderTimer; |
| + private List<uint> _pluginFinderForbiddenPids; |
| + |
| + private IProcessSearcher _processSearcher; |
| + |
| + public event EventHandler<PluginFoundEventArgs> PluginFoundEvent; |
| + |
| + /// <summary> |
| + /// Indicates true if the debugger is running (even during breakpoints) |
| + /// </summary> |
| + public bool IsDebuggerRunning |
| + { |
| + get { return _isDebuggingNow; } |
| + } |
| + } |
| + |
| + /// <summary> |
| + /// The event arguments when a plug-in is found |
| + /// </summary> |
| + public class PluginFoundEventArgs : EventArgs |
| + { |
| + public uint _processId; |
| + |
| + public PluginFoundEventArgs(uint pid) |
| + { |
| + _processId = pid; |
| + } |
| + |
| + public uint ProcessID |
| + { |
| + get { return _processId; } |
| + set { _processId = value; } |
| + } |
| + } |
| +} |