blob: 9da21c0e79bc557894e65579b58642544f928f19 [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.IO;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using EnvDTE;
using EnvDTE80;
/// <summary>
/// This class handles the details of finding a nexe and attaching to it.
/// </summary>
public class PluginDebuggerGDB : PluginDebuggerBase
{
/// <summary>
/// Path to the actual nexe.
/// </summary>
private string targetNexe_;
/// <summary>
/// Directory of the project we are debugging.
/// </summary>
private string projectDirectory_;
/// <summary>
/// Path to the NaCl IRT.
/// </summary>
private string irtPath_;
/// <summary>
/// Path to the project's nmf file.
/// </summary>
private string manifestPath_;
/// <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>
/// Constructs the PluginDebuggerHelper.
/// </summary>
/// <param name="dte">Automation object from Visual Studio.</param>
/// <param name="properties">PropertyManager pointing to a valid project/platform.</param>
public PluginDebuggerGDB(DTE2 dte, PropertyManager properties)
: base(dte, properties)
{
string arch = "i686";
if (Environment.Is64BitOperatingSystem)
{
arch = "x86_64";
}
if (!properties.IsPNaCl())
{
if (properties.TargetArchitecture != arch)
{
MessageBox.Show(string.Format("Debugging of {0} NaCl modules is not possible on this system ({1}).",
properties.TargetArchitecture, arch));
}
}
// check chrome version
string chrome_path = properties.LocalDebuggerCommand;
FileVersionInfo version_info = FileVersionInfo.GetVersionInfo(chrome_path);
string file_version = version_info.FileVersion;
if (file_version != null)
{
string major_version = file_version.Split('.')[0];
int major_version_int = 0;
try
{
major_version_int = Convert.ToInt32(major_version);
}
catch
{
}
if (major_version_int < 22)
{
MessageBox.Show("Chrome 22 or above required for NaCl debugging (your version is "
+ major_version + ")");
return;
}
}
// We look for the IRT in several ways, mimicing what chrome itself
// does in chrome/app/client_util.cc:MakeMainDllLoader.
// First look for the IRT alongside chrome.exe
string irt_basename = "nacl_irt_" + arch + ".nexe";
irtPath_ = Path.Combine(Path.GetDirectoryName(chrome_path), irt_basename);
if (!File.Exists(irtPath_))
{
// Next look for a folder alongside chrome.exe with the same name
// as the version embedded in chrome.exe.
if (file_version == null)
{
if (!File.Exists(irtPath_))
{
MessageBox.Show("NaCl IRT not found in chrome install.\nLooking for: " + irtPath_);
irtPath_ = null;
}
}
else
{
irtPath_ = Path.Combine(Path.GetDirectoryName(chrome_path),
file_version, irt_basename);
if (!File.Exists(irtPath_))
{
MessageBox.Show("NaCl IRT not found in chrome install.\nLooking for: " + irtPath_);
irtPath_ = null;
}
}
}
manifestPath_ = properties.ManifestPath;
targetNexe_ = properties.PluginAssembly;
if (properties.IsPNaCl())
{
string basename = Path.GetFileNameWithoutExtension(targetNexe_);
targetNexe_ = Path.Combine(Path.GetDirectoryName(targetNexe_),
basename + "_" + arch + ".nexe");
if (!File.Exists(targetNexe_))
{
MessageBox.Show(
string.Format("Failed to find nexe to debug: {0}", targetNexe_));
}
}
projectDirectory_ = properties.ProjectDirectory;
gdbPath_ = Path.Combine(
properties.SDKRootDirectory,
"toolchain",
string.Concat("win_x86_", properties.ToolchainName),
@"bin\x86_64-nacl-gdb.exe");
Debug.WriteLine("Found gdb: {0}", gdbPath_);
PluginFoundEvent += new EventHandler<PluginFoundEventArgs>(Attach);
}
/// <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 garbage collection.</param>
protected override void Dispose(bool disposing)
{
if (!Disposed)
{
base.Dispose(disposing);
if (disposing)
{
CleanUpGDBProcess();
}
// This is repeated functionality from CleanUpGDBProcess but will
// only touch unmanaged resources as required by disposing=false.
if (!string.IsNullOrEmpty(gdbInitFileName_) && File.Exists(gdbInitFileName_))
{
File.Delete(gdbInitFileName_);
gdbInitFileName_ = null;
}
Disposed = true;
}
}
/// <summary>
/// Called to check if a process is a valid nacl module 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 override bool IsPluginProcess(ProcessInfo proc, string mainChromeFlags)
{
// Ensure the main chrome process has the nacl debug flag, otherwise we shouldn't
// try to attach to anything.
if (!mainChromeFlags.Contains(Strings.NaClDebugFlag))
{
return false;
}
StringComparison ignoreCase = StringComparison.InvariantCultureIgnoreCase;
return proc.Name.Equals(Strings.NaClProcessName, ignoreCase) &&
proc.CommandLine.Contains(Strings.NaClLoaderFlag, ignoreCase);
}
/// <summary>
/// This function cleans up the started GDB process.
/// </summary>
private void CleanUpGDBProcess()
{
Utility.EnsureProcessKill(ref gdbProcess_);
if (!string.IsNullOrEmpty(gdbInitFileName_) && File.Exists(gdbInitFileName_))
{
File.Delete(gdbInitFileName_);
gdbInitFileName_ = null;
}
}
/// <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 Attach(object src, PluginFoundEventArgs args)
{
// Clean up any pre-existing GDB process (can happen if user reloads page).
CleanUpGDBProcess();
// Create the initialization file to read in on GDB start.
gdbInitFileName_ = Path.GetTempFileName();
StringBuilder contents = new StringBuilder();
if (!string.IsNullOrEmpty(manifestPath_))
{
string manifestEscaped = manifestPath_.Replace("\\", "\\\\");
contents.AppendFormat("nacl-manifest {0}\n", manifestEscaped);
}
else
{
string pluginAssemblyEscaped = targetNexe_.Replace("\\", "\\\\");
contents.AppendFormat("file \"{0}\"\n", pluginAssemblyEscaped);
}
// irtPath_ could be null if the irt nexe was not found in the chrome
// install.
if (irtPath_ != null)
{
string irtPathEscaped = irtPath_.Replace("\\", "\\\\");
contents.AppendFormat("nacl-irt \"{0}\"\n", irtPathEscaped);
}
contents.AppendFormat("target remote localhost:{0}\n", 4014);
// Insert breakpoints from Visual Studio project.
if (Dte.Debugger.Breakpoints != null)
{
foreach (Breakpoint bp in Dte.Debugger.Breakpoints)
{
if (!bp.Enabled)
{
continue;
}
if (bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTypeFile)
{
contents.AppendFormat("b {0}:{1}\n", Path.GetFileName(bp.File), bp.FileLine);
}
else if (bp.LocationType == dbgBreakpointLocationType.dbgBreakpointLocationTypeFunction)
{
contents.AppendFormat("b {0}\n", bp.FunctionName);
}
else
{
Utility.WebServerWriteLine(
Dte,
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 = projectDirectory_;
gdbProcess_.Start();
}
catch (Exception e)
{
MessageBox.Show(
string.Format("NaCl-GDB Start Failed. {0}. Path: {1}", e.Message, gdbPath_));
}
}
}
}