Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | #include "base/debug/gdi_debug_util_win.h" |
| 5 | |
Takuto Ikuta | c8d6b16f | 2024-04-15 16:59:19 | [diff] [blame] | 6 | #include <windows.h> |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 7 | #include <winternl.h> |
Takuto Ikuta | c8d6b16f | 2024-04-15 16:59:19 | [diff] [blame] | 8 | |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 9 | #include <TlHelp32.h> |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 10 | #include <psapi.h> |
avi | ebe805c | 2015-12-24 08:20:28 | [diff] [blame] | 11 | #include <stddef.h> |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 12 | |
Arthur Sonzogni | e5fff99c | 2024-02-21 15:58:24 | [diff] [blame] | 13 | #include <algorithm> |
| 14 | #include <cmath> |
| 15 | #include <optional> |
| 16 | |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 17 | #include "base/debug/alias.h" |
| 18 | #include "base/logging.h" |
Peter Boström | 5411965 | 2024-11-14 00:16:38 | [diff] [blame] | 19 | #include "base/notreached.h" |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 20 | #include "base/process/process.h" |
Peter Kasting | 9d13b970 | 2025-01-31 15:15:51 | [diff] [blame] | 21 | #include "base/win/scoped_gdi_object.h" |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 22 | #include "base/win/scoped_handle.h" |
anpol | 80e1d0d | 2016-11-02 11:07:03 | [diff] [blame] | 23 | #include "base/win/win_util.h" |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 24 | #include "base/win/windows_version.h" |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 25 | |
| 26 | namespace { |
| 27 | |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 28 | // A partial PEB up until GdiSharedHandleTable. |
| 29 | // Derived from the ntdll symbols (ntdll!_PEB). |
| 30 | template <typename PointerType> |
| 31 | struct PartialWinPeb { |
| 32 | unsigned char InheritedAddressSpace; |
| 33 | unsigned char ReadImageFileExecOptions; |
| 34 | unsigned char BeingDebugged; |
| 35 | unsigned char ImageUsesLargePages : 1; |
| 36 | unsigned char IsProtectedProcess : 1; |
| 37 | unsigned char IsLegacyProcess : 1; |
| 38 | unsigned char IsImageDynamicallyRelocated : 1; |
| 39 | unsigned char SkipPatchingUser32Forwarders : 1; |
| 40 | unsigned char IsAppContainer : 1; |
| 41 | unsigned char IsProtectedProcessLight : 1; |
| 42 | unsigned char IsLongPathAwareProcess : 1; |
| 43 | PointerType Mutant; |
| 44 | PointerType ImageBaseAddress; |
| 45 | PointerType Ldr; |
| 46 | PointerType ProcessParamters; |
| 47 | PointerType SubSystemData; |
| 48 | PointerType ProcessHeap; |
| 49 | PointerType FastPebLock; |
| 50 | PointerType AtlThunkSListPtr; |
| 51 | PointerType IFEOKey; |
| 52 | uint32_t ProcessInJob : 1; |
| 53 | uint32_t ProcessInitializing : 1; |
| 54 | uint32_t ProcessUsingVEH : 1; |
| 55 | uint32_t ProcessUsingVCH : 1; |
| 56 | uint32_t ProcessUsingFTH : 1; |
| 57 | uint32_t ProcessPreviouslyThrottled : 1; |
| 58 | uint32_t ProcessCurrentlyThrottled : 1; |
| 59 | uint32_t ProcessImagesHotPatched : 1; |
| 60 | PointerType KernelCallbackTable; |
| 61 | uint32_t SystemReserved; |
| 62 | uint32_t AtlThunkSListPtr32; |
| 63 | PointerType ApiSetMap; |
| 64 | uint32_t TlsExpansionCounter; |
| 65 | PointerType TlsBitmap; |
| 66 | uint32_t TlsBitmapBits[2]; |
| 67 | PointerType ReadOnlySharedMemoryBase; |
| 68 | PointerType HotpatchInformation; |
| 69 | PointerType ReadOnlyStaticServerData; |
| 70 | PointerType AnsiCodePageData; |
| 71 | PointerType OemCodePageData; |
| 72 | PointerType UnicodeCaseTableData; |
| 73 | uint32_t NumberOfProcessors; |
| 74 | uint32_t NtGlobalFlag; |
| 75 | uint64_t CriticalSectionTimeout; |
| 76 | PointerType HeapSegmentReserve; |
| 77 | PointerType HeapSegmentCommit; |
| 78 | PointerType HeapDeCommitTotalFreeThreshold; |
| 79 | PointerType HeapDeCommitFreeBlockThreshold; |
| 80 | uint32_t NumberOfHeaps; |
| 81 | uint32_t MaximumNumberOfHeaps; |
| 82 | PointerType ProcessHeaps; |
| 83 | PointerType GdiSharedHandleTable; |
| 84 | }; |
| 85 | |
| 86 | // Found from |
| 87 | // https://stackoverflow.com/questions/13905661/how-to-get-list-of-gdi-handles. |
| 88 | enum GdiHandleType : USHORT { |
| 89 | kDC = 1, |
| 90 | kRegion = 4, |
| 91 | kBitmap = 5, |
| 92 | kPalette = 8, |
| 93 | kFont = 10, |
| 94 | kBrush = 16, |
| 95 | kPen = 48, |
| 96 | }; |
| 97 | |
| 98 | // Adapted from GDICELL. |
| 99 | template <typename PointerType> |
| 100 | struct GdiTableEntry { |
| 101 | PointerType pKernelAddress; |
| 102 | USHORT wProcessId; |
| 103 | USHORT wCount; |
| 104 | USHORT wUpper; |
| 105 | GdiHandleType wType; |
| 106 | PointerType pUserAddress; |
| 107 | }; |
| 108 | |
| 109 | // Types and names used for regular processes. |
| 110 | struct RegularProcessTypes { |
| 111 | using QueryInformationProcessFunc = decltype(NtQueryInformationProcess); |
| 112 | static const char* query_information_process_name; |
| 113 | // PROCESS_BASIC_INFORMATION |
| 114 | struct ProcessBasicInformation { |
| 115 | PVOID Reserved1; |
| 116 | PVOID PebBaseAddress; |
| 117 | PVOID Reserved2[2]; |
| 118 | ULONG_PTR UniqueProcessId; |
| 119 | PVOID Reserved3; |
| 120 | }; |
| 121 | |
| 122 | using ReadVirtualMemoryFunc = NTSTATUS NTAPI(IN HANDLE ProcessHandle, |
| 123 | IN PVOID BaseAddress, |
| 124 | OUT PVOID Buffer, |
| 125 | IN SIZE_T Size, |
| 126 | OUT PSIZE_T NumberOfBytesRead); |
| 127 | static const char* read_virtual_memory_func_name; |
| 128 | using NativePointerType = PVOID; |
| 129 | }; |
| 130 | |
| 131 | // static |
| 132 | const char* RegularProcessTypes::query_information_process_name = |
| 133 | "NtQueryInformationProcess"; |
| 134 | |
| 135 | // static |
| 136 | const char* RegularProcessTypes::read_virtual_memory_func_name = |
| 137 | "NtReadVirtualMemory"; |
| 138 | |
| 139 | // Types and names used for WOW based processes. |
| 140 | struct WowProcessTypes { |
| 141 | // http://crbug.com/972185: Clang doesn't handle PVOID64 correctly, so we use |
| 142 | // uint64_t as a substitute. |
| 143 | |
| 144 | // NtWow64QueryInformationProcess64 and NtQueryInformationProcess share the |
| 145 | // same signature. |
| 146 | using QueryInformationProcessFunc = decltype(NtQueryInformationProcess); |
| 147 | static const char* query_information_process_name; |
| 148 | // PROCESS_BASIC_INFORMATION_WOW64 |
| 149 | struct ProcessBasicInformation { |
| 150 | PVOID Reserved1[2]; |
| 151 | uint64_t PebBaseAddress; |
| 152 | PVOID Reserved2[4]; |
| 153 | ULONG_PTR UniqueProcessId[2]; |
| 154 | PVOID Reserved3[2]; |
| 155 | }; |
| 156 | |
| 157 | using ReadVirtualMemoryFunc = NTSTATUS NTAPI(IN HANDLE ProcessHandle, |
| 158 | IN uint64_t BaseAddress, |
| 159 | OUT PVOID Buffer, |
| 160 | IN ULONG64 Size, |
| 161 | OUT PULONG64 NumberOfBytesRead); |
| 162 | static const char* read_virtual_memory_func_name; |
| 163 | using NativePointerType = uint64_t; |
| 164 | }; |
| 165 | |
| 166 | // static |
| 167 | const char* WowProcessTypes::query_information_process_name = |
| 168 | "NtWow64QueryInformationProcess64"; |
| 169 | |
| 170 | // static |
| 171 | const char* WowProcessTypes::read_virtual_memory_func_name = |
| 172 | "NtWow64ReadVirtualMemory64"; |
| 173 | |
| 174 | // To prevent from having to write a regular and WOW codepaths that do the same |
| 175 | // thing with different structures and functions, GetGdiTableEntries is |
| 176 | // templated to expect either RegularProcessTypes or WowProcessTypes. |
| 177 | template <typename ProcessType> |
| 178 | std::vector<GdiTableEntry<typename ProcessType::NativePointerType>> |
| 179 | GetGdiTableEntries(const base::Process& process) { |
| 180 | using GdiTableEntryVector = |
| 181 | std::vector<GdiTableEntry<typename ProcessType::NativePointerType>>; |
| 182 | HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 183 | if (!ntdll) { |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 184 | return GdiTableEntryVector(); |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 185 | } |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 186 | |
| 187 | static auto query_information_process_func = |
| 188 | reinterpret_cast<typename ProcessType::QueryInformationProcessFunc*>( |
| 189 | GetProcAddress(ntdll, ProcessType::query_information_process_name)); |
| 190 | if (!query_information_process_func) { |
| 191 | LOG(ERROR) << ProcessType::query_information_process_name << " Missing"; |
| 192 | return GdiTableEntryVector(); |
| 193 | } |
| 194 | |
| 195 | typename ProcessType::ProcessBasicInformation basic_info; |
| 196 | NTSTATUS result = |
| 197 | query_information_process_func(process.Handle(), ProcessBasicInformation, |
| 198 | &basic_info, sizeof(basic_info), nullptr); |
| 199 | if (result != 0) { |
| 200 | LOG(ERROR) << ProcessType::query_information_process_name << " Failed " |
| 201 | << std::hex << result; |
| 202 | return GdiTableEntryVector(); |
| 203 | } |
| 204 | |
| 205 | static auto read_virtual_mem_func = |
| 206 | reinterpret_cast<typename ProcessType::ReadVirtualMemoryFunc*>( |
| 207 | GetProcAddress(ntdll, ProcessType::read_virtual_memory_func_name)); |
| 208 | if (!read_virtual_mem_func) { |
| 209 | LOG(ERROR) << ProcessType::read_virtual_memory_func_name << " Missing"; |
| 210 | return GdiTableEntryVector(); |
| 211 | } |
| 212 | |
| 213 | PartialWinPeb<typename ProcessType::NativePointerType> peb; |
| 214 | result = read_virtual_mem_func(process.Handle(), basic_info.PebBaseAddress, |
| 215 | &peb, sizeof(peb), nullptr); |
| 216 | if (result != 0) { |
| 217 | LOG(ERROR) << ProcessType::read_virtual_memory_func_name << " PEB Failed " |
| 218 | << std::hex << result; |
| 219 | return GdiTableEntryVector(); |
| 220 | } |
| 221 | |
| 222 | // Estimated size derived from address space allocation of the table: |
| 223 | // Windows 10 |
| 224 | // 32-bit Size: 1052672 bytes |
| 225 | // 64-bit Size: 1576960 bytes |
| 226 | // sizeof(GdiTableEntry) |
| 227 | // 32-bit: 16 bytes |
| 228 | // 64-bit: 24 bytes |
| 229 | // Entry Count |
| 230 | // 32-bit: 65792 |
| 231 | // 64-bit: 65706ish |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 232 | // So we'll take a look at 65536 entries since that's the maximum handle |
| 233 | // count. |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 234 | constexpr int kGdiTableEntryCount = 65536; |
| 235 | GdiTableEntryVector entries; |
| 236 | entries.resize(kGdiTableEntryCount); |
| 237 | result = read_virtual_mem_func( |
| 238 | process.Handle(), peb.GdiSharedHandleTable, &entries[0], |
| 239 | sizeof(typename GdiTableEntryVector::value_type) * entries.size(), |
| 240 | nullptr); |
| 241 | if (result != 0) { |
| 242 | LOG(ERROR) << ProcessType::read_virtual_memory_func_name |
| 243 | << " GDI Handle Table Failed " << std::hex << result; |
| 244 | return GdiTableEntryVector(); |
| 245 | } |
| 246 | |
| 247 | return entries; |
| 248 | } |
| 249 | |
| 250 | // Iterates through |gdi_table| and finds handles that belong to |pid|, |
| 251 | // incrementing the appropriate fields in |base::debug::GdiHandleCounts|. |
| 252 | template <typename PointerType> |
| 253 | base::debug::GdiHandleCounts CountHandleTypesFromTable( |
| 254 | DWORD pid, |
| 255 | const std::vector<GdiTableEntry<PointerType>>& gdi_table) { |
| 256 | base::debug::GdiHandleCounts counts{}; |
| 257 | for (const auto& entry : gdi_table) { |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 258 | if (entry.wProcessId != pid) { |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 259 | continue; |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 260 | } |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 261 | |
| 262 | switch (entry.wType & 0x7F) { |
| 263 | case GdiHandleType::kDC: |
| 264 | ++counts.dcs; |
| 265 | break; |
| 266 | case GdiHandleType::kRegion: |
| 267 | ++counts.regions; |
| 268 | break; |
| 269 | case GdiHandleType::kBitmap: |
| 270 | ++counts.bitmaps; |
| 271 | break; |
| 272 | case GdiHandleType::kPalette: |
| 273 | ++counts.palettes; |
| 274 | break; |
| 275 | case GdiHandleType::kFont: |
| 276 | ++counts.fonts; |
| 277 | break; |
| 278 | case GdiHandleType::kBrush: |
| 279 | ++counts.brushes; |
| 280 | break; |
| 281 | case GdiHandleType::kPen: |
| 282 | ++counts.pens; |
| 283 | break; |
| 284 | default: |
| 285 | ++counts.unknown; |
| 286 | break; |
| 287 | } |
| 288 | } |
| 289 | counts.total_tracked = counts.dcs + counts.regions + counts.bitmaps + |
| 290 | counts.palettes + counts.fonts + counts.brushes + |
| 291 | counts.pens + counts.unknown; |
| 292 | return counts; |
| 293 | } |
| 294 | |
| 295 | template <typename ProcessType> |
Arthur Sonzogni | e5fff99c | 2024-02-21 15:58:24 | [diff] [blame] | 296 | std::optional<base::debug::GdiHandleCounts> CollectGdiHandleCountsImpl( |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 297 | DWORD pid) { |
| 298 | base::Process process = base::Process::OpenWithExtraPrivileges(pid); |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 299 | if (!process.IsValid()) { |
Arthur Sonzogni | e5fff99c | 2024-02-21 15:58:24 | [diff] [blame] | 300 | return std::nullopt; |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 301 | } |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 302 | |
| 303 | std::vector<GdiTableEntry<typename ProcessType::NativePointerType>> |
| 304 | gdi_entries = GetGdiTableEntries<ProcessType>(process); |
| 305 | return CountHandleTypesFromTable(pid, gdi_entries); |
| 306 | } |
| 307 | |
| 308 | // Returns the GDI Handle counts from the GDI Shared handle table. Empty on |
| 309 | // failure. |
Arthur Sonzogni | e5fff99c | 2024-02-21 15:58:24 | [diff] [blame] | 310 | std::optional<base::debug::GdiHandleCounts> CollectGdiHandleCounts(DWORD pid) { |
Sarah Murphy | e91a5a30 | 2021-10-19 00:23:17 | [diff] [blame] | 311 | if (base::win::OSInfo::GetInstance()->IsWowX86OnAMD64()) { |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 312 | return CollectGdiHandleCountsImpl<WowProcessTypes>(pid); |
| 313 | } |
| 314 | |
| 315 | return CollectGdiHandleCountsImpl<RegularProcessTypes>(pid); |
| 316 | } |
| 317 | |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 318 | constexpr size_t kLotsOfMemory = 1500 * 1024 * 1024; // 1.5GB |
| 319 | |
Peter Kasting | f541f778 | 2023-03-10 23:44:46 | [diff] [blame] | 320 | NOINLINE HANDLE GetToolhelpSnapshot() { |
pkasting | 767ea6b | 2014-09-29 20:41:20 | [diff] [blame] | 321 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); |
| 322 | CHECK_NE(INVALID_HANDLE_VALUE, snapshot); |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 323 | return snapshot; |
| 324 | } |
| 325 | |
Peter Kasting | f541f778 | 2023-03-10 23:44:46 | [diff] [blame] | 326 | NOINLINE void GetFirstProcess(HANDLE snapshot, PROCESSENTRY32* proc_entry) { |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 327 | proc_entry->dwSize = sizeof(PROCESSENTRY32); |
| 328 | CHECK(Process32First(snapshot, proc_entry)); |
| 329 | } |
| 330 | |
Peter Kasting | f541f778 | 2023-03-10 23:44:46 | [diff] [blame] | 331 | NOINLINE void CrashIfExcessiveHandles(DWORD num_gdi_handles) { |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 332 | // By default, Windows 10 allows a max of 10,000 GDI handles per process. |
| 333 | // Number found by inspecting |
| 334 | // |
| 335 | // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\ |
| 336 | // CurrentVersion\Windows\GDIProcessHandleQuota |
| 337 | // |
| 338 | // on a Windows 10 laptop. |
| 339 | static constexpr DWORD kLotsOfHandles = 9990; |
| 340 | CHECK_LE(num_gdi_handles, kLotsOfHandles); |
| 341 | } |
| 342 | |
Peter Kasting | f541f778 | 2023-03-10 23:44:46 | [diff] [blame] | 343 | NOINLINE void CrashIfPagefileUsageTooLarge( |
| 344 | const PROCESS_MEMORY_COUNTERS_EX& pmc) { |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 345 | CHECK_LE(pmc.PagefileUsage, kLotsOfMemory); |
| 346 | } |
| 347 | |
Peter Kasting | f541f778 | 2023-03-10 23:44:46 | [diff] [blame] | 348 | NOINLINE void CrashIfPrivateUsageTooLarge( |
| 349 | const PROCESS_MEMORY_COUNTERS_EX& pmc) { |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 350 | CHECK_LE(pmc.PrivateUsage, kLotsOfMemory); |
| 351 | } |
| 352 | |
Peter Kasting | f541f778 | 2023-03-10 23:44:46 | [diff] [blame] | 353 | NOINLINE void CrashIfCannotAllocateSmallBitmap(BITMAPINFOHEADER* header, |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 354 | HANDLE shared_section) { |
| 355 | void* small_data = nullptr; |
| 356 | base::debug::Alias(&small_data); |
| 357 | header->biWidth = 5; |
| 358 | header->biHeight = -5; |
Peter Kasting | 9d13b970 | 2025-01-31 15:15:51 | [diff] [blame] | 359 | base::win::ScopedGDIObject<HBITMAP> small_bitmap( |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 360 | CreateDIBSection(nullptr, reinterpret_cast<BITMAPINFO*>(&header), 0, |
Peter Kasting | 9d13b970 | 2025-01-31 15:15:51 | [diff] [blame] | 361 | &small_data, shared_section, 0)); |
| 362 | CHECK(small_bitmap.is_valid()); |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 363 | } |
| 364 | |
Peter Kasting | f541f778 | 2023-03-10 23:44:46 | [diff] [blame] | 365 | NOINLINE void GetProcessMemoryInfo(PROCESS_MEMORY_COUNTERS_EX* pmc) { |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 366 | pmc->cb = sizeof(*pmc); |
| 367 | CHECK(GetProcessMemoryInfo(GetCurrentProcess(), |
| 368 | reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(pmc), |
| 369 | sizeof(*pmc))); |
| 370 | } |
| 371 | |
Peter Kasting | f541f778 | 2023-03-10 23:44:46 | [diff] [blame] | 372 | NOINLINE DWORD GetNumGdiHandles() { |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 373 | DWORD num_gdi_handles = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS); |
| 374 | if (num_gdi_handles == 0) { |
| 375 | DWORD get_gui_resources_error = GetLastError(); |
| 376 | base::debug::Alias(&get_gui_resources_error); |
Peter Boström | 5411965 | 2024-11-14 00:16:38 | [diff] [blame] | 377 | NOTREACHED(); |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 378 | } |
| 379 | return num_gdi_handles; |
| 380 | } |
| 381 | |
| 382 | void CollectChildGDIUsageAndDie(DWORD parent_pid) { |
| 383 | HANDLE snapshot = GetToolhelpSnapshot(); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 384 | |
anpol | 9625fdd | 2016-11-05 09:56:20 | [diff] [blame] | 385 | int total_process_count = 0; |
| 386 | base::debug::Alias(&total_process_count); |
Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 387 | DWORD total_peak_gdi_count = 0; |
anpol | 9625fdd | 2016-11-05 09:56:20 | [diff] [blame] | 388 | base::debug::Alias(&total_peak_gdi_count); |
Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 389 | DWORD total_gdi_count = 0; |
anpol | 9625fdd | 2016-11-05 09:56:20 | [diff] [blame] | 390 | base::debug::Alias(&total_gdi_count); |
Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 391 | DWORD total_user_count = 0; |
anpol | 9625fdd | 2016-11-05 09:56:20 | [diff] [blame] | 392 | base::debug::Alias(&total_user_count); |
| 393 | |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 394 | int child_count = 0; |
| 395 | base::debug::Alias(&child_count); |
Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 396 | DWORD peak_gdi_count = 0; |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 397 | base::debug::Alias(&peak_gdi_count); |
Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 398 | DWORD sum_gdi_count = 0; |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 399 | base::debug::Alias(&sum_gdi_count); |
Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 400 | DWORD sum_user_count = 0; |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 401 | base::debug::Alias(&sum_user_count); |
| 402 | |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 403 | PROCESSENTRY32 proc_entry = {}; |
| 404 | GetFirstProcess(snapshot, &proc_entry); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 405 | |
| 406 | do { |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 407 | base::win::ScopedHandle process(OpenProcess( |
| 408 | PROCESS_QUERY_INFORMATION, FALSE, proc_entry.th32ProcessID)); |
| 409 | if (!process.is_valid()) { |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 410 | continue; |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 411 | } |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 412 | |
Peter Kasting | d892d34 | 2022-05-27 21:56:36 | [diff] [blame] | 413 | DWORD num_gdi_handles = GetGuiResources(process.get(), GR_GDIOBJECTS); |
| 414 | DWORD num_user_handles = GetGuiResources(process.get(), GR_USEROBJECTS); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 415 | |
anpol | 9625fdd | 2016-11-05 09:56:20 | [diff] [blame] | 416 | // Compute sum and peak counts for all processes. |
| 417 | ++total_process_count; |
| 418 | total_user_count += num_user_handles; |
| 419 | total_gdi_count += num_gdi_handles; |
| 420 | total_peak_gdi_count = std::max(total_peak_gdi_count, num_gdi_handles); |
| 421 | |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 422 | if (parent_pid != proc_entry.th32ParentProcessID) { |
anpol | 9625fdd | 2016-11-05 09:56:20 | [diff] [blame] | 423 | continue; |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 424 | } |
anpol | 9625fdd | 2016-11-05 09:56:20 | [diff] [blame] | 425 | |
| 426 | // Compute sum and peak counts for child processes. |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 427 | ++child_count; |
| 428 | sum_user_count += num_user_handles; |
| 429 | sum_gdi_count += num_gdi_handles; |
anpol | 9625fdd | 2016-11-05 09:56:20 | [diff] [blame] | 430 | peak_gdi_count = std::max(peak_gdi_count, num_gdi_handles); |
pkasting | 767ea6b | 2014-09-29 20:41:20 | [diff] [blame] | 431 | } while (Process32Next(snapshot, &proc_entry)); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 432 | |
pkasting | 767ea6b | 2014-09-29 20:41:20 | [diff] [blame] | 433 | CloseHandle(snapshot); |
Peter Boström | 5411965 | 2024-11-14 00:16:38 | [diff] [blame] | 434 | NOTREACHED(); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 435 | } |
| 436 | |
| 437 | } // namespace |
| 438 | |
| 439 | namespace base { |
| 440 | namespace debug { |
| 441 | |
anpol | 80e1d0d | 2016-11-02 11:07:03 | [diff] [blame] | 442 | void CollectGDIUsageAndDie(BITMAPINFOHEADER* header, HANDLE shared_section) { |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 443 | // Make sure parameters are saved in the minidump. |
pkasting | 767ea6b | 2014-09-29 20:41:20 | [diff] [blame] | 444 | DWORD last_error = GetLastError(); |
anpol | 80e1d0d | 2016-11-02 11:07:03 | [diff] [blame] | 445 | bool is_gdi_available = base::win::IsUser32AndGdi32Available(); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 446 | |
anpol | 80e1d0d | 2016-11-02 11:07:03 | [diff] [blame] | 447 | LONG width = header ? header->biWidth : 0; |
| 448 | LONG height = header ? header->biHeight : 0; |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 449 | |
| 450 | base::debug::Alias(&last_error); |
anpol | 80e1d0d | 2016-11-02 11:07:03 | [diff] [blame] | 451 | base::debug::Alias(&is_gdi_available); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 452 | base::debug::Alias(&width); |
anpol | 80e1d0d | 2016-11-02 11:07:03 | [diff] [blame] | 453 | base::debug::Alias(&height); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 454 | base::debug::Alias(&shared_section); |
| 455 | |
pkasting | 189c4d96 | 2015-07-21 01:33:54 | [diff] [blame] | 456 | DWORD num_user_handles = GetGuiResources(GetCurrentProcess(), GR_USEROBJECTS); |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 457 | DWORD num_gdi_handles = GetNumGdiHandles(); |
Robert Liao | 8812aed | 2021-04-06 16:47:14 | [diff] [blame] | 458 | DWORD peak_gdi_handles = |
| 459 | GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS_PEAK); |
Robert Liao | 9845ccd | 2021-04-06 20:20:39 | [diff] [blame] | 460 | DWORD num_global_gdi_handles = GetGuiResources(GR_GLOBAL, GR_GDIOBJECTS); |
| 461 | DWORD num_global_user_handles = GetGuiResources(GR_GLOBAL, GR_USEROBJECTS); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 462 | |
| 463 | base::debug::Alias(&num_gdi_handles); |
| 464 | base::debug::Alias(&num_user_handles); |
Robert Liao | 8812aed | 2021-04-06 16:47:14 | [diff] [blame] | 465 | base::debug::Alias(&peak_gdi_handles); |
Robert Liao | 9845ccd | 2021-04-06 20:20:39 | [diff] [blame] | 466 | base::debug::Alias(&num_global_gdi_handles); |
| 467 | base::debug::Alias(&num_global_user_handles); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 468 | |
Arthur Sonzogni | e5fff99c | 2024-02-21 15:58:24 | [diff] [blame] | 469 | std::optional<GdiHandleCounts> optional_handle_counts = |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 470 | CollectGdiHandleCounts(GetCurrentProcessId()); |
| 471 | bool handle_counts_set = optional_handle_counts.has_value(); |
| 472 | GdiHandleCounts handle_counts = |
| 473 | optional_handle_counts.value_or(GdiHandleCounts()); |
| 474 | int tracked_dcs = handle_counts.dcs; |
| 475 | int tracked_regions = handle_counts.regions; |
| 476 | int tracked_bitmaps = handle_counts.bitmaps; |
| 477 | int tracked_palettes = handle_counts.palettes; |
| 478 | int tracked_fonts = handle_counts.fonts; |
| 479 | int tracked_brushes = handle_counts.brushes; |
| 480 | int tracked_pens = handle_counts.pens; |
| 481 | int tracked_unknown_handles = handle_counts.unknown; |
| 482 | int tracked_total = handle_counts.total_tracked; |
| 483 | |
| 484 | base::debug::Alias(&handle_counts_set); |
| 485 | base::debug::Alias(&tracked_dcs); |
| 486 | base::debug::Alias(&tracked_regions); |
| 487 | base::debug::Alias(&tracked_bitmaps); |
| 488 | base::debug::Alias(&tracked_palettes); |
| 489 | base::debug::Alias(&tracked_fonts); |
| 490 | base::debug::Alias(&tracked_brushes); |
| 491 | base::debug::Alias(&tracked_pens); |
| 492 | base::debug::Alias(&tracked_unknown_handles); |
| 493 | base::debug::Alias(&tracked_total); |
| 494 | |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 495 | CrashIfExcessiveHandles(num_gdi_handles); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 496 | |
| 497 | PROCESS_MEMORY_COUNTERS_EX pmc; |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 498 | GetProcessMemoryInfo(&pmc); |
| 499 | CrashIfPagefileUsageTooLarge(pmc); |
| 500 | CrashIfPrivateUsageTooLarge(pmc); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 501 | |
anpol | 80e1d0d | 2016-11-02 11:07:03 | [diff] [blame] | 502 | if (std::abs(height) * width > 100) { |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 503 | // Huh, that's weird. We don't have crazy handle count, we don't have |
| 504 | // ridiculous memory usage. Try to allocate a small bitmap and see if that |
| 505 | // fails too. |
Albert J. Wong | f6f1e9d2 | 2019-05-09 20:58:22 | [diff] [blame] | 506 | CrashIfCannotAllocateSmallBitmap(header, shared_section); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 507 | } |
| 508 | // Maybe the child processes are the ones leaking GDI or USER resouces. |
pkasting | 767ea6b | 2014-09-29 20:41:20 | [diff] [blame] | 509 | CollectChildGDIUsageAndDie(GetCurrentProcessId()); |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 510 | } |
| 511 | |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 512 | GdiHandleCounts GetGDIHandleCountsInCurrentProcessForTesting() { |
Arthur Sonzogni | e5fff99c | 2024-02-21 15:58:24 | [diff] [blame] | 513 | std::optional<GdiHandleCounts> handle_counts = |
Robert Liao | 58bb1db | 2019-06-08 02:45:23 | [diff] [blame] | 514 | CollectGdiHandleCounts(GetCurrentProcessId()); |
| 515 | DCHECK(handle_counts.has_value()); |
| 516 | return handle_counts.value_or(GdiHandleCounts()); |
| 517 | } |
| 518 | |
[email protected] | 4ddbd142 | 2014-02-11 05:12:07 | [diff] [blame] | 519 | } // namespace debug |
| 520 | } // namespace base |