Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(81)

Unified Diff: visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs

Issue 10758009: Native Client Visual Studio Add-in (Closed) Base URL: https://nativeclient-sdk.googlecode.com/svn/trunk/src
Patch Set: Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..071d63e9234ee15f06377e54d14a6048f38d2143
--- /dev/null
+++ b/visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs
@@ -0,0 +1,563 @@
+// 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 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>
+ /// 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_ = null;
binji 2012/07/20 00:24:53 nit: = null unnecessary? Doesn't c# initialize ref
tysand 2012/08/07 23:01:54 Done.
+
+ /// <summary>
+ /// Visual Studio output window pane that captures output from the web server.
+ /// </summary>
+ private OutputWindowPane webServerOutputPane_ = null;
binji 2012/07/20 00:24:53 ditto here
tysand 2012/08/07 23:01:54 Done.
+
+ /// <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>
+ /// 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;
+
+ // 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 supported, extract necessary information from specific project type.
+ if (Utility.IsVisualCProject(startProject))
+ {
+ VCConfiguration config = Utility.GetActiveVCConfiguration(startProject);
+ VCProject vcproj = (VCProject)startProject.Object;
+ VCLinkerTool linker = config.Tools.Item("VCLinkerTool");
+ pluginProjectDirectory_ = vcproj.ProjectDirectory; // Macros not allowed here.
+ pluginAssembly_ = config.Evaluate(linker.OutputFile);
+ pluginOutputDirectory_ = config.Evaluate(config.OutputDirectory);
+ }
+ else
+ {
+ return false;
+ }
+
+ // TODO(tysand): Add user option to specify this.
+ int webServerPort = 5103;
binji 2012/07/20 00:24:53 move this down to where it is used
tysand 2012/08/07 23:01:54 Done.
+ sdkRootDirectory_ = Environment.GetEnvironmentVariable(Strings.SDKPathEnvironmentVariable);
+ if (sdkRootDirectory_ == null)
+ {
+ MessageBox.Show(
+ string.Format(Strings.SDKPathNotSetFormat, Strings.SDKPathEnvironmentVariable));
+ return false;
+ }
+
+ sdkRootDirectory_ = sdkRootDirectory_.TrimEnd("/\\".ToArray<char>());
+
+ webServerExecutable_ = "python.exe";
+ webServerArguments_ = string.Format(
+ "{0}\\examples\\httpd.py --no_dir_check {1}",
+ sdkRootDirectory_,
+ webServerPort);
+
+ // TODO(tysand): Update this to nacl-gdb when it is ready. Should be able to remove irtPath_.
+ gdbPath_ = sdkRootDirectory_ + @"\customGDB\gdb.exe";
+ irtPath_ = sdkRootDirectory_ + @"\tools\irt_x86_64.nexe";
+
+ 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);
binji 2012/07/20 00:24:53 this looks like if you install the add-in, and try
tysand 2012/08/07 23:01:54 Done.
+ }
+
+ StartWebServer();
+ pluginFinderTimer_.Interval = InitialPluginCheckFrequency;
+ pluginFinderTimer_.Start();
+ }
+
+ /// <summary>
+ /// This function should be called to stop the PluginDebuggerHelper functionality.
+ /// </summary>
+ public void StopDebugging()
+ {
+ isProperlyInitialized_ = false;
binji 2012/07/20 00:24:53 might be simpler to just let the GC collect this o
tysand 2012/08/07 23:01:54 Done.
+ 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();
binji 2012/07/20 00:24:53 this will throw if the web server process was kill
tysand 2012/08/07 23:01:54 Done.
+ webServer_.Dispose();
+ webServer_ = null;
+ }
+
+ KillGDBProcess();
+ }
+
+ /// <summary>
+ /// This function cleans up the started GDB process.
+ /// </summary>
+ private void KillGDBProcess()
+ {
+ if (gdbProcess_ != null)
+ {
+ if (!gdbProcess_.HasExited)
binji 2012/07/20 00:24:53 Same here, just kill and catch exceptions.
tysand 2012/08/07 23:01:54 Done.
+ {
+ gdbProcess_.Kill();
binji 2012/07/20 00:24:53 According to http://msdn.microsoft.com/en-us/libra
tysand 2012/08/07 23:01:54 Done.
+ gdbProcess_.Dispose();
+ }
+
+ if (!string.IsNullOrEmpty(gdbInitFileName_) && File.Exists(gdbInitFileName_))
+ {
+ File.Delete(gdbInitFileName_);
binji 2012/07/20 00:24:53 Again, usually better to just do the operation and
tysand 2012/08/07 23:01:54 The file name is randomly generated so it should b
+ }
+
+ gdbProcess_ = 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)
+ {
+ 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: its process name (e.g. chrome.exe),
+ // identifying command line arguments (e.g. --type=renderer), not already attached
+ // to by us, and must be a descendant process of this instance of Visual Studio.
+ List<ProcessInfo> results = processSearcher_.GetResultsByName(processNameTarget);
+ uint currentProcessId = (uint)System.Diagnostics.Process.GetCurrentProcess().Id;
+ StringComparison ignoreCase = StringComparison.InvariantCultureIgnoreCase;
+ foreach (ProcessInfo process in results)
+ {
+ if (!pluginFinderForbiddenPids_.Contains(process.ID) &&
+ !string.IsNullOrEmpty(process.CommandLine) &&
+ process.CommandLine.Contains(typeFlagTarget, ignoreCase) &&
+ process.CommandLine.Contains(identifierFlagTarget, ignoreCase) &&
+ Utility.IsDescendantOfProcess(processSearcher_, process.ID, currentProcessId))
+ {
+ // 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)
+ {
+ // NOTE: The settings listed here are a placeholder until nacl-gdb is ready.
+ // Specifically, 'set architecture' and 'add-symbol-file' calls. See TODO comments.
+
+ // Clean up any pre-existing GDB process (can happen if user reloads page).
+ KillGDBProcess();
+
+ gdbInitFileName_ = Path.GetTempFileName();
binji 2012/07/20 00:24:53 consider moving all of the nacl-gdb stuff into its
tysand 2012/08/07 23:01:54 Done.
+ string projectDir = pluginProjectDirectory_.TrimEnd('\\');
+ string pluginAssemblyEscaped = pluginAssembly_.Replace("\\", "\\\\");
+ string irtPathEscaped = irtPath_.Replace("\\", "\\\\");
+
+ // Create the initialization file to read in on GDB start.
+ StringBuilder contents = new StringBuilder();
+
+ // TODO(tysand): Allow user setting for debug stub port (currently 4014).
+ contents.AppendFormat("target remote localhost:{0}", 4014);
+ contents.AppendLine();
+
+ // TODO(tysand): Nacl-gdb should detect this automatically making this call unnecessary.
+ contents.Append("set architecture i386:x86-64");
+ contents.AppendLine();
+ contents.AppendFormat("cd {0}", projectDir);
+ contents.AppendLine();
+
+ // TODO(tysand): Nacl-gdb should handle the offset automatically. Remove 0xC00020080.
+ contents.AppendFormat("add-symbol-file {0} 0xC00020080", pluginAssemblyEscaped);
+ contents.AppendLine();
+
+ // TODO(tysand): Nacl-gdb should handle loading the irt automatically. Remove this line.
+ contents.AppendFormat("add-symbol-file {0} 0xC0fc00080", irtPathEscaped);
+ contents.AppendLine();
+
+ // Insert breakpoints from Visual Studio project.
+ foreach (Breakpoint bp in dte_.Debugger.Breakpoints)
+ {
+ if (bp.Enabled &&
binji 2012/07/20 00:24:53 if (!bp.Enabled) continue; if (bp.LocationType...
tysand 2012/08/07 23:01:54 Done.
+ bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTypeFile)
+ {
+ contents.AppendFormat("b {0}:{1}", Path.GetFileName(bp.File), bp.FileLine);
+ contents.AppendLine();
+ }
+ else if (bp.Enabled &&
+ bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTypeFunction)
+ {
+ contents.AppendFormat("b {0}", bp.FunctionName);
+ contents.AppendLine();
+ }
+ else if (bp.Enabled)
+ {
+ 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()
binji 2012/07/20 00:24:53 Consider moving all of the webserver stuff into it
tysand 2012/08/07 23:01:54 Done.
+ {
+ // 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_;
binji 2012/07/20 00:24:53 I think it's cleaner to avoid using a member varia
tysand 2012/08/07 23:01:54 Done.
+ 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)
+ {
+ webServerOutputPane_.OutputString(message + "\n");
+ webServerOutputPane_.Activate();
binji 2012/07/20 00:24:53 This seems to set focus to the output pane... is t
tysand 2012/08/07 23:01:54 Generally nothing else is happening at runtime and
+ }
+
+ /// <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; }
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698