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

Side by Side Diff: visual_studio/NativeClientVSAddIn/NativeClientVSAddIn/PluginDebuggerHelper.cs

Issue 10836143: Refactored the VS add-in (Closed) Base URL: https://nativeclient-sdk.googlecode.com/svn/trunk/src
Patch Set: Created 8 years, 4 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 namespace NativeClientVSAddIn
6 {
7 using System;
8 using System.Collections.Generic;
9 using System.IO;
10 using System.Linq;
11 using System.Text;
12 using System.Windows.Forms;
13
14 using EnvDTE;
15 using EnvDTE80;
16 using Microsoft.VisualStudio.VCProjectEngine;
17
18 /// <summary>
19 /// This class contains functions and utilities which are run when the user
20 /// presses F5 or otherwise starts debugging from within Visual Studio.
21 /// </summary>
22 public class PluginDebuggerHelper
23 {
24 /// <summary>
25 /// This is the initial number of milliseconds to wait between
26 /// checking for plug-in processes to attach the debugger to.
27 /// </summary>
28 private const int InitialPluginCheckFrequency = 1000;
29
30 /// <summary>
31 /// After a plug-in has been found, we slow the frequency of checking
32 /// for new ones. This value is in milliseconds.
33 /// </summary>
34 private const int RelaxedPluginCheckFrequency = 5000;
35
36 /// <summary>
37 /// The web server port to default to if the user does not specify one.
38 /// </summary>
39 private const int DefaultWebServerPort = 5103;
40
41 /// <summary>
42 /// The main visual studio object through which all Visual Studio functions are executed.
43 /// </summary>
44 private DTE2 dte_;
45
46 /// <summary>
47 /// Indicates the PluginDebuggerHelper is configured properly to run.
48 /// </summary>
49 private bool isProperlyInitialized_ = false;
50
51 /// <summary>
52 /// Directory of the plug-in project we are debugging.
53 /// </summary>
54 private string pluginProjectDirectory_;
55
56 /// <summary>
57 /// Directory where the plug-in assembly is placed.
58 /// </summary>
59 private string pluginOutputDirectory_;
60
61 /// <summary>
62 /// Path to the actual plug-in assembly.
63 /// </summary>
64 private string pluginAssembly_;
65
66 /// <summary>
67 /// Path to the NaCl IRT.
68 /// </summary>
69 private string irtPath_;
70
71 /// <summary>
72 /// Path to the project's nmf file.
73 /// </summary>
74 private string manifestPath_;
75
76 /// <summary>
77 /// Root directory of the installed NaCl SDK.
78 /// </summary>
79 private string sdkRootDirectory_;
80
81 /// <summary>
82 /// If debugging a .nexe this is the nacl-gdb process object.
83 /// </summary>
84 private System.Diagnostics.Process gdbProcess_;
85
86 /// <summary>
87 /// Path to NaCl-GDB executable.
88 /// </summary>
89 private string gdbPath_;
90
91 /// <summary>
92 /// Path to the gdb initialization file that we auto-generate from the VS pr oject.
93 /// </summary>
94 private string gdbInitFileName_;
95
96 /// <summary>
97 /// The platform that the start-up project is currently configured with (NaC l or PPAPI).
98 /// </summary>
99 private ProjectPlatformType projectPlatformType_;
100
101 /// <summary>
102 /// When debugging is started this is the web server process object.
103 /// </summary>
104 private System.Diagnostics.Process webServer_;
105
106 /// <summary>
107 /// Visual Studio output window pane that captures output from the web serve r.
108 /// </summary>
109 private OutputWindowPane webServerOutputPane_;
110
111 /// <summary>
112 /// Path to the web server executable.
113 /// </summary>
114 private string webServerExecutable_;
115
116 /// <summary>
117 /// Arguments to be passed to the web server executable to start it.
118 /// </summary>
119 private string webServerArguments_;
120
121 /// <summary>
122 /// Timer object that periodically calls a function to look for the plug-in process to debug.
123 /// </summary>
124 private Timer pluginFinderTimer_;
125
126 /// <summary>
127 /// List of process IDs which we should not attempt to attach the debugger t o. Mainly this
128 /// list contains process IDs of processes we have already attached to.
129 /// </summary>
130 private List<uint> pluginFinderForbiddenPids_;
131
132 /// <summary>
133 /// Process searcher class which allows us to query the system for running p rocesses.
134 /// </summary>
135 private ProcessSearcher processSearcher_;
136
137 /// <summary>
138 /// The main process of chrome that was started by Visual Studio during debu gging.
139 /// </summary>
140 private System.Diagnostics.Process debuggedChromeMainProcess_;
141
142 /// <summary>
143 /// Constructs the PluginDebuggerHelper.
144 /// Object is not usable until LoadProjectSettings() is called.
145 /// </summary>
146 /// <param name="dte">Automation object from Visual Studio.</param>
147 public PluginDebuggerHelper(DTE2 dte)
148 {
149 if (dte == null)
150 {
151 throw new ArgumentNullException("dte");
152 }
153
154 dte_ = dte;
155
156 // Every second, check for a new instance of the plug-in to attach to.
157 // Note that although the timer itself runs on a separate thread, the even t
158 // is fired from the main UI thread during message processing, thus we do not
159 // need to worry about threading issues.
160 pluginFinderTimer_ = new Timer();
161 pluginFinderTimer_.Tick += new EventHandler(FindAndAttachToPlugin);
162 pluginFinderForbiddenPids_ = new List<uint>();
163 processSearcher_ = new ProcessSearcher();
164 }
165
166 /// <summary>
167 /// An event indicating a target plug-in was found on the system.
168 /// </summary>
169 public event EventHandler<PluginFoundEventArgs> PluginFoundEvent;
170
171 /// <summary>
172 /// Specifies the type of plug-in being run in this debug session.
173 /// </summary>
174 private enum ProjectPlatformType
175 {
176 /// <summary>
177 /// Represents all non-pepper/non-nacl platform types.
178 /// </summary>
179 Other,
180
181 /// <summary>
182 /// Indicates project platform is a trusted plug-in (nexe).
183 /// </summary>
184 NaCl,
185
186 /// <summary>
187 /// Indicates project platform is an untrusted plug-in.
188 /// </summary>
189 Pepper
190 }
191
192 /// <summary>
193 /// Initializes the PluginDebuggerHelper with the current project settings
194 /// If project settings are unsupported for NaCl/Pepper debugging then
195 /// the object is not initialized and we return false.
196 /// </summary>
197 /// <returns>True if the object is successfully initialized, false otherwise .</returns>
198 public bool LoadProjectSettings()
199 {
200 isProperlyInitialized_ = false;
201
202 string platformToolset;
203
204 // We require that there is only a single start-up project.
205 // If multiple start-up projects are specified then we use the first and
206 // leave a warning message in the Web Server output pane.
207 Array startupProjects = dte_.Solution.SolutionBuild.StartupProjects as Arr ay;
208 if (startupProjects == null || startupProjects.Length == 0)
209 {
210 throw new ArgumentOutOfRangeException("startupProjects.Length");
211 }
212 else if (startupProjects.Length > 1)
213 {
214 WebServerWriteLine(Strings.WebServerMultiStartProjectWarning);
215 }
216
217 // Get the first start-up project object.
218 List<Project> projList = dte_.Solution.Projects.OfType<Project>().ToList() ;
219 string startProjectName = startupProjects.GetValue(0) as string;
220 Project startProject = projList.Find(proj => proj.UniqueName == startProje ctName);
221
222 // Get the current platform type. If not nacl/pepper then fail.
223 string activePlatform = startProject.ConfigurationManager.ActiveConfigurat ion.PlatformName;
224 if (string.Compare(activePlatform, Strings.PepperPlatformName, true) == 0)
225 {
226 projectPlatformType_ = ProjectPlatformType.Pepper;
227 PluginFoundEvent += new EventHandler<PluginFoundEventArgs>(AttachVSDebug ger);
228 }
229 else if (string.Compare(activePlatform, Strings.NaClPlatformName, true) == 0)
230 {
231 projectPlatformType_ = ProjectPlatformType.NaCl;
232 PluginFoundEvent += new EventHandler<PluginFoundEventArgs>(AttachNaClGDB );
233 }
234 else
235 {
236 projectPlatformType_ = ProjectPlatformType.Other;
237 return false;
238 }
239
240 // We only support certain project types (e.g. C/C++ projects). Otherwise we fail.
241 if (!Utility.IsVisualCProject(startProject))
242 {
243 return false;
244 }
245
246 // Extract necessary information from specific project type.
247 VCConfiguration config = Utility.GetActiveVCConfiguration(startProject);
248 IVCRulePropertyStorage general = config.Rules.Item("ConfigurationGeneral") ;
249 VCLinkerTool linker = config.Tools.Item("VCLinkerTool");
250 VCProject vcproj = (VCProject)startProject.Object;
251
252 sdkRootDirectory_ = general.GetEvaluatedPropertyValue("VSNaClSDKRoot");
253 platformToolset = general.GetEvaluatedPropertyValue("PlatformToolset");
254 pluginOutputDirectory_ = config.Evaluate(config.OutputDirectory);
255 pluginAssembly_ = config.Evaluate(linker.OutputFile);
256 pluginProjectDirectory_ = vcproj.ProjectDirectory; // Macros not allowed here.
257
258 if (projectPlatformType_ == ProjectPlatformType.NaCl)
259 {
260 irtPath_ = general.GetEvaluatedPropertyValue("NaClIrtPath");
261 manifestPath_ = general.GetEvaluatedPropertyValue("NaClManifestPath");
262 }
263
264 if (string.IsNullOrEmpty(sdkRootDirectory_))
265 {
266 MessageBox.Show(Strings.SDKPathNotSetError);
267 return false;
268 }
269
270 sdkRootDirectory_ = sdkRootDirectory_.TrimEnd("/\\".ToArray<char>());
271
272 // TODO(tysand): Move this code getting port to where the web server is st arted.
273 int webServerPort;
274 if (!int.TryParse(general.GetEvaluatedPropertyValue("NaClWebServerPort"), out webServerPort))
275 {
276 webServerPort = DefaultWebServerPort;
277 }
278
279 webServerExecutable_ = "python.exe";
280 webServerArguments_ = string.Format(
281 "{0}\\examples\\httpd.py --no_dir_check {1}", sdkRootDirectory_, webSe rverPort);
282
283 gdbPath_ = Path.Combine(
284 sdkRootDirectory_, "toolchain", platformToolset, @"bin\x86_64-nacl-gdb .exe");
285
286 debuggedChromeMainProcess_ = null;
287
288 isProperlyInitialized_ = true;
289 return true;
290 }
291
292 /// <summary>
293 /// This function should be called to start the PluginDebuggerHelper functio nality.
294 /// </summary>
295 public void StartDebugging()
296 {
297 if (!isProperlyInitialized_)
298 {
299 throw new Exception(Strings.NotInitializedMessage);
300 }
301
302 StartWebServer();
303 pluginFinderTimer_.Interval = InitialPluginCheckFrequency;
304 pluginFinderTimer_.Start();
305 }
306
307 /// <summary>
308 /// This function should be called to stop the PluginDebuggerHelper function ality.
309 /// </summary>
310 public void StopDebugging()
311 {
312 isProperlyInitialized_ = false;
313 pluginFinderTimer_.Stop();
314 pluginFinderForbiddenPids_.Clear();
315
316 // Remove all event handlers from the plug-in found event.
317 if (PluginFoundEvent != null)
318 {
319 foreach (Delegate del in PluginFoundEvent.GetInvocationList())
320 {
321 PluginFoundEvent -= (EventHandler<PluginFoundEventArgs>)del;
322 }
323 }
324
325 Utility.EnsureProcessKill(ref webServer_);
326 WebServerWriteLine(Strings.WebServerStopMessage);
327 CleanUpGDBProcess();
328 }
329
330 /// <summary>
331 /// This function cleans up the started GDB process.
332 /// </summary>
333 private void CleanUpGDBProcess()
334 {
335 Utility.EnsureProcessKill(ref gdbProcess_);
336 if (!string.IsNullOrEmpty(gdbInitFileName_) && File.Exists(gdbInitFileName _))
337 {
338 File.Delete(gdbInitFileName_);
339 gdbInitFileName_ = null;
340 }
341 }
342
343 /// <summary>
344 /// This is called periodically by the Visual Studio UI thread to look for o ur plug-in process
345 /// and attach the debugger to it. The call is triggered by the pluginFinde rTimer_ object.
346 /// </summary>
347 /// <param name="unused">The parameter is not used.</param>
348 /// <param name="unused1">The parameter is not used.</param>
349 private void FindAndAttachToPlugin(object unused, EventArgs unused1)
350 {
351 StringComparison ignoreCase = StringComparison.InvariantCultureIgnoreCase;
352
353 // Set the main chrome process that was started by visual studio. If it's not chrome
354 // or not found then we have no business attaching to any plug-ins so retu rn.
355 if (debuggedChromeMainProcess_ == null)
356 {
357 foreach (Process proc in dte_.Debugger.DebuggedProcesses)
358 {
359 if (proc.Name.EndsWith(Strings.ChromeProcessName, ignoreCase))
360 {
361 debuggedChromeMainProcess_ = System.Diagnostics.Process.GetProcessBy Id(proc.ProcessID);
362 break;
363 }
364 }
365
366 return;
367 }
368
369 // Get the list of all descendants of the main chrome process.
370 uint mainChromeProcId = (uint)debuggedChromeMainProcess_.Id;
371 List<ProcessInfo> chromeDescendants = processSearcher_.GetDescendants(main ChromeProcId);
372
373 // If we didn't start with debug flags then we should not attach.
374 string mainChromeFlags = chromeDescendants.Find(p => p.ID == mainChromePro cId).CommandLine;
375 if (projectPlatformType_ == ProjectPlatformType.NaCl &&
376 !mainChromeFlags.Contains(Strings.NaClDebugFlag))
377 {
378 return;
379 }
380
381 // From the list of descendants, find the plug-in by it's command line arg uments and
382 // process name as well as not being attached to already.
383 List<ProcessInfo> plugins;
384 switch (projectPlatformType_)
385 {
386 case ProjectPlatformType.Pepper:
387 string identifierFlagTarget =
388 string.Format(Strings.PepperProcessPluginFlagFormat, pluginAssembl y_);
389 plugins = chromeDescendants.FindAll(p =>
390 p.Name.Equals(Strings.ChromeProcessName, ignoreCase) &&
391 p.CommandLine.Contains(Strings.ChromeRendererFlag, ignoreCase) &&
392 p.CommandLine.Contains(identifierFlagTarget, ignoreCase) &&
393 !pluginFinderForbiddenPids_.Contains(p.ID));
394 break;
395 case ProjectPlatformType.NaCl:
396 plugins = chromeDescendants.FindAll(p =>
397 p.Name.Equals(Strings.NaClProcessName, ignoreCase) &&
398 p.CommandLine.Contains(Strings.NaClLoaderFlag, ignoreCase) &&
399 !pluginFinderForbiddenPids_.Contains(p.ID));
400 break;
401 default:
402 return;
403 }
404
405 // Attach to all plug-ins that we found.
406 foreach (ProcessInfo process in plugins)
407 {
408 // If we are attaching to a plug-in, add it to the forbidden list to ens ure we
409 // don't try to attach again later.
410 pluginFinderForbiddenPids_.Add(process.ID);
411 PluginFoundEvent.Invoke(this, new PluginFoundEventArgs(process.ID));
412
413 // Slow down the frequency of checks for new plugins.
414 pluginFinderTimer_.Interval = RelaxedPluginCheckFrequency;
415 }
416 }
417
418 /// <summary>
419 /// Attaches the visual studio debugger to a given process ID.
420 /// </summary>
421 /// <param name="src">The parameter is not used.</param>
422 /// <param name="args">Contains the process ID to attach to.</param>
423 private void AttachVSDebugger(object src, PluginFoundEventArgs args)
424 {
425 foreach (EnvDTE.Process proc in dte_.Debugger.LocalProcesses)
426 {
427 if (proc.ProcessID == args.ProcessID)
428 {
429 proc.Attach();
430 break;
431 }
432 }
433 }
434
435 /// <summary>
436 /// Attaches the NaCl GDB debugger to the NaCl plug-in process. Handles loa ding symbols
437 /// and breakpoints from Visual Studio.
438 /// </summary>
439 /// <param name="src">The parameter is not used.</param>
440 /// <param name="args">
441 /// Contains the process ID to attach to, unused since debug stub is already attached.
442 /// </param>
443 private void AttachNaClGDB(object src, PluginFoundEventArgs args)
444 {
445 // Clean up any pre-existing GDB process (can happen if user reloads page) .
446 CleanUpGDBProcess();
447
448 gdbInitFileName_ = Path.GetTempFileName();
449 string pluginAssemblyEscaped = pluginAssembly_.Replace("\\", "\\\\");
450 string irtPathEscaped = irtPath_.Replace("\\", "\\\\");
451
452 // Create the initialization file to read in on GDB start.
453 StringBuilder contents = new StringBuilder();
454
455 if (!string.IsNullOrEmpty(manifestPath_))
456 {
457 string manifestEscaped = manifestPath_.Replace("\\", "\\\\");
458 contents.AppendFormat("nacl-manifest {0}\n", manifestEscaped);
459 }
460 else
461 {
462 contents.AppendFormat("file \"{0}\"\n", pluginAssemblyEscaped);
463 }
464
465 contents.AppendFormat("nacl-irt {0}\n", irtPathEscaped);
466 contents.AppendFormat("target remote localhost:{0}\n", 4014);
467
468 // Insert breakpoints from Visual Studio project.
469 foreach (Breakpoint bp in dte_.Debugger.Breakpoints)
470 {
471 if (!bp.Enabled)
472 {
473 continue;
474 }
475
476 if (bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTy peFile)
477 {
478 contents.AppendFormat("b {0}:{1}\n", Path.GetFileName(bp.File), bp.Fil eLine);
479 }
480 else if (bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocat ionTypeFunction)
481 {
482 contents.AppendFormat("b {0}\n", bp.FunctionName);
483 }
484 else
485 {
486 WebServerWriteLine(
487 string.Format(Strings.UnsupportedBreakpointTypeFormat, bp.LocationTy pe.ToString()));
488 }
489 }
490
491 contents.AppendLine("continue");
492 File.WriteAllText(gdbInitFileName_, contents.ToString());
493
494 // Start NaCl-GDB.
495 try
496 {
497 gdbProcess_ = new System.Diagnostics.Process();
498 gdbProcess_.StartInfo.UseShellExecute = true;
499 gdbProcess_.StartInfo.FileName = gdbPath_;
500 gdbProcess_.StartInfo.Arguments = string.Format("-x {0}", gdbInitFileNam e_);
501 gdbProcess_.StartInfo.WorkingDirectory = pluginProjectDirectory_;
502 gdbProcess_.Start();
503 }
504 catch (Exception e)
505 {
506 MessageBox.Show(
507 string.Format("NaCl-GDB Start Failed. {0}. Path: {1}", e.Message, gd bPath_));
508 }
509 }
510
511 /// <summary>
512 /// Spins up the web server process to host our plug-in.
513 /// </summary>
514 private void StartWebServer()
515 {
516 // Add a panel to the output window which is used to capture output
517 // from the web server hosting the plugin.
518 if (webServerOutputPane_ == null)
519 {
520 webServerOutputPane_ = dte_.ToolWindows.OutputWindow.OutputWindowPanes.A dd(
521 Strings.WebServerOutputWindowTitle);
522 }
523
524 webServerOutputPane_.Clear();
525 WebServerWriteLine(Strings.WebServerStartMessage);
526
527 try
528 {
529 webServer_ = new System.Diagnostics.Process();
530 webServer_.StartInfo.CreateNoWindow = true;
531 webServer_.StartInfo.UseShellExecute = false;
532 webServer_.StartInfo.RedirectStandardOutput = true;
533 webServer_.StartInfo.RedirectStandardError = true;
534 webServer_.StartInfo.FileName = webServerExecutable_;
535 webServer_.StartInfo.Arguments = webServerArguments_;
536 webServer_.StartInfo.WorkingDirectory = pluginProjectDirectory_;
537 webServer_.OutputDataReceived += WebServerMessageReceive;
538 webServer_.ErrorDataReceived += WebServerMessageReceive;
539 webServer_.Start();
540 webServer_.BeginOutputReadLine();
541 webServer_.BeginErrorReadLine();
542 }
543 catch (Exception e)
544 {
545 WebServerWriteLine(Strings.WebServerStartFail);
546 WebServerWriteLine("Exception: " + e.Message);
547 }
548 }
549
550 /// <summary>
551 /// Receives output from the web server process to display in the Visual Stu dio UI.
552 /// </summary>
553 /// <param name="sender">The parameter is not used.</param>
554 /// <param name="e">Contains the data to display.</param>
555 private void WebServerMessageReceive(object sender, System.Diagnostics.DataR eceivedEventArgs e)
556 {
557 WebServerWriteLine(e.Data);
558 }
559
560 /// <summary>
561 /// Helper function to write data to the Web Server Output Pane.
562 /// </summary>
563 /// <param name="message">Message to write.</param>
564 private void WebServerWriteLine(string message)
565 {
566 if (webServerOutputPane_ != null)
567 {
568 webServerOutputPane_.OutputString(message + "\n");
569 }
570 }
571
572 /// <summary>
573 /// The event arguments when a plug-in is found.
574 /// </summary>
575 public class PluginFoundEventArgs : EventArgs
576 {
577 /// <summary>
578 /// Construct the PluginFoundEventArgs.
579 /// </summary>
580 /// <param name="pid">Process ID of the found plug-in.</param>
581 public PluginFoundEventArgs(uint pid)
582 {
583 this.ProcessID = pid;
584 }
585
586 /// <summary>
587 /// Gets or sets process ID of the found plug-in.
588 /// </summary>
589 public uint ProcessID { get; set; }
590 }
591 }
592 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698