• 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 <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