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