OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 // This tool scans a PDB file and prints out information about 'interesting' |
| 6 // global variables. This includes duplicates and large globals. This is often |
| 7 // helpful inunderstanding code bloat or finding inefficient globals. |
| 8 // |
| 9 // Duplicate global variables often happen when constructs like this are placed |
| 10 // in a header file: |
| 11 // |
| 12 // const double sqrt_two = sqrt(2.0); |
| 13 // |
| 14 // Many (although usually not all) of the translation units that include this |
| 15 // header file will get a copy of sqrt_two, possibly including an initializer. |
| 16 // Because 'const' implies 'static' there are no warnings or errors from the |
| 17 // linker. This duplication can happen with float/double, structs and classes, |
| 18 // and arrays - any non-integral type. |
| 19 // |
| 20 // Global variables are not necessarily a problem but it is useful to understand |
| 21 // them, and monitoring their changes can be instructive. |
| 22 |
| 23 #include <atlbase.h> |
| 24 #include <dia2.h> |
| 25 #include <stdio.h> |
| 26 |
| 27 #include <algorithm> |
| 28 #include <vector> |
| 29 |
| 30 // Helper function for comparing strings - returns a strcmp/wcscmp compatible |
| 31 // value. |
| 32 int StringCompare(const std::wstring& lhs, const std::wstring& rhs) { |
| 33 return wcscmp(lhs.c_str(), rhs.c_str()); |
| 34 } |
| 35 |
| 36 // Use this struct to record data about symbols for sorting and analysis. |
| 37 struct SymbolData { |
| 38 SymbolData(ULONGLONG size, DWORD section, const wchar_t* name) |
| 39 : size(size), section(section), name(name) {} |
| 40 |
| 41 ULONGLONG size; |
| 42 DWORD section; |
| 43 std::wstring name; |
| 44 }; |
| 45 |
| 46 // Comparison function for when sorting symbol data by name, in order to allow |
| 47 // looking for duplicate symbols. It uses the symbol size as a tiebreaker. This |
| 48 // is necessary because sometimes there are symbols with matching names but |
| 49 // different sizes in which case they aren't actually duplicates. These false |
| 50 // positives happen because namespaces are omitted from the symbol names that |
| 51 // DIA2 returns. |
| 52 bool NameCompare(const SymbolData& lhs, const SymbolData& rhs) { |
| 53 int nameCompare = StringCompare(lhs.name, rhs.name); |
| 54 if (nameCompare == 0) |
| 55 return lhs.size < rhs.size; |
| 56 return nameCompare < 0; |
| 57 } |
| 58 |
| 59 // Comparison function for when sorting symbols by size, in order to allow |
| 60 // finding the largest global variables. Use the symbol names as a tiebreaker |
| 61 // in order to get consistent ordering. |
| 62 bool SizeCompare(const SymbolData& lhs, const SymbolData& rhs) { |
| 63 if (lhs.size == rhs.size) |
| 64 return StringCompare(lhs.name, rhs.name) < 0; |
| 65 return lhs.size < rhs.size; |
| 66 } |
| 67 |
| 68 // Use this struct to store data about repeated globals, for later sorting. |
| 69 struct RepeatData { |
| 70 RepeatData(ULONGLONG repeat_count, |
| 71 ULONGLONG bytes_wasted, |
| 72 const std::wstring& name) |
| 73 : repeat_count(repeat_count), bytes_wasted(bytes_wasted), name(name) {} |
| 74 bool operator<(const RepeatData& rhs) { |
| 75 return bytes_wasted < rhs.bytes_wasted; |
| 76 } |
| 77 |
| 78 ULONGLONG repeat_count; |
| 79 ULONGLONG bytes_wasted; |
| 80 std::wstring name; |
| 81 }; |
| 82 |
| 83 bool DumpInterestingGlobals(IDiaSymbol* global, const wchar_t* filename) { |
| 84 wprintf(L"#Dups\tDupSize\t Size\tSection\tSymbol name\tPDB name\n"); |
| 85 |
| 86 // How many bytes must be wasted on repeats before being listed. |
| 87 const int kWastageThreshold = 100; |
| 88 // How big must an individual symbol be before being listed. |
| 89 const int kBigSizeThreshold = 500; |
| 90 |
| 91 std::vector<SymbolData> symbols; |
| 92 std::vector<RepeatData> repeats; |
| 93 |
| 94 CComPtr<IDiaEnumSymbols> enum_symbols; |
| 95 HRESULT result = |
| 96 global->findChildren(SymTagData, NULL, nsNone, &enum_symbols); |
| 97 if (FAILED(result)) { |
| 98 wprintf(L"ERROR - DumpInterestingGlobals() returned no symbols.\n"); |
| 99 return false; |
| 100 } |
| 101 |
| 102 CComPtr<IDiaSymbol> symbol; |
| 103 // Must call symbol.Release() at end of loop to prepare for reuse of symbol |
| 104 // smart pointer, because DIA2 is not smart-pointer aware. |
| 105 for (ULONG celt = 0; |
| 106 SUCCEEDED(enum_symbols->Next(1, &symbol, &celt)) && (celt == 1); |
| 107 symbol.Release()) { |
| 108 // If we call get_length on symbol it works for functions but not for |
| 109 // data. For some reason for data we have to call get_type() to get |
| 110 // another IDiaSymbol object which we can query for length. |
| 111 CComPtr<IDiaSymbol> type_symbol; |
| 112 if (FAILED(symbol->get_type(&type_symbol))) { |
| 113 wprintf(L"Get_type failed.\n"); |
| 114 continue; |
| 115 } |
| 116 |
| 117 // Errors in the remainder of this loop can be ignored silently. |
| 118 ULONGLONG size = 0; |
| 119 type_symbol->get_length(&size); |
| 120 |
| 121 // Use -1 and -2 as canary values to indicate various failures. |
| 122 DWORD section = static_cast<DWORD>(-1); |
| 123 if (symbol->get_addressSection(§ion) != S_OK) |
| 124 section = static_cast<DWORD>(-2); |
| 125 |
| 126 CComBSTR name; |
| 127 if (symbol->get_name(&name) == S_OK) { |
| 128 symbols.push_back(SymbolData(size, section, name)); |
| 129 } |
| 130 } |
| 131 |
| 132 // Sort the symbols by name/size so that we can print a report about duplicate |
| 133 // variables. |
| 134 std::sort(symbols.begin(), symbols.end(), NameCompare); |
| 135 for (auto p = symbols.begin(); p != symbols.end(); /**/) { |
| 136 auto pScan = p; |
| 137 // Scan the data looking for symbols that have the same name |
| 138 // and size. |
| 139 while (pScan != symbols.end() && p->size == pScan->size && |
| 140 StringCompare(p->name, pScan->name) == 0) |
| 141 ++pScan; |
| 142 |
| 143 // Calculate how many times the symbol name/size appears in this PDB. |
| 144 size_t repeat_count = pScan - p; |
| 145 if (repeat_count > 1) { |
| 146 // Change the count from how many instances of this variable there are to |
| 147 // how many *excess* instances there are. |
| 148 --repeat_count; |
| 149 ULONGLONG bytes_wasted = repeat_count * p->size; |
| 150 if (bytes_wasted > kWastageThreshold) { |
| 151 repeats.push_back(RepeatData(repeat_count, bytes_wasted, p->name)); |
| 152 } |
| 153 } |
| 154 |
| 155 p = pScan; |
| 156 } |
| 157 |
| 158 // Print a summary of duplicated variables, sorted to put the worst offenders |
| 159 // first. |
| 160 std::sort(repeats.begin(), repeats.end()); |
| 161 std::reverse(repeats.begin(), repeats.end()); |
| 162 for (const auto& repeat : repeats) { |
| 163 // The empty field contain a zero so that Excel/sheets will more easily |
| 164 // create the pivot tables that I want. |
| 165 wprintf(L"%llu\t%llu\t%6u\t%u\t%s\t%s\n", repeat.repeat_count, |
| 166 repeat.bytes_wasted, 0, 0, repeat.name.c_str(), filename); |
| 167 } |
| 168 wprintf(L"\n"); |
| 169 |
| 170 // Print a summary of the largest global variables |
| 171 std::sort(symbols.begin(), symbols.end(), SizeCompare); |
| 172 std::reverse(symbols.begin(), symbols.end()); |
| 173 for (const auto& s : symbols) { |
| 174 if (s.size < kBigSizeThreshold) |
| 175 break; |
| 176 // The empty fields contain a zero so that the columns line up which can |
| 177 // be important when pasting the data into a spreadsheet. |
| 178 wprintf(L"%u\t%u\t%6llu\t%u\t%s\t%s\n", 0, 0, s.size, s.section, |
| 179 s.name.c_str(), filename); |
| 180 } |
| 181 |
| 182 return true; |
| 183 } |
| 184 |
| 185 bool Initialize(const wchar_t* filename, |
| 186 CComPtr<IDiaDataSource>& source, |
| 187 CComPtr<IDiaSession>& session, |
| 188 CComPtr<IDiaSymbol>& global) { |
| 189 // Initialize DIA2 |
| 190 HRESULT hr = CoCreateInstance(__uuidof(DiaSource), NULL, CLSCTX_INPROC_SERVER, |
| 191 __uuidof(IDiaDataSource), (void**)&source); |
| 192 if (FAILED(hr)) { |
| 193 wprintf(L"Failed to initialized DIA2 - %08X.\n", hr); |
| 194 return false; |
| 195 } |
| 196 |
| 197 // Open the PDB |
| 198 hr = source->loadDataFromPdb(filename); |
| 199 if (FAILED(hr)) { |
| 200 wprintf(L"LoadDataFromPdb failed - %08X.\n", hr); |
| 201 return false; |
| 202 } |
| 203 |
| 204 hr = source->openSession(&session); |
| 205 if (FAILED(hr)) { |
| 206 wprintf(L"OpenSession failed - %08X.\n", hr); |
| 207 return false; |
| 208 } |
| 209 |
| 210 // Retrieve a reference to the global scope |
| 211 hr = session->get_globalScope(&global); |
| 212 if (hr != S_OK) { |
| 213 wprintf(L"Get_globalScope failed - %08X.\n", hr); |
| 214 return false; |
| 215 } |
| 216 |
| 217 return true; |
| 218 } |
| 219 |
| 220 int wmain(int argc, wchar_t* argv[]) { |
| 221 if (argc < 2) { |
| 222 wprintf(L"Usage: ShowGlobals file.pdb"); |
| 223 return -1; |
| 224 } |
| 225 |
| 226 const wchar_t* filename = argv[1]; |
| 227 |
| 228 HRESULT hr = CoInitialize(NULL); |
| 229 if (FAILED(hr)) { |
| 230 wprintf(L"CoInitialize failed - %08X.", hr); |
| 231 return false; |
| 232 } |
| 233 |
| 234 // Extra scope so that we can call CoUninitialize after we destroy our local |
| 235 // variables. |
| 236 { |
| 237 CComPtr<IDiaDataSource> source; |
| 238 CComPtr<IDiaSession> session; |
| 239 CComPtr<IDiaSymbol> global; |
| 240 if (!(Initialize(filename, source, session, global))) |
| 241 return -1; |
| 242 |
| 243 DumpInterestingGlobals(global, filename); |
| 244 } |
| 245 |
| 246 CoUninitialize(); |
| 247 } |
OLD | NEW |