diff options
Diffstat (limited to 'chromium/base/debug')
-rw-r--r-- | chromium/base/debug/gdi_debug_util_win.cc | 327 | ||||
-rw-r--r-- | chromium/base/debug/gdi_debug_util_win.h | 16 | ||||
-rw-r--r-- | chromium/base/debug/gdi_debug_util_win_unittest.cc | 28 |
3 files changed, 369 insertions, 2 deletions
diff --git a/chromium/base/debug/gdi_debug_util_win.cc b/chromium/base/debug/gdi_debug_util_win.cc index a2bb410c9d4..5f6294f87c8 100644 --- a/chromium/base/debug/gdi_debug_util_win.cc +++ b/chromium/base/debug/gdi_debug_util_win.cc @@ -6,17 +6,309 @@ #include <algorithm> #include <cmath> +#include <TlHelp32.h> #include <psapi.h> #include <stddef.h> -#include <TlHelp32.h> +#include <windows.h> +#include <winternl.h> #include "base/debug/alias.h" #include "base/logging.h" +#include "base/optional.h" +#include "base/process/process.h" #include "base/win/scoped_handle.h" #include "base/win/win_util.h" +#include "base/win/windows_version.h" namespace { +// A partial PEB up until GdiSharedHandleTable. +// Derived from the ntdll symbols (ntdll!_PEB). +template <typename PointerType> +struct PartialWinPeb { + unsigned char InheritedAddressSpace; + unsigned char ReadImageFileExecOptions; + unsigned char BeingDebugged; + unsigned char ImageUsesLargePages : 1; + unsigned char IsProtectedProcess : 1; + unsigned char IsLegacyProcess : 1; + unsigned char IsImageDynamicallyRelocated : 1; + unsigned char SkipPatchingUser32Forwarders : 1; + unsigned char IsAppContainer : 1; + unsigned char IsProtectedProcessLight : 1; + unsigned char IsLongPathAwareProcess : 1; + PointerType Mutant; + PointerType ImageBaseAddress; + PointerType Ldr; + PointerType ProcessParamters; + PointerType SubSystemData; + PointerType ProcessHeap; + PointerType FastPebLock; + PointerType AtlThunkSListPtr; + PointerType IFEOKey; + uint32_t ProcessInJob : 1; + uint32_t ProcessInitializing : 1; + uint32_t ProcessUsingVEH : 1; + uint32_t ProcessUsingVCH : 1; + uint32_t ProcessUsingFTH : 1; + uint32_t ProcessPreviouslyThrottled : 1; + uint32_t ProcessCurrentlyThrottled : 1; + uint32_t ProcessImagesHotPatched : 1; + PointerType KernelCallbackTable; + uint32_t SystemReserved; + uint32_t AtlThunkSListPtr32; + PointerType ApiSetMap; + uint32_t TlsExpansionCounter; + PointerType TlsBitmap; + uint32_t TlsBitmapBits[2]; + PointerType ReadOnlySharedMemoryBase; + PointerType HotpatchInformation; + PointerType ReadOnlyStaticServerData; + PointerType AnsiCodePageData; + PointerType OemCodePageData; + PointerType UnicodeCaseTableData; + uint32_t NumberOfProcessors; + uint32_t NtGlobalFlag; + uint64_t CriticalSectionTimeout; + PointerType HeapSegmentReserve; + PointerType HeapSegmentCommit; + PointerType HeapDeCommitTotalFreeThreshold; + PointerType HeapDeCommitFreeBlockThreshold; + uint32_t NumberOfHeaps; + uint32_t MaximumNumberOfHeaps; + PointerType ProcessHeaps; + PointerType GdiSharedHandleTable; +}; + +// Found from +// https://stackoverflow.com/questions/13905661/how-to-get-list-of-gdi-handles. +enum GdiHandleType : USHORT { + kDC = 1, + kRegion = 4, + kBitmap = 5, + kPalette = 8, + kFont = 10, + kBrush = 16, + kPen = 48, +}; + +// Adapted from GDICELL. +template <typename PointerType> +struct GdiTableEntry { + PointerType pKernelAddress; + USHORT wProcessId; + USHORT wCount; + USHORT wUpper; + GdiHandleType wType; + PointerType pUserAddress; +}; + +// Types and names used for regular processes. +struct RegularProcessTypes { + using QueryInformationProcessFunc = decltype(NtQueryInformationProcess); + static const char* query_information_process_name; + // PROCESS_BASIC_INFORMATION + struct ProcessBasicInformation { + PVOID Reserved1; + PVOID PebBaseAddress; + PVOID Reserved2[2]; + ULONG_PTR UniqueProcessId; + PVOID Reserved3; + }; + + using ReadVirtualMemoryFunc = NTSTATUS NTAPI(IN HANDLE ProcessHandle, + IN PVOID BaseAddress, + OUT PVOID Buffer, + IN SIZE_T Size, + OUT PSIZE_T NumberOfBytesRead); + static const char* read_virtual_memory_func_name; + using NativePointerType = PVOID; +}; + +// static +const char* RegularProcessTypes::query_information_process_name = + "NtQueryInformationProcess"; + +// static +const char* RegularProcessTypes::read_virtual_memory_func_name = + "NtReadVirtualMemory"; + +// Types and names used for WOW based processes. +struct WowProcessTypes { + // http://crbug.com/972185: Clang doesn't handle PVOID64 correctly, so we use + // uint64_t as a substitute. + + // NtWow64QueryInformationProcess64 and NtQueryInformationProcess share the + // same signature. + using QueryInformationProcessFunc = decltype(NtQueryInformationProcess); + static const char* query_information_process_name; + // PROCESS_BASIC_INFORMATION_WOW64 + struct ProcessBasicInformation { + PVOID Reserved1[2]; + uint64_t PebBaseAddress; + PVOID Reserved2[4]; + ULONG_PTR UniqueProcessId[2]; + PVOID Reserved3[2]; + }; + + using ReadVirtualMemoryFunc = NTSTATUS NTAPI(IN HANDLE ProcessHandle, + IN uint64_t BaseAddress, + OUT PVOID Buffer, + IN ULONG64 Size, + OUT PULONG64 NumberOfBytesRead); + static const char* read_virtual_memory_func_name; + using NativePointerType = uint64_t; +}; + +// static +const char* WowProcessTypes::query_information_process_name = + "NtWow64QueryInformationProcess64"; + +// static +const char* WowProcessTypes::read_virtual_memory_func_name = + "NtWow64ReadVirtualMemory64"; + +// To prevent from having to write a regular and WOW codepaths that do the same +// thing with different structures and functions, GetGdiTableEntries is +// templated to expect either RegularProcessTypes or WowProcessTypes. +template <typename ProcessType> +std::vector<GdiTableEntry<typename ProcessType::NativePointerType>> +GetGdiTableEntries(const base::Process& process) { + using GdiTableEntryVector = + std::vector<GdiTableEntry<typename ProcessType::NativePointerType>>; + HMODULE ntdll = GetModuleHandle(L"ntdll.dll"); + if (!ntdll) + return GdiTableEntryVector(); + + static auto query_information_process_func = + reinterpret_cast<typename ProcessType::QueryInformationProcessFunc*>( + GetProcAddress(ntdll, ProcessType::query_information_process_name)); + if (!query_information_process_func) { + LOG(ERROR) << ProcessType::query_information_process_name << " Missing"; + return GdiTableEntryVector(); + } + + typename ProcessType::ProcessBasicInformation basic_info; + NTSTATUS result = + query_information_process_func(process.Handle(), ProcessBasicInformation, + &basic_info, sizeof(basic_info), nullptr); + if (result != 0) { + LOG(ERROR) << ProcessType::query_information_process_name << " Failed " + << std::hex << result; + return GdiTableEntryVector(); + } + + static auto read_virtual_mem_func = + reinterpret_cast<typename ProcessType::ReadVirtualMemoryFunc*>( + GetProcAddress(ntdll, ProcessType::read_virtual_memory_func_name)); + if (!read_virtual_mem_func) { + LOG(ERROR) << ProcessType::read_virtual_memory_func_name << " Missing"; + return GdiTableEntryVector(); + } + + PartialWinPeb<typename ProcessType::NativePointerType> peb; + result = read_virtual_mem_func(process.Handle(), basic_info.PebBaseAddress, + &peb, sizeof(peb), nullptr); + if (result != 0) { + LOG(ERROR) << ProcessType::read_virtual_memory_func_name << " PEB Failed " + << std::hex << result; + return GdiTableEntryVector(); + } + + // Estimated size derived from address space allocation of the table: + // Windows 10 + // 32-bit Size: 1052672 bytes + // 64-bit Size: 1576960 bytes + // sizeof(GdiTableEntry) + // 32-bit: 16 bytes + // 64-bit: 24 bytes + // Entry Count + // 32-bit: 65792 + // 64-bit: 65706ish + // So we'll take a look at 65536 entries since that's the maximum handle count. + constexpr int kGdiTableEntryCount = 65536; + GdiTableEntryVector entries; + entries.resize(kGdiTableEntryCount); + result = read_virtual_mem_func( + process.Handle(), peb.GdiSharedHandleTable, &entries[0], + sizeof(typename GdiTableEntryVector::value_type) * entries.size(), + nullptr); + if (result != 0) { + LOG(ERROR) << ProcessType::read_virtual_memory_func_name + << " GDI Handle Table Failed " << std::hex << result; + return GdiTableEntryVector(); + } + + return entries; +} + +// Iterates through |gdi_table| and finds handles that belong to |pid|, +// incrementing the appropriate fields in |base::debug::GdiHandleCounts|. +template <typename PointerType> +base::debug::GdiHandleCounts CountHandleTypesFromTable( + DWORD pid, + const std::vector<GdiTableEntry<PointerType>>& gdi_table) { + base::debug::GdiHandleCounts counts{}; + for (const auto& entry : gdi_table) { + if (entry.wProcessId != pid) + continue; + + switch (entry.wType & 0x7F) { + case GdiHandleType::kDC: + ++counts.dcs; + break; + case GdiHandleType::kRegion: + ++counts.regions; + break; + case GdiHandleType::kBitmap: + ++counts.bitmaps; + break; + case GdiHandleType::kPalette: + ++counts.palettes; + break; + case GdiHandleType::kFont: + ++counts.fonts; + break; + case GdiHandleType::kBrush: + ++counts.brushes; + break; + case GdiHandleType::kPen: + ++counts.pens; + break; + default: + ++counts.unknown; + break; + } + } + counts.total_tracked = counts.dcs + counts.regions + counts.bitmaps + + counts.palettes + counts.fonts + counts.brushes + + counts.pens + counts.unknown; + return counts; +} + +template <typename ProcessType> +base::Optional<base::debug::GdiHandleCounts> CollectGdiHandleCountsImpl( + DWORD pid) { + base::Process process = base::Process::OpenWithExtraPrivileges(pid); + if (!process.IsValid()) + return base::nullopt; + + std::vector<GdiTableEntry<typename ProcessType::NativePointerType>> + gdi_entries = GetGdiTableEntries<ProcessType>(process); + return CountHandleTypesFromTable(pid, gdi_entries); +} + +// Returns the GDI Handle counts from the GDI Shared handle table. Empty on +// failure. +base::Optional<base::debug::GdiHandleCounts> CollectGdiHandleCounts(DWORD pid) { + if (base::win::OSInfo::GetInstance()->wow64_status() == + base::win::OSInfo::WOW64_ENABLED) { + return CollectGdiHandleCountsImpl<WowProcessTypes>(pid); + } + + return CollectGdiHandleCountsImpl<RegularProcessTypes>(pid); +} + constexpr size_t kLotsOfMemory = 1500 * 1024 * 1024; // 1.5GB HANDLE NOINLINE GetToolhelpSnapshot() { @@ -163,6 +455,32 @@ void CollectGDIUsageAndDie(BITMAPINFOHEADER* header, HANDLE shared_section) { base::debug::Alias(&num_gdi_handles); base::debug::Alias(&num_user_handles); + base::Optional<GdiHandleCounts> optional_handle_counts = + CollectGdiHandleCounts(GetCurrentProcessId()); + bool handle_counts_set = optional_handle_counts.has_value(); + GdiHandleCounts handle_counts = + optional_handle_counts.value_or(GdiHandleCounts()); + int tracked_dcs = handle_counts.dcs; + int tracked_regions = handle_counts.regions; + int tracked_bitmaps = handle_counts.bitmaps; + int tracked_palettes = handle_counts.palettes; + int tracked_fonts = handle_counts.fonts; + int tracked_brushes = handle_counts.brushes; + int tracked_pens = handle_counts.pens; + int tracked_unknown_handles = handle_counts.unknown; + int tracked_total = handle_counts.total_tracked; + + base::debug::Alias(&handle_counts_set); + base::debug::Alias(&tracked_dcs); + base::debug::Alias(&tracked_regions); + base::debug::Alias(&tracked_bitmaps); + base::debug::Alias(&tracked_palettes); + base::debug::Alias(&tracked_fonts); + base::debug::Alias(&tracked_brushes); + base::debug::Alias(&tracked_pens); + base::debug::Alias(&tracked_unknown_handles); + base::debug::Alias(&tracked_total); + CrashIfExcessiveHandles(num_gdi_handles); PROCESS_MEMORY_COUNTERS_EX pmc; @@ -180,5 +498,12 @@ void CollectGDIUsageAndDie(BITMAPINFOHEADER* header, HANDLE shared_section) { CollectChildGDIUsageAndDie(GetCurrentProcessId()); } +GdiHandleCounts GetGDIHandleCountsInCurrentProcessForTesting() { + base::Optional<GdiHandleCounts> handle_counts = + CollectGdiHandleCounts(GetCurrentProcessId()); + DCHECK(handle_counts.has_value()); + return handle_counts.value_or(GdiHandleCounts()); +} + } // namespace debug } // namespace base diff --git a/chromium/base/debug/gdi_debug_util_win.h b/chromium/base/debug/gdi_debug_util_win.h index 3383a4d522d..a72569570aa 100644 --- a/chromium/base/debug/gdi_debug_util_win.h +++ b/chromium/base/debug/gdi_debug_util_win.h @@ -12,13 +12,27 @@ namespace base { namespace debug { +struct BASE_EXPORT GdiHandleCounts { + int dcs = 0; + int regions = 0; + int bitmaps = 0; + int palettes = 0; + int fonts = 0; + int brushes = 0; + int pens = 0; + int unknown = 0; + int total_tracked = 0; +}; + // Crashes the process, using base::debug::Alias to leave valuable debugging // information in the crash dump. Pass values for |header| and |shared_section| // in the event of a bitmap allocation failure, to gather information about // those as well. -void BASE_EXPORT CollectGDIUsageAndDie(BITMAPINFOHEADER* header = nullptr, +BASE_EXPORT void CollectGDIUsageAndDie(BITMAPINFOHEADER* header = nullptr, HANDLE shared_section = nullptr); +BASE_EXPORT GdiHandleCounts GetGDIHandleCountsInCurrentProcessForTesting(); + } // namespace debug } // namespace base diff --git a/chromium/base/debug/gdi_debug_util_win_unittest.cc b/chromium/base/debug/gdi_debug_util_win_unittest.cc new file mode 100644 index 00000000000..c060768731d --- /dev/null +++ b/chromium/base/debug/gdi_debug_util_win_unittest.cc @@ -0,0 +1,28 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> + +#include "base/debug/gdi_debug_util_win.h" +#include "base/win/scoped_hdc.h" +#include "testing/gtest/include/gtest/gtest.h" + +// GDI handles can occasionally come out of nowhere on the shared table, so +// when writing the tests below, make sure you do differential snapshots to +// count handles. + +TEST(GdiDebugUtilWin, GdiHandleCountsCreateDC) { + base::debug::GdiHandleCounts handle_counts_start = + base::debug::GetGDIHandleCountsInCurrentProcessForTesting(); + base::win::ScopedGetDC dc(nullptr); + ASSERT_TRUE(static_cast<HDC>(dc)); + base::debug::GdiHandleCounts handle_counts_now = + base::debug::GetGDIHandleCountsInCurrentProcessForTesting(); + EXPECT_EQ(1, handle_counts_now.dcs - handle_counts_start.dcs); + EXPECT_EQ( + 1, handle_counts_now.total_tracked - handle_counts_start.total_tracked); +} + +// TODO(robliao): Create tests for other types once we figure out how often GDI +// updates the handle table. |