1 // Copyright 2014 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 #include <Windows.h>
6 #include <dbghelp.h>
7 #include <dia2.h>
8 #include <stdio.h>
9 
10 #include <string>
11 
12 static const size_t kMaxSymbolLength = 4096;
13 
14 // Create an IDiaData source and open a PDB file.
LoadDataFromPdb(const wchar_t * filename,IDiaDataSource ** source,IDiaSession ** session,IDiaSymbol ** global,DWORD * machine_type)15 static bool LoadDataFromPdb(const wchar_t* filename,
16                             IDiaDataSource** source,
17                             IDiaSession** session,
18                             IDiaSymbol** global,
19                             DWORD* machine_type) {
20   // Alternate path to search for debug data.
21   const wchar_t search_path[] = L"SRV**\\\\symbols\\symbols";
22   DWORD mach_type = 0;
23   HRESULT hr = CoInitialize(NULL);
24 
25   // Obtain access to the provider.
26   hr = CoCreateInstance(__uuidof(DiaSource),
27                         NULL,
28                         CLSCTX_INPROC_SERVER,
29                         __uuidof(IDiaDataSource),
30                         (void**)source);
31 
32   if (FAILED(hr)) {
33     printf("CoCreateInstance failed - HRESULT = %08lX\n", hr);
34     return false;
35   }
36 
37   wchar_t ext[MAX_PATH];
38   _wsplitpath_s(filename, NULL, 0, NULL, 0, NULL, 0, ext, MAX_PATH);
39 
40   if (wcsicmp(ext, L".pdb") == 0) {
41     // Open and prepare the debug data specified.
42     hr = (*source)->loadDataFromPdb(filename);
43     if (FAILED(hr)) {
44       printf("loadDataFromPdb failed - HRESULT = %08lX\n", hr);
45       return false;
46     }
47   } else {
48     // Open and prepare the debug data associated with the executable.
49     hr = (*source)->loadDataForExe(filename, search_path, NULL);
50     if (FAILED(hr)) {
51       printf("loadDataForExe failed - HRESULT = %08lX\n", hr);
52       printf(
53           "Try copying the .pdb beside the PE file or passing the .pdb path "
54           "to this tool directly.");
55       return false;
56     }
57   }
58 
59   // Open a session for querying symbols.
60   hr = (*source)->openSession(session);
61 
62   if (FAILED(hr)) {
63     printf("openSession failed - HRESULT = %08lX\n", hr);
64     return false;
65   }
66 
67   // Retrieve a reference to the global scope.
68   hr = (*session)->get_globalScope(global);
69 
70   if (FAILED(hr)) {
71     printf("get_globalScope failed\n");
72     return false;
73   }
74 
75   // Set machine type for getting correct register names.
76   if (SUCCEEDED((*global)->get_machineType(&mach_type))) {
77     switch (mach_type) {
78       case IMAGE_FILE_MACHINE_I386:
79         *machine_type = CV_CFL_80386;
80         break;
81       case IMAGE_FILE_MACHINE_IA64:
82         *machine_type = CV_CFL_IA64;
83         break;
84       case IMAGE_FILE_MACHINE_AMD64:
85         *machine_type = CV_CFL_AMD64;
86         break;
87       default:
88         printf("unexpected machine type\n");
89         return false;
90     }
91   }
92 
93   return true;
94 }
95 
96 // Release DIA objects and CoUninitialize.
Cleanup(IDiaSymbol * global_symbol,IDiaSession * dia_session)97 static void Cleanup(IDiaSymbol* global_symbol, IDiaSession* dia_session) {
98   if (global_symbol)
99     global_symbol->Release();
100   if (dia_session)
101     dia_session->Release();
102   CoUninitialize();
103 }
104 
PrintIfDynamicInitializer(const std::wstring & module,IDiaSymbol * symbol)105 static void PrintIfDynamicInitializer(const std::wstring& module,
106                                       IDiaSymbol* symbol) {
107   DWORD symtag;
108 
109   if (FAILED(symbol->get_symTag(&symtag)))
110     return;
111 
112   if (symtag != SymTagFunction && symtag != SymTagBlock)
113     return;
114 
115   BSTR bstr_name;
116   if (SUCCEEDED(symbol->get_name(&bstr_name))) {
117     if (wcsstr(bstr_name, L"`dynamic initializer for '") ||
118         wcsstr(bstr_name, L"`dynamic atexit destructor for '")) {
119       wprintf(L"%s: %s\n", module.c_str(), bstr_name);
120     }
121     // If there are multiple dynamic initializers in one translation unit then
122     // a shared function is created and the individual initializers may be
123     // inlined into it. These functions start with a characteristic name that
124     // includes the source file. Finding the actual objects can be done through
125     // source inspection or by setting a breakpoint on the printed name. The
126     // "dynamic initializer" string is printed for consistent grepping.
127     if (wcsstr(bstr_name, L"_GLOBAL__sub_I")) {
128       wprintf(L"%s: %s (dynamic initializer)\n", module.c_str(), bstr_name);
129     }
130     // As of this writing, Clang does not undecorate the symbol names for
131     // dynamic initializers, so the debug info contains the decorated name,
132     // which starts with "??__E" or "??__F" for atexit destructors. Check for
133     // that, and print the undecorated name if it matches.
134     if (wcsncmp(bstr_name, L"??__E", 5) == 0 ||
135         wcsncmp(bstr_name, L"??__F", 5) == 0) {
136       wchar_t undecorated[kMaxSymbolLength];
137       if (UnDecorateSymbolNameW(bstr_name, undecorated, kMaxSymbolLength,
138                                 UNDNAME_NAME_ONLY) == 0) {
139         printf("UnDecorateSymbolNameW failed, %d\n", GetLastError());
140         return;
141       }
142       wprintf(L"%s: %s\n", module.c_str(), undecorated);
143     }
144     SysFreeString(bstr_name);
145   }
146 }
147 
DumpStaticInitializers(IDiaSymbol * global_symbol)148 static bool DumpStaticInitializers(IDiaSymbol* global_symbol) {
149   // Retrieve the compilands first.
150   IDiaEnumSymbols* enum_symbols;
151   if (FAILED(global_symbol->findChildren(
152           SymTagCompiland, NULL, nsNone, &enum_symbols))) {
153     return false;
154   }
155 
156   IDiaSymbol* compiland;
157   ULONG element_count = 0;
158 
159   std::wstring current_module;
160   while (SUCCEEDED(enum_symbols->Next(1, &compiland, &element_count)) &&
161          (element_count == 1)) {
162     BSTR bstr_name;
163     if (FAILED(compiland->get_name(&bstr_name))) {
164       current_module = L"<unknown>";
165     } else {
166       current_module = bstr_name;
167       SysFreeString(bstr_name);
168     }
169 
170     // Find all the symbols defined in this compiland, and print them if they
171     // have the name corresponding to an initializer.
172     IDiaEnumSymbols* enum_children;
173     if (SUCCEEDED(compiland->findChildren(
174             SymTagNull, NULL, nsNone, &enum_children))) {
175       IDiaSymbol* symbol;
176       ULONG children = 0;
177       while (SUCCEEDED(enum_children->Next(1, &symbol, &children)) &&
178              children == 1) {  // Enumerate until we don't get any more symbols.
179         PrintIfDynamicInitializer(current_module, symbol);
180         symbol->Release();
181       }
182       enum_children->Release();
183     }
184     compiland->Release();
185   }
186 
187   enum_symbols->Release();
188   return true;
189 }
190 
wmain(int argc,wchar_t * argv[])191 int wmain(int argc, wchar_t* argv[]) {
192   if (argc != 2) {
193     wprintf(L"usage: %ls binary_name\n", argv[0]);
194     return 1;
195   }
196 
197   IDiaDataSource* dia_data_source;
198   IDiaSession* dia_session;
199   IDiaSymbol* global_symbol;
200   DWORD machine_type = CV_CFL_80386;
201   if (!LoadDataFromPdb(argv[1],
202                        &dia_data_source,
203                        &dia_session,
204                        &global_symbol,
205                        &machine_type)) {
206     wprintf(L"Couldn't load data from pdb.\n");
207     return 1;
208   }
209 
210   wprintf(L"Static initializers in %s:\n", argv[1]);
211 
212   if (!DumpStaticInitializers(global_symbol))
213     return 1;
214 
215   Cleanup(global_symbol, dia_session);
216 
217   return 0;
218 }
219