Home | History | Annotate | Download | only in converter
      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 
     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 
    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:
    150   AutoSymSrv() : initialized_(false) {}
    151 
    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 
    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 
    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:
    188   explicit AutoDeleter(const string &path) : path_(path) {}
    189 
    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 
    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 
    209   void Release() {
    210     path_.clear();
    211   }
    212 
    213  private:
    214   string path_;
    215 };
    216 
    217 MSSymbolServerConverter::LocateResult
    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
    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
    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
    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
    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
    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