xref: /aosp_15_r20/external/google-breakpad/src/tools/windows/converter_exe/converter.cc (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
1 // Copyright 2019 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 #pragma comment(lib, "winhttp.lib")
30 #pragma comment(lib, "wininet.lib")
31 #pragma comment(lib, "diaguids.lib")
32 #pragma comment(lib, "imagehlp.lib")
33 
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>  // Must come first
36 #endif
37 
38 #include <cassert>
39 #include <cstdio>
40 #include <ctime>
41 #include <map>
42 #include <regex>
43 #include <string>
44 #include <vector>
45 
46 #include "common/windows/http_upload.h"
47 #include "common/windows/string_utils-inl.h"
48 #include "common/windows/sym_upload_v2_protocol.h"
49 #include "tools/windows/converter/ms_symbol_server_converter.h"
50 #include "tools/windows/converter_exe/escaping.h"
51 #include "tools/windows/converter_exe/http_download.h"
52 #include "tools/windows/converter_exe/tokenizer.h"
53 
54 using strings::WebSafeBase64Unescape;
55 using strings::WebSafeBase64Escape;
56 
57 namespace {
58 
59 using std::map;
60 using std::string;
61 using std::vector;
62 using std::wstring;
63 using crash::HTTPDownload;
64 using crash::Tokenizer;
65 using google_breakpad::HTTPUpload;
66 using google_breakpad::MissingSymbolInfo;
67 using google_breakpad::MSSymbolServerConverter;
68 using google_breakpad::WindowsStringUtils;
69 
70 const char* kMissingStringDelimiters = "|";
71 const char* kLocalCachePath = "c:\\symbols";
72 const char* kNoExeMSSSServer = "http://msdl.microsoft.com/download/symbols/";
73 const wchar_t* kSymbolUploadTypeBreakpad = L"BREAKPAD";
74 const wchar_t* kSymbolUploadTypePE = L"PE";
75 const wchar_t* kSymbolUploadTypePDB = L"PDB";
76 const wchar_t* kConverterProductName = L"WinSymConv";
77 
78 // Windows stdio doesn't do line buffering.  Use this function to flush after
79 // writing to stdout and stderr so that a log will be available if the
80 // converter crashes.
FprintfFlush(FILE * file,const char * format,...)81 static int FprintfFlush(FILE* file, const char* format, ...) {
82   va_list arguments;
83   va_start(arguments, format);
84   int retval = vfprintf(file, format, arguments);
85   va_end(arguments);
86   fflush(file);
87   return retval;
88 }
89 
CurrentDateAndTime()90 static string CurrentDateAndTime() {
91   const string kUnknownDateAndTime = R"(????-??-?? ??:??:??)";
92 
93   time_t current_time;
94   time(&current_time);
95 
96   // localtime_s is safer but is only available in MSVC8.  Use localtime
97   // in earlier environments.
98   struct tm* time_pointer;
99 #if _MSC_VER >= 1400  // MSVC 2005/8
100   struct tm time_struct;
101   time_pointer =& time_struct;
102   if (localtime_s(time_pointer,& current_time) != 0) {
103     return kUnknownDateAndTime;
104   }
105 #else  // _MSC_VER >= 1400
106   time_pointer = localtime(&current_time);
107   if (!time_pointer) {
108     return kUnknownDateAndTime;
109   }
110 #endif  // _MSC_VER >= 1400
111 
112   char buffer[256];
113   if (!strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", time_pointer)) {
114     return kUnknownDateAndTime;
115   }
116 
117   return string(buffer);
118 }
119 
120 // ParseMissingString turns |missing_string| into a MissingSymbolInfo
121 // structure.  It returns true on success, and false if no such conversion
122 // is possible.
ParseMissingString(const string & missing_string,MissingSymbolInfo * missing_info)123 static bool ParseMissingString(const string& missing_string,
124                                MissingSymbolInfo* missing_info) {
125   assert(missing_info);
126 
127   vector<string> tokens;
128   Tokenizer::Tokenize(kMissingStringDelimiters, missing_string,& tokens);
129   if (tokens.size() != 5) {
130     return false;
131   }
132 
133   missing_info->debug_file = tokens[0];
134   missing_info->debug_identifier = tokens[1];
135   missing_info->version = tokens[2];
136   missing_info->code_file = tokens[3];
137   missing_info->code_identifier = tokens[4];
138 
139   return true;
140 }
141 
142 // StringMapToWStringMap takes each element in a map that associates
143 // (narrow) strings to strings and converts the keys and values to wstrings.
144 // Returns true on success and false on failure, printing an error message.
StringMapToWStringMap(const map<string,string> & smap,map<wstring,wstring> * wsmap)145 static bool StringMapToWStringMap(const map<string, string>& smap,
146                                   map<wstring, wstring>* wsmap) {
147   assert(wsmap);
148   wsmap->clear();
149 
150   for (map<string, string>::const_iterator iterator = smap.begin();
151        iterator != smap.end();
152        ++iterator) {
153     wstring key;
154     if (!WindowsStringUtils::safe_mbstowcs(iterator->first,& key)) {
155       FprintfFlush(stderr,
156                    "StringMapToWStringMap: safe_mbstowcs failed for key %s\n",
157                    iterator->first.c_str());
158       return false;
159     }
160 
161     wstring value;
162     if (!WindowsStringUtils::safe_mbstowcs(iterator->second,& value)) {
163       FprintfFlush(stderr, "StringMapToWStringMap: safe_mbstowcs failed "
164                            "for value %s\n",
165                    iterator->second.c_str());
166       return false;
167     }
168 
169     wsmap->insert(make_pair(key, value));
170   }
171 
172   return true;
173 }
174 
175 // MissingSymbolInfoToParameters turns a MissingSymbolInfo structure into a
176 // map of parameters suitable for passing to HTTPDownload or HTTPUpload.
177 // Returns true on success and false on failure, printing an error message.
MissingSymbolInfoToParameters(const MissingSymbolInfo & missing_info,map<wstring,wstring> * wparameters)178 static bool MissingSymbolInfoToParameters(const MissingSymbolInfo& missing_info,
179                                           map<wstring, wstring>* wparameters) {
180   assert(wparameters);
181 
182   map<string, string> parameters;
183   string encoded_param;
184   // Indicate the params are encoded.
185   parameters["encoded"] = "true";  // The string value here does not matter.
186 
187   WebSafeBase64Escape(missing_info.code_file,& encoded_param);
188   parameters["code_file"] = encoded_param;
189 
190   WebSafeBase64Escape(missing_info.code_identifier,& encoded_param);
191   parameters["code_identifier"] = encoded_param;
192 
193   WebSafeBase64Escape(missing_info.debug_file,& encoded_param);
194   parameters["debug_file"] = encoded_param;
195 
196   WebSafeBase64Escape(missing_info.debug_identifier,& encoded_param);
197   parameters["debug_identifier"] = encoded_param;
198 
199   if (!missing_info.version.empty()) {
200     // The version is optional.
201     WebSafeBase64Escape(missing_info.version,& encoded_param);
202     parameters["version"] = encoded_param;
203   }
204 
205   WebSafeBase64Escape("WinSymConv",& encoded_param);
206   parameters["product"] = encoded_param;
207 
208   if (!StringMapToWStringMap(parameters, wparameters)) {
209     // StringMapToWStringMap will have printed an error.
210     return false;
211   }
212 
213   return true;
214 }
215 
216 // UploadSymbolFile sends |converted_file| as identified by |debug_file| and
217 // |debug_identifier|, to the symbol server rooted at |upload_symbol_url|.
218 // Returns true on success and false on failure, printing an error message.
UploadSymbolFile(const wstring & upload_symbol_url,const wstring & api_key,const string & debug_file,const string & debug_identifier,const string & symbol_file,const wstring & symbol_type)219 static bool UploadSymbolFile(const wstring& upload_symbol_url,
220                              const wstring& api_key,
221                              const string& debug_file,
222                              const string& debug_identifier,
223                              const string& symbol_file,
224                              const wstring& symbol_type) {
225   wstring debug_file_w;
226   if (!WindowsStringUtils::safe_mbstowcs(debug_file, &debug_file_w)) {
227     FprintfFlush(stderr, "UploadSymbolFile: safe_mbstowcs failed for %s\n",
228                  symbol_file.c_str());
229     return false;
230   }
231 
232   wstring debug_id_w;
233   if (!WindowsStringUtils::safe_mbstowcs(debug_identifier, &debug_id_w)) {
234     FprintfFlush(stderr, "UploadSymbolFile: safe_mbstowcs failed for %s\n",
235                  symbol_file.c_str());
236     return false;
237   }
238 
239   wstring symbol_file_w;
240   if (!WindowsStringUtils::safe_mbstowcs(symbol_file, &symbol_file_w)) {
241     FprintfFlush(stderr, "UploadSymbolFile: safe_mbstowcs failed for %s\n",
242                  symbol_file.c_str());
243     return false;
244   }
245 
246   int timeout_ms = 60 * 1000;
247   FprintfFlush(stderr, "Uploading %s\n", symbol_file.c_str());
248   if (!google_breakpad::SymUploadV2ProtocolSend(
249           upload_symbol_url.c_str(), api_key.c_str(), &timeout_ms, debug_file_w,
250           debug_id_w, symbol_file_w, symbol_type, kConverterProductName,
251           /*force=*/true)) {
252     FprintfFlush(stderr,
253                  "UploadSymbolFile: HTTPUpload::SendRequest failed "
254                  "for %s %s\n",
255                  debug_file.c_str(), debug_identifier.c_str());
256     return false;
257   }
258 
259   return true;
260 }
261 
262 // SendFetchFailedPing informs the symbol server based at
263 // |fetch_symbol_failure_url| that the symbol file identified by
264 // |missing_info| could authoritatively not be located.  Returns
265 // true on success and false on failure.
SendFetchFailedPing(const wstring & fetch_symbol_failure_url,const MissingSymbolInfo & missing_info)266 static bool SendFetchFailedPing(const wstring& fetch_symbol_failure_url,
267                                 const MissingSymbolInfo& missing_info) {
268   map<wstring, wstring> parameters;
269   if (!MissingSymbolInfoToParameters(missing_info,& parameters)) {
270     // MissingSymbolInfoToParameters or a callee will have printed an error.
271     return false;
272   }
273 
274   string content;
275   if (!HTTPDownload::Download(fetch_symbol_failure_url,
276                              & parameters,
277                              & content,
278                               NULL)) {
279     FprintfFlush(stderr, "SendFetchFailedPing: HTTPDownload::Download failed "
280                          "for %s %s %s\n",
281                  missing_info.debug_file.c_str(),
282                  missing_info.debug_identifier.c_str(),
283                  missing_info.version.c_str());
284     return false;
285   }
286 
287   return true;
288 }
289 
290 // Returns true if it's safe to make an external request for the symbol
291 // file described in missing_info. It's considered safe to make an
292 // external request unless the symbol file's debug_file string matches
293 // the given blacklist regular expression.
294 // The debug_file name is used from the MissingSymbolInfo struct,
295 // matched against the blacklist_regex.
SafeToMakeExternalRequest(const MissingSymbolInfo & missing_info,std::regex blacklist_regex)296 static bool SafeToMakeExternalRequest(const MissingSymbolInfo& missing_info,
297                                       std::regex blacklist_regex) {
298   string file_name = missing_info.debug_file;
299   // Use regex_search because we want to match substrings.
300   if (std::regex_search(file_name, blacklist_regex)) {
301     FprintfFlush(stderr, "Not safe to make external request for file %s\n",
302                  file_name.c_str());
303     return false;
304   }
305 
306   return true;
307 }
308 
309 // Converter options derived from command line parameters.
310 struct ConverterOptions {
ConverterOptions__anon2e878b9f0111::ConverterOptions311   ConverterOptions()
312       : report_fetch_failures(true), trace_symsrv(false), keep_files(false) {}
313 
~ConverterOptions__anon2e878b9f0111::ConverterOptions314   ~ConverterOptions() {
315   }
316 
317   // Names of MS Symbol Supplier Servers that are internal to Google, and may
318   // have symbols for any request.
319   vector<string> full_internal_msss_servers;
320 
321   // Names of MS Symbol Supplier Servers that are internal to Google, and
322   // shouldn't be checked for symbols for any .exe files.
323   vector<string> full_external_msss_servers;
324 
325   // Names of MS Symbol Supplier Servers that are external to Google, and may
326   // have symbols for any request.
327   vector<string> no_exe_internal_msss_servers;
328 
329   // Names of MS Symbol Supplier Servers that are external to Google, and
330   // shouldn't be checked for symbols for any .exe files.
331   vector<string> no_exe_external_msss_servers;
332 
333   // Temporary local storage for symbols.
334   string local_cache_path;
335 
336   // URL for uploading symbols.
337   wstring upload_symbols_url;
338 
339   // API key to use when uploading symbols.
340   wstring api_key;
341 
342   // URL to fetch list of missing symbols.
343   wstring missing_symbols_url;
344 
345   // URL to report symbol fetch failure.
346   wstring fetch_symbol_failure_url;
347 
348   // Are symbol fetch failures reported.
349   bool report_fetch_failures;
350 
351   // File containing the list of missing symbols.  Fetch failures are not
352   // reported if such file is provided.
353   string missing_symbols_file;
354 
355   // Regex used to blacklist files to prevent external symbol requests.
356   // Owned and cleaned up by this struct.
357   std::regex blacklist_regex;
358 
359   // If set then SymSrv callbacks are logged to stderr.
360   bool trace_symsrv;
361 
362   // If set then Breakpad/PE/PDB files won't be deleted after processing.
363   bool keep_files;
364 
365  private:
366   // DISABLE_COPY_AND_ASSIGN
367   ConverterOptions(const ConverterOptions&);
368   ConverterOptions& operator=(const ConverterOptions&);
369 };
370 
371 // ConverMissingSymbolFile takes a single MissingSymbolInfo structure and
372 // attempts to locate it from the symbol servers provided in the
373 // |options.*_msss_servers| arguments.  "Full" servers are those that will be
374 // queried for all symbol files; "No-EXE" servers will only be queried for
375 // modules whose missing symbol data indicates are not main program executables.
376 // Results will be sent to the |options.upload_symbols_url| on success or
377 // |options.fetch_symbol_failure_url| on failure, and the local cache will be
378 // stored at |options.local_cache_path|.  Because nothing can be done even in
379 // the event of a failure, this function returns no value, although it
380 // may result in error messages being printed.
ConvertMissingSymbolFile(const MissingSymbolInfo & missing_info,const ConverterOptions & options)381 static void ConvertMissingSymbolFile(const MissingSymbolInfo& missing_info,
382                                      const ConverterOptions& options) {
383   string time_string = CurrentDateAndTime();
384   FprintfFlush(stdout, "converter: %s: attempting %s %s %s\n",
385                time_string.c_str(),
386                missing_info.debug_file.c_str(),
387                missing_info.debug_identifier.c_str(),
388                missing_info.version.c_str());
389 
390   // The first lookup is always to internal symbol servers.
391   // Always ask the symbol servers identified as "full."
392   vector<string> msss_servers = options.full_internal_msss_servers;
393 
394   // If the file is not an .exe file, also ask an additional set of symbol
395   // servers, such as Microsoft's public symbol server.
396   bool is_exe = false;
397 
398   if (missing_info.code_file.length() >= 4) {
399     string code_extension =
400         missing_info.code_file.substr(missing_info.code_file.size() - 4);
401 
402     // Firefox is a special case: .dll-only servers should be consulted for
403     // its symbols.  This enables us to get its symbols from Mozilla's
404     // symbol server when crashes occur in Google extension code hosted by a
405     // Firefox process.
406     if (_stricmp(code_extension.c_str(), ".exe") == 0 &&
407         _stricmp(missing_info.code_file.c_str(), "firefox.exe") != 0) {
408       is_exe = true;
409     }
410   }
411 
412   if (!is_exe) {
413     msss_servers.insert(msss_servers.end(),
414                         options.no_exe_internal_msss_servers.begin(),
415                         options.no_exe_internal_msss_servers.end());
416   }
417 
418   // If there are any suitable internal symbol servers, make a request.
419   MSSymbolServerConverter::LocateResult located =
420       MSSymbolServerConverter::LOCATE_FAILURE;
421   string converted_file;
422   string symbol_file;
423   string pe_file;
424   if (msss_servers.size() > 0) {
425     // Attempt to fetch the symbol file and convert it.
426     FprintfFlush(stderr, "Making internal request for %s (%s)\n",
427                    missing_info.debug_file.c_str(),
428                    missing_info.debug_identifier.c_str());
429     MSSymbolServerConverter converter(options.local_cache_path, msss_servers,
430                                       options.trace_symsrv);
431     located = converter.LocateAndConvertSymbolFile(
432         missing_info,
433         /*keep_symbol_file=*/true,
434         /*keep_pe_file=*/true, &converted_file, &symbol_file, &pe_file);
435     switch (located) {
436       case MSSymbolServerConverter::LOCATE_SUCCESS:
437         FprintfFlush(stderr, "LocateResult = LOCATE_SUCCESS\n");
438         // Upload it. Don't bother checking the return value. If this
439         // succeeds, it should disappear from the missing symbol list.
440         // If it fails, something will print an error message indicating
441         // the cause of the failure, and the item will remain on the
442         // missing symbol list.
443         UploadSymbolFile(options.upload_symbols_url, options.api_key,
444                          missing_info.debug_file, missing_info.debug_identifier,
445                          converted_file, kSymbolUploadTypeBreakpad);
446         if (!options.keep_files)
447           remove(converted_file.c_str());
448 
449         // Upload PDB/PE if we have them
450         if (!symbol_file.empty()) {
451           UploadSymbolFile(options.upload_symbols_url, options.api_key,
452                            missing_info.debug_file,
453                            missing_info.debug_identifier, symbol_file,
454                            kSymbolUploadTypePDB);
455           if (!options.keep_files)
456             remove(symbol_file.c_str());
457         }
458         if (!pe_file.empty()) {
459           UploadSymbolFile(options.upload_symbols_url, options.api_key,
460                            missing_info.code_file,
461                            missing_info.debug_identifier, pe_file,
462                            kSymbolUploadTypePE);
463           if (!options.keep_files)
464             remove(pe_file.c_str());
465         }
466 
467         // Note: this does leave some directories behind that could be
468         // cleaned up.  The directories inside options.local_cache_path for
469         // debug_file/debug_identifier can be removed at this point.
470         break;
471 
472       case MSSymbolServerConverter::LOCATE_NOT_FOUND:
473         FprintfFlush(stderr, "LocateResult = LOCATE_NOT_FOUND\n");
474         // The symbol file definitively did not exist. Fall through,
475         // so we can attempt an external query if it's safe to do so.
476         break;
477 
478       case MSSymbolServerConverter::LOCATE_RETRY:
479         FprintfFlush(stderr, "LocateResult = LOCATE_RETRY\n");
480         // Fall through in case we should make an external request.
481         // If not, or if an external request fails in the same way,
482         // we'll leave the entry in the symbol file list and
483         // try again on a future pass.  Print a message so that there's
484         // a record.
485         break;
486 
487       case MSSymbolServerConverter::LOCATE_HTTP_HTTPS_REDIR:
488         FprintfFlush(
489             stderr,
490             "LocateResult = LOCATE_HTTP_HTTPS_REDIR\n"
491             "One of the specified URLs is using HTTP, which causes a redirect "
492             "from the server to HTTPS, which causes the SymSrv lookup to "
493             "fail.\n"
494             "This URL must be replaced with the correct HTTPS URL.\n");
495         break;
496 
497       case MSSymbolServerConverter::LOCATE_FAILURE:
498         FprintfFlush(stderr, "LocateResult = LOCATE_FAILURE\n");
499         // LocateAndConvertSymbolFile printed an error message.
500         break;
501 
502       default:
503         FprintfFlush(
504             stderr,
505             "FATAL: Unexpected return value '%d' from "
506             "LocateAndConvertSymbolFile()\n",
507             located);
508         assert(0);
509         break;
510     }
511   } else {
512     // No suitable internal symbol servers.  This is fine because the converter
513     // is mainly used for downloading and converting of external symbols.
514   }
515 
516   // Make a request to an external server if the internal request didn't
517   // succeed, and it's safe to do so.
518   if (located != MSSymbolServerConverter::LOCATE_SUCCESS &&
519       SafeToMakeExternalRequest(missing_info, options.blacklist_regex)) {
520     msss_servers = options.full_external_msss_servers;
521     if (!is_exe) {
522       msss_servers.insert(msss_servers.end(),
523                           options.no_exe_external_msss_servers.begin(),
524                           options.no_exe_external_msss_servers.end());
525     }
526     if (msss_servers.size() > 0) {
527       FprintfFlush(stderr, "Making external request for %s (%s)\n",
528                    missing_info.debug_file.c_str(),
529                    missing_info.debug_identifier.c_str());
530       MSSymbolServerConverter external_converter(
531           options.local_cache_path, msss_servers, options.trace_symsrv);
532       located = external_converter.LocateAndConvertSymbolFile(
533           missing_info,
534           /*keep_symbol_file=*/true,
535           /*keep_pe_file=*/true, &converted_file, &symbol_file, &pe_file);
536     } else {
537       FprintfFlush(stderr, "ERROR: No suitable external symbol servers.\n");
538     }
539   }
540 
541   // Final handling for this symbol file is based on the result from the
542   // external request (if performed above), or on the result from the
543   // previous internal lookup.
544   switch (located) {
545     case MSSymbolServerConverter::LOCATE_SUCCESS:
546       FprintfFlush(stderr, "LocateResult = LOCATE_SUCCESS\n");
547       // Upload it. Don't bother checking the return value. If this
548       // succeeds, it should disappear from the missing symbol list.
549       // If it fails, something will print an error message indicating
550       // the cause of the failure, and the item will remain on the
551       // missing symbol list.
552       UploadSymbolFile(options.upload_symbols_url, options.api_key,
553                        missing_info.debug_file, missing_info.debug_identifier,
554                        converted_file, kSymbolUploadTypeBreakpad);
555       if (!options.keep_files)
556         remove(converted_file.c_str());
557 
558       // Upload PDB/PE if we have them
559       if (!symbol_file.empty()) {
560         UploadSymbolFile(options.upload_symbols_url, options.api_key,
561                          missing_info.debug_file, missing_info.debug_identifier,
562                          symbol_file, kSymbolUploadTypePDB);
563         if (!options.keep_files)
564           remove(symbol_file.c_str());
565       }
566       if (!pe_file.empty()) {
567         UploadSymbolFile(options.upload_symbols_url, options.api_key,
568                          missing_info.code_file, missing_info.debug_identifier,
569                          pe_file, kSymbolUploadTypePE);
570         if (!options.keep_files)
571           remove(pe_file.c_str());
572       }
573 
574       // Note: this does leave some directories behind that could be
575       // cleaned up.  The directories inside options.local_cache_path for
576       // debug_file/debug_identifier can be removed at this point.
577       break;
578 
579     case MSSymbolServerConverter::LOCATE_NOT_FOUND:
580       // The symbol file definitively didn't exist.  Inform the server.
581       // If this fails, something will print an error message indicating
582       // the cause of the failure, but there's really nothing more to
583       // do.  If this succeeds, the entry should be removed from the
584       // missing symbols list.
585       if (!options.report_fetch_failures) {
586         FprintfFlush(stderr, "SendFetchFailedPing skipped\n");
587       } else if (SendFetchFailedPing(options.fetch_symbol_failure_url,
588                                      missing_info)) {
589         FprintfFlush(stderr, "SendFetchFailedPing succeeded\n");
590       } else {
591         FprintfFlush(stderr, "SendFetchFailedPing failed\n");
592       }
593       break;
594 
595     case MSSymbolServerConverter::LOCATE_RETRY:
596       FprintfFlush(stderr, "LocateResult = LOCATE_RETRY\n");
597       // Nothing to do but leave the entry in the symbol file list and
598       // try again on a future pass.  Print a message so that there's
599       // a record.
600       FprintfFlush(stderr, "ConvertMissingSymbolFile: deferring retry "
601                            "for %s %s %s\n",
602                    missing_info.debug_file.c_str(),
603                    missing_info.debug_identifier.c_str(),
604                    missing_info.version.c_str());
605       break;
606 
607     case MSSymbolServerConverter::LOCATE_HTTP_HTTPS_REDIR:
608       FprintfFlush(
609           stderr,
610           "LocateResult = LOCATE_HTTP_HTTPS_REDIR\n"
611           "One of the specified URLs is using HTTP, which causes a redirect "
612           "from the server to HTTPS, which causes the SymSrv lookup to fail.\n"
613           "This URL must be replaced with the correct HTTPS URL.\n");
614       break;
615 
616     case MSSymbolServerConverter::LOCATE_FAILURE:
617       FprintfFlush(stderr, "LocateResult = LOCATE_FAILURE\n");
618       // LocateAndConvertSymbolFile printed an error message.
619 
620       // This is due to a bad debug file name, so fetch failed.
621       if (!options.report_fetch_failures) {
622         FprintfFlush(stderr, "SendFetchFailedPing skipped\n");
623       } else if (SendFetchFailedPing(options.fetch_symbol_failure_url,
624                                      missing_info)) {
625         FprintfFlush(stderr, "SendFetchFailedPing succeeded\n");
626       } else {
627         FprintfFlush(stderr, "SendFetchFailedPing failed\n");
628       }
629       break;
630 
631     default:
632       FprintfFlush(
633           stderr,
634           "FATAL: Unexpected return value '%d' from "
635           "LocateAndConvertSymbolFile()\n",
636           located);
637       assert(0);
638       break;
639   }
640 }
641 
642 
643 // Reads the contents of file |file_name| and populates |contents|.
644 // Returns true on success.
ReadFile(string file_name,string * contents)645 static bool ReadFile(string file_name, string* contents) {
646   char buffer[1024 * 8];
647   FILE* fp = fopen(file_name.c_str(), "rt");
648   if (!fp) {
649     return false;
650   }
651   contents->clear();
652   while (fgets(buffer, sizeof(buffer), fp) != NULL) {
653     contents->append(buffer);
654   }
655   fclose(fp);
656   return true;
657 }
658 
659 // ConvertMissingSymbolsList obtains a missing symbol list from
660 // |options.missing_symbols_url| or |options.missing_symbols_file| and calls
661 // ConvertMissingSymbolFile for each missing symbol file in the list.
ConvertMissingSymbolsList(const ConverterOptions & options)662 static bool ConvertMissingSymbolsList(const ConverterOptions& options) {
663   // Set param to indicate requesting for encoded response.
664   map<wstring, wstring> parameters;
665   parameters[L"product"] = kConverterProductName;
666   parameters[L"encoded"] = L"true";
667   // Get the missing symbol list.
668   string missing_symbol_list;
669   if (!options.missing_symbols_file.empty()) {
670     if (!ReadFile(options.missing_symbols_file,& missing_symbol_list)) {
671       return false;
672     }
673   } else if (!HTTPDownload::Download(options.missing_symbols_url,& parameters,
674                                     & missing_symbol_list, NULL)) {
675     return false;
676   }
677 
678   // Tokenize the content into a vector.
679   vector<string> missing_symbol_lines;
680   Tokenizer::Tokenize("\n", missing_symbol_list,& missing_symbol_lines);
681 
682   FprintfFlush(stderr, "Found %d missing symbol files in list.\n",
683                missing_symbol_lines.size() - 1);  // last line is empty.
684   int convert_attempts = 0;
685   for (vector<string>::const_iterator iterator = missing_symbol_lines.begin();
686        iterator != missing_symbol_lines.end();
687        ++iterator) {
688     // Decode symbol line.
689     const string& encoded_line = *iterator;
690     // Skip lines that are blank.
691     if (encoded_line.empty()) {
692       continue;
693     }
694 
695     string line;
696     if (!WebSafeBase64Unescape(encoded_line,& line)) {
697       // If decoding fails, assume the line is not encoded.
698       // This is helpful when the program connects to a debug server without
699       // encoding.
700       line = encoded_line;
701     }
702 
703     FprintfFlush(stderr, "\nLine: %s\n", line.c_str());
704 
705     // Turn each element into a MissingSymbolInfo structure.
706     MissingSymbolInfo missing_info;
707     if (!ParseMissingString(line,& missing_info)) {
708       FprintfFlush(stderr, "ConvertMissingSymbols: ParseMissingString failed "
709                            "for %s from %ws\n",
710                    line.c_str(), options.missing_symbols_url.c_str());
711       continue;
712     }
713 
714     ++convert_attempts;
715     ConvertMissingSymbolFile(missing_info, options);
716   }
717 
718   // Say something reassuring, since ConvertMissingSymbolFile was never called
719   // and therefore never reported any progress.
720   if (convert_attempts == 0) {
721     string current_time = CurrentDateAndTime();
722     FprintfFlush(stdout, "converter: %s: nothing to convert\n",
723                  current_time.c_str());
724   }
725 
726   return true;
727 }
728 
729 // usage prints the usage message.  It returns 1 as a convenience, to be used
730 // as a return value from main.
usage(const char * program_name)731 static int usage(const char* program_name) {
732   FprintfFlush(
733       stderr,
734       "usage: %s [options]\n"
735       "    -f  <full_msss_server>     MS servers to ask for all symbols\n"
736       "    -n  <no_exe_msss_server>   same, but prevent asking for EXEs\n"
737       "    -l  <local_cache_path>     Temporary local storage for symbols\n"
738       "    -s  <upload_url>           URL for uploading symbols\n"
739       "    -k  <api_key>              API key to use when uploading symbols\n"
740       "    -m  <missing_symbols_url>  URL to fetch list of missing symbols\n"
741       "    -mf <missing_symbols_file> File containing the list of missing\n"
742       "                               symbols.  Fetch failures are not\n"
743       "                               reported if such file is provided.\n"
744       "    -t  <fetch_failure_url>    URL to report symbol fetch failure\n"
745       "    -b  <regex>                Regex used to blacklist files to\n"
746       "                               prevent external symbol requests\n"
747       "    -tss                       If set then SymSrv callbacks will be\n"
748       "                               traced to stderr.\n"
749       "    -keep-files                If set then don't delete Breakpad/PE/\n"
750       "                               PDB files after conversion.\n"
751       " Note that any server specified by -f or -n that starts with \\filer\n"
752       " will be treated as internal, and all others as external.\n",
753       program_name);
754 
755   return 1;
756 }
757 
758 // "Internal" servers consist only of those whose names start with
759 // the literal string "\\filer\".
IsInternalServer(const string & server_name)760 static bool IsInternalServer(const string& server_name) {
761   if (server_name.find("\\\\filer\\") == 0) {
762     return true;
763   }
764   return false;
765 }
766 
767 // Adds a server with the given name to the list of internal or external
768 // servers, as appropriate.
AddServer(const string & server_name,vector<string> * internal_servers,vector<string> * external_servers)769 static void AddServer(const string& server_name,
770                       vector<string>* internal_servers,
771                       vector<string>* external_servers) {
772   if (IsInternalServer(server_name)) {
773     internal_servers->push_back(server_name);
774   } else {
775     external_servers->push_back(server_name);
776   }
777 }
778 
779 }  // namespace
780 
main(int argc,char ** argv)781 int main(int argc, char** argv) {
782   string time_string = CurrentDateAndTime();
783   FprintfFlush(stdout, "converter: %s: starting\n", time_string.c_str());
784 
785   ConverterOptions options;
786   options.report_fetch_failures = true;
787 
788   string blacklist_regex_str;
789   bool have_any_msss_servers = false;
790   for (int argi = 1; argi < argc; argi++) {
791     string option = argv[argi];
792     if (option == "-tss") {
793       printf("Tracing SymSrv callbacks to stderr.\n");
794       options.trace_symsrv = true;
795       continue;
796     } else if (option == "-keep-files") {
797       printf("Keeping Breakpad/PE/PDB files after conversion.\n");
798       options.keep_files = true;
799       continue;
800     }
801 
802     string value = argv[++argi];
803     if (option == "-f") {
804       AddServer(value,& options.full_internal_msss_servers,
805                & options.full_external_msss_servers);
806       have_any_msss_servers = true;
807     } else if (option == "-n") {
808       AddServer(value,& options.no_exe_internal_msss_servers,
809                & options.no_exe_external_msss_servers);
810       have_any_msss_servers = true;
811     } else if (option == "-l") {
812       if (!options.local_cache_path.empty()) {
813         return usage(argv[0]);
814       }
815       options.local_cache_path = value;
816     } else if (option == "-s") {
817       if (!WindowsStringUtils::safe_mbstowcs(value,
818                                             & options.upload_symbols_url)) {
819         FprintfFlush(stderr, "main: safe_mbstowcs failed for %s\n",
820                      value.c_str());
821         return 1;
822       }
823     } else if (option == "-k") {
824       if (!WindowsStringUtils::safe_mbstowcs(value, &options.api_key)) {
825         FprintfFlush(stderr, "main: safe_mbstowcs failed for %s\n",
826                      value.c_str());
827         return 1;
828       }
829     } else if (option == "-m") {
830       if (!WindowsStringUtils::safe_mbstowcs(value,
831                                             & options.missing_symbols_url)) {
832         FprintfFlush(stderr, "main: safe_mbstowcs failed for %s\n",
833                      value.c_str());
834         return 1;
835       }
836     } else if (option == "-mf") {
837       options.missing_symbols_file = value;
838       printf("Getting the list of missing symbols from a file.  Fetch failures"
839              " will not be reported.\n");
840       options.report_fetch_failures = false;
841     } else if (option == "-t") {
842       if (!WindowsStringUtils::safe_mbstowcs(
843           value,
844          & options.fetch_symbol_failure_url)) {
845         FprintfFlush(stderr, "main: safe_mbstowcs failed for %s\n",
846                      value.c_str());
847         return 1;
848       }
849     } else if (option == "-b") {
850       blacklist_regex_str = value;
851     } else {
852       return usage(argv[0]);
853     }
854   }
855 
856   if (blacklist_regex_str.empty()) {
857     FprintfFlush(stderr, "No blacklist specified.\n");
858     return usage(argv[0]);
859   }
860 
861   // Compile the blacklist regular expression for later use.
862   options.blacklist_regex = std::regex(blacklist_regex_str.c_str(),
863       std::regex_constants::icase);
864 
865   // Set the defaults.  If the user specified any MSSS servers, don't use
866   // any default.
867   if (!have_any_msss_servers) {
868     AddServer(kNoExeMSSSServer,& options.no_exe_internal_msss_servers,
869        & options.no_exe_external_msss_servers);
870   }
871 
872   if (options.local_cache_path.empty()) {
873     options.local_cache_path = kLocalCachePath;
874   }
875 
876   if (options.upload_symbols_url.empty()) {
877     FprintfFlush(stderr, "No upload symbols URL specified.\n");
878     return usage(argv[0]);
879   }
880   if (options.api_key.empty()) {
881     FprintfFlush(stderr, "No API key specified.\n");
882     return usage(argv[0]);
883   }
884   if (options.missing_symbols_url.empty() &&
885       options.missing_symbols_file.empty()) {
886     FprintfFlush(stderr, "No missing symbols URL or file specified.\n");
887     return usage(argv[0]);
888   }
889   if (options.fetch_symbol_failure_url.empty()) {
890     FprintfFlush(stderr, "No fetch symbol failure URL specified.\n");
891     return usage(argv[0]);
892   }
893 
894   FprintfFlush(stdout,
895                "# of Symbol Servers (int/ext): %d/%d full, %d/%d no_exe\n",
896                options.full_internal_msss_servers.size(),
897                options.full_external_msss_servers.size(),
898                options.no_exe_internal_msss_servers.size(),
899                options.no_exe_external_msss_servers.size());
900 
901   if (!ConvertMissingSymbolsList(options)) {
902     return 1;
903   }
904 
905   time_string = CurrentDateAndTime();
906   FprintfFlush(stdout, "converter: %s: finished\n", time_string.c_str());
907   return 0;
908 }
909