blob: 71c23a0d3d260dee2393f97741923c7aa5036c05 [file] [log] [blame]
// 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.Windows.Forms;
using EnvDTE;
using EnvDTE80;
/// <summary>
/// This is a base class for encapsulating functionality related to attaching a debugger
/// to a nacl/pepper plug-in. This base class mostly contains functionality related to finding
/// the plug-in.
/// </summary>
public class PluginDebuggerBase : IDisposable
{
/// <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>
/// 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.
/// </summary>
/// <param name="dte">Automation object from Visual Studio.</param>
/// <param name="properties">PropertyManager set to a valid project/platform.</param>
protected PluginDebuggerBase(DTE2 dte, PropertyManager properties)
{
if (dte == null)
{
throw new ArgumentNullException("dte");
}
if (properties == null)
{
throw new ArgumentNullException("properties");
}
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();
pluginFinderTimer_.Interval = InitialPluginCheckFrequency;
pluginFinderTimer_.Start();
}
/// <summary>
/// Finalizer. Should clean up unmanaged resources. Should not be overriden in derived classes.
/// </summary>
~PluginDebuggerBase()
{
Dispose(false);
}
/// <summary>
/// An event indicating a target plug-in was found on the system.
/// </summary>
public event EventHandler<PluginFoundEventArgs> PluginFoundEvent;
/// <summary>
/// Gets or sets a value indicating whether this object has been disposed of already.
/// </summary>
protected bool Disposed { get; set; }
/// <summary>
/// Gets or sets the main visual studio object.
/// </summary>
protected DTE2 Dte { get; set; }
/// <summary>
/// Disposes the object when called by user code (not directly by garbage collector).
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <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>
public void FindAndAttachToPlugin(object unused, EventArgs unused1)
{
StringComparison ignoreCase = StringComparison.InvariantCultureIgnoreCase;
// This function is called by the main Visual Studio event loop and we may have put the event
// on the queue just before disposing it meaning this could be called after we've disposed.
if (Disposed)
{
return;
}
// 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;
}
if (debuggedChromeMainProcess_.HasExited)
{
// Might happen if we're shutting down debugging.
return;
}
// Get the list of all descendants of the main chrome process.
uint mainChromeProcId = (uint)debuggedChromeMainProcess_.Id;
List<ProcessInfo> chromeDescendants = processSearcher_.GetDescendants(mainChromeProcId);
if (chromeDescendants.Count == 0)
{
// Might happen if we're shutting down debugging.
return;
}
ProcessInfo mainProcInfo = chromeDescendants.Find(p => p.ID == mainChromeProcId);
if (mainProcInfo == null)
{
// Might happen if we're shutting down debugging.
return;
}
string mainChromeFlags = mainProcInfo.CommandLine;
// 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 = chromeDescendants.FindAll(p =>
IsPluginProcess(p, mainChromeFlags) && !pluginFinderForbiddenPids_.Contains(p.ID));
// 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>
/// Disposes the object. If disposing is false then this has been called by garbage collection,
/// and we shouldn't reference managed objects.
/// </summary>
/// <param name="disposing">True if user call to Dispose, false if garbase collection.</param>
protected virtual void Dispose(bool disposing)
{
if (!Disposed && disposing)
{
pluginFinderTimer_.Stop();
}
Disposed = true;
}
/// <summary>
/// Called to check if a process is a valid plugin to attach to.
/// </summary>
/// <param name="proc">Contains information about the process in question.</param>
/// <param name="mainChromeFlags">Flags on the main Chrome process.</param>
/// <returns>True if we should attach to the process.</returns>
protected virtual bool IsPluginProcess(ProcessInfo proc, string mainChromeFlags)
{
throw new InvalidOperationException();
}
/// <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; }
}
}
}