Index: tools/win/ChromeDebug/ChromeDebug/ProcessDetail.cs |
diff --git a/tools/win/ChromeDebug/ChromeDebug/ProcessDetail.cs b/tools/win/ChromeDebug/ChromeDebug/ProcessDetail.cs |
new file mode 100644 |
index 0000000000000000000000000000000000000000..63dacd53709cbeaa4e8445886844974360546d0e |
--- /dev/null |
+++ b/tools/win/ChromeDebug/ChromeDebug/ProcessDetail.cs |
@@ -0,0 +1,238 @@ |
+// Copyright (c) 2013 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. |
+ |
+using Microsoft.Win32.SafeHandles; |
+using System; |
+using System.Collections.Generic; |
+using System.ComponentModel; |
+using System.IO; |
+using System.Linq; |
+using System.Text; |
+using System.Threading.Tasks; |
+ |
+using ChromeDebug.LowLevel; |
+ |
+namespace ChromeDebug { |
+ internal class ProcessDetail : IDisposable { |
+ public ProcessDetail(int pid) { |
+ // Initialize everything to null in case something fails. |
+ this.processId = pid; |
+ this.processHandleFlags = LowLevelTypes.ProcessAccessFlags.NONE; |
+ this.cachedProcessBasicInfo = null; |
+ this.machineTypeIsLoaded = false; |
+ this.machineType = LowLevelTypes.MachineType.UNKNOWN; |
+ this.cachedPeb = null; |
+ this.cachedProcessParams = null; |
+ this.cachedCommandLine = null; |
+ this.processHandle = IntPtr.Zero; |
+ |
+ OpenAndCacheProcessHandle(); |
+ } |
+ |
+ // Returns the machine type (x86, x64, etc) of this process. Uses lazy evaluation and caches |
+ // the result. |
+ public LowLevelTypes.MachineType MachineType { |
+ get { |
+ if (machineTypeIsLoaded) |
+ return machineType; |
+ if (!CanQueryProcessInformation) |
+ return LowLevelTypes.MachineType.UNKNOWN; |
+ |
+ CacheMachineType(); |
+ return machineType; |
+ } |
+ } |
+ |
+ // Returns the command line that this process was launched with. Uses lazy evaluation and |
+ // caches the result. Reads the command line from the PEB of the running process. |
+ public string CommandLine { |
+ get { |
+ if (!CanReadPeb) |
+ throw new InvalidOperationException(); |
+ CacheProcessInformation(); |
+ CachePeb(); |
+ CacheProcessParams(); |
+ CacheCommandLine(); |
+ return cachedCommandLine; |
+ } |
+ } |
+ |
+ // Determines if we have permission to read the process's PEB. |
+ public bool CanReadPeb { |
+ get { |
+ LowLevelTypes.ProcessAccessFlags required_flags = |
+ LowLevelTypes.ProcessAccessFlags.VM_READ |
+ | LowLevelTypes.ProcessAccessFlags.QUERY_INFORMATION; |
+ |
+ // In order to read the PEB, we must have *both* of these flags. |
+ if ((processHandleFlags & required_flags) != required_flags) |
+ return false; |
+ |
+ // If we're on a 64-bit OS, in a 32-bit process, and the target process is not 32-bit, |
+ // we can't read its PEB. |
+ if (Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess |
+ && (MachineType != LowLevelTypes.MachineType.X86)) |
+ return false; |
+ |
+ return true; |
+ } |
+ } |
+ |
+ // If we can't read the process's PEB, we may still be able to get other kinds of information |
+ // from the process. This flag determines if we can get lesser information. |
+ private bool CanQueryProcessInformation { |
+ get { |
+ LowLevelTypes.ProcessAccessFlags required_flags = |
+ LowLevelTypes.ProcessAccessFlags.QUERY_LIMITED_INFORMATION |
+ | LowLevelTypes.ProcessAccessFlags.QUERY_INFORMATION; |
+ |
+ // In order to query the process, we need *either* of these flags. |
+ return (processHandleFlags & required_flags) != LowLevelTypes.ProcessAccessFlags.NONE; |
+ } |
+ } |
+ |
+ // Loads the top-level structure of the process's information block and caches it. |
+ private void CacheProcessInformation() { |
+ System.Diagnostics.Debug.Assert(CanReadPeb); |
+ |
+ // Fetch the process info and set the fields. |
+ LowLevelTypes.PROCESS_BASIC_INFORMATION temp = new LowLevelTypes.PROCESS_BASIC_INFORMATION(); |
+ int size; |
+ LowLevelTypes.NTSTATUS status = NativeMethods.NtQueryInformationProcess( |
+ processHandle, |
+ LowLevelTypes.PROCESSINFOCLASS.PROCESS_BASIC_INFORMATION, |
+ ref temp, |
+ Utility.UnmanagedStructSize<LowLevelTypes.PROCESS_BASIC_INFORMATION>(), |
+ out size); |
+ |
+ if (status != LowLevelTypes.NTSTATUS.SUCCESS) { |
+ throw new Win32Exception(); |
+ } |
+ |
+ cachedProcessBasicInfo = temp; |
+ } |
+ |
+ // Follows a pointer from the PROCESS_BASIC_INFORMATION structure in the target process's |
+ // address space to read the PEB. |
+ private void CachePeb() { |
+ System.Diagnostics.Debug.Assert(CanReadPeb); |
+ |
+ if (cachedPeb == null) { |
+ cachedPeb = Utility.ReadUnmanagedStructFromProcess<LowLevelTypes.PEB>( |
+ processHandle, |
+ cachedProcessBasicInfo.Value.PebBaseAddress); |
+ } |
+ } |
+ |
+ // Follows a pointer from the PEB structure in the target process's address space to read the |
+ // RTL_USER_PROCESS_PARAMETERS structure. |
+ private void CacheProcessParams() { |
+ System.Diagnostics.Debug.Assert(CanReadPeb); |
+ |
+ if (cachedProcessParams == null) { |
+ cachedProcessParams = |
+ Utility.ReadUnmanagedStructFromProcess<LowLevelTypes.RTL_USER_PROCESS_PARAMETERS>( |
+ processHandle, cachedPeb.Value.ProcessParameters); |
+ } |
+ } |
+ |
+ private void CacheCommandLine() { |
+ System.Diagnostics.Debug.Assert(CanReadPeb); |
+ |
+ if (cachedCommandLine == null) { |
+ cachedCommandLine = Utility.ReadStringUniFromProcess( |
+ processHandle, |
+ cachedProcessParams.Value.CommandLine.Buffer, |
+ cachedProcessParams.Value.CommandLine.Length / 2); |
+ } |
+ } |
+ |
+ private void CacheMachineType() { |
+ System.Diagnostics.Debug.Assert(CanQueryProcessInformation); |
+ |
+ StringBuilder moduleBuffer = new StringBuilder(1024); |
+ int size = moduleBuffer.Capacity; |
+ |
+ // If our extension is running in a 32-bit process (which it is), then attempts to access |
+ // files in C:\windows\system (and a few other files) will redirect to C:\Windows\SysWOW64 |
+ // and we will mistakenly think that the image file is a 32-bit image. The way around this |
+ // is to use a native system format path, of the form: |
+ // \\?\GLOBALROOT\Device\HarddiskVolume0\Windows\System\foo.dat |
+ // By using the NATIVE_SYSTEM_FORMAT flag to QueryFullProcessImageName, we can get the path |
+ // in this format. |
+ NativeMethods.QueryFullProcessImageName( |
+ processHandle, |
+ LowLevelTypes.ProcessQueryImageNameMode.NATIVE_SYSTEM_FORMAT, |
+ moduleBuffer, |
+ ref size); |
+ moduleBuffer.Insert(0, "\\\\?\\GLOBALROOT"); |
+ string module = moduleBuffer.ToString(); |
+ |
+ // Open the PE File as a binary file, and parse just enough information to determine the |
+ // machine type. |
+ //http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx |
+ using (SafeFileHandle safeHandle = NativeMethods.CreateFile( |
+ module, |
+ LowLevelTypes.FileAccessFlags.GENERIC_READ, |
+ LowLevelTypes.FileShareFlags.SHARE_READ, |
+ IntPtr.Zero, |
+ LowLevelTypes.FileCreationDisposition.OPEN_EXISTING, |
+ LowLevelTypes.FileFlagsAndAttributes.NORMAL, |
+ IntPtr.Zero)) { |
+ FileStream fs = new FileStream(safeHandle, FileAccess.Read); |
+ using (BinaryReader br = new BinaryReader(fs)) { |
+ fs.Seek(0x3c, SeekOrigin.Begin); |
+ Int32 peOffset = br.ReadInt32(); |
+ fs.Seek(peOffset, SeekOrigin.Begin); |
+ UInt32 peHead = br.ReadUInt32(); |
+ if (peHead != 0x00004550) // "PE\0\0", little-endian |
+ throw new Exception("Can't find PE header"); |
+ machineType = (LowLevelTypes.MachineType)br.ReadUInt16(); |
+ machineTypeIsLoaded = true; |
+ } |
+ } |
+ } |
+ |
+ private void OpenAndCacheProcessHandle() { |
+ // Try to open a handle to the process with the highest level of privilege, but if we can't |
+ // do that then fallback to requesting access with a lower privilege level. |
+ processHandleFlags = LowLevelTypes.ProcessAccessFlags.QUERY_INFORMATION |
+ | LowLevelTypes.ProcessAccessFlags.VM_READ; |
+ processHandle = NativeMethods.OpenProcess(processHandleFlags, false, processId); |
+ if (processHandle == IntPtr.Zero) { |
+ processHandleFlags = LowLevelTypes.ProcessAccessFlags.QUERY_LIMITED_INFORMATION; |
+ processHandle = NativeMethods.OpenProcess(processHandleFlags, false, processId); |
+ if (processHandle == IntPtr.Zero) { |
+ processHandleFlags = LowLevelTypes.ProcessAccessFlags.NONE; |
+ throw new Win32Exception(); |
+ } |
+ } |
+ } |
+ |
+ // An open handle to the process, along with the set of access flags that the handle was |
+ // open with. |
+ private int processId; |
+ private IntPtr processHandle; |
+ LowLevelTypes.ProcessAccessFlags processHandleFlags; |
+ |
+ // The machine type is read by parsing the PE image file of the running process, so we cache |
+ // its value since the operation expensive. |
+ private bool machineTypeIsLoaded; |
+ private LowLevelTypes.MachineType machineType; |
+ |
+ // The following fields exist ultimately so that we can access the command line. However, |
+ // each field must be read separately through a pointer into another process's address |
+ // space so the access is expensive, hence we cache the values. |
+ private Nullable<LowLevelTypes.PROCESS_BASIC_INFORMATION> cachedProcessBasicInfo; |
+ private Nullable<LowLevelTypes.PEB> cachedPeb; |
+ private Nullable<LowLevelTypes.RTL_USER_PROCESS_PARAMETERS> cachedProcessParams; |
+ private string cachedCommandLine; |
+ |
+ public void Dispose() { |
+ if (processHandle != IntPtr.Zero) |
+ NativeMethods.CloseHandle(processHandle); |
+ processHandle = IntPtr.Zero; |
+ } |
+ } |
+} |