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