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