• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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