1 // Copyright (c) 2007, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 // ms_symbol_server_converter.cc: Obtain symbol files from a Microsoft
31 // symbol server, and convert them to Breakpad's dumped format.
32 //
33 // See ms_symbol_server_converter.h for documentation.
34 //
35 // Author: Mark Mentovai
36
37 #include <windows.h>
38 #include <dbghelp.h>
39
40 #include <cassert>
41 #include <cstdio>
42
43 #include "tools/windows/converter/ms_symbol_server_converter.h"
44 #include "common/windows/pdb_source_line_writer.h"
45 #include "common/windows/string_utils-inl.h"
46
47 // SYMOPT_NO_PROMPTS is not defined in earlier platform SDKs. Define it
48 // in that case, in the event that this code is used with a newer version
49 // of DbgHelp at runtime that recognizes the option. The presence of this
50 // bit in the symbol options should not harm earlier versions of DbgHelp.
51 #ifndef SYMOPT_NO_PROMPTS
52 #define SYMOPT_NO_PROMPTS 0x00080000
53 #endif // SYMOPT_NO_PROMPTS
54
55 namespace google_breakpad {
56
57 // Use sscanf_s if it is available, to quench the warning about scanf being
58 // deprecated. Use scanf where sscanf_is not available. Note that the
59 // parameters passed to sscanf and sscanf_s are only compatible as long as
60 // fields of type c, C, s, S, and [ are not used.
61 #if _MSC_VER >= 1400 // MSVC 2005/8
62 #define SSCANF sscanf_s
63 #else // _MSC_VER >= 1400
64 #define SSCANF sscanf
65 #endif // _MSC_VER >= 1400
66
InitializeFromString(const string & identifier)67 bool GUIDOrSignatureIdentifier::InitializeFromString(
68 const string &identifier) {
69 type_ = TYPE_NONE;
70
71 size_t length = identifier.length();
72
73 if (length > 32 && length <= 40) {
74 // GUID
75 if (SSCANF(identifier.c_str(),
76 "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%X",
77 &guid_.Data1, &guid_.Data2, &guid_.Data3,
78 &guid_.Data4[0], &guid_.Data4[1],
79 &guid_.Data4[2], &guid_.Data4[3],
80 &guid_.Data4[4], &guid_.Data4[5],
81 &guid_.Data4[6], &guid_.Data4[7],
82 &age_) != 12) {
83 return false;
84 }
85
86 type_ = TYPE_GUID;
87 } else if (length > 8 && length <= 15) {
88 // Signature
89 if (SSCANF(identifier.c_str(), "%08X%x", &signature_, &age_) != 2) {
90 return false;
91 }
92
93 type_ = TYPE_SIGNATURE;
94 } else {
95 return false;
96 }
97
98 return true;
99 }
100
101 #undef SSCANF
102
MSSymbolServerConverter(const string & local_cache,const vector<string> & symbol_servers)103 MSSymbolServerConverter::MSSymbolServerConverter(
104 const string &local_cache, const vector<string> &symbol_servers)
105 : symbol_path_(),
106 fail_dns_(false),
107 fail_timeout_(false),
108 fail_not_found_(false) {
109 // Setting local_cache can be done without verifying that it exists because
110 // SymSrv will create it if it is missing - any creation failures will occur
111 // at that time, so there's nothing to check here, making it safe to
112 // assign this in the constructor.
113
114 assert(symbol_servers.size() > 0);
115
116 // These are characters that are interpreted as having special meanings in
117 // symbol_path_.
118 const char *kInvalidCharacters = "*;";
119 assert(local_cache.find_first_of(kInvalidCharacters) == string::npos);
120
121 for (vector<string>::const_iterator symbol_server = symbol_servers.begin();
122 symbol_server != symbol_servers.end();
123 ++symbol_server) {
124 // The symbol path format is explained by
125 // http://msdn.microsoft.com/library/en-us/debug/base/using_symsrv.asp .
126 // "srv*" is the same as "symsrv*symsrv.dll*", which means that
127 // symsrv.dll is to be responsible for locating symbols. symsrv.dll
128 // interprets the rest of the string as a series of symbol stores separated
129 // by '*'. "srv*local_cache*symbol_server" means to check local_cache
130 // first for the symbol file, and if it is not found there, to check
131 // symbol_server. Symbol files found on the symbol server will be placed
132 // in the local cache, decompressed.
133 //
134 // Multiple specifications in this format may be presented, separated by
135 // semicolons.
136
137 assert((*symbol_server).find_first_of(kInvalidCharacters) == string::npos);
138 symbol_path_ += "srv*" + local_cache + "*" + *symbol_server + ";";
139 }
140
141 // Strip the trailing semicolon.
142 symbol_path_.erase(symbol_path_.length() - 1);
143 }
144
145 // A stack-based class that manages SymInitialize and SymCleanup calls.
146 class AutoSymSrv {
147 public:
AutoSymSrv()148 AutoSymSrv() : initialized_(false) {}
149
~AutoSymSrv()150 ~AutoSymSrv() {
151 if (!Cleanup()) {
152 // Print the error message here, because destructors have no return
153 // value.
154 fprintf(stderr, "~AutoSymSrv: SymCleanup: error %d\n", GetLastError());
155 }
156 }
157
Initialize(HANDLE process,char * path,bool invade_process)158 bool Initialize(HANDLE process, char *path, bool invade_process) {
159 process_ = process;
160 initialized_ = SymInitialize(process, path, invade_process) == TRUE;
161 return initialized_;
162 }
163
Cleanup()164 bool Cleanup() {
165 if (initialized_) {
166 if (SymCleanup(process_)) {
167 initialized_ = false;
168 return true;
169 }
170 return false;
171 }
172
173 return true;
174 }
175
176 private:
177 HANDLE process_;
178 bool initialized_;
179 };
180
181 // A stack-based class that "owns" a pathname and deletes it when destroyed,
182 // unless told not to by having its Release() method called. Early deletions
183 // are supported by calling Delete().
184 class AutoDeleter {
185 public:
AutoDeleter(const string & path)186 AutoDeleter(const string &path) : path_(path) {}
187
~AutoDeleter()188 ~AutoDeleter() {
189 int error;
190 if ((error = Delete()) != 0) {
191 // Print the error message here, because destructors have no return
192 // value.
193 fprintf(stderr, "~AutoDeleter: Delete: error %d for %s\n",
194 error, path_.c_str());
195 }
196 }
197
Delete()198 int Delete() {
199 if (path_.empty())
200 return 0;
201
202 int error = remove(path_.c_str());
203 Release();
204 return error;
205 }
206
Release()207 void Release() {
208 path_.clear();
209 }
210
211 private:
212 string path_;
213 };
214
215 MSSymbolServerConverter::LocateResult
LocateSymbolFile(const MissingSymbolInfo & missing,string * symbol_file)216 MSSymbolServerConverter::LocateSymbolFile(const MissingSymbolInfo &missing,
217 string *symbol_file) {
218 assert(symbol_file);
219 symbol_file->clear();
220
221 GUIDOrSignatureIdentifier identifier;
222 if (!identifier.InitializeFromString(missing.debug_identifier)) {
223 fprintf(stderr,
224 "LocateSymbolFile: Unparseable debug_identifier for %s %s %s\n",
225 missing.debug_file.c_str(),
226 missing.debug_identifier.c_str(),
227 missing.version.c_str());
228 return LOCATE_FAILURE;
229 }
230
231 HANDLE process = GetCurrentProcess(); // CloseHandle is not needed.
232 AutoSymSrv symsrv;
233 if (!symsrv.Initialize(process,
234 const_cast<char *>(symbol_path_.c_str()),
235 false)) {
236 fprintf(stderr, "LocateSymbolFile: SymInitialize: error %d for %s %s %s\n",
237 GetLastError(),
238 missing.debug_file.c_str(),
239 missing.debug_identifier.c_str(),
240 missing.version.c_str());
241 return LOCATE_FAILURE;
242 }
243
244 if (!SymRegisterCallback64(process, SymCallback,
245 reinterpret_cast<ULONG64>(this))) {
246 fprintf(stderr,
247 "LocateSymbolFile: SymRegisterCallback64: error %d for %s %s %s\n",
248 GetLastError(),
249 missing.debug_file.c_str(),
250 missing.debug_identifier.c_str(),
251 missing.version.c_str());
252 return LOCATE_FAILURE;
253 }
254
255 // SYMOPT_DEBUG arranges for SymCallback to be called with additional
256 // debugging information. This is used to determine the nature of failures.
257 DWORD options = SymGetOptions() | SYMOPT_DEBUG | SYMOPT_NO_PROMPTS |
258 SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_SECURE;
259 SymSetOptions(options);
260
261 // SymCallback will set these as needed inisde the SymFindFileInPath call.
262 fail_dns_ = false;
263 fail_timeout_ = false;
264 fail_not_found_ = false;
265
266 // Do the lookup.
267 char path[MAX_PATH];
268 if (!SymFindFileInPath(
269 process, NULL,
270 const_cast<char *>(missing.debug_file.c_str()),
271 const_cast<void *>(identifier.guid_or_signature_pointer()),
272 identifier.age(), 0,
273 identifier.type() == GUIDOrSignatureIdentifier::TYPE_GUID ?
274 SSRVOPT_GUIDPTR : SSRVOPT_DWORDPTR,
275 path, SymFindFileInPathCallback, this)) {
276 DWORD error = GetLastError();
277 if (error == ERROR_FILE_NOT_FOUND) {
278 // This can be returned for a number of reasons. Use the crumbs
279 // collected by SymCallback to determine which one is relevant.
280
281 // These errors are possibly transient.
282 if (fail_dns_ || fail_timeout_) {
283 return LOCATE_RETRY;
284 }
285
286 // This is an authoritiative file-not-found message.
287 if (fail_not_found_) {
288 fprintf(stderr,
289 "LocateSymbolFile: SymFindFileInPath: LOCATE_NOT_FOUND error "
290 "for %s %s %s\n",
291 missing.debug_file.c_str(),
292 missing.debug_identifier.c_str(),
293 missing.version.c_str());
294 return LOCATE_NOT_FOUND;
295 }
296
297 // If the error is FILE_NOT_FOUND but none of the known error
298 // conditions are matched, fall through to LOCATE_FAILURE.
299 }
300
301 fprintf(stderr,
302 "LocateSymbolFile: SymFindFileInPath: error %d for %s %s %s\n",
303 error,
304 missing.debug_file.c_str(),
305 missing.debug_identifier.c_str(),
306 missing.version.c_str());
307 return LOCATE_FAILURE;
308 }
309
310 // The AutoDeleter ensures that the file is only kept when returning
311 // LOCATE_SUCCESS.
312 AutoDeleter deleter(path);
313
314 // Do the cleanup here even though it will happen when symsrv goes out of
315 // scope, to allow it to influence the return value.
316 if (!symsrv.Cleanup()) {
317 fprintf(stderr, "LocateSymbolFile: SymCleanup: error %d for %s %s %s\n",
318 GetLastError(),
319 missing.debug_file.c_str(),
320 missing.debug_identifier.c_str(),
321 missing.version.c_str());
322 return LOCATE_FAILURE;
323 }
324
325 deleter.Release();
326
327 *symbol_file = path;
328 return LOCATE_SUCCESS;
329 }
330
331 // static
SymCallback(HANDLE process,ULONG action,ULONG64 data,ULONG64 context)332 BOOL CALLBACK MSSymbolServerConverter::SymCallback(HANDLE process,
333 ULONG action,
334 ULONG64 data,
335 ULONG64 context) {
336 MSSymbolServerConverter *self =
337 reinterpret_cast<MSSymbolServerConverter *>(context);
338
339 switch (action) {
340 case CBA_EVENT: {
341 IMAGEHLP_CBA_EVENT *cba_event =
342 reinterpret_cast<IMAGEHLP_CBA_EVENT *>(data);
343
344 // Put the string into a string object to be able to use string::find
345 // for substring matching. This is important because the not-found
346 // message does not use the entire string but is appended to the URL
347 // that SymSrv attempted to retrieve.
348 string desc(cba_event->desc);
349
350 // desc_action maps strings (in desc) to boolean pointers that are to
351 // be set to true if the string matches.
352 struct desc_action {
353 const char *desc; // The substring to match.
354 bool *action; // On match, this pointer will be set to true.
355 };
356
357 static const desc_action desc_actions[] = {
358 // When a DNS error occurs, it could be indiciative of network
359 // problems.
360 { "SYMSRV: The server name or address could not be resolved\n",
361 &self->fail_dns_ },
362
363 // This message is produced if no connection is opened.
364 { "SYMSRV: A connection with the server could not be established\n",
365 &self->fail_timeout_ },
366
367 // This message is produced if a connection is established but the
368 // server fails to respond to the HTTP request.
369 { "SYMSRV: The operation timed out\n",
370 &self->fail_timeout_ },
371
372 // This message is produced when the requested file is not found,
373 // even if one or more of the above messages are also produced.
374 // It's trapped to distinguish between not-found and unknown-failure
375 // conditions. Note that this message will not be produced if a
376 // connection is established and the server begins to respond to the
377 // HTTP request but does not finish transmitting the file.
378 { " not found\n",
379 &self->fail_not_found_ }
380 };
381
382 for (int desc_action_index = 0;
383 desc_action_index < sizeof(desc_actions) / sizeof(desc_action);
384 ++desc_action_index) {
385 if (desc.find(desc_actions[desc_action_index].desc) != string::npos) {
386 *(desc_actions[desc_action_index].action) = true;
387 break;
388 }
389 }
390
391 break;
392 }
393 }
394
395 // This function is a mere fly on the wall. Treat everything as unhandled.
396 return FALSE;
397 }
398
399 // static
SymFindFileInPathCallback(PCSTR filename,PVOID context)400 BOOL CALLBACK MSSymbolServerConverter::SymFindFileInPathCallback(
401 PCSTR filename, PVOID context) {
402 // FALSE ends the search, indicating that the located symbol file is
403 // satisfactory.
404 return FALSE;
405 }
406
407 MSSymbolServerConverter::LocateResult
LocateAndConvertSymbolFile(const MissingSymbolInfo & missing,bool keep_symbol_file,string * converted_symbol_file,string * symbol_file)408 MSSymbolServerConverter::LocateAndConvertSymbolFile(
409 const MissingSymbolInfo &missing,
410 bool keep_symbol_file,
411 string *converted_symbol_file,
412 string *symbol_file) {
413 assert(converted_symbol_file);
414 converted_symbol_file->clear();
415 if (symbol_file) {
416 symbol_file->clear();
417 }
418
419 string pdb_file;
420 LocateResult result = LocateSymbolFile(missing, &pdb_file);
421 if (result != LOCATE_SUCCESS) {
422 return result;
423 }
424
425 if (symbol_file && keep_symbol_file) {
426 *symbol_file = pdb_file;
427 }
428
429 // Conversion may fail because the file is corrupt. If a broken file is
430 // kept in the local cache, LocateSymbolFile will not hit the network again
431 // to attempt to locate it. To guard against problems like this, the
432 // symbol file in the local cache will be removed if conversion fails.
433 AutoDeleter pdb_deleter(pdb_file);
434
435 // Be sure that it's a .pdb file, since we'll be replacing .pdb with .sym
436 // for the converted file's name.
437 string pdb_extension = pdb_file.substr(pdb_file.length() - 4);
438 // strcasecmp is called _stricmp here.
439 if (_stricmp(pdb_extension.c_str(), ".pdb") != 0) {
440 fprintf(stderr, "LocateAndConvertSymbolFile: "
441 "LocateSymbolFile: no .pdb extension for %s %s %s %s\n",
442 missing.debug_file.c_str(),
443 missing.debug_identifier.c_str(),
444 missing.version.c_str(),
445 pdb_file.c_str());
446 return LOCATE_FAILURE;
447 }
448
449 // PDBSourceLineWriter wants the filename as a wstring, so convert it.
450 wstring pdb_file_w;
451 if (!WindowsStringUtils::safe_mbstowcs(pdb_file, &pdb_file_w)) {
452 fprintf(stderr, "LocateAndConvertSymbolFile: "
453 "WindowsStringUtils::safe_mbstowcs failed for %s %s %s %s\n",
454 missing.debug_file.c_str(),
455 missing.debug_identifier.c_str(),
456 missing.version.c_str(),
457 pdb_file.c_str());
458 return LOCATE_FAILURE;
459 }
460
461 PDBSourceLineWriter writer;
462 if (!writer.Open(pdb_file_w, PDBSourceLineWriter::PDB_FILE)) {
463 fprintf(stderr, "LocateAndConvertSymbolFile: "
464 "PDBSourceLineWriter::Open failed for %s %s %s %ws\n",
465 missing.debug_file.c_str(),
466 missing.debug_identifier.c_str(),
467 missing.version.c_str(),
468 pdb_file_w.c_str());
469 return LOCATE_FAILURE;
470 }
471
472 *converted_symbol_file = pdb_file.substr(0, pdb_file.length() - 4) + ".sym";
473
474 FILE *converted_output = NULL;
475 #if _MSC_VER >= 1400 // MSVC 2005/8
476 errno_t err;
477 if ((err = fopen_s(&converted_output, converted_symbol_file->c_str(), "w"))
478 != 0) {
479 #else // _MSC_VER >= 1400
480 // fopen_s and errno_t were introduced in MSVC8. Use fopen for earlier
481 // environments. Don't use fopen with MSVC8 and later, because it's
482 // deprecated. fopen does not provide reliable error codes, so just use
483 // -1 in the event of a failure.
484 int err;
485 if (!(converted_output = fopen(converted_symbol_file->c_str(), "w"))) {
486 err = -1;
487 #endif // _MSC_VER >= 1400
488 fprintf(stderr, "LocateAndConvertSymbolFile: "
489 "fopen_s: error %d for %s %s %s %s\n",
490 err,
491 missing.debug_file.c_str(),
492 missing.debug_identifier.c_str(),
493 missing.version.c_str(),
494 converted_symbol_file->c_str());
495 return LOCATE_FAILURE;
496 }
497
498 AutoDeleter sym_deleter(*converted_symbol_file);
499
500 bool success = writer.WriteMap(converted_output);
501 fclose(converted_output);
502
503 if (!success) {
504 fprintf(stderr, "LocateAndConvertSymbolFile: "
505 "PDBSourceLineWriter::WriteMap failed for %s %s %s %s\n",
506 missing.debug_file.c_str(),
507 missing.debug_identifier.c_str(),
508 missing.version.c_str(),
509 pdb_file.c_str());
510 return LOCATE_FAILURE;
511 }
512
513 if (keep_symbol_file) {
514 pdb_deleter.Release();
515 }
516
517 sym_deleter.Release();
518
519 return LOCATE_SUCCESS;
520 }
521
522 } // namespace google_breakpad
523