xref: /aosp_15_r20/external/google-breakpad/src/tools/windows/converter/ms_symbol_server_converter.cc (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
1 // Copyright 2007 Google LLC
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //     * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 //     * Neither the name of Google LLC nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 // ms_symbol_server_converter.cc: Obtain symbol files from a Microsoft
30 // symbol server, and convert them to Breakpad's dumped format.
31 //
32 // See ms_symbol_server_converter.h for documentation.
33 //
34 // Author: Mark Mentovai
35 
36 #ifdef HAVE_CONFIG_H
37 #include <config.h>  // Must come first
38 #endif
39 
40 #include <windows.h>
41 #include <dbghelp.h>
42 #include <pathcch.h>
43 
44 #include <cassert>
45 #include <cstdio>
46 
47 #include "tools/windows/converter/ms_symbol_server_converter.h"
48 #include "common/windows/pdb_source_line_writer.h"
49 #include "common/windows/pe_source_line_writer.h"
50 #include "common/windows/string_utils-inl.h"
51 
52 // SYMOPT_NO_PROMPTS is not defined in earlier platform SDKs.  Define it
53 // in that case, in the event that this code is used with a newer version
54 // of DbgHelp at runtime that recognizes the option.  The presence of this
55 // bit in the symbol options should not harm earlier versions of DbgHelp.
56 #ifndef SYMOPT_NO_PROMPTS
57 #define SYMOPT_NO_PROMPTS 0x00080000
58 #endif  // SYMOPT_NO_PROMPTS
59 
60 namespace {
61 
GetExeDirectory()62 std::wstring GetExeDirectory() {
63   wchar_t directory[MAX_PATH];
64 
65   // Get path to this process exe.
66   DWORD result = GetModuleFileName(/*hModule=*/nullptr, directory, MAX_PATH);
67   if (result <= 0 || result == MAX_PATH) {
68     fprintf(stderr,
69         "GetExeDirectory: failed to get path to process exe.\n");
70     return L"";
71   }
72   HRESULT hr = PathCchRemoveFileSpec(directory, result + 1);
73   if (hr != S_OK) {
74     fprintf(stderr,
75         "GetExeDirectory: failed to remove basename from path '%ls'.\n",
76         directory);
77     return L"";
78   }
79 
80   return std::wstring(directory);
81 }
82 
83 }  // namespace
84 
85 namespace google_breakpad {
86 
87 // Use sscanf_s if it is available, to quench the warning about scanf being
88 // deprecated.  Use scanf where sscanf_is not available.  Note that the
89 // parameters passed to sscanf and sscanf_s are only compatible as long as
90 // fields of type c, C, s, S, and [ are not used.
91 #if _MSC_VER >= 1400  // MSVC 2005/8
92 #define SSCANF sscanf_s
93 #else  // _MSC_VER >= 1400
94 #define SSCANF sscanf
95 #endif  // _MSC_VER >= 1400
96 
InitializeFromString(const string & identifier)97 bool GUIDOrSignatureIdentifier::InitializeFromString(
98     const string& identifier) {
99   type_ = TYPE_NONE;
100 
101   size_t length = identifier.length();
102 
103   if (length > 32 && length <= 40) {
104     // GUID
105     if (SSCANF(identifier.c_str(),
106                "%08X%04hX%04hX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%X",
107                &guid_.Data1, &guid_.Data2, &guid_.Data3,
108                &guid_.Data4[0], &guid_.Data4[1],
109                &guid_.Data4[2], &guid_.Data4[3],
110                &guid_.Data4[4], &guid_.Data4[5],
111                &guid_.Data4[6], &guid_.Data4[7],
112                &age_) != 12) {
113       return false;
114     }
115 
116     type_ = TYPE_GUID;
117   } else if (length > 8 && length <= 15) {
118     // Signature
119     if (SSCANF(identifier.c_str(), "%08X%x", &signature_, &age_) != 2) {
120       return false;
121     }
122 
123     type_ = TYPE_SIGNATURE;
124   } else {
125     return false;
126   }
127 
128   return true;
129 }
130 
131 #undef SSCANF
132 
MSSymbolServerConverter(const string & local_cache,const vector<string> & symbol_servers,bool trace_symsrv)133 MSSymbolServerConverter::MSSymbolServerConverter(
134     const string& local_cache,
135     const vector<string>& symbol_servers,
136     bool trace_symsrv)
137     : symbol_path_(),
138       fail_dns_(false),
139       fail_timeout_(false),
140       fail_http_https_redir_(false),
141       fail_not_found_(false),
142       trace_symsrv_(trace_symsrv) {
143   // Setting local_cache can be done without verifying that it exists because
144   // SymSrv will create it if it is missing - any creation failures will occur
145   // at that time, so there's nothing to check here, making it safe to
146   // assign this in the constructor.
147 
148   assert(symbol_servers.size() > 0);
149 
150 #if !defined(NDEBUG)
151   // These are characters that are interpreted as having special meanings in
152   // symbol_path_.
153   const char kInvalidCharacters[] = "*;";
154   assert(local_cache.find_first_of(kInvalidCharacters) == string::npos);
155 #endif  // !defined(NDEBUG)
156 
157   for (vector<string>::const_iterator symbol_server = symbol_servers.begin();
158        symbol_server != symbol_servers.end();
159        ++symbol_server) {
160     // The symbol path format is explained by
161     // http://msdn.microsoft.com/library/en-us/debug/base/using_symsrv.asp .
162     // "srv*" is the same as "symsrv*symsrv.dll*", which means that
163     // symsrv.dll is to be responsible for locating symbols.  symsrv.dll
164     // interprets the rest of the string as a series of symbol stores separated
165     // by '*'.  "srv*local_cache*symbol_server" means to check local_cache
166     // first for the symbol file, and if it is not found there, to check
167     // symbol_server.  Symbol files found on the symbol server will be placed
168     // in the local cache, decompressed.
169     //
170     // Multiple specifications in this format may be presented, separated by
171     // semicolons.
172 
173     assert((*symbol_server).find_first_of(kInvalidCharacters) == string::npos);
174     symbol_path_ += "srv*" + local_cache + "*" + *symbol_server + ";";
175   }
176 
177   // Strip the trailing semicolon.
178   symbol_path_.erase(symbol_path_.length() - 1);
179 }
180 
181 // A stack-based class that manages SymInitialize and SymCleanup calls.
182 class AutoSymSrv {
183  public:
AutoSymSrv()184   AutoSymSrv() : initialized_(false) {}
185 
~AutoSymSrv()186   ~AutoSymSrv() {
187     if (!Cleanup()) {
188       // Print the error message here, because destructors have no return
189       // value.
190       fprintf(stderr, "~AutoSymSrv: SymCleanup: error %lu\n", GetLastError());
191     }
192   }
193 
Initialize(HANDLE process,char * path,bool invade_process)194   bool Initialize(HANDLE process, char* path, bool invade_process) {
195     process_ = process;
196 
197     // TODO(nbilling): Figure out why dbghelp.dll is being loaded from
198     // system32/SysWOW64 before exe folder.
199 
200     // Attempt to locate and load dbghelp.dll beside the process exe. This is
201     // somewhat of a workaround to loader delay load behavior that is occurring
202     // when we call into symsrv APIs. dbghelp.dll must be loaded from beside
203     // the process exe so that we are guaranteed to find symsrv.dll alongside
204     // dbghelp.dll (a security requirement of dbghelp.dll) and so that the
205     // symsrv.dll file that is loaded has a symsrv.yes file alongside it (a
206     // requirement of symsrv.dll when accessing Microsoft-owned symbol
207     // servers).
208     // 'static local' because we don't care about the value but we need the
209     // initialization to happen exactly once.
210     static HMODULE dbghelp_module = [] () -> HMODULE {
211       std::wstring exe_directory = GetExeDirectory();
212       if (exe_directory.empty()) {
213         return nullptr;
214       }
215       std::wstring dbghelp_path = exe_directory + L"\\dbghelp.dll";
216       return LoadLibrary(dbghelp_path.c_str());
217     }();
218     if (dbghelp_module == nullptr) {
219       fprintf(stderr,
220           "AutoSymSrv::Initialize: failed to load dbghelp.dll beside exe.");
221       return false;
222     }
223 
224     initialized_ = SymInitialize(process, path, invade_process) == TRUE;
225     return initialized_;
226   }
227 
Cleanup()228   bool Cleanup() {
229     if (initialized_) {
230       if (SymCleanup(process_)) {
231         initialized_ = false;
232         return true;
233       }
234       return false;
235     }
236 
237     return true;
238   }
239 
240  private:
241   HANDLE process_;
242   bool initialized_;
243 };
244 
245 // A stack-based class that "owns" a pathname and deletes it when destroyed,
246 // unless told not to by having its Release() method called.  Early deletions
247 // are supported by calling Delete().
248 class AutoDeleter {
249  public:
AutoDeleter(const string & path)250   explicit AutoDeleter(const string& path) : path_(path) {}
251 
~AutoDeleter()252   ~AutoDeleter() {
253     int error;
254     if ((error = Delete()) != 0) {
255       // Print the error message here, because destructors have no return
256       // value.
257       fprintf(stderr, "~AutoDeleter: Delete: error %d for %s\n",
258               error, path_.c_str());
259     }
260   }
261 
Delete()262   int Delete() {
263     if (path_.empty())
264       return 0;
265 
266     int error = remove(path_.c_str());
267     Release();
268     return error;
269   }
270 
Release()271   void Release() {
272     path_.clear();
273   }
274 
275  private:
276   string path_;
277 };
278 
279 MSSymbolServerConverter::LocateResult
LocateFile(const string & debug_or_code_file,const string & debug_or_code_id,const string & version,string * file_name)280 MSSymbolServerConverter::LocateFile(const string& debug_or_code_file,
281                                     const string& debug_or_code_id,
282                                     const string& version,
283                                     string* file_name) {
284   assert(file_name);
285   file_name->clear();
286 
287   GUIDOrSignatureIdentifier identifier;
288   if (!identifier.InitializeFromString(debug_or_code_id)) {
289     fprintf(stderr,
290             "LocateFile: Unparseable identifier for %s %s %s\n",
291             debug_or_code_file.c_str(),
292             debug_or_code_id.c_str(),
293             version.c_str());
294     return LOCATE_FAILURE;
295   }
296 
297   HANDLE process = GetCurrentProcess();  // CloseHandle is not needed.
298   AutoSymSrv symsrv;
299   if (!symsrv.Initialize(process,
300                          const_cast<char*>(symbol_path_.c_str()),
301                          false)) {
302     fprintf(stderr, "LocateFile: SymInitialize: error %lu for %s %s %s\n",
303             GetLastError(),
304             debug_or_code_file.c_str(),
305             debug_or_code_id.c_str(),
306             version.c_str());
307     return LOCATE_FAILURE;
308   }
309 
310   if (!SymRegisterCallback64(process, SymCallback,
311                              reinterpret_cast<ULONG64>(this))) {
312     fprintf(stderr,
313             "LocateFile: SymRegisterCallback64: error %lu for %s %s %s\n",
314             GetLastError(),
315             debug_or_code_file.c_str(),
316             debug_or_code_id.c_str(),
317             version.c_str());
318     return LOCATE_FAILURE;
319   }
320 
321   // SYMOPT_DEBUG arranges for SymCallback to be called with additional
322   // debugging information.  This is used to determine the nature of failures.
323   DWORD options = SymGetOptions() | SYMOPT_DEBUG | SYMOPT_NO_PROMPTS |
324                   SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_SECURE;
325   SymSetOptions(options);
326 
327   // SymCallback will set these as needed inisde the SymFindFileInPath call.
328   fail_dns_ = false;
329   fail_timeout_ = false;
330   fail_not_found_ = false;
331 
332   // Do the lookup.
333   char path[MAX_PATH];
334   if (!SymFindFileInPath(
335           process, NULL,
336           const_cast<char*>(debug_or_code_file.c_str()),
337           const_cast<void*>(identifier.guid_or_signature_pointer()),
338           identifier.age(), 0,
339           identifier.type() == GUIDOrSignatureIdentifier::TYPE_GUID ?
340               SSRVOPT_GUIDPTR : SSRVOPT_DWORDPTR,
341           path, SymFindFileInPathCallback, this)) {
342     DWORD error = GetLastError();
343     if (error == ERROR_FILE_NOT_FOUND) {
344       // This can be returned for a number of reasons.  Use the crumbs
345       // collected by SymCallback to determine which one is relevant.
346 
347       // These errors are possibly transient.
348       if (fail_dns_ || fail_timeout_) {
349         return LOCATE_RETRY;
350       }
351 
352       if (fail_http_https_redir_) {
353         return LOCATE_HTTP_HTTPS_REDIR;
354       }
355 
356       // This is an authoritiative file-not-found message.
357       if (fail_not_found_) {
358         fprintf(stderr,
359                 "LocateFile: SymFindFileInPath: LOCATE_NOT_FOUND error "
360                 "for %s %s %s\n",
361                 debug_or_code_file.c_str(),
362                 debug_or_code_id.c_str(),
363                 version.c_str());
364         return LOCATE_NOT_FOUND;
365       }
366 
367       // If the error is FILE_NOT_FOUND but none of the known error
368       // conditions are matched, fall through to LOCATE_FAILURE.
369     }
370 
371     fprintf(stderr,
372             "LocateFile: SymFindFileInPath: error %lu for %s %s %s\n",
373             error,
374             debug_or_code_file.c_str(),
375             debug_or_code_id.c_str(),
376             version.c_str());
377     return LOCATE_FAILURE;
378   }
379 
380   // Making sure path is null-terminated.
381   path[MAX_PATH - 1] = '\0';
382 
383   // The AutoDeleter ensures that the file is only kept when returning
384   // LOCATE_SUCCESS.
385   AutoDeleter deleter(path);
386 
387   // Do the cleanup here even though it will happen when symsrv goes out of
388   // scope, to allow it to influence the return value.
389   if (!symsrv.Cleanup()) {
390     fprintf(stderr, "LocateFile: SymCleanup: error %lu for %s %s %s\n",
391             GetLastError(),
392             debug_or_code_file.c_str(),
393             debug_or_code_id.c_str(),
394             version.c_str());
395     return LOCATE_FAILURE;
396   }
397 
398   deleter.Release();
399 
400   printf("Downloaded: %s\n", path);
401   *file_name = path;
402   return LOCATE_SUCCESS;
403 }
404 
405 
406 MSSymbolServerConverter::LocateResult
LocatePEFile(const MissingSymbolInfo & missing,string * pe_file)407 MSSymbolServerConverter::LocatePEFile(const MissingSymbolInfo& missing,
408                                       string* pe_file) {
409   return LocateFile(missing.code_file, missing.code_identifier,
410                     missing.version, pe_file);
411 }
412 
413 MSSymbolServerConverter::LocateResult
LocateSymbolFile(const MissingSymbolInfo & missing,string * symbol_file)414 MSSymbolServerConverter::LocateSymbolFile(const MissingSymbolInfo& missing,
415                                           string* symbol_file) {
416   return LocateFile(missing.debug_file, missing.debug_identifier,
417                     missing.version, symbol_file);
418 }
419 
420 
421 // static
SymCallback(HANDLE process,ULONG action,ULONG64 data,ULONG64 context)422 BOOL CALLBACK MSSymbolServerConverter::SymCallback(HANDLE process,
423                                                    ULONG action,
424                                                    ULONG64 data,
425                                                    ULONG64 context) {
426   MSSymbolServerConverter* self =
427       reinterpret_cast<MSSymbolServerConverter*>(context);
428 
429   switch (action) {
430     case CBA_EVENT: {
431       IMAGEHLP_CBA_EVENT* cba_event =
432           reinterpret_cast<IMAGEHLP_CBA_EVENT*>(data);
433 
434       // Put the string into a string object to be able to use string::find
435       // for substring matching.  This is important because the not-found
436       // message does not use the entire string but is appended to the URL
437       // that SymSrv attempted to retrieve.
438       string desc(cba_event->desc);
439       if (self->trace_symsrv_) {
440         fprintf(stderr, "LocateFile: SymCallback: action desc '%s'\n",
441                 desc.c_str());
442       }
443 
444       // desc_action maps strings (in desc) to boolean pointers that are to
445       // be set to true if the string matches.
446       struct desc_action {
447         const char* desc;  // The substring to match.
448         bool* action;      // On match, this pointer will be set to true.
449       };
450 
451       static const desc_action desc_actions[] = {
452           // When a DNS error occurs, it could be indiciative of network
453           // problems.
454           {"SYMSRV:  The server name or address could not be resolved\n",
455            &self->fail_dns_},
456 
457           // This message is produced if no connection is opened.
458           {"SYMSRV:  A connection with the server could not be established\n",
459            &self->fail_timeout_},
460 
461           // This message is produced if a connection is established but the
462           // server fails to respond to the HTTP request.
463           {"SYMSRV:  The operation timed out\n", &self->fail_timeout_},
464 
465           // This message is produced if the server is redirecting us from http
466           // to https. When this happens SymSrv will fail and we need to use
467           // the https URL in our call-- we've made a mistake.
468           {"ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR\n",
469            &self->fail_http_https_redir_},
470 
471           // This message is produced when the requested file is not found,
472           // even if one or more of the above messages are also produced.
473           // It's trapped to distinguish between not-found and unknown-failure
474           // conditions.  Note that this message will not be produced if a
475           // connection is established and the server begins to respond to the
476           // HTTP request but does not finish transmitting the file.
477           {" not found\n", &self->fail_not_found_}};
478 
479       for (int desc_action_index = 0;
480            desc_action_index <
481            static_cast<int>(sizeof(desc_actions) / sizeof(desc_action));
482            ++desc_action_index) {
483         if (desc.find(desc_actions[desc_action_index].desc) != string::npos) {
484           *(desc_actions[desc_action_index].action) = true;
485           break;
486         }
487       }
488 
489       break;
490     }
491   }
492 
493   // This function is a mere fly on the wall.  Treat everything as unhandled.
494   return FALSE;
495 }
496 
497 // static
SymFindFileInPathCallback(const char * filename,void * context)498 BOOL CALLBACK MSSymbolServerConverter::SymFindFileInPathCallback(
499     const char* filename, void* context) {
500   // FALSE ends the search, indicating that the located symbol file is
501   // satisfactory.
502   return FALSE;
503 }
504 
505 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)506 MSSymbolServerConverter::LocateAndConvertSymbolFile(
507     const MissingSymbolInfo& missing,
508     bool keep_symbol_file,
509     bool keep_pe_file,
510     string* converted_symbol_file,
511     string* symbol_file,
512     string* out_pe_file) {
513   assert(converted_symbol_file);
514   converted_symbol_file->clear();
515   if (symbol_file) {
516     symbol_file->clear();
517   }
518 
519   string pdb_file;
520   LocateResult result = LocateSymbolFile(missing, &pdb_file);
521   if (result != LOCATE_SUCCESS) {
522     fprintf(stderr, "Fallback to PE-only symbol generation for: %s\n",
523         missing.debug_file.c_str());
524     return LocateAndConvertPEFile(missing, keep_pe_file, converted_symbol_file,
525         out_pe_file);
526   }
527 
528   if (symbol_file && keep_symbol_file) {
529     *symbol_file = pdb_file;
530   }
531 
532   // The conversion of a symbol file for a Windows 64-bit module requires
533   // loading of the executable file.  If there is no executable file, convert
534   // using only the PDB file.  Without an executable file, the conversion will
535   // fail for 64-bit modules but it should succeed for 32-bit modules.
536   string pe_file;
537   result = LocatePEFile(missing, &pe_file);
538   if (result != LOCATE_SUCCESS) {
539     fprintf(stderr, "WARNING: Could not download: %s\n", pe_file.c_str());
540   }
541 
542   if (out_pe_file && keep_pe_file) {
543     *out_pe_file = pe_file;
544   }
545 
546   // Conversion may fail because the file is corrupt.  If a broken file is
547   // kept in the local cache, LocateSymbolFile will not hit the network again
548   // to attempt to locate it.  To guard against problems like this, the
549   // symbol file in the local cache will be removed if conversion fails.
550   AutoDeleter pdb_deleter(pdb_file);
551   AutoDeleter pe_deleter(pe_file);
552 
553   // Be sure that it's a .pdb file, since we'll be replacing .pdb with .sym
554   // for the converted file's name.
555   string pdb_extension = pdb_file.substr(pdb_file.length() - 4);
556   // strcasecmp is called _stricmp here.
557   if (_stricmp(pdb_extension.c_str(), ".pdb") != 0) {
558     fprintf(stderr, "LocateAndConvertSymbolFile: "
559             "no .pdb extension for %s %s %s %s\n",
560             missing.debug_file.c_str(),
561             missing.debug_identifier.c_str(),
562             missing.version.c_str(),
563             pdb_file.c_str());
564     return LOCATE_FAILURE;
565   }
566 
567   PDBSourceLineWriter writer;
568   wstring pe_file_w;
569   if (!WindowsStringUtils::safe_mbstowcs(pe_file, &pe_file_w)) {
570     fprintf(stderr,
571             "LocateAndConvertSymbolFile: "
572                 "WindowsStringUtils::safe_mbstowcs failed for %s\n",
573             pe_file.c_str());
574     return LOCATE_FAILURE;
575   }
576   wstring pdb_file_w;
577   if (!WindowsStringUtils::safe_mbstowcs(pdb_file, &pdb_file_w)) {
578     fprintf(stderr,
579             "LocateAndConvertSymbolFile: "
580                 "WindowsStringUtils::safe_mbstowcs failed for %ws\n",
581             pdb_file_w.c_str());
582     return LOCATE_FAILURE;
583   }
584   if (!writer.Open(pdb_file_w, PDBSourceLineWriter::PDB_FILE)) {
585     fprintf(stderr,
586             "ERROR: PDBSourceLineWriter::Open failed for %s %s %s %ws\n",
587             missing.debug_file.c_str(), missing.debug_identifier.c_str(),
588             missing.version.c_str(), pdb_file_w.c_str());
589     return LOCATE_FAILURE;
590   }
591   if (!writer.SetCodeFile(pe_file_w)) {
592     fprintf(stderr,
593             "ERROR: PDBSourceLineWriter::SetCodeFile failed for %s %s %s %ws\n",
594             missing.debug_file.c_str(), missing.debug_identifier.c_str(),
595             missing.version.c_str(), pe_file_w.c_str());
596     return LOCATE_FAILURE;
597   }
598 
599   *converted_symbol_file = pdb_file.substr(0, pdb_file.length() - 4) + ".sym";
600 
601   FILE* converted_output = NULL;
602 #if _MSC_VER >= 1400  // MSVC 2005/8
603   errno_t err;
604   if ((err = fopen_s(&converted_output, converted_symbol_file->c_str(), "w"))
605     != 0) {
606 #else  // _MSC_VER >= 1400
607   // fopen_s and errno_t were introduced in MSVC8.  Use fopen for earlier
608   // environments.  Don't use fopen with MSVC8 and later, because it's
609   // deprecated.  fopen does not provide reliable error codes, so just use
610   // -1 in the event of a failure.
611   int err;
612   if (!(converted_output = fopen(converted_symbol_file->c_str(), "w"))) {
613     err = -1;
614 #endif  // _MSC_VER >= 1400
615     fprintf(stderr, "LocateAndConvertSymbolFile: "
616         "fopen_s: error %d for %s %s %s %s\n",
617         err,
618         missing.debug_file.c_str(),
619         missing.debug_identifier.c_str(),
620         missing.version.c_str(),
621         converted_symbol_file->c_str());
622     return LOCATE_FAILURE;
623   }
624 
625   AutoDeleter sym_deleter(*converted_symbol_file);
626 
627   bool success = writer.WriteSymbols(converted_output);
628   fclose(converted_output);
629 
630   if (!success) {
631     fprintf(stderr, "LocateAndConvertSymbolFile: "
632             "PDBSourceLineWriter::WriteMap failed for %s %s %s %s\n",
633             missing.debug_file.c_str(),
634             missing.debug_identifier.c_str(),
635             missing.version.c_str(),
636             pdb_file.c_str());
637     return LOCATE_FAILURE;
638   }
639 
640   if (keep_symbol_file) {
641     pdb_deleter.Release();
642   }
643 
644   if (keep_pe_file) {
645     pe_deleter.Release();
646   }
647 
648   sym_deleter.Release();
649 
650   return LOCATE_SUCCESS;
651 }
652 
653 MSSymbolServerConverter::LocateResult
654 MSSymbolServerConverter::LocateAndConvertPEFile(
655     const MissingSymbolInfo& missing,
656     bool keep_pe_file,
657     string* converted_symbol_file,
658     string* out_pe_file) {
659   assert(converted_symbol_file);
660   converted_symbol_file->clear();
661 
662   string pe_file;
663   MSSymbolServerConverter::LocateResult result = LocatePEFile(missing,
664       &pe_file);
665   if (result != LOCATE_SUCCESS) {
666     fprintf(stderr, "WARNING: Could not download: %s\n", pe_file.c_str());
667     return result;
668   }
669 
670   if (out_pe_file && keep_pe_file) {
671     *out_pe_file = pe_file;
672   }
673 
674   // Conversion may fail because the file is corrupt.  If a broken file is
675   // kept in the local cache, LocatePEFile will not hit the network again
676   // to attempt to locate it.  To guard against problems like this, the
677   // PE file in the local cache will be removed if conversion fails.
678   AutoDeleter pe_deleter(pe_file);
679 
680   // Be sure that it's a .exe or .dll file, since we'll be replacing extension
681   // with .sym for the converted file's name.
682   string pe_extension = pe_file.substr(pe_file.length() - 4);
683   // strcasecmp is called _stricmp here.
684   if (_stricmp(pe_extension.c_str(), ".exe") != 0 &&
685     _stricmp(pe_extension.c_str(), ".dll") != 0) {
686     fprintf(stderr, "LocateAndConvertPEFile: "
687         "no .dll/.exe extension for %s %s %s %s\n",
688         missing.debug_file.c_str(),
689         missing.debug_identifier.c_str(),
690         missing.version.c_str(),
691         pe_file.c_str());
692     return LOCATE_FAILURE;
693   }
694 
695   *converted_symbol_file = pe_file.substr(0, pe_file.length() - 4) + ".sym";
696 
697   FILE* converted_output = NULL;
698 #if _MSC_VER >= 1400  // MSVC 2005/8
699   errno_t err;
700   if ((err = fopen_s(&converted_output, converted_symbol_file->c_str(), "w"))
701       != 0) {
702 #else  // _MSC_VER >= 1400
703   // fopen_s and errno_t were introduced in MSVC8.  Use fopen for earlier
704   // environments.  Don't use fopen with MSVC8 and later, because it's
705   // deprecated.  fopen does not provide reliable error codes, so just use
706   // -1 in the event of a failure.
707   int err;
708   if (!(converted_output = fopen(converted_symbol_file->c_str(), "w"))) {
709     err = -1;
710 #endif  // _MSC_VER >= 1400
711     fprintf(stderr, "LocateAndConvertPEFile: "
712         "fopen_s: error %d for %s %s %s %s\n",
713         err,
714         missing.debug_file.c_str(),
715         missing.debug_identifier.c_str(),
716         missing.version.c_str(),
717         converted_symbol_file->c_str());
718     return LOCATE_FAILURE;
719   }
720   AutoDeleter sym_deleter(*converted_symbol_file);
721 
722   wstring pe_file_w;
723   if (!WindowsStringUtils::safe_mbstowcs(pe_file, &pe_file_w)) {
724     fprintf(stderr,
725         "LocateAndConvertPEFile: "
726         "WindowsStringUtils::safe_mbstowcs failed for %s\n",
727         pe_file.c_str());
728     return LOCATE_FAILURE;
729   }
730   PESourceLineWriter writer(pe_file_w);
731   PDBModuleInfo module_info;
732   if (!writer.GetModuleInfo(&module_info)) {
733     fprintf(stderr, "LocateAndConvertPEFile: "
734         "PESourceLineWriter::GetModuleInfo failed for %s %s %s %s\n",
735         missing.debug_file.c_str(),
736         missing.debug_identifier.c_str(),
737         missing.version.c_str(),
738         pe_file.c_str());
739     return LOCATE_FAILURE;
740   }
741   if (module_info.cpu.compare(L"x86_64") != 0) {
742     // This module is not x64 so we cannot generate Breakpad symbols from the
743     // PE alone. Don't delete PE-- no need to retry download.
744     pe_deleter.Release();
745     return LOCATE_FAILURE;
746   }
747 
748   bool success = writer.WriteSymbols(converted_output);
749   fclose(converted_output);
750 
751   if (!success) {
752     fprintf(stderr, "LocateAndConvertPEFile: "
753         "PESourceLineWriter::WriteMap failed for %s %s %s %s\n",
754         missing.debug_file.c_str(),
755         missing.debug_identifier.c_str(),
756         missing.version.c_str(),
757         pe_file.c_str());
758     return LOCATE_FAILURE;
759   }
760 
761   if (keep_pe_file) {
762     pe_deleter.Release();
763   }
764 
765   sym_deleter.Release();
766 
767   return LOCATE_SUCCESS;
768 }
769 
770 }  // namespace google_breakpad
771