// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Buffers;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
{
public static IntPtr GetMainWindowHandle(int processId)
{
- MainWindowFinder finder = new MainWindowFinder();
- return finder.FindMainWindow(processId);
+ return new MainWindowFinder().FindMainWindow(processId);
}
}
public IntPtr FindMainWindow(int processId)
{
- _bestHandle = (IntPtr)0;
+ _bestHandle = IntPtr.Zero;
_processId = processId;
- Interop.User32.EnumThreadWindowsCallback callback = new Interop.User32.EnumThreadWindowsCallback(EnumWindowsCallback);
- Interop.User32.EnumWindows(callback, IntPtr.Zero);
+ Interop.User32.EnumWindows(EnumWindowsCallback, IntPtr.Zero);
- GC.KeepAlive(callback);
return _bestHandle;
}
private bool IsMainWindow(IntPtr handle)
{
- if (Interop.User32.GetWindow(handle, GW_OWNER) != (IntPtr)0 || !Interop.User32.IsWindowVisible(handle))
+ if (Interop.User32.GetWindow(handle, GW_OWNER) != IntPtr.Zero || !Interop.User32.IsWindowVisible(handle))
return false;
return true;
{
processHandle = ProcessManager.OpenProcess(processId, Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION | Interop.Advapi32.ProcessOptions.PROCESS_VM_READ, true);
- IntPtr[] moduleHandles = new IntPtr[64];
- GCHandle moduleHandlesArrayHandle = default;
- int moduleCount = 0;
- while (true)
+ bool succeeded = Interop.Kernel32.EnumProcessModules(processHandle, null, 0, out int needed);
+
+ // The API we need to use to enumerate process modules differs on two factors:
+ // 1) If our process is running in WOW64.
+ // 2) The bitness of the process we wish to introspect.
+ //
+ // If we are not running in WOW64 or we ARE in WOW64 but want to inspect a 32 bit process
+ // we can call psapi!EnumProcessModules.
+ //
+ // If we are running in WOW64 and we want to inspect the modules of a 64 bit process then
+ // psapi!EnumProcessModules will return false with ERROR_PARTIAL_COPY (299). In this case we can't
+ // do the enumeration at all. So we'll detect this case and bail out.
+ if (!succeeded)
{
- bool enumResult = false;
+ SafeProcessHandle hCurProcess = SafeProcessHandle.InvalidHandle;
try
{
- moduleHandlesArrayHandle = GCHandle.Alloc(moduleHandles, GCHandleType.Pinned);
- enumResult = Interop.Kernel32.EnumProcessModules(processHandle, moduleHandlesArrayHandle.AddrOfPinnedObject(), moduleHandles.Length * IntPtr.Size, ref moduleCount);
-
- // The API we need to use to enumerate process modules differs on two factors:
- // 1) If our process is running in WOW64.
- // 2) The bitness of the process we wish to introspect.
- //
- // If we are not running in WOW64 or we ARE in WOW64 but want to inspect a 32 bit process
- // we can call psapi!EnumProcessModules.
- //
- // If we are running in WOW64 and we want to inspect the modules of a 64 bit process then
- // psapi!EnumProcessModules will return false with ERROR_PARTIAL_COPY (299). In this case we can't
- // do the enumeration at all. So we'll detect this case and bail out.
- //
- // Also, EnumProcessModules is not a reliable method to get the modules for a process.
- // If OS loader is touching module information, this method might fail and copy part of the data.
- // This is no easy solution to this problem. The only reliable way to fix this is to
- // suspend all the threads in target process. Of course we don't want to do this in Process class.
- // So we just to try avoid the race by calling the same method 50 (an arbitrary number) times.
- //
- if (!enumResult)
- {
- bool sourceProcessIsWow64 = false;
- bool targetProcessIsWow64 = false;
- SafeProcessHandle hCurProcess = SafeProcessHandle.InvalidHandle;
- try
- {
- hCurProcess = ProcessManager.OpenProcess(unchecked((int)Interop.Kernel32.GetCurrentProcessId()), Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION, true);
- bool wow64Ret;
+ hCurProcess = ProcessManager.OpenProcess((int)Interop.Kernel32.GetCurrentProcessId(), Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION, true);
- wow64Ret = Interop.Kernel32.IsWow64Process(hCurProcess, ref sourceProcessIsWow64);
- if (!wow64Ret)
- {
- throw new Win32Exception();
- }
-
- wow64Ret = Interop.Kernel32.IsWow64Process(processHandle, ref targetProcessIsWow64);
- if (!wow64Ret)
- {
- throw new Win32Exception();
- }
+ if (!Interop.Kernel32.IsWow64Process(hCurProcess, out bool sourceProcessIsWow64))
+ {
+ throw new Win32Exception();
+ }
- if (sourceProcessIsWow64 && !targetProcessIsWow64)
- {
- // Wow64 isn't going to allow this to happen, the best we can do is give a descriptive error to the user.
- throw new Win32Exception(Interop.Errors.ERROR_PARTIAL_COPY, SR.EnumProcessModuleFailedDueToWow);
- }
- }
- finally
- {
- if (hCurProcess != SafeProcessHandle.InvalidHandle)
- {
- hCurProcess.Dispose();
- }
- }
+ if (!Interop.Kernel32.IsWow64Process(processHandle, out bool targetProcessIsWow64))
+ {
+ throw new Win32Exception();
+ }
- // If the failure wasn't due to Wow64, try again.
- for (int i = 0; i < 50; i++)
- {
- enumResult = Interop.Kernel32.EnumProcessModules(processHandle, moduleHandlesArrayHandle.AddrOfPinnedObject(), moduleHandles.Length * IntPtr.Size, ref moduleCount);
- if (enumResult)
- {
- break;
- }
- Thread.Sleep(1);
- }
+ if (sourceProcessIsWow64 && !targetProcessIsWow64)
+ {
+ // Wow64 isn't going to allow this to happen, the best we can do is give a descriptive error to the user.
+ throw new Win32Exception(Interop.Errors.ERROR_PARTIAL_COPY, SR.EnumProcessModuleFailedDueToWow);
}
}
finally
{
- moduleHandlesArrayHandle.Free();
+ if (hCurProcess != SafeProcessHandle.InvalidHandle)
+ {
+ hCurProcess.Dispose();
+ }
}
- if (!enumResult)
+ EnumProcessModulesUntilSuccess(processHandle, null, 0, out needed);
+ }
+
+ int modulesCount = needed / IntPtr.Size;
+ IntPtr[] moduleHandles = new IntPtr[modulesCount];
+ while (true)
+ {
+ int size = needed;
+ EnumProcessModulesUntilSuccess(processHandle, moduleHandles, size, out needed);
+ if (size == needed)
{
- throw new Win32Exception();
+ break;
}
- moduleCount /= IntPtr.Size;
- if (moduleCount <= moduleHandles.Length)
- break;
- moduleHandles = new IntPtr[moduleHandles.Length * 2];
+ if (needed > size && needed / IntPtr.Size > modulesCount)
+ {
+ modulesCount = needed / IntPtr.Size;
+ moduleHandles = new IntPtr[modulesCount];
+ }
}
- var modules = new ProcessModuleCollection(firstModuleOnly ? 1 : moduleCount);
-
- char[] chars = new char[1024];
+ var modules = new ProcessModuleCollection(firstModuleOnly ? 1 : modulesCount);
- for (int i = 0; i < moduleCount; i++)
+ char[] chars = ArrayPool<char>.Shared.Rent(1024);
+ try
{
- if (i > 0)
+ for (int i = 0; i < modulesCount; i++)
{
- // If the user is only interested in the main module, break now.
- // This avoid some waste of time. In addition, if the application unloads a DLL
- // we will not get an exception.
- if (firstModuleOnly)
+ if (i > 0)
{
- break;
+ // If the user is only interested in the main module, break now.
+ // This avoid some waste of time. In addition, if the application unloads a DLL
+ // we will not get an exception.
+ if (firstModuleOnly)
+ {
+ break;
+ }
}
- }
- IntPtr moduleHandle = moduleHandles[i];
- Interop.Kernel32.NtModuleInfo ntModuleInfo;
- if (!Interop.Kernel32.GetModuleInformation(processHandle, moduleHandle, out ntModuleInfo))
- {
- HandleError();
- continue;
- }
+ IntPtr moduleHandle = moduleHandles[i];
+ Interop.Kernel32.NtModuleInfo ntModuleInfo;
+ if (!Interop.Kernel32.GetModuleInformation(processHandle, moduleHandle, out ntModuleInfo))
+ {
+ HandleLastWin32Error();
+ continue;
+ }
- var module = new ProcessModule()
- {
- ModuleMemorySize = ntModuleInfo.SizeOfImage,
- EntryPointAddress = ntModuleInfo.EntryPoint,
- BaseAddress = ntModuleInfo.BaseOfDll
- };
+ var module = new ProcessModule()
+ {
+ ModuleMemorySize = ntModuleInfo.SizeOfImage,
+ EntryPointAddress = ntModuleInfo.EntryPoint,
+ BaseAddress = ntModuleInfo.BaseOfDll
+ };
- int length = Interop.Kernel32.GetModuleBaseName(processHandle, moduleHandle, chars, chars.Length);
- if (length == 0)
- {
- HandleError();
- continue;
- }
+ int length = Interop.Kernel32.GetModuleBaseName(processHandle, moduleHandle, chars, chars.Length);
+ if (length == 0)
+ {
+ HandleLastWin32Error();
+ continue;
+ }
- module.ModuleName = new string(chars, 0, length);
+ module.ModuleName = new string(chars, 0, length);
- length = Interop.Kernel32.GetModuleFileNameEx(processHandle, moduleHandle, chars, chars.Length);
- if (length == 0)
- {
- HandleError();
- continue;
- }
+ length = Interop.Kernel32.GetModuleFileNameEx(processHandle, moduleHandle, chars, chars.Length);
+ if (length == 0)
+ {
+ HandleLastWin32Error();
+ continue;
+ }
- module.FileName = (length >= 4 && chars[0] == '\\' && chars[1] == '\\' && chars[2] == '?' && chars[3] == '\\') ?
- new string(chars, 4, length - 4) :
- new string(chars, 0, length);
+ module.FileName = (length >= 4 && chars[0] == '\\' && chars[1] == '\\' && chars[2] == '?' && chars[3] == '\\') ?
+ new string(chars, 4, length - 4) :
+ new string(chars, 0, length);
- modules.Add(module);
+ modules.Add(module);
+ }
+ }
+ finally
+ {
+ ArrayPool<char>.Shared.Return(chars);
}
return modules;
}
}
}
+
+ private static void EnumProcessModulesUntilSuccess(SafeProcessHandle processHandle, IntPtr[]? modules, int size, out int needed)
+ {
+ // When called on a running process, EnumProcessModules may fail with ERROR_PARTIAL_COPY
+ // if the target process is not yet initialized or if the module list changes during the function call.
+ // We just try to avoid the race by retring 50 (an arbitrary number) times.
+ int i = 0;
+ while (true)
+ {
+ if (Interop.Kernel32.EnumProcessModules(processHandle, modules, size, out needed))
+ {
+ return;
+ }
+
+ if (i++ > 50)
+ {
+ throw new Win32Exception();
+ }
+
+ Thread.Sleep(1);
+ }
+ }
+
+ private static void HandleLastWin32Error()
+ {
+ int lastError = Marshal.GetLastWin32Error();
+ switch (lastError)
+ {
+ case Interop.Errors.ERROR_INVALID_HANDLE:
+ case Interop.Errors.ERROR_PARTIAL_COPY:
+ // It's possible that another thread caused this module to become
+ // unloaded (e.g FreeLibrary was called on the module). Ignore it and
+ // move on.
+ break;
+ default:
+ throw new Win32Exception(lastError);
+ }
+ }
}
internal static class NtProcessInfoHelper
if (processIdFilter == null || processIdFilter(processInfoProcessId))
{
// get information for a process
- ProcessInfo processInfo = new ProcessInfo();
- processInfo.ProcessId = processInfoProcessId;
- processInfo.SessionId = (int)pi.SessionId;
- processInfo.PoolPagedBytes = (long)pi.QuotaPagedPoolUsage;
- processInfo.PoolNonPagedBytes = (long)pi.QuotaNonPagedPoolUsage;
- processInfo.VirtualBytes = (long)pi.VirtualSize;
- processInfo.VirtualBytesPeak = (long)pi.PeakVirtualSize;
- processInfo.WorkingSetPeak = (long)pi.PeakWorkingSetSize;
- processInfo.WorkingSet = (long)pi.WorkingSetSize;
- processInfo.PageFileBytesPeak = (long)pi.PeakPagefileUsage;
- processInfo.PageFileBytes = (long)pi.PagefileUsage;
- processInfo.PrivateBytes = (long)pi.PrivatePageCount;
- processInfo.BasePriority = pi.BasePriority;
- processInfo.HandleCount = (int)pi.HandleCount;
+ ProcessInfo processInfo = new ProcessInfo((int)pi.NumberOfThreads)
+ {
+ ProcessId = processInfoProcessId,
+ SessionId = (int)pi.SessionId,
+ PoolPagedBytes = (long)pi.QuotaPagedPoolUsage,
+ PoolNonPagedBytes = (long)pi.QuotaNonPagedPoolUsage,
+ VirtualBytes = (long)pi.VirtualSize,
+ VirtualBytesPeak = (long)pi.PeakVirtualSize,
+ WorkingSetPeak = (long)pi.PeakWorkingSetSize,
+ WorkingSet = (long)pi.WorkingSetSize,
+ PageFileBytesPeak = (long)pi.PeakPagefileUsage,
+ PageFileBytes = (long)pi.PagefileUsage,
+ PrivateBytes = (long)pi.PrivatePageCount,
+ BasePriority = pi.BasePriority,
+ HandleCount = (int)pi.HandleCount,
+ };
if (pi.ImageName.Buffer == IntPtr.Zero)
{
}
else
{
- string processName = GetProcessShortName(Marshal.PtrToStringUni(pi.ImageName.Buffer, pi.ImageName.Length / sizeof(char)));
+ string processName = GetProcessShortName(new ReadOnlySpan<char>(pi.ImageName.Buffer.ToPointer(), pi.ImageName.Length / sizeof(char)));
processInfo.ProcessName = processName;
}
{
ref readonly SYSTEM_THREAD_INFORMATION ti = ref MemoryMarshal.AsRef<SYSTEM_THREAD_INFORMATION>(data.Slice(threadInformationOffset));
- ThreadInfo threadInfo = new ThreadInfo();
-
- threadInfo._processId = (int)ti.ClientId.UniqueProcess;
- threadInfo._threadId = (ulong)ti.ClientId.UniqueThread;
- threadInfo._basePriority = ti.BasePriority;
- threadInfo._currentPriority = ti.Priority;
- threadInfo._startAddress = ti.StartAddress;
- threadInfo._threadState = (ThreadState)ti.ThreadState;
- threadInfo._threadWaitReason = NtProcessManager.GetThreadWaitReason((int)ti.WaitReason);
+ ThreadInfo threadInfo = new ThreadInfo
+ {
+ _processId = (int)ti.ClientId.UniqueProcess,
+ _threadId = (ulong)ti.ClientId.UniqueThread,
+ _basePriority = ti.BasePriority,
+ _currentPriority = ti.Priority,
+ _startAddress = ti.StartAddress,
+ _threadState = (ThreadState)ti.ThreadState,
+ _threadWaitReason = NtProcessManager.GetThreadWaitReason((int)ti.WaitReason),
+ };
processInfo._threadInfoList.Add(threadInfo);
//
// This is from GetProcessShortName in NT code base.
// Check base\screg\winreg\perfdlls\process\perfsprc.c for details.
- internal static string GetProcessShortName(string name)
+ internal static string GetProcessShortName(ReadOnlySpan<char> name)
{
- if (string.IsNullOrEmpty(name))
+ if (name.IsEmpty)
{
return string.Empty;
}
// if a period was found, then see if the extension is
// .EXE, if so drop it, if not, then use end of string
// (i.e. include extension in name)
- ReadOnlySpan<char> extension = name.AsSpan(period);
+ ReadOnlySpan<char> extension = name.Slice(period);
if (extension.Equals(".exe", StringComparison.OrdinalIgnoreCase))
period--; // point to character before period
// copy characters between period (or end of string) and
// slash (or start of string) to make image name
- return name.Substring(slash, period - slash + 1);
+ return name.Slice(slash, period - slash + 1).ToString();
}
}
}
using System.ComponentModel;
using System.Globalization;
using System.Runtime.InteropServices;
-using System.Threading;
using Microsoft.Win32.SafeHandles;
using static Interop.Advapi32;
// So we will try to get the privilege here.
// We could fail if the user account doesn't have right to do this, but that's fair.
- Interop.Advapi32.LUID luid = default;
+ Interop.Advapi32.LUID luid;
if (!Interop.Advapi32.LookupPrivilegeValue(null, Interop.Advapi32.SeDebugPrivilege, out luid))
{
return;
if (processId == 0)
{
- throw new Win32Exception(5);
+ throw new Win32Exception(Interop.Errors.ERROR_ACCESS_DENIED);
}
// If the handle is invalid because the process has exited, only throw an exception if throwIfExited is true.
public static int[] GetProcessIds()
{
int[] processIds = new int[256];
- int size;
+ int needed;
while (true)
{
- if (!Interop.Kernel32.EnumProcesses(processIds, processIds.Length * 4, out size))
+ int size = processIds.Length * sizeof(int);
+ if (!Interop.Kernel32.EnumProcesses(processIds, size, out needed))
throw new Win32Exception();
- if (size == processIds.Length * 4)
+
+ if (needed == size)
{
processIds = new int[processIds.Length * 2];
continue;
}
+
break;
}
- int[] ids = new int[size / 4];
+
+ int[] ids = new int[needed / sizeof(int)];
Array.Copy(processIds, ids, ids.Length);
return ids;
}
return modules.Count == 0 ? null : modules[0];
}
- private static void HandleError()
- {
- int lastError = Marshal.GetLastWin32Error();
- switch (lastError)
- {
- case Interop.Errors.ERROR_INVALID_HANDLE:
- case Interop.Errors.ERROR_PARTIAL_COPY:
- // It's possible that another thread caused this module to become
- // unloaded (e.g FreeLibrary was called on the module). Ignore it and
- // move on.
- break;
- default:
- throw new Win32Exception(lastError);
- }
- }
-
public static int GetProcessIdFromHandle(SafeProcessHandle processHandle)
{
return Interop.Kernel32.GetProcessId(processHandle);
public static ProcessInfo[] GetProcessInfos(string machineName, bool isRemoteMachine)
{
- PerformanceCounterLib? library = null;
try
{
- library = PerformanceCounterLib.GetPerformanceCounterLib(machineName, new CultureInfo("en"));
+ // We don't want to call library.Close() here because that would cause us to unload all of the perflibs.
+ // On the next call to GetProcessInfos, we'd have to load them all up again, which is SLOW!
+ PerformanceCounterLib library = PerformanceCounterLib.GetPerformanceCounterLib(machineName, new CultureInfo("en"));
return GetProcessInfos(library);
}
catch (Exception e)
throw;
}
}
- // We don't want to call library.Close() here because that would cause us to unload all of the perflibs.
- // On the next call to GetProcessInfos, we'd have to load them all up again, which is SLOW!
}
private static ProcessInfo[] GetProcessInfos(PerformanceCounterLib library)
}
else if (type.ObjectNameTitleIndex == processIndex)
{
- ProcessInfo processInfo = GetProcessInfo(in type, data.Slice(instancePos + instance.ByteLength), counters);
+ ProcessInfo processInfo = GetProcessInfo(data.Slice(instancePos + instance.ByteLength), counters);
if (processInfo.ProcessId == 0 && !instanceName.Equals("Idle", StringComparison.OrdinalIgnoreCase))
{
// Sometimes we'll get a process structure that is not completely filled in.
// at the end. If instanceName ends in ".", ".e", or ".ex" we remove it.
if (instanceName.Length == 15)
{
- if (instanceName.EndsWith(".", StringComparison.Ordinal)) instanceName = instanceName.Slice(0, 14);
- else if (instanceName.EndsWith(".e", StringComparison.Ordinal)) instanceName = instanceName.Slice(0, 13);
- else if (instanceName.EndsWith(".ex", StringComparison.Ordinal)) instanceName = instanceName.Slice(0, 12);
+ if (instanceName.EndsWith(".", StringComparison.Ordinal))
+ {
+ instanceName = instanceName.Slice(0, 14);
+ }
+ else if (instanceName.EndsWith(".e", StringComparison.Ordinal))
+ {
+ instanceName = instanceName.Slice(0, 13);
+ }
+ else if (instanceName.EndsWith(".ex", StringComparison.Ordinal))
+ {
+ instanceName = instanceName.Slice(0, 12);
+ }
}
processInfo.ProcessName = instanceName.ToString();
processInfos.Add(processInfo.ProcessId, processInfo);
}
else if (type.ObjectNameTitleIndex == threadIndex)
{
- ThreadInfo threadInfo = GetThreadInfo(in type, data.Slice(instancePos + instance.ByteLength), counters);
- if (threadInfo._threadId != 0) threadInfos.Add(threadInfo);
+ ThreadInfo threadInfo = GetThreadInfo(data.Slice(instancePos + instance.ByteLength), counters);
+ if (threadInfo._threadId != 0)
+ {
+ threadInfos.Add(threadInfo);
+ }
}
instancePos += instance.ByteLength;
return temp;
}
- private static ThreadInfo GetThreadInfo(in PERF_OBJECT_TYPE type, ReadOnlySpan<byte> instanceData, PERF_COUNTER_DEFINITION[] counters)
+ private static ThreadInfo GetThreadInfo(ReadOnlySpan<byte> instanceData, PERF_COUNTER_DEFINITION[] counters)
{
ThreadInfo threadInfo = new ThreadInfo();
for (int i = 0; i < counters.Length; i++)
case 12: return ThreadWaitReason.Suspended;
case 6:
case 13: return ThreadWaitReason.UserRequest;
- case 14: return ThreadWaitReason.EventPairHigh; ;
+ case 14: return ThreadWaitReason.EventPairHigh;
case 15: return ThreadWaitReason.EventPairLow;
case 16: return ThreadWaitReason.LpcReceive;
case 17: return ThreadWaitReason.LpcReply;
}
}
- private static ProcessInfo GetProcessInfo(in PERF_OBJECT_TYPE type, ReadOnlySpan<byte> instanceData, PERF_COUNTER_DEFINITION[] counters)
+ private static ProcessInfo GetProcessInfo(ReadOnlySpan<byte> instanceData, PERF_COUNTER_DEFINITION[] counters)
{
ProcessInfo processInfo = new ProcessInfo();
for (int i = 0; i < counters.Length; i++)
if ((counterType & PerfCounterOptions.NtPerfCounterSizeLarge) != 0)
return MemoryMarshal.Read<long>(data);
else
- return (long)MemoryMarshal.Read<int>(data);
+ return MemoryMarshal.Read<int>(data);
}
private enum ValueId