Improve ProcessManager.Win (#32010)
authorNext Turn <45985406+NextTurn@users.noreply.github.com>
Wed, 11 Mar 2020 15:48:50 +0000 (23:48 +0800)
committerGitHub <noreply@github.com>
Wed, 11 Mar 2020 15:48:50 +0000 (11:48 -0400)
* ---- Style changes start here ----

* Remove unnecessary using directives

* Remove unnecessary assignments

* Use IntPtr.Zero

* Replace magic numbers

* Remove unused parameters

* Replace constructor with initializer

* Simplify delegate interop

* Inline temporary variables

* Remove unnecessary checked keyword

* Remove unnecessary cast

* Introduce using statement

* ---- Style changes end here ----

* Remove magic sleep

* Prealloc list

* Reduce copying

* Use ArrayPool

* Support long path

* Improve P/Invoke signatures

* Optimize modules enumeration

* Rename and move HandleLastWin32Error

* while (true)

* Move comments near codes

* Revert "Support long path"

This reverts commit 1f06435144925668e7273ec458e78b869aad6055.

* Revert "Introduce using statement"

This reverts commit e3d383a229c13b096dbdef9aa9c857ee6cf986e1.

* Revert "Remove magic sleep"

This reverts commit d8bc0017131a9d9d6c1f66569a87699f9f7faea6.

* Add braces

* Simplify initialization

* Fix last Win32 error

* Avoid reallocation if modules count decreased

* Nits

* Simplify codes

src/libraries/Common/src/Interop/Windows/Kernel32/Interop.EnumProcessModules.cs
src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IsWow64Process_SafeProcessHandle.cs
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessInfo.cs
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Win32.cs
src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Windows.cs

index 15850d2..581188f 100644 (file)
@@ -11,6 +11,6 @@ internal partial class Interop
     internal partial class Kernel32
     {
         [DllImport(Libraries.Kernel32, CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "K32EnumProcessModules")]
-        internal static extern bool EnumProcessModules(SafeProcessHandle handle, IntPtr modules, int size, ref int needed);
+        internal static extern bool EnumProcessModules(SafeProcessHandle handle, IntPtr[]? modules, int size, out int needed);
     }
 }
index e5a32fa..dc8626a 100644 (file)
@@ -10,6 +10,6 @@ internal partial class Interop
     internal partial class Kernel32
     {
         [DllImport(Libraries.Kernel32, SetLastError = true)]
-        internal static extern bool IsWow64Process(SafeProcessHandle hProcess, ref bool Wow64Process);
+        internal static extern bool IsWow64Process(SafeProcessHandle hProcess, out bool Wow64Process);
     }
 }
index 3a01a36..2524eb4 100644 (file)
@@ -3,7 +3,6 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
-using System.ComponentModel;
 
 namespace System.Diagnostics
 {
@@ -15,9 +14,9 @@ namespace System.Diagnostics
     /// </summary>
     internal sealed class ProcessInfo
     {
-        internal readonly List<ThreadInfo> _threadInfoList = new List<ThreadInfo>();
+        internal readonly List<ThreadInfo> _threadInfoList;
         internal int BasePriority { get; set; }
-        internal string ProcessName { get; set; }
+        internal string ProcessName { get; set; } = string.Empty;
         internal int ProcessId { get; set; }
         internal long PoolPagedBytes { get; set; }
         internal long PoolNonPagedBytes { get; set; }
@@ -33,20 +32,12 @@ namespace System.Diagnostics
 
         internal ProcessInfo()
         {
-            BasePriority = 0;
-            ProcessName = "";
-            ProcessId = 0;
-            PoolPagedBytes = 0;
-            PoolNonPagedBytes = 0;
-            VirtualBytes = 0;
-            VirtualBytesPeak = 0;
-            WorkingSet = 0;
-            WorkingSetPeak = 0;
-            PageFileBytes = 0;
-            PageFileBytesPeak = 0;
-            PrivateBytes = 0;
-            SessionId = 0;
-            HandleCount = 0;
+            _threadInfoList = new List<ThreadInfo>();
+        }
+
+        internal ProcessInfo(int threadsNumber)
+        {
+            _threadInfoList = new List<ThreadInfo>(threadsNumber);
         }
     }
 }
index 665ecba..762cfbc 100644 (file)
@@ -2,6 +2,7 @@
 // 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;
@@ -18,8 +19,7 @@ namespace System.Diagnostics
     {
         public static IntPtr GetMainWindowHandle(int processId)
         {
-            MainWindowFinder finder = new MainWindowFinder();
-            return finder.FindMainWindow(processId);
+            return new MainWindowFinder().FindMainWindow(processId);
         }
     }
 
@@ -31,19 +31,17 @@ namespace System.Diagnostics
 
         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;
@@ -82,151 +80,129 @@ namespace System.Diagnostics
             {
                 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;
@@ -239,6 +215,44 @@ namespace System.Diagnostics
                 }
             }
         }
+
+        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
@@ -360,20 +374,22 @@ namespace System.Diagnostics
                 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)
                     {
@@ -393,7 +409,7 @@ namespace System.Diagnostics
                     }
                     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;
                     }
 
@@ -405,15 +421,16 @@ namespace System.Diagnostics
                     {
                         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);
 
@@ -437,9 +454,9 @@ namespace System.Diagnostics
         //
         // 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;
             }
@@ -462,7 +479,7 @@ namespace System.Diagnostics
                 // 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
@@ -477,7 +494,7 @@ namespace System.Diagnostics
 
             // 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();
         }
     }
 }
index e2c166a..1ca0bc4 100644 (file)
@@ -6,7 +6,6 @@ using System.Collections.Generic;
 using System.ComponentModel;
 using System.Globalization;
 using System.Runtime.InteropServices;
-using System.Threading;
 using Microsoft.Win32.SafeHandles;
 
 using static Interop.Advapi32;
@@ -144,7 +143,7 @@ namespace System.Diagnostics
             // 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;
@@ -189,7 +188,7 @@ namespace System.Diagnostics
 
             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.
@@ -279,19 +278,23 @@ namespace System.Diagnostics
         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;
         }
@@ -307,22 +310,6 @@ namespace System.Diagnostics
             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);
@@ -330,10 +317,11 @@ namespace System.Diagnostics
 
         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)
@@ -347,8 +335,6 @@ namespace System.Diagnostics
                     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)
@@ -421,7 +407,7 @@ namespace System.Diagnostics
                     }
                     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.
@@ -443,9 +429,18 @@ namespace System.Diagnostics
                                 // 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);
@@ -454,8 +449,11 @@ namespace System.Diagnostics
                     }
                     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;
@@ -480,7 +478,7 @@ namespace System.Diagnostics
             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++)
@@ -534,7 +532,7 @@ namespace System.Diagnostics
                 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;
@@ -544,7 +542,7 @@ namespace System.Diagnostics
             }
         }
 
-        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++)
@@ -611,7 +609,7 @@ namespace System.Diagnostics
             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