Index: visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs |
diff --git a/visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs b/visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs |
deleted file mode 100644 |
index a8be9a92b96090a0d7928e8f32165cf4cf4c23f7..0000000000000000000000000000000000000000 |
--- a/visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs |
+++ /dev/null |
@@ -1,592 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-namespace NativeClientVSAddIn |
-{ |
- using System; |
- using System.Collections.Generic; |
- using System.IO; |
- using System.Linq; |
- using System.Text; |
- using System.Windows.Forms; |
- |
- using EnvDTE; |
- using EnvDTE80; |
- using Microsoft.VisualStudio.VCProjectEngine; |
- |
- /// <summary> |
- /// This class contains functions and utilities which are run when the user |
- /// presses F5 or otherwise starts debugging from within Visual Studio. |
- /// </summary> |
- public class PluginDebuggerHelper |
- { |
- /// <summary> |
- /// This is the initial number of milliseconds to wait between |
- /// checking for plug-in processes to attach the debugger to. |
- /// </summary> |
- private const int InitialPluginCheckFrequency = 1000; |
- |
- /// <summary> |
- /// After a plug-in has been found, we slow the frequency of checking |
- /// for new ones. This value is in milliseconds. |
- /// </summary> |
- private const int RelaxedPluginCheckFrequency = 5000; |
- |
- /// <summary> |
- /// The web server port to default to if the user does not specify one. |
- /// </summary> |
- private const int DefaultWebServerPort = 5103; |
- |
- /// <summary> |
- /// The main visual studio object through which all Visual Studio functions are executed. |
- /// </summary> |
- private DTE2 dte_; |
- |
- /// <summary> |
- /// Indicates the PluginDebuggerHelper is configured properly to run. |
- /// </summary> |
- private bool isProperlyInitialized_ = false; |
- |
- /// <summary> |
- /// Directory of the plug-in project we are debugging. |
- /// </summary> |
- private string pluginProjectDirectory_; |
- |
- /// <summary> |
- /// Directory where the plug-in assembly is placed. |
- /// </summary> |
- private string pluginOutputDirectory_; |
- |
- /// <summary> |
- /// Path to the actual plug-in assembly. |
- /// </summary> |
- private string pluginAssembly_; |
- |
- /// <summary> |
- /// Path to the NaCl IRT. |
- /// </summary> |
- private string irtPath_; |
- |
- /// <summary> |
- /// Path to the project's nmf file. |
- /// </summary> |
- private string manifestPath_; |
- |
- /// <summary> |
- /// Root directory of the installed NaCl SDK. |
- /// </summary> |
- private string sdkRootDirectory_; |
- |
- /// <summary> |
- /// If debugging a .nexe this is the nacl-gdb process object. |
- /// </summary> |
- private System.Diagnostics.Process gdbProcess_; |
- |
- /// <summary> |
- /// Path to NaCl-GDB executable. |
- /// </summary> |
- private string gdbPath_; |
- |
- /// <summary> |
- /// Path to the gdb initialization file that we auto-generate from the VS project. |
- /// </summary> |
- private string gdbInitFileName_; |
- |
- /// <summary> |
- /// The platform that the start-up project is currently configured with (NaCl or PPAPI). |
- /// </summary> |
- private ProjectPlatformType projectPlatformType_; |
- |
- /// <summary> |
- /// When debugging is started this is the web server process object. |
- /// </summary> |
- private System.Diagnostics.Process webServer_; |
- |
- /// <summary> |
- /// Visual Studio output window pane that captures output from the web server. |
- /// </summary> |
- private OutputWindowPane webServerOutputPane_; |
- |
- /// <summary> |
- /// Path to the web server executable. |
- /// </summary> |
- private string webServerExecutable_; |
- |
- /// <summary> |
- /// Arguments to be passed to the web server executable to start it. |
- /// </summary> |
- private string webServerArguments_; |
- |
- /// <summary> |
- /// Timer object that periodically calls a function to look for the plug-in process to debug. |
- /// </summary> |
- private Timer pluginFinderTimer_; |
- |
- /// <summary> |
- /// List of process IDs which we should not attempt to attach the debugger to. Mainly this |
- /// list contains process IDs of processes we have already attached to. |
- /// </summary> |
- private List<uint> pluginFinderForbiddenPids_; |
- |
- /// <summary> |
- /// Process searcher class which allows us to query the system for running processes. |
- /// </summary> |
- private ProcessSearcher processSearcher_; |
- |
- /// <summary> |
- /// The main process of chrome that was started by Visual Studio during debugging. |
- /// </summary> |
- private System.Diagnostics.Process debuggedChromeMainProcess_; |
- |
- /// <summary> |
- /// Constructs the PluginDebuggerHelper. |
- /// Object is not usable until LoadProjectSettings() is called. |
- /// </summary> |
- /// <param name="dte">Automation object from Visual Studio.</param> |
- public PluginDebuggerHelper(DTE2 dte) |
- { |
- if (dte == null) |
- { |
- throw new ArgumentNullException("dte"); |
- } |
- |
- dte_ = dte; |
- |
- // Every second, check for a new instance of the plug-in to attach to. |
- // Note that although the timer itself runs on a separate thread, the event |
- // is fired from the main UI thread during message processing, thus we do not |
- // need to worry about threading issues. |
- pluginFinderTimer_ = new Timer(); |
- pluginFinderTimer_.Tick += new EventHandler(FindAndAttachToPlugin); |
- pluginFinderForbiddenPids_ = new List<uint>(); |
- processSearcher_ = new ProcessSearcher(); |
- } |
- |
- /// <summary> |
- /// An event indicating a target plug-in was found on the system. |
- /// </summary> |
- public event EventHandler<PluginFoundEventArgs> PluginFoundEvent; |
- |
- /// <summary> |
- /// Specifies the type of plug-in being run in this debug session. |
- /// </summary> |
- private enum ProjectPlatformType |
- { |
- /// <summary> |
- /// Represents all non-pepper/non-nacl platform types. |
- /// </summary> |
- Other, |
- |
- /// <summary> |
- /// Indicates project platform is a trusted plug-in (nexe). |
- /// </summary> |
- NaCl, |
- |
- /// <summary> |
- /// Indicates project platform is an untrusted plug-in. |
- /// </summary> |
- Pepper |
- } |
- |
- /// <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 LoadProjectSettings() |
- { |
- isProperlyInitialized_ = false; |
- |
- string platformToolset; |
- |
- // We require that there is only a single start-up project. |
- // If multiple start-up projects are specified then we use the first and |
- // leave a warning message in the Web Server output pane. |
- 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 first start-up project object. |
- List<Project> projList = dte_.Solution.Projects.OfType<Project>().ToList(); |
- string startProjectName = startupProjects.GetValue(0) as string; |
- Project startProject = projList.Find(proj => proj.UniqueName == startProjectName); |
- |
- // Get the current platform type. If not nacl/pepper then fail. |
- string activePlatform = startProject.ConfigurationManager.ActiveConfiguration.PlatformName; |
- if (string.Compare(activePlatform, Strings.PepperPlatformName, true) == 0) |
- { |
- projectPlatformType_ = ProjectPlatformType.Pepper; |
- PluginFoundEvent += new EventHandler<PluginFoundEventArgs>(AttachVSDebugger); |
- } |
- else if (string.Compare(activePlatform, Strings.NaClPlatformName, true) == 0) |
- { |
- projectPlatformType_ = ProjectPlatformType.NaCl; |
- PluginFoundEvent += new EventHandler<PluginFoundEventArgs>(AttachNaClGDB); |
- } |
- else |
- { |
- projectPlatformType_ = ProjectPlatformType.Other; |
- return false; |
- } |
- |
- // We only support certain project types (e.g. C/C++ projects). Otherwise we fail. |
- if (!Utility.IsVisualCProject(startProject)) |
- { |
- return false; |
- } |
- |
- // Extract necessary information from specific project type. |
- VCConfiguration config = Utility.GetActiveVCConfiguration(startProject); |
- IVCRulePropertyStorage general = config.Rules.Item("ConfigurationGeneral"); |
- VCLinkerTool linker = config.Tools.Item("VCLinkerTool"); |
- VCProject vcproj = (VCProject)startProject.Object; |
- |
- sdkRootDirectory_ = general.GetEvaluatedPropertyValue("VSNaClSDKRoot"); |
- platformToolset = general.GetEvaluatedPropertyValue("PlatformToolset"); |
- pluginOutputDirectory_ = config.Evaluate(config.OutputDirectory); |
- pluginAssembly_ = config.Evaluate(linker.OutputFile); |
- pluginProjectDirectory_ = vcproj.ProjectDirectory; // Macros not allowed here. |
- |
- if (projectPlatformType_ == ProjectPlatformType.NaCl) |
- { |
- irtPath_ = general.GetEvaluatedPropertyValue("NaClIrtPath"); |
- manifestPath_ = general.GetEvaluatedPropertyValue("NaClManifestPath"); |
- } |
- |
- if (string.IsNullOrEmpty(sdkRootDirectory_)) |
- { |
- MessageBox.Show(Strings.SDKPathNotSetError); |
- return false; |
- } |
- |
- sdkRootDirectory_ = sdkRootDirectory_.TrimEnd("/\\".ToArray<char>()); |
- |
- // TODO(tysand): Move this code getting port to where the web server is started. |
- int webServerPort; |
- if (!int.TryParse(general.GetEvaluatedPropertyValue("NaClWebServerPort"), out webServerPort)) |
- { |
- webServerPort = DefaultWebServerPort; |
- } |
- |
- webServerExecutable_ = "python.exe"; |
- webServerArguments_ = string.Format( |
- "{0}\\examples\\httpd.py --no_dir_check {1}", sdkRootDirectory_, webServerPort); |
- |
- gdbPath_ = Path.Combine( |
- sdkRootDirectory_, "toolchain", platformToolset, @"bin\x86_64-nacl-gdb.exe"); |
- |
- debuggedChromeMainProcess_ = null; |
- |
- isProperlyInitialized_ = true; |
- return true; |
- } |
- |
- /// <summary> |
- /// This function should be called to start the PluginDebuggerHelper functionality. |
- /// </summary> |
- public void StartDebugging() |
- { |
- if (!isProperlyInitialized_) |
- { |
- throw new Exception(Strings.NotInitializedMessage); |
- } |
- |
- StartWebServer(); |
- pluginFinderTimer_.Interval = InitialPluginCheckFrequency; |
- pluginFinderTimer_.Start(); |
- } |
- |
- /// <summary> |
- /// This function should be called to stop the PluginDebuggerHelper functionality. |
- /// </summary> |
- public void StopDebugging() |
- { |
- 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; |
- } |
- } |
- |
- Utility.EnsureProcessKill(ref webServer_); |
- WebServerWriteLine(Strings.WebServerStopMessage); |
- CleanUpGDBProcess(); |
- } |
- |
- /// <summary> |
- /// This function cleans up the started GDB process. |
- /// </summary> |
- private void CleanUpGDBProcess() |
- { |
- Utility.EnsureProcessKill(ref gdbProcess_); |
- if (!string.IsNullOrEmpty(gdbInitFileName_) && File.Exists(gdbInitFileName_)) |
- { |
- File.Delete(gdbInitFileName_); |
- gdbInitFileName_ = null; |
- } |
- } |
- |
- /// <summary> |
- /// This is 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">The parameter is not used.</param> |
- /// <param name="unused1">The parameter is not used.</param> |
- private void FindAndAttachToPlugin(object unused, EventArgs unused1) |
- { |
- StringComparison ignoreCase = StringComparison.InvariantCultureIgnoreCase; |
- |
- // Set the main chrome process that was started by visual studio. If it's not chrome |
- // or not found then we have no business attaching to any plug-ins so return. |
- if (debuggedChromeMainProcess_ == null) |
- { |
- foreach (Process proc in dte_.Debugger.DebuggedProcesses) |
- { |
- if (proc.Name.EndsWith(Strings.ChromeProcessName, ignoreCase)) |
- { |
- debuggedChromeMainProcess_ = System.Diagnostics.Process.GetProcessById(proc.ProcessID); |
- break; |
- } |
- } |
- |
- return; |
- } |
- |
- // Get the list of all descendants of the main chrome process. |
- uint mainChromeProcId = (uint)debuggedChromeMainProcess_.Id; |
- List<ProcessInfo> chromeDescendants = processSearcher_.GetDescendants(mainChromeProcId); |
- |
- // If we didn't start with debug flags then we should not attach. |
- string mainChromeFlags = chromeDescendants.Find(p => p.ID == mainChromeProcId).CommandLine; |
- if (projectPlatformType_ == ProjectPlatformType.NaCl && |
- !mainChromeFlags.Contains(Strings.NaClDebugFlag)) |
- { |
- return; |
- } |
- |
- // From the list of descendants, find the plug-in by it's command line arguments and |
- // process name as well as not being attached to already. |
- List<ProcessInfo> plugins; |
- switch (projectPlatformType_) |
- { |
- case ProjectPlatformType.Pepper: |
- string identifierFlagTarget = |
- string.Format(Strings.PepperProcessPluginFlagFormat, pluginAssembly_); |
- plugins = chromeDescendants.FindAll(p => |
- p.Name.Equals(Strings.ChromeProcessName, ignoreCase) && |
- p.CommandLine.Contains(Strings.ChromeRendererFlag, ignoreCase) && |
- p.CommandLine.Contains(identifierFlagTarget, ignoreCase) && |
- !pluginFinderForbiddenPids_.Contains(p.ID)); |
- break; |
- case ProjectPlatformType.NaCl: |
- plugins = chromeDescendants.FindAll(p => |
- p.Name.Equals(Strings.NaClProcessName, ignoreCase) && |
- p.CommandLine.Contains(Strings.NaClLoaderFlag, ignoreCase) && |
- !pluginFinderForbiddenPids_.Contains(p.ID)); |
- break; |
- default: |
- return; |
- } |
- |
- // Attach to all plug-ins that we found. |
- foreach (ProcessInfo process in plugins) |
- { |
- // If we are attaching to a plug-in, add it to the forbidden list to ensure we |
- // don't try to attach again later. |
- pluginFinderForbiddenPids_.Add(process.ID); |
- PluginFoundEvent.Invoke(this, new PluginFoundEventArgs(process.ID)); |
- |
- // Slow down the frequency of checks for new plugins. |
- pluginFinderTimer_.Interval = RelaxedPluginCheckFrequency; |
- } |
- } |
- |
- /// <summary> |
- /// Attaches the visual studio debugger to a given process ID. |
- /// </summary> |
- /// <param name="src">The parameter is not used.</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(); |
- break; |
- } |
- } |
- } |
- |
- /// <summary> |
- /// Attaches the NaCl GDB debugger to the NaCl plug-in process. Handles loading symbols |
- /// and breakpoints from Visual Studio. |
- /// </summary> |
- /// <param name="src">The parameter is not used.</param> |
- /// <param name="args"> |
- /// Contains the process ID to attach to, unused since debug stub is already attached. |
- /// </param> |
- private void AttachNaClGDB(object src, PluginFoundEventArgs args) |
- { |
- // Clean up any pre-existing GDB process (can happen if user reloads page). |
- CleanUpGDBProcess(); |
- |
- gdbInitFileName_ = Path.GetTempFileName(); |
- string pluginAssemblyEscaped = pluginAssembly_.Replace("\\", "\\\\"); |
- string irtPathEscaped = irtPath_.Replace("\\", "\\\\"); |
- |
- // Create the initialization file to read in on GDB start. |
- StringBuilder contents = new StringBuilder(); |
- |
- if (!string.IsNullOrEmpty(manifestPath_)) |
- { |
- string manifestEscaped = manifestPath_.Replace("\\", "\\\\"); |
- contents.AppendFormat("nacl-manifest {0}\n", manifestEscaped); |
- } |
- else |
- { |
- contents.AppendFormat("file \"{0}\"\n", pluginAssemblyEscaped); |
- } |
- |
- contents.AppendFormat("nacl-irt {0}\n", irtPathEscaped); |
- contents.AppendFormat("target remote localhost:{0}\n", 4014); |
- |
- // Insert breakpoints from Visual Studio project. |
- foreach (Breakpoint bp in dte_.Debugger.Breakpoints) |
- { |
- if (!bp.Enabled) |
- { |
- continue; |
- } |
- |
- if (bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTypeFile) |
- { |
- contents.AppendFormat("b {0}:{1}\n", Path.GetFileName(bp.File), bp.FileLine); |
- } |
- else if (bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTypeFunction) |
- { |
- contents.AppendFormat("b {0}\n", bp.FunctionName); |
- } |
- else |
- { |
- WebServerWriteLine( |
- string.Format(Strings.UnsupportedBreakpointTypeFormat, bp.LocationType.ToString())); |
- } |
- } |
- |
- contents.AppendLine("continue"); |
- File.WriteAllText(gdbInitFileName_, contents.ToString()); |
- |
- // Start NaCl-GDB. |
- 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) |
- { |
- MessageBox.Show( |
- string.Format("NaCl-GDB Start Failed. {0}. Path: {1}", e.Message, gdbPath_)); |
- } |
- } |
- |
- /// <summary> |
- /// Spins up the web server process to host our plug-in. |
- /// </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 += WebServerMessageReceive; |
- webServer_.ErrorDataReceived += 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">The parameter is not used.</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) |
- { |
- if (webServerOutputPane_ != null) |
- { |
- webServerOutputPane_.OutputString(message + "\n"); |
- } |
- } |
- |
- /// <summary> |
- /// The event arguments when a plug-in is found. |
- /// </summary> |
- public class PluginFoundEventArgs : EventArgs |
- { |
- /// <summary> |
- /// Construct the PluginFoundEventArgs. |
- /// </summary> |
- /// <param name="pid">Process ID of the found plug-in.</param> |
- public PluginFoundEventArgs(uint pid) |
- { |
- this.ProcessID = pid; |
- } |
- |
- /// <summary> |
- /// Gets or sets process ID of the found plug-in. |
- /// </summary> |
- public uint ProcessID { get; set; } |
- } |
- } |
-} |