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