Index: tools/win/ChromeDebug/ChromeDebug/AttachDialog.cs |
diff --git a/tools/win/ChromeDebug/ChromeDebug/AttachDialog.cs b/tools/win/ChromeDebug/ChromeDebug/AttachDialog.cs |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d62b6043242cb4a1e864737db59d98c046392014 |
--- /dev/null |
+++ b/tools/win/ChromeDebug/ChromeDebug/AttachDialog.cs |
@@ -0,0 +1,250 @@ |
+using System; |
+using System.Collections.Generic; |
+using System.ComponentModel; |
+using System.Data; |
+using System.Diagnostics; |
+using System.Drawing; |
+using System.IO; |
+using System.Linq; |
+using System.Management; |
+using System.Text; |
+using System.Threading.Tasks; |
+using System.Windows.Forms; |
+ |
+using ChromeDebug.LowLevel; |
+ |
+namespace ChromeDebug { |
+ // The form that is displayed to allow the user to select processes to attach to. Note that we |
+ // cannot interact with the DTE object from here (I assume this is because the dialog is running |
+ // on a different thread, although I don't fully understand), so any access to the DTE object |
+ // will have to be done through events that get posted back to the main package thread. |
+ public partial class AttachDialog : Form { |
+ private class ProcessViewItem : ListViewItem { |
+ public ProcessViewItem() { |
+ Category = ProcessCategory.Other; |
+ MachineType = LowLevelTypes.MachineType.UNKNOWN; |
+ } |
+ |
+ public string Exe; |
+ public int ProcessId; |
+ public int SessionId; |
+ public string Title; |
+ public string DisplayCmdLine; |
+ public string[] CmdLineArgs; |
+ public ProcessCategory Category; |
+ public LowLevelTypes.MachineType MachineType; |
+ |
+ public ProcessDetail Detail; |
+ } |
+ |
+ private Dictionary<ProcessCategory, List<ProcessViewItem>> loadedProcessTable = null; |
+ private Dictionary<ProcessCategory, ListViewGroup> processGroups = null; |
+ private List<int> selectedProcesses = null; |
+ |
+ public AttachDialog() { |
+ InitializeComponent(); |
+ |
+ loadedProcessTable = new Dictionary<ProcessCategory, List<ProcessViewItem>>(); |
+ processGroups = new Dictionary<ProcessCategory, ListViewGroup>(); |
+ selectedProcesses = new List<int>(); |
+ |
+ // Create and initialize the groups and process lists only once. On a reset |
+ // we don't clear the groups manually, clearing the list view should clear the |
+ // groups. And we don't clear the entire processes_ dictionary, only the |
+ // individual buckets inside the dictionary. |
+ foreach (object value in Enum.GetValues(typeof(ProcessCategory))) { |
+ ProcessCategory category = (ProcessCategory)value; |
+ |
+ ListViewGroup group = new ListViewGroup(category.ToGroupTitle()); |
+ processGroups[category] = group; |
+ listViewProcesses.Groups.Add(group); |
+ |
+ loadedProcessTable[category] = new List<ProcessViewItem>(); |
+ } |
+ } |
+ |
+ // Provides an iterator that evaluates to the process ids of the entries that are selected |
+ // in the list view. |
+ public IEnumerable<int> SelectedItems { |
+ get { |
+ foreach (ProcessViewItem item in listViewProcesses.SelectedItems) |
+ yield return item.ProcessId; |
+ } |
+ } |
+ |
+ private void AttachDialog_Load(object sender, EventArgs e) { |
+ RepopulateListView(); |
+ } |
+ |
+ // Remove command line arguments that we aren't interested in displaying as part of the command |
+ // line of the process. |
+ private string[] FilterCommandLine(string[] args) { |
+ Func<string, int, bool> AllowArgument = delegate(string arg, int index) { |
+ if (index == 0) |
+ return false; |
+ return !arg.StartsWith("--force-fieldtrials", StringComparison.CurrentCultureIgnoreCase); |
+ }; |
+ |
+ // The force-fieldtrials command line option makes the command line view useless, so remove |
+ // it. Also remove args[0] since that is the process name. |
+ args = args.Where(AllowArgument).ToArray(); |
+ return args; |
+ } |
+ |
+ private void ReloadNativeProcessInfo() { |
+ foreach (List<ProcessViewItem> list in loadedProcessTable.Values) { |
+ list.Clear(); |
+ } |
+ |
+ Process[] processes = Process.GetProcesses(); |
+ foreach (Process p in processes) { |
+ ProcessViewItem item = new ProcessViewItem(); |
+ try { |
+ item.Detail = new ProcessDetail(p.Id); |
+ if (item.Detail.CanReadPeb && item.Detail.CommandLine != null) { |
+ item.CmdLineArgs = Utility.SplitArgs(item.Detail.CommandLine); |
+ item.DisplayCmdLine = GetFilteredCommandLineString(item.CmdLineArgs); |
+ } |
+ item.MachineType = item.Detail.MachineType; |
+ } |
+ catch (Exception) { |
+ // Generally speaking, an exception here means the process is privileged and we cannot |
+ // get any information about the process. For those processes, we will just display the |
+ // information that the Framework gave us in the Process structure. |
+ } |
+ |
+ // If we don't have the machine type, its privilege level is high enough that we won't be |
+ // able to attach a debugger to it anyway, so skip it. |
+ if (item.MachineType == LowLevelTypes.MachineType.UNKNOWN) |
+ continue; |
+ |
+ item.ProcessId = p.Id; |
+ item.SessionId = p.SessionId; |
+ item.Title = p.MainWindowTitle; |
+ item.Exe = p.ProcessName; |
+ if (item.CmdLineArgs != null) |
+ item.Category = DetermineProcessCategory(item.CmdLineArgs); |
+ |
+ List<ProcessViewItem> items = loadedProcessTable[item.Category]; |
+ item.Group = processGroups[item.Category]; |
+ items.Add(item); |
+ } |
+ } |
+ |
+ // Filter the command line arguments to remove extraneous arguments that we don't wish to |
+ // display. |
+ private string GetFilteredCommandLineString(string[] args) { |
+ if (args == null || args.Length == 0) |
+ return string.Empty; |
+ |
+ args = FilterCommandLine(args); |
+ return string.Join(" ", args, 0, args.Length); |
+ } |
+ |
+ // Using a heuristic based on the command line, tries to determine what type of process this |
+ // is. |
+ private ProcessCategory DetermineProcessCategory(string[] cmdline) { |
+ if (cmdline == null || cmdline.Length == 0) |
+ return ProcessCategory.Other; |
+ |
+ string file = Path.GetFileName(cmdline[0]); |
+ if (file.Equals("delegate_execute.exe", StringComparison.CurrentCultureIgnoreCase)) |
+ return ProcessCategory.DelegateExecute; |
+ else if (file.Equals("chrome.exe", StringComparison.CurrentCultureIgnoreCase)) { |
+ if (cmdline.Contains("--type=renderer")) |
+ return ProcessCategory.Renderer; |
+ else if (cmdline.Contains("--type=plugin") || cmdline.Contains("--type=ppapi")) |
+ return ProcessCategory.Plugin; |
+ else if (cmdline.Contains("--type=gpu-process")) |
+ return ProcessCategory.Gpu; |
+ else if (cmdline.Contains("--type=service")) |
+ return ProcessCategory.Service; |
+ else if (cmdline.Any(arg => arg.StartsWith("-ServerName"))) |
+ return ProcessCategory.MetroViewer; |
+ else |
+ return ProcessCategory.Browser; |
+ } else |
+ return ProcessCategory.Other; |
+ } |
+ |
+ private void InsertCategoryItems(ProcessCategory category) { |
+ foreach (ProcessViewItem item in loadedProcessTable[category]) { |
+ item.SubItems.Add(item.Exe); |
+ item.SubItems.Add(item.ProcessId.ToString()); |
+ item.SubItems.Add(item.Title); |
+ item.SubItems.Add(item.MachineType.ToString()); |
+ item.SubItems.Add(item.SessionId.ToString()); |
+ item.SubItems.Add(item.DisplayCmdLine); |
+ listViewProcesses.Items.Add(item); |
+ } |
+ } |
+ |
+ private void AutoResizeColumns() { |
+ // First adjust to the width of the headers, since it's fast. |
+ listViewProcesses.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); |
+ |
+ // Save the widths so we can use them again later. |
+ List<int> widths = new List<int>(); |
+ foreach (ColumnHeader header in listViewProcesses.Columns) |
+ widths.Add(header.Width); |
+ |
+ // Now let Windows do the slow adjustment based on the content. |
+ listViewProcesses.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); |
+ |
+ // Finally, iterate over each column, and resize those columns that just got smaller. |
+ listViewProcesses.Columns[0].Width = 0; |
+ int total = 0; |
+ for (int i = 1; i < listViewProcesses.Columns.Count; ++i) { |
+ // Resize to the largest of the two, but don't let it go over a pre-defined maximum. |
+ int max = Math.Max(listViewProcesses.Columns[i].Width, widths[i]); |
+ int capped = Math.Min(max, 300); |
+ |
+ // We do still want to fill up the available space in the list view however, so if we're |
+ // under then we can fill. |
+ int globalMinWidth = listViewProcesses.Width - SystemInformation.VerticalScrollBarWidth; |
+ if (i == listViewProcesses.Columns.Count - 1 && (total + capped) < (globalMinWidth - 4)) |
+ capped = globalMinWidth - total - 4; |
+ |
+ total += capped; |
+ listViewProcesses.Columns[i].Width = capped; |
+ } |
+ } |
+ |
+ private void RepopulateListView() { |
+ listViewProcesses.Items.Clear(); |
+ |
+ ReloadNativeProcessInfo(); |
+ |
+ InsertCategoryItems(ProcessCategory.Browser); |
+ InsertCategoryItems(ProcessCategory.Renderer); |
+ InsertCategoryItems(ProcessCategory.Gpu); |
+ InsertCategoryItems(ProcessCategory.Plugin); |
+ InsertCategoryItems(ProcessCategory.MetroViewer); |
+ InsertCategoryItems(ProcessCategory.Service); |
+ InsertCategoryItems(ProcessCategory.DelegateExecute); |
+ if (!checkBoxOnlyChrome.Checked) |
+ InsertCategoryItems(ProcessCategory.Other); |
+ |
+ AutoResizeColumns(); |
+ } |
+ |
+ private void buttonRefresh_Click(object sender, EventArgs e) { |
+ RepopulateListView(); |
+ } |
+ |
+ private void buttonAttach_Click(object sender, EventArgs e) { |
+ System.Diagnostics.Debug.WriteLine("Closing dialog."); |
+ this.Close(); |
+ } |
+ |
+ private void checkBoxOnlyChrome_CheckedChanged(object sender, EventArgs e) { |
+ if (!checkBoxOnlyChrome.Checked) |
+ InsertCategoryItems(ProcessCategory.Other); |
+ else { |
+ foreach (ProcessViewItem item in loadedProcessTable[ProcessCategory.Other]) { |
+ listViewProcesses.Items.Remove(item); |
+ } |
+ } |
+ } |
+ } |
+} |