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(¤t_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(¤t_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