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

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: Style Fixed 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..854574b8bc2e96eafe8b39ca577b14609b1337d1
--- /dev/null
+++ b/visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs
@@ -0,0 +1,455 @@
+// 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.
+
+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;
elijahtaylor1 2012/07/11 20:56:18 ABC order of 'using' directives, I don't mind if y
Petr Hosek 2012/07/12 00:25:46 You can use 'Remove and Sort' feature from 'Organi
tysand 2012/07/12 23:56:15 Done.
tysand 2012/07/12 23:56:15 Done.
+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.
elijahtaylor1 2012/07/11 20:56:18 quotes not needed
tysand 2012/07/12 23:56:15 Done.
+ /// </summary>
+ class PluginDebuggerHelper
+ {
+ /// <summary>
+ /// Constructs the PluginDebuggerHelper.
+ /// Object is not useable until InitializeFromProjectSettings() is called.
elijahtaylor1 2012/07/11 20:56:18 s/useable/usable/
tysand 2012/07/12 23:56:15 Done.
+ /// </summary>
+ /// <param name="dte">Automation object from Visual Studio</param>
+ public PluginDebuggerHelper(DTE2 dte)
+ {
+ if (dte == null)
+ {
+ throw new ArgumentNullException("dte");
+ }
+
+ dte_ = dte;
+ IsDebuggerRunning = false;
+
+ // Every second, check for a new instance of the plug-in to attach to
+ pluginFinderTimer_ = new Timer();
noelallen1 2012/07/11 22:16:46 Who is providing Timer? Is this a system Timer, i
tysand 2012/07/12 23:56:15 This is a System.Windows.Forms.Timer. It works by
+ 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
elijahtaylor1 2012/07/11 20:56:18 This doesn't seem to be the case... it looks like
tysand 2012/07/12 23:56:15 Yes, you can specify multiple start-up projects in
+ 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();
+ String startProjectName = startupProjects.GetValue(0) as String;
+ Project startProject = projList.Find(proj => proj.UniqueName == startProjectName);
+
+ // Get the current platform type
+ String activePlatform = startProject.ConfigurationManager.ActiveConfiguration.PlatformName;
+ if (String.Compare(activePlatform, Strings.PepperPlatformName, true) == 0)
+ {
+ projectPlatformType_ = ProjectPlatformType.Pepper;
+ debuggerType_ = DebuggerType.VS;
+ PluginFoundEvent += new EventHandler<PluginFoundEventArgs>(AttachVSDebugger);
+ }
+ else if (String.Compare(activePlatform, 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 supported, extract necessary information from specific project type
+ if (Utility.IsVisualCProject(startProject))
elijahtaylor1 2012/07/11 20:56:18 should this be an exception/error if this is not t
tysand 2012/07/12 23:56:15 No, the idea is that we return true if everything
+ {
+ VCConfiguration config = Utility.GetActiveVCConfiguration(startProject);
+ VCProject vcproj = (VCProject)startProject.Object;
Petr Hosek 2012/07/12 00:25:46 I would rather use `startProject.Object as VCProje
tysand 2012/07/12 23:56:15 The function IsVisualCProject should ensure that t
+ VCLinkerTool linker = config.Tools.Item("VCLinkerTool");
+ pluginProjectDirectory_ = vcproj.ProjectDirectory; // Macros not allowed here
+ pluginAssembly_ = config.Evaluate(linker.OutputFile);
+ pluginOutputDirectory_ = config.Evaluate(config.OutputDirectory);
+ isProperlyInitialized_ = true;
+ }
+
noelallen1 2012/07/11 22:16:46 Why track isPropertyInitialized, why not return fa
tysand 2012/07/12 23:56:15 Done.
+ int webServerPort = 5103;
elijahtaylor1 2012/07/11 20:56:18 consider using a user parameter or a random/increa
tysand 2012/07/12 23:56:15 This is a future todo. On 2012/07/11 20:56:18, eli
+ sdkRootDirectory_ = Environment.GetEnvironmentVariable(Strings.SDKPathEnvironmentVariable);
+ sdkRootDirectory_ = sdkRootDirectory_.TrimEnd("/\\".ToArray<char>());
+
+ webServerExecutable_ = "python.exe";
+ webServerArguments_ = String.Format("{0}\\examples\\httpd.py --no_dir_check {1}",
+ sdkRootDirectory_, webServerPort);
+
+ gdbPath_ = sdkRootDirectory_ + @"customGDB\gdb.exe";
+ irtPath_ = sdkRootDirectory_ + @"\tools\irt_x86_64.nexe";
elijahtaylor1 2012/07/11 20:56:18 why is this hardcoded to 64-bit? What about 32 bi
tysand 2012/07/12 23:56:15 This code is a placeholder for the real nacl-gdb,
+
+ return isProperlyInitialized_;
+ }
+
+ /// <summary>
+ /// This function should be called to start the PluginDebuggerHelper functionality
+ /// </summary>
+ public void StartDebugging()
+ {
noelallen1 2012/07/11 22:16:46 Should this throw or return false?
tysand 2012/07/12 23:56:15 Probably. I'm changing it to throw. On 2012/07/11
+ if (!isProperlyInitialized_)
+ {
+ return;
+ }
+
+ StartWebServer();
+ pluginFinderTimer_.Interval = 1000;
elijahtaylor1 2012/07/11 20:56:18 comment on units?
Petr Hosek 2012/07/12 00:25:46 How about using a constant?
tysand 2012/07/12 23:56:15 Done.
tysand 2012/07/12 23:56:15 Done.
+ pluginFinderTimer_.Start();
+ IsDebuggerRunning = true;
+ }
+
+ /// <summary>
+ /// This function should be called to stop the PluginDebuggerHelper functionality
+ /// </summary>
+ public void StopDebugging()
+ {
noelallen1 2012/07/11 22:16:46 Are there really two sets of state you care about?
tysand 2012/07/12 23:56:15 Initially IsDebuggerRunning was used externally by
+ IsDebuggerRunning = 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),
elijahtaylor1 2012/07/11 20:56:18 s/it's/its/
tysand 2012/07/12 23:56:15 Done.
+ * 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))
+ {
+ pluginFinderForbiddenPids_.Add(process.ID);
elijahtaylor1 2012/07/11 20:56:18 I'm unclear what the "forbidden" list is for, comm
tysand 2012/07/12 23:56:15 Comment added: If we are attaching to a plug-in, a
+ PluginFoundEvent.Invoke(this, new PluginFoundEventArgs(process.ID));
+
+ // Slow down the frequency of checks for new plugins to 5 seconds
+ pluginFinderTimer_.Interval = 5000;
elijahtaylor1 2012/07/11 20:56:18 Why do we still need to check for connections if w
tysand 2012/07/12 23:56:15 Although contrived, there could be multiple proces
+ }
+ }
+ }
+
+ /// <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)
elijahtaylor1 2012/07/11 20:56:18 src unused
tysand 2012/07/12 23:56:15 Done.
+ {
+ foreach (EnvDTE.Process proc in dte_.Debugger.LocalProcesses)
+ {
+ if (proc.ProcessID == args.ProcessID)
+ {
+ proc.Attach();
elijahtaylor1 2012/07/11 20:56:18 break
noelallen1 2012/07/11 22:16:46 Should you break or return. Can the same ProcessI
tysand 2012/07/12 23:56:15 Done.
+ }
+ }
+ }
+
+ /// <summary>
+ /// Will do magic to attach GDB to NaCl process properly, load symbols, and load breakpoints
elijahtaylor1 2012/07/11 20:56:18 Ha, can you change this comment to something more
tysand 2012/07/12 23:56:15 Heh yep I'll change it now. But the magic should g
+ /// </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)
elijahtaylor1 2012/07/11 20:56:18 src unused
tysand 2012/07/12 23:56:15 Done.
+ {
+ // NOTE: The settings listed here are a placeholder until nacl-gdb is ready
elijahtaylor1 2012/07/11 20:56:18 Can you be more specific? what settings?
tysand 2012/07/12 23:56:15 Done.
+
+ // Clean up any pre-existing GDB process (can happen if user reloads page)
+ KillGDBProcess();
+
+ gdbInitFileName_ = Path.GetTempFileName();
+ 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();
+ contents.AppendFormat("target remote localhost:{0}", 4014);
elijahtaylor1 2012/07/11 20:56:18 comment on 4014 constant, maybe set a constant var
elijahtaylor1 2012/07/11 20:56:18 comment on 4014, maybe use a constant variable in
tysand 2012/07/12 23:56:15 Done.
+ contents.AppendLine();
+ contents.Append("set architecture i386:x86-64");
elijahtaylor1 2012/07/11 20:56:18 same comment as above with assuming 64-bit
tysand 2012/07/12 23:56:15 Done.
+ contents.AppendLine();
+ contents.AppendFormat("cd {0}", projectDir);
+ contents.AppendLine();
+ contents.AppendFormat("add-symbol-file {0} 0xC00020080", pluginAssemblyEscaped);
elijahtaylor1 2012/07/11 20:56:18 same comment as above for hardcoded constants
noelallen1 2012/07/11 22:16:46 Are you sure this value is fixed? Can't it change
tysand 2012/07/12 23:56:15 The value technically shouldn't be fixed in some c
+ contents.AppendLine();
+ contents.AppendFormat("add-symbol-file {0} 0xC0fc00080", irtPathEscaped);
elijahtaylor1 2012/07/11 20:56:18 same comment as above for hardcoded constants
tysand 2012/07/12 23:56:15 Done.
+ contents.AppendLine();
+
+ // Insert breakpoints from Visual Studio project
+ foreach (Breakpoint bp in dte_.Debugger.Breakpoints)
+ {
+ if (bp.Enabled &&
+ 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();
+ }
elijahtaylor1 2012/07/11 20:56:18 are there any other types of breakpoints we can or
tysand 2012/07/12 23:56:15 Done.
+ }
+
+ 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 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 += 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">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
noelallen1 2012/07/11 22:16:46 is WinGDB unused?
tysand 2012/07/12 23:56:15 Done.
+ }
+
+ /// <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_ = false;
+ 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_ = null;
+ private OutputWindowPane webServerOutputPane_ = null;
+ private String webServerExecutable_;
+ private String webServerArguments_;
+
+ private Timer pluginFinderTimer_;
+ private List<uint> pluginFinderForbiddenPids_;
+
+ private IProcessSearcher processSearcher_;
+
+ public event EventHandler<PluginFoundEventArgs> PluginFoundEvent;
elijahtaylor1 2012/07/11 20:56:18 member variable naming
tysand 2012/07/12 23:56:15 Microsoft style on events is PascalCase because I
+
+ /// <summary>
+ /// Indicates true if the debugger is running (even during breakpoints)
+ /// </summary>
+ public bool IsDebuggerRunning { get; private set; }
elijahtaylor1 2012/07/11 20:56:18 property naming
tysand 2012/07/12 23:56:15 Done.
+ }
+
+ /// <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)
+ {
+ ProcessID = pid;
+ }
+
+ /// <summary>
+ /// Process ID of the found plug-in
+ /// </summary>
+ public uint ProcessID { get; set; }
elijahtaylor1 2012/07/11 20:56:18 property naming
tysand 2012/07/12 23:56:15 Done.
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698