| Index: tools/win/ShowGlobals/ShowGlobals.cc
|
| diff --git a/tools/win/ShowGlobals/ShowGlobals.cc b/tools/win/ShowGlobals/ShowGlobals.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3dcea86c0c1d67a26a3d40b9fca2e5bfb3386de0
|
| --- /dev/null
|
| +++ b/tools/win/ShowGlobals/ShowGlobals.cc
|
| @@ -0,0 +1,247 @@
|
| +// Copyright (c) 2016 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.
|
| +
|
| +// This tool scans a PDB file and prints out information about 'interesting'
|
| +// global variables. This includes duplicates and large globals. This is often
|
| +// helpful inunderstanding code bloat or finding inefficient globals.
|
| +//
|
| +// Duplicate global variables often happen when constructs like this are placed
|
| +// in a header file:
|
| +//
|
| +// const double sqrt_two = sqrt(2.0);
|
| +//
|
| +// Many (although usually not all) of the translation units that include this
|
| +// header file will get a copy of sqrt_two, possibly including an initializer.
|
| +// Because 'const' implies 'static' there are no warnings or errors from the
|
| +// linker. This duplication can happen with float/double, structs and classes,
|
| +// and arrays - any non-integral type.
|
| +//
|
| +// Global variables are not necessarily a problem but it is useful to understand
|
| +// them, and monitoring their changes can be instructive.
|
| +
|
| +#include <atlbase.h>
|
| +#include <dia2.h>
|
| +#include <stdio.h>
|
| +
|
| +#include <algorithm>
|
| +#include <vector>
|
| +
|
| +// Helper function for comparing strings - returns a strcmp/wcscmp compatible
|
| +// value.
|
| +int StringCompare(const std::wstring& lhs, const std::wstring& rhs) {
|
| + return wcscmp(lhs.c_str(), rhs.c_str());
|
| +}
|
| +
|
| +// Use this struct to record data about symbols for sorting and analysis.
|
| +struct SymbolData {
|
| + SymbolData(ULONGLONG size, DWORD section, const wchar_t* name)
|
| + : size(size), section(section), name(name) {}
|
| +
|
| + ULONGLONG size;
|
| + DWORD section;
|
| + std::wstring name;
|
| +};
|
| +
|
| +// Comparison function for when sorting symbol data by name, in order to allow
|
| +// looking for duplicate symbols. It uses the symbol size as a tiebreaker. This
|
| +// is necessary because sometimes there are symbols with matching names but
|
| +// different sizes in which case they aren't actually duplicates. These false
|
| +// positives happen because namespaces are omitted from the symbol names that
|
| +// DIA2 returns.
|
| +bool NameCompare(const SymbolData& lhs, const SymbolData& rhs) {
|
| + int nameCompare = StringCompare(lhs.name, rhs.name);
|
| + if (nameCompare == 0)
|
| + return lhs.size < rhs.size;
|
| + return nameCompare < 0;
|
| +}
|
| +
|
| +// Comparison function for when sorting symbols by size, in order to allow
|
| +// finding the largest global variables. Use the symbol names as a tiebreaker
|
| +// in order to get consistent ordering.
|
| +bool SizeCompare(const SymbolData& lhs, const SymbolData& rhs) {
|
| + if (lhs.size == rhs.size)
|
| + return StringCompare(lhs.name, rhs.name) < 0;
|
| + return lhs.size < rhs.size;
|
| +}
|
| +
|
| +// Use this struct to store data about repeated globals, for later sorting.
|
| +struct RepeatData {
|
| + RepeatData(ULONGLONG repeat_count,
|
| + ULONGLONG bytes_wasted,
|
| + const std::wstring& name)
|
| + : repeat_count(repeat_count), bytes_wasted(bytes_wasted), name(name) {}
|
| + bool operator<(const RepeatData& rhs) {
|
| + return bytes_wasted < rhs.bytes_wasted;
|
| + }
|
| +
|
| + ULONGLONG repeat_count;
|
| + ULONGLONG bytes_wasted;
|
| + std::wstring name;
|
| +};
|
| +
|
| +bool DumpInterestingGlobals(IDiaSymbol* global, const wchar_t* filename) {
|
| + wprintf(L"#Dups\tDupSize\t Size\tSection\tSymbol name\tPDB name\n");
|
| +
|
| + // How many bytes must be wasted on repeats before being listed.
|
| + const int kWastageThreshold = 100;
|
| + // How big must an individual symbol be before being listed.
|
| + const int kBigSizeThreshold = 500;
|
| +
|
| + std::vector<SymbolData> symbols;
|
| + std::vector<RepeatData> repeats;
|
| +
|
| + CComPtr<IDiaEnumSymbols> enum_symbols;
|
| + HRESULT result =
|
| + global->findChildren(SymTagData, NULL, nsNone, &enum_symbols);
|
| + if (FAILED(result)) {
|
| + wprintf(L"ERROR - DumpInterestingGlobals() returned no symbols.\n");
|
| + return false;
|
| + }
|
| +
|
| + CComPtr<IDiaSymbol> symbol;
|
| + // Must call symbol.Release() at end of loop to prepare for reuse of symbol
|
| + // smart pointer, because DIA2 is not smart-pointer aware.
|
| + for (ULONG celt = 0;
|
| + SUCCEEDED(enum_symbols->Next(1, &symbol, &celt)) && (celt == 1);
|
| + symbol.Release()) {
|
| + // If we call get_length on symbol it works for functions but not for
|
| + // data. For some reason for data we have to call get_type() to get
|
| + // another IDiaSymbol object which we can query for length.
|
| + CComPtr<IDiaSymbol> type_symbol;
|
| + if (FAILED(symbol->get_type(&type_symbol))) {
|
| + wprintf(L"Get_type failed.\n");
|
| + continue;
|
| + }
|
| +
|
| + // Errors in the remainder of this loop can be ignored silently.
|
| + ULONGLONG size = 0;
|
| + type_symbol->get_length(&size);
|
| +
|
| + // Use -1 and -2 as canary values to indicate various failures.
|
| + DWORD section = static_cast<DWORD>(-1);
|
| + if (symbol->get_addressSection(§ion) != S_OK)
|
| + section = static_cast<DWORD>(-2);
|
| +
|
| + CComBSTR name;
|
| + if (symbol->get_name(&name) == S_OK) {
|
| + symbols.push_back(SymbolData(size, section, name));
|
| + }
|
| + }
|
| +
|
| + // Sort the symbols by name/size so that we can print a report about duplicate
|
| + // variables.
|
| + std::sort(symbols.begin(), symbols.end(), NameCompare);
|
| + for (auto p = symbols.begin(); p != symbols.end(); /**/) {
|
| + auto pScan = p;
|
| + // Scan the data looking for symbols that have the same name
|
| + // and size.
|
| + while (pScan != symbols.end() && p->size == pScan->size &&
|
| + StringCompare(p->name, pScan->name) == 0)
|
| + ++pScan;
|
| +
|
| + // Calculate how many times the symbol name/size appears in this PDB.
|
| + size_t repeat_count = pScan - p;
|
| + if (repeat_count > 1) {
|
| + // Change the count from how many instances of this variable there are to
|
| + // how many *excess* instances there are.
|
| + --repeat_count;
|
| + ULONGLONG bytes_wasted = repeat_count * p->size;
|
| + if (bytes_wasted > kWastageThreshold) {
|
| + repeats.push_back(RepeatData(repeat_count, bytes_wasted, p->name));
|
| + }
|
| + }
|
| +
|
| + p = pScan;
|
| + }
|
| +
|
| + // Print a summary of duplicated variables, sorted to put the worst offenders
|
| + // first.
|
| + std::sort(repeats.begin(), repeats.end());
|
| + std::reverse(repeats.begin(), repeats.end());
|
| + for (const auto& repeat : repeats) {
|
| + // The empty field contain a zero so that Excel/sheets will more easily
|
| + // create the pivot tables that I want.
|
| + wprintf(L"%llu\t%llu\t%6u\t%u\t%s\t%s\n", repeat.repeat_count,
|
| + repeat.bytes_wasted, 0, 0, repeat.name.c_str(), filename);
|
| + }
|
| + wprintf(L"\n");
|
| +
|
| + // Print a summary of the largest global variables
|
| + std::sort(symbols.begin(), symbols.end(), SizeCompare);
|
| + std::reverse(symbols.begin(), symbols.end());
|
| + for (const auto& s : symbols) {
|
| + if (s.size < kBigSizeThreshold)
|
| + break;
|
| + // The empty fields contain a zero so that the columns line up which can
|
| + // be important when pasting the data into a spreadsheet.
|
| + wprintf(L"%u\t%u\t%6llu\t%u\t%s\t%s\n", 0, 0, s.size, s.section,
|
| + s.name.c_str(), filename);
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool Initialize(const wchar_t* filename,
|
| + CComPtr<IDiaDataSource>& source,
|
| + CComPtr<IDiaSession>& session,
|
| + CComPtr<IDiaSymbol>& global) {
|
| + // Initialize DIA2
|
| + HRESULT hr = CoCreateInstance(__uuidof(DiaSource), NULL, CLSCTX_INPROC_SERVER,
|
| + __uuidof(IDiaDataSource), (void**)&source);
|
| + if (FAILED(hr)) {
|
| + wprintf(L"Failed to initialized DIA2 - %08X.\n", hr);
|
| + return false;
|
| + }
|
| +
|
| + // Open the PDB
|
| + hr = source->loadDataFromPdb(filename);
|
| + if (FAILED(hr)) {
|
| + wprintf(L"LoadDataFromPdb failed - %08X.\n", hr);
|
| + return false;
|
| + }
|
| +
|
| + hr = source->openSession(&session);
|
| + if (FAILED(hr)) {
|
| + wprintf(L"OpenSession failed - %08X.\n", hr);
|
| + return false;
|
| + }
|
| +
|
| + // Retrieve a reference to the global scope
|
| + hr = session->get_globalScope(&global);
|
| + if (hr != S_OK) {
|
| + wprintf(L"Get_globalScope failed - %08X.\n", hr);
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +int wmain(int argc, wchar_t* argv[]) {
|
| + if (argc < 2) {
|
| + wprintf(L"Usage: ShowGlobals file.pdb");
|
| + return -1;
|
| + }
|
| +
|
| + const wchar_t* filename = argv[1];
|
| +
|
| + HRESULT hr = CoInitialize(NULL);
|
| + if (FAILED(hr)) {
|
| + wprintf(L"CoInitialize failed - %08X.", hr);
|
| + return false;
|
| + }
|
| +
|
| + // Extra scope so that we can call CoUninitialize after we destroy our local
|
| + // variables.
|
| + {
|
| + CComPtr<IDiaDataSource> source;
|
| + CComPtr<IDiaSession> session;
|
| + CComPtr<IDiaSymbol> global;
|
| + if (!(Initialize(filename, source, session, global)))
|
| + return -1;
|
| +
|
| + DumpInterestingGlobals(global, filename);
|
| + }
|
| +
|
| + CoUninitialize();
|
| +}
|
|
|