summaryrefslogtreecommitdiff
path: root/chromium/base/debug
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/base/debug')
-rw-r--r--chromium/base/debug/gdi_debug_util_win.cc327
-rw-r--r--chromium/base/debug/gdi_debug_util_win.h16
-rw-r--r--chromium/base/debug/gdi_debug_util_win_unittest.cc28
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.