• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #if !defined(NDEBUG)
117   // These are characters that are interpreted as having special meanings in
118   // symbol_path_.
119   const char kInvalidCharacters[] = "*;";
120   assert(local_cache.find_first_of(kInvalidCharacters) == string::npos);
121 #endif  // !defined(NDEBUG)
122 
123   for (vector<string>::const_iterator symbol_server = symbol_servers.begin();
124        symbol_server != symbol_servers.end();
125        ++symbol_server) {
126     // The symbol path format is explained by
127     // http://msdn.microsoft.com/library/en-us/debug/base/using_symsrv.asp .
128     // "srv*" is the same as "symsrv*symsrv.dll*", which means that
129     // symsrv.dll is to be responsible for locating symbols.  symsrv.dll
130     // interprets the rest of the string as a series of symbol stores separated
131     // by '*'.  "srv*local_cache*symbol_server" means to check local_cache
132     // first for the symbol file, and if it is not found there, to check
133     // symbol_server.  Symbol files found on the symbol server will be placed
134     // in the local cache, decompressed.
135     //
136     // Multiple specifications in this format may be presented, separated by
137     // semicolons.
138 
139     assert((*symbol_server).find_first_of(kInvalidCharacters) == string::npos);
140     symbol_path_ += "srv*" + local_cache + "*" + *symbol_server + ";";
141   }
142 
143   // Strip the trailing semicolon.
144   symbol_path_.erase(symbol_path_.length() - 1);
145 }
146 
147 // A stack-based class that manages SymInitialize and SymCleanup calls.
148 class AutoSymSrv {
149  public:
AutoSymSrv()150   AutoSymSrv() : initialized_(false) {}
151 
~AutoSymSrv()152   ~AutoSymSrv() {
153     if (!Cleanup()) {
154       // Print the error message here, because destructors have no return
155       // value.
156       fprintf(stderr, "~AutoSymSrv: SymCleanup: error %d\n", GetLastError());
157     }
158   }
159 
Initialize(HANDLE process,char * path,bool invade_process)160   bool Initialize(HANDLE process, char *path, bool invade_process) {
161     process_ = process;
162     initialized_ = SymInitialize(process, path, invade_process) == TRUE;
163     return initialized_;
164   }
165 
Cleanup()166   bool Cleanup() {
167     if (initialized_) {
168       if (SymCleanup(process_)) {
169         initialized_ = false;
170         return true;
171       }
172       return false;
173     }
174 
175     return true;
176   }
177 
178  private:
179   HANDLE process_;
180   bool initialized_;
181 };
182 
183 // A stack-based class that "owns" a pathname and deletes it when destroyed,
184 // unless told not to by having its Release() method called.  Early deletions
185 // are supported by calling Delete().
186 class AutoDeleter {
187  public:
AutoDeleter(const string & path)188   explicit AutoDeleter(const string &path) : path_(path) {}
189 
~AutoDeleter()190   ~AutoDeleter() {
191     int error;
192     if ((error = Delete()) != 0) {
193       // Print the error message here, because destructors have no return
194       // value.
195       fprintf(stderr, "~AutoDeleter: Delete: error %d for %s\n",
196               error, path_.c_str());
197     }
198   }
199 
Delete()200   int Delete() {
201     if (path_.empty())
202       return 0;
203 
204     int error = remove(path_.c_str());
205     Release();
206     return error;
207   }
208 
Release()209   void Release() {
210     path_.clear();
211   }
212 
213  private:
214   string path_;
215 };
216 
217 MSSymbolServerConverter::LocateResult
LocateFile(const string & debug_or_code_file,const string & debug_or_code_id,const string & version,string * file_name)218 MSSymbolServerConverter::LocateFile(const string &debug_or_code_file,
219                                     const string &debug_or_code_id,
220                                     const string &version,
221                                     string *file_name) {
222   assert(file_name);
223   file_name->clear();
224 
225   GUIDOrSignatureIdentifier identifier;
226   if (!identifier.InitializeFromString(debug_or_code_id)) {
227     fprintf(stderr,
228             "LocateFile: Unparseable identifier for %s %s %s\n",
229             debug_or_code_file.c_str(),
230             debug_or_code_id.c_str(),
231             version.c_str());
232     return LOCATE_FAILURE;
233   }
234 
235   HANDLE process = GetCurrentProcess();  // CloseHandle is not needed.
236   AutoSymSrv symsrv;
237   if (!symsrv.Initialize(process,
238                          const_cast<char *>(symbol_path_.c_str()),
239                          false)) {
240     fprintf(stderr, "LocateFile: SymInitialize: error %d for %s %s %s\n",
241             GetLastError(),
242             debug_or_code_file.c_str(),
243             debug_or_code_id.c_str(),
244             version.c_str());
245     return LOCATE_FAILURE;
246   }
247 
248   if (!SymRegisterCallback64(process, SymCallback,
249                              reinterpret_cast<ULONG64>(this))) {
250     fprintf(stderr,
251             "LocateFile: SymRegisterCallback64: error %d for %s %s %s\n",
252             GetLastError(),
253             debug_or_code_file.c_str(),
254             debug_or_code_id.c_str(),
255             version.c_str());
256     return LOCATE_FAILURE;
257   }
258 
259   // SYMOPT_DEBUG arranges for SymCallback to be called with additional
260   // debugging information.  This is used to determine the nature of failures.
261   DWORD options = SymGetOptions() | SYMOPT_DEBUG | SYMOPT_NO_PROMPTS |
262                   SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_SECURE;
263   SymSetOptions(options);
264 
265   // SymCallback will set these as needed inisde the SymFindFileInPath call.
266   fail_dns_ = false;
267   fail_timeout_ = false;
268   fail_not_found_ = false;
269 
270   // Do the lookup.
271   char path[MAX_PATH];
272   if (!SymFindFileInPath(
273           process, NULL,
274           const_cast<char *>(debug_or_code_file.c_str()),
275           const_cast<void *>(identifier.guid_or_signature_pointer()),
276           identifier.age(), 0,
277           identifier.type() == GUIDOrSignatureIdentifier::TYPE_GUID ?
278               SSRVOPT_GUIDPTR : SSRVOPT_DWORDPTR,
279           path, SymFindFileInPathCallback, this)) {
280     DWORD error = GetLastError();
281     if (error == ERROR_FILE_NOT_FOUND) {
282       // This can be returned for a number of reasons.  Use the crumbs
283       // collected by SymCallback to determine which one is relevant.
284 
285       // These errors are possibly transient.
286       if (fail_dns_ || fail_timeout_) {
287         return LOCATE_RETRY;
288       }
289 
290       // This is an authoritiative file-not-found message.
291       if (fail_not_found_) {
292         fprintf(stderr,
293                 "LocateFile: SymFindFileInPath: LOCATE_NOT_FOUND error "
294                 "for %s %s %s\n",
295                 debug_or_code_file.c_str(),
296                 debug_or_code_id.c_str(),
297                 version.c_str());
298         return LOCATE_NOT_FOUND;
299       }
300 
301       // If the error is FILE_NOT_FOUND but none of the known error
302       // conditions are matched, fall through to LOCATE_FAILURE.
303     }
304 
305     fprintf(stderr,
306             "LocateFile: SymFindFileInPath: error %d for %s %s %s\n",
307             error,
308             debug_or_code_file.c_str(),
309             debug_or_code_id.c_str(),
310             version.c_str());
311     return LOCATE_FAILURE;
312   }
313 
314   // Making sure path is null-terminated.
315   path[MAX_PATH - 1] = '\0';
316 
317   // The AutoDeleter ensures that the file is only kept when returning
318   // LOCATE_SUCCESS.
319   AutoDeleter deleter(path);
320 
321   // Do the cleanup here even though it will happen when symsrv goes out of
322   // scope, to allow it to influence the return value.
323   if (!symsrv.Cleanup()) {
324     fprintf(stderr, "LocateFile: SymCleanup: error %d for %s %s %s\n",
325             GetLastError(),
326             debug_or_code_file.c_str(),
327             debug_or_code_id.c_str(),
328             version.c_str());
329     return LOCATE_FAILURE;
330   }
331 
332   deleter.Release();
333 
334   printf("Downloaded: %s\n", path);
335   *file_name = path;
336   return LOCATE_SUCCESS;
337 }
338 
339 
340 MSSymbolServerConverter::LocateResult
LocatePEFile(const MissingSymbolInfo & missing,string * pe_file)341 MSSymbolServerConverter::LocatePEFile(const MissingSymbolInfo &missing,
342                                       string *pe_file) {
343   return LocateFile(missing.code_file, missing.code_identifier,
344                     missing.version, pe_file);
345 }
346 
347 MSSymbolServerConverter::LocateResult
LocateSymbolFile(const MissingSymbolInfo & missing,string * symbol_file)348 MSSymbolServerConverter::LocateSymbolFile(const MissingSymbolInfo &missing,
349                                           string *symbol_file) {
350   return LocateFile(missing.debug_file, missing.debug_identifier,
351                     missing.version, symbol_file);
352 }
353 
354 
355 // static
SymCallback(HANDLE process,ULONG action,ULONG64 data,ULONG64 context)356 BOOL CALLBACK MSSymbolServerConverter::SymCallback(HANDLE process,
357                                                    ULONG action,
358                                                    ULONG64 data,
359                                                    ULONG64 context) {
360   MSSymbolServerConverter *self =
361       reinterpret_cast<MSSymbolServerConverter *>(context);
362 
363   switch (action) {
364     case CBA_EVENT: {
365       IMAGEHLP_CBA_EVENT *cba_event =
366           reinterpret_cast<IMAGEHLP_CBA_EVENT *>(data);
367 
368       // Put the string into a string object to be able to use string::find
369       // for substring matching.  This is important because the not-found
370       // message does not use the entire string but is appended to the URL
371       // that SymSrv attempted to retrieve.
372       string desc(cba_event->desc);
373 
374       // desc_action maps strings (in desc) to boolean pointers that are to
375       // be set to true if the string matches.
376       struct desc_action {
377         const char *desc;  // The substring to match.
378         bool *action;      // On match, this pointer will be set to true.
379       };
380 
381       static const desc_action desc_actions[] = {
382         // When a DNS error occurs, it could be indiciative of network
383         // problems.
384         { "SYMSRV:  The server name or address could not be resolved\n",
385           &self->fail_dns_ },
386 
387         // This message is produced if no connection is opened.
388         { "SYMSRV:  A connection with the server could not be established\n",
389           &self->fail_timeout_ },
390 
391         // This message is produced if a connection is established but the
392         // server fails to respond to the HTTP request.
393         { "SYMSRV:  The operation timed out\n",
394           &self->fail_timeout_ },
395 
396         // This message is produced when the requested file is not found,
397         // even if one or more of the above messages are also produced.
398         // It's trapped to distinguish between not-found and unknown-failure
399         // conditions.  Note that this message will not be produced if a
400         // connection is established and the server begins to respond to the
401         // HTTP request but does not finish transmitting the file.
402         { " not found\n",
403           &self->fail_not_found_ }
404       };
405 
406       for (int desc_action_index = 0;
407            desc_action_index < sizeof(desc_actions) / sizeof(desc_action);
408            ++desc_action_index) {
409         if (desc.find(desc_actions[desc_action_index].desc) != string::npos) {
410           *(desc_actions[desc_action_index].action) = true;
411           break;
412         }
413       }
414 
415       break;
416     }
417   }
418 
419   // This function is a mere fly on the wall.  Treat everything as unhandled.
420   return FALSE;
421 }
422 
423 // static
SymFindFileInPathCallback(const char * filename,void * context)424 BOOL CALLBACK MSSymbolServerConverter::SymFindFileInPathCallback(
425     const char *filename, void *context) {
426   // FALSE ends the search, indicating that the located symbol file is
427   // satisfactory.
428   return FALSE;
429 }
430 
431 MSSymbolServerConverter::LocateResult
LocateAndConvertSymbolFile(const MissingSymbolInfo & missing,bool keep_symbol_file,bool keep_pe_file,string * converted_symbol_file,string * symbol_file,string * out_pe_file)432 MSSymbolServerConverter::LocateAndConvertSymbolFile(
433     const MissingSymbolInfo &missing,
434     bool keep_symbol_file,
435     bool keep_pe_file,
436     string *converted_symbol_file,
437     string *symbol_file,
438     string *out_pe_file) {
439   assert(converted_symbol_file);
440   converted_symbol_file->clear();
441   if (symbol_file) {
442     symbol_file->clear();
443   }
444 
445   string pdb_file;
446   LocateResult result = LocateSymbolFile(missing, &pdb_file);
447   if (result != LOCATE_SUCCESS) {
448     return result;
449   }
450 
451   if (symbol_file && keep_symbol_file) {
452     *symbol_file = pdb_file;
453   }
454 
455   // The conversion of a symbol file for a Windows 64-bit module requires
456   // loading of the executable file.  If there is no executable file, convert
457   // using only the PDB file.  Without an executable file, the conversion will
458   // fail for 64-bit modules but it should succeed for 32-bit modules.
459   string pe_file;
460   result = LocatePEFile(missing, &pe_file);
461   if (result != LOCATE_SUCCESS) {
462     fprintf(stderr, "WARNING: Could not download: %s\n", pe_file.c_str());
463   }
464 
465   if (out_pe_file && keep_pe_file) {
466     *out_pe_file = pe_file;
467   }
468 
469   // Conversion may fail because the file is corrupt.  If a broken file is
470   // kept in the local cache, LocateSymbolFile will not hit the network again
471   // to attempt to locate it.  To guard against problems like this, the
472   // symbol file in the local cache will be removed if conversion fails.
473   AutoDeleter pdb_deleter(pdb_file);
474   AutoDeleter pe_deleter(pe_file);
475 
476   // Be sure that it's a .pdb file, since we'll be replacing .pdb with .sym
477   // for the converted file's name.
478   string pdb_extension = pdb_file.substr(pdb_file.length() - 4);
479   // strcasecmp is called _stricmp here.
480   if (_stricmp(pdb_extension.c_str(), ".pdb") != 0) {
481     fprintf(stderr, "LocateAndConvertSymbolFile: "
482             "no .pdb extension for %s %s %s %s\n",
483             missing.debug_file.c_str(),
484             missing.debug_identifier.c_str(),
485             missing.version.c_str(),
486             pdb_file.c_str());
487     return LOCATE_FAILURE;
488   }
489 
490   PDBSourceLineWriter writer;
491   wstring pe_file_w;
492   if (!WindowsStringUtils::safe_mbstowcs(pe_file, &pe_file_w)) {
493     fprintf(stderr,
494             "LocateAndConvertSymbolFile: "
495                 "WindowsStringUtils::safe_mbstowcs failed for %s\n",
496             pe_file.c_str());
497     return LOCATE_FAILURE;
498   }
499   wstring pdb_file_w;
500   if (!WindowsStringUtils::safe_mbstowcs(pdb_file, &pdb_file_w)) {
501     fprintf(stderr,
502             "LocateAndConvertSymbolFile: "
503                 "WindowsStringUtils::safe_mbstowcs failed for %s\n",
504             pdb_file_w.c_str());
505     return LOCATE_FAILURE;
506   }
507   if (!writer.Open(pdb_file_w, PDBSourceLineWriter::PDB_FILE)) {
508     fprintf(stderr,
509             "ERROR: PDBSourceLineWriter::Open failed for %s %s %s %ws\n",
510             missing.debug_file.c_str(), missing.debug_identifier.c_str(),
511             missing.version.c_str(), pdb_file_w.c_str());
512     return LOCATE_FAILURE;
513   }
514   if (!writer.SetCodeFile(pe_file_w)) {
515     fprintf(stderr,
516             "ERROR: PDBSourceLineWriter::SetCodeFile failed for %s %s %s %ws\n",
517             missing.debug_file.c_str(), missing.debug_identifier.c_str(),
518             missing.version.c_str(), pe_file_w.c_str());
519     return LOCATE_FAILURE;
520   }
521 
522   *converted_symbol_file = pdb_file.substr(0, pdb_file.length() - 4) + ".sym";
523 
524   FILE *converted_output = NULL;
525 #if _MSC_VER >= 1400  // MSVC 2005/8
526   errno_t err;
527   if ((err = fopen_s(&converted_output, converted_symbol_file->c_str(), "w"))
528       != 0) {
529 #else  // _MSC_VER >= 1400
530   // fopen_s and errno_t were introduced in MSVC8.  Use fopen for earlier
531   // environments.  Don't use fopen with MSVC8 and later, because it's
532   // deprecated.  fopen does not provide reliable error codes, so just use
533   // -1 in the event of a failure.
534   int err;
535   if (!(converted_output = fopen(converted_symbol_file->c_str(), "w"))) {
536     err = -1;
537 #endif  // _MSC_VER >= 1400
538     fprintf(stderr, "LocateAndConvertSymbolFile: "
539             "fopen_s: error %d for %s %s %s %s\n",
540             err,
541             missing.debug_file.c_str(),
542             missing.debug_identifier.c_str(),
543             missing.version.c_str(),
544             converted_symbol_file->c_str());
545     return LOCATE_FAILURE;
546   }
547 
548   AutoDeleter sym_deleter(*converted_symbol_file);
549 
550   bool success = writer.WriteMap(converted_output);
551   fclose(converted_output);
552 
553   if (!success) {
554     fprintf(stderr, "LocateAndConvertSymbolFile: "
555             "PDBSourceLineWriter::WriteMap failed for %s %s %s %s\n",
556             missing.debug_file.c_str(),
557             missing.debug_identifier.c_str(),
558             missing.version.c_str(),
559             pdb_file.c_str());
560     return LOCATE_FAILURE;
561   }
562 
563   if (keep_symbol_file) {
564     pdb_deleter.Release();
565   }
566 
567   if (keep_pe_file) {
568     pe_deleter.Release();
569   }
570 
571   sym_deleter.Release();
572 
573   return LOCATE_SUCCESS;
574 }
575 
576 }  // namespace google_breakpad
577