xref: /aosp_15_r20/external/cronet/base/debug/gdi_debug_util_win.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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