1// Copyright 2013 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 5using System; 6using System.Collections.Generic; 7using System.ComponentModel; 8using System.Data; 9using System.Diagnostics; 10using System.Drawing; 11using System.IO; 12using System.Linq; 13using System.Management; 14using System.Text; 15using System.Threading.Tasks; 16using System.Windows.Forms; 17 18using ChromeDebug.LowLevel; 19 20namespace ChromeDebug { 21 // The form that is displayed to allow the user to select processes to attach to. Note that we 22 // cannot interact with the DTE object from here (I assume this is because the dialog is running 23 // on a different thread, although I don't fully understand), so any access to the DTE object 24 // will have to be done through events that get posted back to the main package thread. 25 public partial class AttachDialog : Form { 26 private class ProcessViewItem : ListViewItem { 27 public ProcessViewItem() { 28 Category = ProcessCategory.Other; 29 MachineType = LowLevelTypes.MachineType.UNKNOWN; 30 } 31 32 public string Exe; 33 public int ProcessId; 34 public int SessionId; 35 public string Title; 36 public string DisplayCmdLine; 37 public string[] CmdLineArgs; 38 public ProcessCategory Category; 39 public LowLevelTypes.MachineType MachineType; 40 41 public ProcessDetail Detail; 42 } 43 44 private Dictionary<ProcessCategory, List<ProcessViewItem>> loadedProcessTable = null; 45 private Dictionary<ProcessCategory, ListViewGroup> processGroups = null; 46 private List<int> selectedProcesses = null; 47 48 public AttachDialog() { 49 InitializeComponent(); 50 51 loadedProcessTable = new Dictionary<ProcessCategory, List<ProcessViewItem>>(); 52 processGroups = new Dictionary<ProcessCategory, ListViewGroup>(); 53 selectedProcesses = new List<int>(); 54 55 // Create and initialize the groups and process lists only once. On a reset 56 // we don't clear the groups manually, clearing the list view should clear the 57 // groups. And we don't clear the entire processes_ dictionary, only the 58 // individual buckets inside the dictionary. 59 foreach (object value in Enum.GetValues(typeof(ProcessCategory))) { 60 ProcessCategory category = (ProcessCategory)value; 61 62 ListViewGroup group = new ListViewGroup(category.ToGroupTitle()); 63 processGroups[category] = group; 64 listViewProcesses.Groups.Add(group); 65 66 loadedProcessTable[category] = new List<ProcessViewItem>(); 67 } 68 } 69 70 // Provides an iterator that evaluates to the process ids of the entries that are selected 71 // in the list view. 72 public IEnumerable<int> SelectedItems { 73 get { 74 foreach (ProcessViewItem item in listViewProcesses.SelectedItems) 75 yield return item.ProcessId; 76 } 77 } 78 79 private void AttachDialog_Load(object sender, EventArgs e) { 80 RepopulateListView(); 81 } 82 83 // Remove command line arguments that we aren't interested in displaying as part of the command 84 // line of the process. 85 private string[] FilterCommandLine(string[] args) { 86 Func<string, int, bool> AllowArgument = delegate(string arg, int index) { 87 if (index == 0) 88 return false; 89 return !arg.StartsWith("--force-fieldtrials", StringComparison.CurrentCultureIgnoreCase); 90 }; 91 92 // The force-fieldtrials command line option makes the command line view useless, so remove 93 // it. Also remove args[0] since that is the process name. 94 args = args.Where(AllowArgument).ToArray(); 95 return args; 96 } 97 98 private void ReloadNativeProcessInfo() { 99 foreach (List<ProcessViewItem> list in loadedProcessTable.Values) { 100 list.Clear(); 101 } 102 103 Process[] processes = Process.GetProcesses(); 104 foreach (Process p in processes) { 105 ProcessViewItem item = new ProcessViewItem(); 106 try { 107 item.Detail = new ProcessDetail(p.Id); 108 if (item.Detail.CanReadPeb && item.Detail.CommandLine != null) { 109 item.CmdLineArgs = Utility.SplitArgs(item.Detail.CommandLine); 110 item.DisplayCmdLine = GetFilteredCommandLineString(item.CmdLineArgs); 111 } 112 item.MachineType = item.Detail.MachineType; 113 } 114 catch (Exception) { 115 // Generally speaking, an exception here means the process is privileged and we cannot 116 // get any information about the process. For those processes, we will just display the 117 // information that the Framework gave us in the Process structure. 118 } 119 120 // If we don't have the machine type, its privilege level is high enough that we won't be 121 // able to attach a debugger to it anyway, so skip it. 122 if (item.MachineType == LowLevelTypes.MachineType.UNKNOWN) 123 continue; 124 125 item.ProcessId = p.Id; 126 item.SessionId = p.SessionId; 127 item.Title = p.MainWindowTitle; 128 item.Exe = p.ProcessName; 129 if (item.CmdLineArgs != null) 130 item.Category = DetermineProcessCategory(item.Detail.Win32ProcessImagePath, 131 item.CmdLineArgs); 132 133 Icon icon = item.Detail.SmallIcon; 134 List<ProcessViewItem> items = loadedProcessTable[item.Category]; 135 item.Group = processGroups[item.Category]; 136 items.Add(item); 137 } 138 } 139 140 // Filter the command line arguments to remove extraneous arguments that we don't wish to 141 // display. 142 private string GetFilteredCommandLineString(string[] args) { 143 if (args == null || args.Length == 0) 144 return string.Empty; 145 146 args = FilterCommandLine(args); 147 return string.Join(" ", args, 0, args.Length); 148 } 149 150 // Using a heuristic based on the command line, tries to determine what type of process this 151 // is. 152 private ProcessCategory DetermineProcessCategory(string imagePath, string[] cmdline) { 153 if (cmdline == null || cmdline.Length == 0) 154 return ProcessCategory.Other; 155 156 string file = Path.GetFileName(imagePath); 157 if (file.Equals("delegate_execute.exe", StringComparison.CurrentCultureIgnoreCase)) 158 return ProcessCategory.DelegateExecute; 159 else if (file.Equals("chrome.exe", StringComparison.CurrentCultureIgnoreCase)) { 160 if (cmdline.Contains("--type=renderer")) 161 return ProcessCategory.Renderer; 162 else if (cmdline.Contains("--type=plugin") || cmdline.Contains("--type=ppapi")) 163 return ProcessCategory.Plugin; 164 else if (cmdline.Contains("--type=gpu-process")) 165 return ProcessCategory.Gpu; 166 else if (cmdline.Contains("--type=service")) 167 return ProcessCategory.Service; 168 else if (cmdline.Any(arg => arg.StartsWith("-ServerName"))) 169 return ProcessCategory.MetroViewer; 170 else 171 return ProcessCategory.Browser; 172 } else 173 return ProcessCategory.Other; 174 } 175 176 private void InsertCategoryItems(ProcessCategory category) { 177 foreach (ProcessViewItem item in loadedProcessTable[category]) { 178 item.Text = item.Exe; 179 item.SubItems.Add(item.ProcessId.ToString()); 180 item.SubItems.Add(item.Title); 181 item.SubItems.Add(item.MachineType.ToString()); 182 item.SubItems.Add(item.SessionId.ToString()); 183 item.SubItems.Add(item.DisplayCmdLine); 184 listViewProcesses.Items.Add(item); 185 186 Icon icon = item.Detail.SmallIcon; 187 if (icon != null) { 188 item.ImageList.Images.Add(icon); 189 item.ImageIndex = item.ImageList.Images.Count - 1; 190 } 191 } 192 } 193 194 private void AutoResizeColumns() { 195 // First adjust to the width of the headers, since it's fast. 196 listViewProcesses.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); 197 198 // Save the widths so we can use them again later. 199 List<int> widths = new List<int>(); 200 foreach (ColumnHeader header in listViewProcesses.Columns) 201 widths.Add(header.Width); 202 203 // Now let Windows do the slow adjustment based on the content. 204 listViewProcesses.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); 205 206 // Finally, iterate over each column, and resize those columns that just got smaller. 207 int total = 0; 208 for (int i = 0; i < listViewProcesses.Columns.Count; ++i) { 209 // Resize to the largest of the two, but don't let it go over a pre-defined maximum. 210 int max = Math.Max(listViewProcesses.Columns[i].Width, widths[i]); 211 int capped = Math.Min(max, 300); 212 213 // We do still want to fill up the available space in the list view however, so if we're 214 // under then we can fill. 215 int globalMinWidth = listViewProcesses.Width - SystemInformation.VerticalScrollBarWidth; 216 if (i == listViewProcesses.Columns.Count - 1 && (total + capped) < (globalMinWidth - 4)) 217 capped = globalMinWidth - total - 4; 218 219 total += capped; 220 listViewProcesses.Columns[i].Width = capped; 221 } 222 } 223 224 private void RepopulateListView() { 225 listViewProcesses.Items.Clear(); 226 listViewProcesses.SmallImageList = new ImageList(); 227 listViewProcesses.SmallImageList.ImageSize = new Size(16, 16); 228 229 ReloadNativeProcessInfo(); 230 231 InsertCategoryItems(ProcessCategory.Browser); 232 InsertCategoryItems(ProcessCategory.Renderer); 233 InsertCategoryItems(ProcessCategory.Gpu); 234 InsertCategoryItems(ProcessCategory.Plugin); 235 InsertCategoryItems(ProcessCategory.MetroViewer); 236 InsertCategoryItems(ProcessCategory.Service); 237 InsertCategoryItems(ProcessCategory.DelegateExecute); 238 if (!checkBoxOnlyChrome.Checked) 239 InsertCategoryItems(ProcessCategory.Other); 240 241 AutoResizeColumns(); 242 } 243 244 private void buttonRefresh_Click(object sender, EventArgs e) { 245 RepopulateListView(); 246 } 247 248 private void buttonAttach_Click(object sender, EventArgs e) { 249 System.Diagnostics.Debug.WriteLine("Closing dialog."); 250 this.Close(); 251 } 252 253 private void checkBoxOnlyChrome_CheckedChanged(object sender, EventArgs e) { 254 if (!checkBoxOnlyChrome.Checked) 255 InsertCategoryItems(ProcessCategory.Other); 256 else { 257 foreach (ProcessViewItem item in loadedProcessTable[ProcessCategory.Other]) { 258 listViewProcesses.Items.Remove(item); 259 } 260 } 261 } 262 } 263} 264