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

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..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; }
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698