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