OLD | NEW |
| (Empty) |
1 // Copyright 2007-2010 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 // | |
16 // TODO(omaha): for reliability sake, the code that sets up the exception | |
17 // handler should be very minimalist and not call so much outside of this | |
18 // module. One idea is to split the crash module in two: one minimalist part | |
19 // responsible for setting up the exception handler and one that is uploading | |
20 // the crash. | |
21 | |
22 #include "omaha/goopdate/crash.h" | |
23 | |
24 #include <windows.h> | |
25 #include <shlwapi.h> | |
26 #include <atlbase.h> | |
27 #include <atlstr.h> | |
28 #include <cmath> | |
29 #include <map> | |
30 #include <string> | |
31 #include <vector> | |
32 #include "base/basictypes.h" | |
33 #include "omaha/base/app_util.h" | |
34 #include "omaha/base/const_addresses.h" | |
35 #include "omaha/base/const_object_names.h" | |
36 #include "omaha/base/constants.h" | |
37 #include "omaha/base/debug.h" | |
38 #include "omaha/base/error.h" | |
39 #include "omaha/base/exception_barrier.h" | |
40 #include "omaha/base/file.h" | |
41 #include "omaha/base/logging.h" | |
42 #include "omaha/base/module_utils.h" | |
43 #include "omaha/base/omaha_version.h" | |
44 #include "omaha/base/path.h" | |
45 #include "omaha/base/reg_key.h" | |
46 #include "omaha/base/safe_format.h" | |
47 #include "omaha/base/scope_guard.h" | |
48 #include "omaha/base/scoped_any.h" | |
49 #include "omaha/base/time.h" | |
50 #include "omaha/base/utils.h" | |
51 #include "omaha/base/vistautil.h" | |
52 #include "omaha/common/command_line_builder.h" | |
53 #include "omaha/common/config_manager.h" | |
54 #include "omaha/common/event_logger.h" | |
55 #include "omaha/common/goopdate_utils.h" | |
56 #include "omaha/common/stats_uploader.h" | |
57 #include "omaha/goopdate/goopdate_metrics.h" | |
58 #include "third_party/breakpad/src/client/windows/common/ipc_protocol.h" | |
59 #include "third_party/breakpad/src/client/windows/crash_generation/client_info.h
" | |
60 #include "third_party/breakpad/src/client/windows/crash_generation/crash_generat
ion_server.h" | |
61 #include "third_party/breakpad/src/client/windows/sender/crash_report_sender.h" | |
62 | |
63 using google_breakpad::ClientInfo; | |
64 using google_breakpad::CrashGenerationServer; | |
65 using google_breakpad::CrashReportSender; | |
66 using google_breakpad::CustomClientInfo; | |
67 using google_breakpad::ExceptionHandler; | |
68 using google_breakpad::ReportResult; | |
69 | |
70 namespace omaha { | |
71 | |
72 const ACCESS_MASK kPipeAccessMask = FILE_READ_ATTRIBUTES | | |
73 FILE_READ_DATA | | |
74 FILE_WRITE_ATTRIBUTES | | |
75 FILE_WRITE_DATA | | |
76 SYNCHRONIZE; | |
77 | |
78 CString Crash::module_filename_; | |
79 CString Crash::crash_dir_; | |
80 CString Crash::checkpoint_file_; | |
81 CString Crash::version_postfix_ = kCrashVersionPostfixString; | |
82 CString Crash::crash_report_url_ = kUrlCrashReport; | |
83 int Crash::max_reports_per_day_ = kCrashReportMaxReportsPerDay; | |
84 ExceptionHandler* Crash::exception_handler_ = NULL; | |
85 CrashGenerationServer* Crash::crash_server_ = NULL; | |
86 | |
87 bool Crash::is_machine_ = false; | |
88 const TCHAR* const Crash::kDefaultProductName = | |
89 SHORT_COMPANY_NAME _T(" Error Reporting"); | |
90 | |
91 HRESULT Crash::Initialize(bool is_machine) { | |
92 is_machine_ = is_machine; | |
93 | |
94 HRESULT hr = GetModuleFileName(NULL, &module_filename_); | |
95 if (FAILED(hr)) { | |
96 return hr; | |
97 } | |
98 | |
99 hr = InitializeCrashDir(); | |
100 if (FAILED(hr)) { | |
101 return hr; | |
102 } | |
103 ASSERT1(!crash_dir_.IsEmpty()); | |
104 CORE_LOG(L2, (_T("[crash dir %s]"), crash_dir_)); | |
105 | |
106 // The checkpoint file maintains state information for the crash report | |
107 // client, such as the number of reports per day successfully sent. | |
108 checkpoint_file_ = ConcatenatePath(crash_dir_, _T("checkpoint")); | |
109 if (checkpoint_file_.IsEmpty()) { | |
110 return GOOPDATE_E_PATH_APPEND_FAILED; | |
111 } | |
112 | |
113 return S_OK; | |
114 } | |
115 | |
116 HRESULT Crash::InstallCrashHandler(bool is_machine) { | |
117 CORE_LOG(L3, (_T("[Crash::InstallCrashHandler][is_machine %d]"), is_machine)); | |
118 | |
119 HRESULT hr = Initialize(is_machine); | |
120 if (FAILED(hr)) { | |
121 CORE_LOG(LE, (_T("[failed to initialize Crash][0x%08x]"), hr)); | |
122 return hr; | |
123 } | |
124 | |
125 // Only installs the exception handler if the process is not a crash report | |
126 // process. If the crash reporter crashes as well, this results in an | |
127 // infinite loop. | |
128 bool is_crash_report_process = false; | |
129 if (FAILED(IsCrashReportProcess(&is_crash_report_process)) || | |
130 is_crash_report_process) { | |
131 return S_OK; | |
132 } | |
133 | |
134 // Allocate this instance dynamically so that it is going to be | |
135 // around until the process terminates. Technically, this instance "leaks", | |
136 // but this is actually the correct behavior. | |
137 if (exception_handler_) { | |
138 delete exception_handler_; | |
139 } | |
140 | |
141 MINIDUMP_TYPE dump_type = ConfigManager::Instance()->IsInternalUser() ? | |
142 MiniDumpWithFullMemory : MiniDumpNormal; | |
143 exception_handler_ = new ExceptionHandler(crash_dir_.GetString(), | |
144 NULL, | |
145 &Crash::MinidumpCallback, | |
146 NULL, | |
147 ExceptionHandler::HANDLER_ALL, | |
148 dump_type, | |
149 NULL, | |
150 NULL); | |
151 | |
152 // Breakpad does not get the exceptions that are not propagated to the | |
153 // UnhandledExceptionFilter. This is the case where we crashed on a stack | |
154 // which we do not own, such as an RPC stack. To get these exceptions we | |
155 // initialize a static ExceptionBarrier object. | |
156 ExceptionBarrier::set_handler(&Crash::EBHandler); | |
157 | |
158 CORE_LOG(L2, (_T("[exception handler has been installed]"))); | |
159 return S_OK; | |
160 } | |
161 | |
162 void Crash::UninstallCrashHandler() { | |
163 ExceptionBarrier::set_handler(NULL); | |
164 delete exception_handler_; | |
165 exception_handler_ = NULL; | |
166 | |
167 CORE_LOG(L2, (_T("[exception handler has been uninstalled]"))); | |
168 } | |
169 | |
170 HRESULT Crash::CrashHandler(bool is_machine, | |
171 const google_breakpad::ClientInfo& client_info, | |
172 const CString& crash_filename) { | |
173 // GoogleCrashHandler.exe is only aggregating metrics at process exit. Since | |
174 // GoogleCrashHandler.exe is long-running however, we hardly ever exit. As a | |
175 // consequence, the metrics below will be reported very infrequently. This | |
176 // call below will do additional aggregation of metrics, so that we can report | |
177 // the metrics below in a timely manner. | |
178 ON_SCOPE_EXIT(AggregateMetrics, is_machine); | |
179 | |
180 // Count the number of crashes requested by applications. | |
181 ++metric_oop_crashes_requested; | |
182 | |
183 DWORD pid = client_info.pid(); | |
184 OPT_LOG(L1, (_T("[client requested dump][pid %d]"), pid)); | |
185 | |
186 ASSERT1(!crash_filename.IsEmpty()); | |
187 if (crash_filename.IsEmpty()) { | |
188 OPT_LOG(L1, (_T("[no crash file]"))); | |
189 ++metric_oop_crashes_crash_filename_empty; | |
190 return E_UNEXPECTED; | |
191 } | |
192 | |
193 CString custom_info_filename; | |
194 HRESULT hr = CreateCustomInfoFile(crash_filename, | |
195 client_info.GetCustomInfo(), | |
196 &custom_info_filename); | |
197 if (FAILED(hr)) { | |
198 OPT_LOG(LE, (_T("[CreateCustomInfoFile failed][0x%08x]"), hr)); | |
199 ++metric_oop_crashes_createcustominfofile_failed; | |
200 return hr; | |
201 } | |
202 | |
203 // Start a sender process to handle the crash. | |
204 CommandLineBuilder builder(COMMANDLINE_MODE_REPORTCRASH); | |
205 builder.set_crash_filename(crash_filename); | |
206 builder.set_custom_info_filename(custom_info_filename); | |
207 builder.set_is_machine_set(is_machine); | |
208 CString cmd_line = builder.GetCommandLine(module_filename_); | |
209 | |
210 hr = StartSenderWithCommandLine(&cmd_line); | |
211 if (FAILED(hr)) { | |
212 OPT_LOG(LE, (_T("[StartSenderWithCommandLine failed][0x%08x]"), hr)); | |
213 ++metric_oop_crashes_startsenderwithcommandline_failed; | |
214 return hr; | |
215 } | |
216 | |
217 OPT_LOG(L1, (_T("[client dump handled][pid %d]"), pid)); | |
218 return S_OK; | |
219 } | |
220 | |
221 // The implementation must be as simple as possible and use only the | |
222 // resources it needs to start a reporter process and then exit. | |
223 bool Crash::MinidumpCallback(const wchar_t* dump_path, | |
224 const wchar_t* minidump_id, | |
225 void*, | |
226 EXCEPTION_POINTERS*, | |
227 MDRawAssertionInfo*, | |
228 bool succeeded) { | |
229 if (succeeded && *dump_path && *minidump_id) { | |
230 // We need a way to see if the crash happens while we are installing | |
231 // something. This is a tough spot to be doing anything at all since | |
232 // we've been handling a crash. | |
233 // TODO(omaha): redesign a better mechanism. | |
234 bool is_interactive = Crash::IsInteractive(); | |
235 | |
236 // TODO(omaha): format a command line without extra memory allocations. | |
237 CString crash_filename; | |
238 SafeCStringFormat(&crash_filename, _T("%s\\%s.dmp"), | |
239 dump_path, minidump_id); | |
240 EnclosePath(&crash_filename); | |
241 StartReportCrash(is_interactive, crash_filename); | |
242 | |
243 // For in-proc crash generation, ExceptionHandler either creates a Normal | |
244 // MiniDump, or a Full MiniDump, based on the dump_type. However, in the | |
245 // case of OOP crash generation both the Normal and Full dumps are created | |
246 // by the crash handling server, with the default full dump filename having | |
247 // a suffix of "-full.dmp". If Omaha switches to using OOP crash generation, | |
248 // this file is uploaded as well. | |
249 if (ConfigManager::Instance()->IsInternalUser()) { | |
250 SafeCStringFormat(&crash_filename, _T("%s\\%s-full.dmp"), | |
251 dump_path, minidump_id); | |
252 EnclosePath(&crash_filename); | |
253 if (File::Exists(crash_filename)) { | |
254 StartReportCrash(is_interactive, crash_filename); | |
255 } | |
256 } | |
257 } | |
258 | |
259 // There are two ways to stop execution of the current process: ExitProcess | |
260 // and TerminateProcess. Calling ExitProcess results in calling the | |
261 // destructors of the static objects before the process exits. | |
262 // TerminateProcess unconditionally stops the process so no user mode code | |
263 // executes beyond this point. | |
264 ::TerminateProcess(::GetCurrentProcess(), | |
265 static_cast<UINT>(GOOPDATE_E_CRASH)); | |
266 return true; | |
267 } | |
268 | |
269 void Crash::StartReportCrash(bool is_interactive, | |
270 const CString& crash_filename) { | |
271 // CommandLineBuilder escapes the program name before returning the | |
272 // command line to the caller. | |
273 CommandLineBuilder builder(COMMANDLINE_MODE_REPORTCRASH); | |
274 builder.set_is_interactive_set(is_interactive); | |
275 builder.set_crash_filename(crash_filename); | |
276 builder.set_is_machine_set(is_machine_); | |
277 CString cmd_line = builder.GetCommandLine(module_filename_); | |
278 | |
279 // Set an environment variable which the crash reporter process will | |
280 // inherit. We don't want to install a crash handler for the reporter | |
281 // process to avoid an infinite loop in the case the reporting process | |
282 // crashes also. When the reporting process begins execution, the presence | |
283 // of this environment variable is tested, and the crash handler will not be | |
284 // installed. | |
285 if (::SetEnvironmentVariable(kNoCrashHandlerEnvVariableName, (_T("1")))) { | |
286 STARTUPINFO si = {sizeof(si)}; | |
287 PROCESS_INFORMATION pi = {0}; | |
288 if (::CreateProcess(NULL, | |
289 cmd_line.GetBuffer(), | |
290 NULL, | |
291 NULL, | |
292 false, | |
293 0, | |
294 NULL, | |
295 NULL, | |
296 &si, | |
297 &pi)) { | |
298 ::CloseHandle(pi.hProcess); | |
299 ::CloseHandle(pi.hThread); | |
300 } | |
301 } | |
302 } | |
303 | |
304 HRESULT Crash::StartSenderWithCommandLine(CString* cmd_line) { | |
305 TCHAR* env_vars = ::GetEnvironmentStrings(); | |
306 if (env_vars == NULL) { | |
307 return HRESULTFromLastError(); | |
308 } | |
309 | |
310 // Add an environment variable to the crash reporter process to indicate it | |
311 // not to install a crash handler. This avoids an infinite loop in the case | |
312 // the reporting process crashes also. When the reporting process begins | |
313 // execution, the presence of this environment variable is tested, and the | |
314 // crash handler will not be installed. | |
315 CString new_var; | |
316 new_var.Append(kNoCrashHandlerEnvVariableName); | |
317 new_var.Append(_T("=1")); | |
318 | |
319 // Compute the length of environment variables string. The format of the | |
320 // string is Name1=Value1\0Name2=Value2\0Name3=Value3\0\0. | |
321 const TCHAR* current = env_vars; | |
322 size_t env_vars_char_count = 0; | |
323 while (*current) { | |
324 size_t sub_length = _tcslen(current) + 1; | |
325 env_vars_char_count += sub_length; | |
326 current += sub_length; | |
327 } | |
328 // Add one to length to count the trailing NULL character marking the end of | |
329 // all environment variables. | |
330 ++env_vars_char_count; | |
331 | |
332 // Copy the new environment variable and the existing variables in a new | |
333 // buffer. | |
334 size_t new_var_char_count = new_var.GetLength() + 1; | |
335 scoped_array<TCHAR> new_env_vars( | |
336 new TCHAR[env_vars_char_count + new_var_char_count]); | |
337 size_t new_var_byte_count = new_var_char_count * sizeof(TCHAR); | |
338 memcpy(new_env_vars.get(), | |
339 static_cast<const TCHAR*>(new_var), | |
340 new_var_byte_count); | |
341 size_t env_vars_byte_count = env_vars_char_count * sizeof(TCHAR); | |
342 memcpy(new_env_vars.get() + new_var_char_count, | |
343 env_vars, | |
344 env_vars_byte_count); | |
345 ::FreeEnvironmentStrings(env_vars); | |
346 | |
347 STARTUPINFO si = {sizeof(si)}; | |
348 PROCESS_INFORMATION pi = {0}; | |
349 if (!::CreateProcess(NULL, | |
350 cmd_line->GetBuffer(), | |
351 NULL, | |
352 NULL, | |
353 false, | |
354 CREATE_UNICODE_ENVIRONMENT, | |
355 new_env_vars.get(), | |
356 NULL, | |
357 &si, | |
358 &pi)) { | |
359 return HRESULTFromLastError(); | |
360 } | |
361 | |
362 ::CloseHandle(pi.hProcess); | |
363 ::CloseHandle(pi.hThread); | |
364 return S_OK; | |
365 } | |
366 | |
367 HRESULT Crash::CreateCustomInfoFile(const CString& dump_file, | |
368 const CustomClientInfo& custom_client_info, | |
369 CString* custom_info_filepath) { | |
370 // Since goopdate_utils::WriteNameValuePairsToFile is implemented in terms | |
371 // of WritePrivateProfile API, relative paths are relative to the Windows | |
372 // directory instead of the current directory of the process. | |
373 ASSERT(!::PathIsRelative(dump_file), (_T("Path must be absolute"))); | |
374 | |
375 // Determine the path for custom info file. | |
376 CString filepath = GetPathRemoveExtension(dump_file); | |
377 SafeCStringAppendFormat(&filepath, _T(".txt")); | |
378 | |
379 // Create a map of name/value pairs from custom client info. | |
380 std::map<CString, CString> custom_info_map; | |
381 for (size_t i = 0; i < custom_client_info.count; ++i) { | |
382 custom_info_map[custom_client_info.entries[i].name] = | |
383 custom_client_info.entries[i].value; | |
384 } | |
385 | |
386 HRESULT hr = goopdate_utils::WriteNameValuePairsToFile(filepath, | |
387 kCustomClientInfoGroup, | |
388 custom_info_map); | |
389 if (FAILED(hr)) { | |
390 return hr; | |
391 } | |
392 | |
393 *custom_info_filepath = filepath; | |
394 return S_OK; | |
395 } | |
396 | |
397 // Backs up the crash and uploads it if allowed to. | |
398 HRESULT Crash::DoSendCrashReport(bool can_upload, | |
399 bool is_out_of_process, | |
400 const CString& crash_filename, | |
401 const ParameterMap& parameters, | |
402 CString* report_id) { | |
403 ASSERT1(!crash_filename.IsEmpty()); | |
404 ASSERT1(report_id); | |
405 report_id->Empty(); | |
406 | |
407 if (!File::Exists(crash_filename)) { | |
408 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); | |
409 } | |
410 | |
411 CString product_name = GetProductName(parameters); | |
412 VERIFY1(SUCCEEDED(SaveLastCrash(crash_filename, product_name))); | |
413 | |
414 HRESULT hr = S_OK; | |
415 if (can_upload) { | |
416 hr = UploadCrash(is_out_of_process, crash_filename, parameters, report_id); | |
417 } else { | |
418 CORE_LOG(L2, (_T("[crash uploads are not allowed]"))); | |
419 } | |
420 | |
421 return hr; | |
422 } | |
423 | |
424 HRESULT Crash::UploadCrash(bool is_out_of_process, | |
425 const CString& crash_filename, | |
426 const ParameterMap& parameters, | |
427 CString* report_id) { | |
428 ASSERT1(report_id); | |
429 report_id->Empty(); | |
430 | |
431 // Calling this avoids crashes in WinINet. See: http://b/1258692 | |
432 EnsureRasmanLoaded(); | |
433 | |
434 ASSERT1(!crash_dir_.IsEmpty()); | |
435 ASSERT1(!checkpoint_file_.IsEmpty()); | |
436 | |
437 // Do best effort to send the crash. If it can't communicate with the backend, | |
438 // it retries a few times over a few hours time interval. | |
439 HRESULT hr = S_OK; | |
440 for (int i = 0; i != kCrashReportAttempts; ++i) { | |
441 std::wstring report_code; | |
442 CrashReportSender sender(checkpoint_file_.GetString()); | |
443 sender.set_max_reports_per_day(max_reports_per_day_); | |
444 CORE_LOG(L2, (_T("[Uploading crash report]") | |
445 _T("[%s][%s]"), crash_report_url_, crash_filename)); | |
446 ASSERT1(!crash_report_url_.IsEmpty()); | |
447 ReportResult res = sender.SendCrashReport(crash_report_url_.GetString(), | |
448 parameters, | |
449 crash_filename.GetString(), | |
450 &report_code); | |
451 switch (res) { | |
452 case google_breakpad::RESULT_SUCCEEDED: | |
453 report_id->SetString(report_code.c_str()); | |
454 hr = S_OK; | |
455 break; | |
456 | |
457 case google_breakpad::RESULT_FAILED: | |
458 OPT_LOG(L2, (_T("[Crash report failed but it will retry sending]"))); | |
459 ::Sleep(kCrashReportResendPeriodMs); | |
460 hr = E_FAIL; | |
461 break; | |
462 | |
463 case google_breakpad::RESULT_REJECTED: | |
464 hr = GOOPDATE_E_CRASH_REJECTED; | |
465 break; | |
466 | |
467 case google_breakpad::RESULT_THROTTLED: | |
468 hr = GOOPDATE_E_CRASH_THROTTLED; | |
469 break; | |
470 | |
471 default: | |
472 hr = E_FAIL; | |
473 break; | |
474 }; | |
475 | |
476 // Continue the retry loop only when it could not contact the server. | |
477 if (res != google_breakpad::RESULT_FAILED) { | |
478 break; | |
479 } | |
480 } | |
481 | |
482 CORE_LOG(L2, (_T("[crash report code = %s]"), *report_id)); | |
483 | |
484 // The event source for the out-of-process crashes is the product name. | |
485 // Therefore, in the event of an out-of-process crash, the log entry | |
486 // appears to be generated by the product that crashed. | |
487 CString product_name = is_out_of_process ? GetProductName(parameters) : | |
488 kAppName; | |
489 CString event_text; | |
490 uint16 event_type(0); | |
491 if (!report_id->IsEmpty()) { | |
492 event_type = EVENTLOG_INFORMATION_TYPE; | |
493 SafeCStringFormat(&event_text, _T("Crash uploaded. Id=%s."), *report_id); | |
494 } else { | |
495 ASSERT1(FAILED(hr)); | |
496 event_type = EVENTLOG_WARNING_TYPE; | |
497 SafeCStringFormat(&event_text, _T("Crash not uploaded. Error=0x%x."), hr); | |
498 } | |
499 VERIFY1(SUCCEEDED(Crash::Log(event_type, | |
500 kCrashUploadEventId, | |
501 product_name, | |
502 event_text))); | |
503 | |
504 UpdateCrashUploadMetrics(is_out_of_process, hr); | |
505 | |
506 return hr; | |
507 } | |
508 | |
509 HRESULT Crash::SaveLastCrash(const CString& crash_filename, | |
510 const CString& product_name) { | |
511 if (product_name.IsEmpty()) { | |
512 return E_INVALIDARG; | |
513 } | |
514 CString tmp; | |
515 SafeCStringFormat(&tmp, _T("%s-last.dmp"), product_name); | |
516 CString save_filename = ConcatenatePath(crash_dir_, tmp); | |
517 if (save_filename.IsEmpty()) { | |
518 return GOOPDATE_E_PATH_APPEND_FAILED; | |
519 } | |
520 | |
521 CORE_LOG(L2, (_T("[Crash::SaveLastCrash]") | |
522 _T("[to %s][from %s]"), save_filename, crash_filename)); | |
523 | |
524 return ::CopyFile(crash_filename, | |
525 save_filename, | |
526 false) ? S_OK : HRESULTFromLastError(); | |
527 } | |
528 | |
529 HRESULT Crash::CleanStaleCrashes() { | |
530 CORE_LOG(L3, (_T("[Crash::CleanStaleCrashes]"))); | |
531 | |
532 // ??- sequence is a c++ trigraph corresponding to a ~. Escape it. | |
533 const TCHAR kWildCards[] = _T("???????\?-???\?-???\?-???\?-????????????.dmp"); | |
534 std::vector<CString> crash_files; | |
535 HRESULT hr = File::GetWildcards(crash_dir_, kWildCards, &crash_files); | |
536 if (FAILED(hr)) { | |
537 return hr; | |
538 } | |
539 | |
540 time64 now = GetCurrent100NSTime(); | |
541 for (size_t i = 0; i != crash_files.size(); ++i) { | |
542 CORE_LOG(L3, (_T("[found crash file][%s]"), crash_files[i])); | |
543 FILETIME creation_time = {0}; | |
544 if (SUCCEEDED(File::GetFileTime(crash_files[i], | |
545 &creation_time, | |
546 NULL, | |
547 NULL))) { | |
548 double time_diff = | |
549 static_cast<double>(now - FileTimeToTime64(creation_time)); | |
550 if (abs(time_diff) >= kDaysTo100ns) { | |
551 VERIFY1(::DeleteFile(crash_files[i])); | |
552 } | |
553 } | |
554 } | |
555 | |
556 return S_OK; | |
557 } | |
558 | |
559 HRESULT Crash::Report(bool can_upload_in_process, | |
560 const CString& crash_filename, | |
561 const CString& custom_info_filename, | |
562 const CString& lang) { | |
563 const bool is_out_of_process = !custom_info_filename.IsEmpty(); | |
564 HRESULT hr = S_OK; | |
565 if (is_out_of_process) { | |
566 hr = ReportProductCrash(true, crash_filename, custom_info_filename, lang); | |
567 ::DeleteFile(custom_info_filename); | |
568 } else { | |
569 hr = ReportGoogleUpdateCrash(can_upload_in_process, | |
570 crash_filename, | |
571 custom_info_filename, | |
572 lang); | |
573 } | |
574 ::DeleteFile(crash_filename); | |
575 CleanStaleCrashes(); | |
576 return hr; | |
577 } | |
578 | |
579 HRESULT Crash::ReportGoogleUpdateCrash(bool can_upload, | |
580 const CString& crash_filename, | |
581 const CString& custom_info_filename, | |
582 const CString& lang) { | |
583 OPT_LOG(L1, (_T("[Crash::ReportGoogleUpdateCrash]"))); | |
584 | |
585 UNREFERENCED_PARAMETER(custom_info_filename); | |
586 | |
587 ++metric_crashes_total; | |
588 | |
589 // Build the map of additional parameters to report along with the crash. | |
590 CString ver(GetVersionString() + version_postfix_); | |
591 CString uid = goopdate_utils::GetUserIdLazyInit(is_machine_); | |
592 | |
593 ParameterMap parameters; | |
594 parameters[_T("prod")] = _T("Update2"); | |
595 parameters[_T("ver")] = ver; | |
596 parameters[_T("userid")] = uid; | |
597 parameters[_T("lang")] = lang; | |
598 | |
599 CString event_text; | |
600 SafeCStringFormat(&event_text, | |
601 _T("%s has encountered a fatal error.\r\n") | |
602 _T("ver=%s;lang=%s;id=%s;is_machine=%d;upload=%d;minidump=%s"), | |
603 kAppName, | |
604 ver, lang, uid, is_machine_, can_upload ? 1 : 0, crash_filename); | |
605 VERIFY1(SUCCEEDED(Crash::Log(EVENTLOG_ERROR_TYPE, | |
606 kCrashReportEventId, | |
607 kAppName, | |
608 event_text))); | |
609 | |
610 CString report_id; | |
611 return DoSendCrashReport(can_upload, | |
612 false, // Omaha crash. | |
613 crash_filename, | |
614 parameters, | |
615 &report_id); | |
616 } | |
617 | |
618 HRESULT Crash::ReportProductCrash(bool can_upload, | |
619 const CString& crash_filename, | |
620 const CString& custom_info_filename, | |
621 const CString& lang) { | |
622 OPT_LOG(L1, (_T("[Crash::ReportProductCrash]"))); | |
623 | |
624 UNREFERENCED_PARAMETER(lang); | |
625 | |
626 // All product crashes must be uploaded. | |
627 ASSERT1(can_upload); | |
628 | |
629 // Count the number of crashes the sender was requested to handle. | |
630 ++metric_oop_crashes_total; | |
631 | |
632 std::map<CString, CString> parameters_temp; | |
633 HRESULT hr = goopdate_utils::ReadNameValuePairsFromFile( | |
634 custom_info_filename, | |
635 kCustomClientInfoGroup, | |
636 ¶meters_temp); | |
637 if (FAILED(hr)) { | |
638 return hr; | |
639 } | |
640 | |
641 ParameterMap parameters; | |
642 std::map<CString, CString>::const_iterator iter; | |
643 for (iter = parameters_temp.begin(); iter != parameters_temp.end(); ++iter) { | |
644 parameters[iter->first.GetString()] = iter->second.GetString(); | |
645 } | |
646 | |
647 const CString product_name = GetProductName(parameters); | |
648 const std::wstring ver = parameters[_T("ver")]; | |
649 | |
650 CString event_text; | |
651 SafeCStringFormat(&event_text, | |
652 _T("%s has encountered a fatal error.\r\n") | |
653 _T("ver=%s;is_machine=%d;minidump=%s"), | |
654 product_name, ver.c_str(), is_machine_, crash_filename); | |
655 VERIFY1(SUCCEEDED(Crash::Log(EVENTLOG_ERROR_TYPE, | |
656 kCrashReportEventId, | |
657 product_name, | |
658 event_text))); | |
659 | |
660 CString report_id; | |
661 return hr = DoSendCrashReport(can_upload, | |
662 true, // Out of process crash. | |
663 crash_filename, | |
664 parameters, | |
665 &report_id); | |
666 } | |
667 | |
668 void __stdcall Crash::EBHandler(EXCEPTION_POINTERS* ptrs) { | |
669 if (exception_handler_) { | |
670 exception_handler_->WriteMinidumpForException(ptrs); | |
671 } | |
672 } | |
673 | |
674 HRESULT Crash::GetExceptionInfo(const CString& crash_filename, | |
675 MINIDUMP_EXCEPTION* ex_info) { | |
676 ASSERT1(ex_info); | |
677 ASSERT1(!crash_filename.IsEmpty()); | |
678 | |
679 // Dynamically link with the dbghelp to avoid runtime resource bloat. | |
680 scoped_library dbghelp(::LoadLibrary(_T("dbghelp.dll"))); | |
681 if (!dbghelp) { | |
682 return HRESULTFromLastError(); | |
683 } | |
684 | |
685 typedef BOOL (WINAPI *MiniDumpReadDumpStreamFun)(void* base_of_dump, | |
686 ULONG stream_number, | |
687 MINIDUMP_DIRECTORY* dir, | |
688 void** stream_pointer, | |
689 ULONG* stream_size); | |
690 | |
691 MiniDumpReadDumpStreamFun minidump_read_dump_stream = | |
692 reinterpret_cast<MiniDumpReadDumpStreamFun>(::GetProcAddress(get(dbghelp), | |
693 "MiniDumpReadDumpStream")); | |
694 ASSERT1(minidump_read_dump_stream); | |
695 if (!minidump_read_dump_stream) { | |
696 return HRESULTFromLastError(); | |
697 } | |
698 | |
699 // The minidump file must be mapped in memory before reading the streams. | |
700 scoped_hfile file(::CreateFile(crash_filename, | |
701 GENERIC_READ, | |
702 FILE_SHARE_READ, | |
703 NULL, | |
704 OPEN_EXISTING, | |
705 FILE_ATTRIBUTE_READONLY, | |
706 NULL)); | |
707 ASSERT1(file); | |
708 if (!file) { | |
709 return HRESULTFromLastError(); | |
710 } | |
711 scoped_file_mapping file_mapping(::CreateFileMapping(get(file), | |
712 NULL, | |
713 PAGE_READONLY, | |
714 0, | |
715 0, | |
716 NULL)); | |
717 if (!file_mapping) { | |
718 return HRESULTFromLastError(); | |
719 } | |
720 scoped_file_view base_of_dump(::MapViewOfFile(get(file_mapping), | |
721 FILE_MAP_READ, | |
722 0, | |
723 0, | |
724 0)); | |
725 if (!base_of_dump) { | |
726 return HRESULTFromLastError(); | |
727 } | |
728 | |
729 // Read the exception stream and pick up the exception record. | |
730 MINIDUMP_DIRECTORY minidump_directory = {0}; | |
731 void* stream_pointer = NULL; | |
732 ULONG stream_size = 0; | |
733 bool result = !!(*minidump_read_dump_stream)(get(base_of_dump), | |
734 ExceptionStream, | |
735 &minidump_directory, | |
736 &stream_pointer, | |
737 &stream_size); | |
738 if (!result) { | |
739 return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); | |
740 } | |
741 MINIDUMP_EXCEPTION_STREAM* exception_stream = | |
742 static_cast<MINIDUMP_EXCEPTION_STREAM*>(stream_pointer); | |
743 ASSERT1(stream_pointer); | |
744 ASSERT1(stream_size); | |
745 | |
746 *ex_info = exception_stream->ExceptionRecord; | |
747 return S_OK; | |
748 } | |
749 | |
750 bool Crash::IsInteractive() { | |
751 bool result = false; | |
752 ::EnumWindows(&Crash::EnumWindowsCallback, reinterpret_cast<LPARAM>(&result)); | |
753 return result; | |
754 } | |
755 | |
756 // Finds if the given window is in the current process. | |
757 BOOL CALLBACK Crash::EnumWindowsCallback(HWND hwnd, LPARAM param) { | |
758 DWORD pid = 0; | |
759 ::GetWindowThreadProcessId(hwnd, &pid); | |
760 if (::IsWindowVisible(hwnd) && pid == ::GetCurrentProcessId() && param) { | |
761 *reinterpret_cast<bool*>(param) = true; | |
762 return false; | |
763 } | |
764 return true; | |
765 } | |
766 | |
767 HRESULT Crash::InitializeCrashDir() { | |
768 crash_dir_.Empty(); | |
769 ConfigManager* cm = ConfigManager::Instance(); | |
770 CString dir = is_machine_ ? cm->GetMachineCrashReportsDir() : | |
771 cm->GetUserCrashReportsDir(); | |
772 | |
773 if (is_machine_ && !dir.IsEmpty()) { | |
774 HRESULT hr = InitializeDirSecurity(&dir); | |
775 if (FAILED(hr)) { | |
776 CORE_LOG(LW, (_T("[failed to initialize crash dir security][0x%x]"), hr)); | |
777 ::RemoveDirectory(dir); | |
778 } | |
779 } | |
780 | |
781 // Use the temporary directory of the process if the crash directory can't be | |
782 // initialized for any reason. Users can't read files in other users' | |
783 // temporary directories so the temp dir is good option to still have | |
784 // crash handling. | |
785 if (dir.IsEmpty()) { | |
786 dir = app_util::GetTempDir(); | |
787 } | |
788 | |
789 if (dir.IsEmpty()) { | |
790 return GOOPDATE_E_CRASH_NO_DIR; | |
791 } | |
792 | |
793 crash_dir_ = dir; | |
794 return S_OK; | |
795 } | |
796 | |
797 HRESULT Crash::InitializeDirSecurity(CString* dir) { | |
798 ASSERT1(dir); | |
799 | |
800 // Users can only read permissions on the crash dir. | |
801 CDacl dacl; | |
802 const uint8 kAceFlags = SUB_CONTAINERS_AND_OBJECTS_INHERIT; | |
803 if (!dacl.AddAllowedAce(Sids::System(), GENERIC_ALL, kAceFlags) || | |
804 !dacl.AddAllowedAce(Sids::Admins(), GENERIC_ALL, kAceFlags) || | |
805 !dacl.AddAllowedAce(Sids::Users(), READ_CONTROL, kAceFlags)) { | |
806 return GOOPDATE_E_CRASH_SECURITY_FAILED; | |
807 } | |
808 | |
809 SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION | | |
810 PROTECTED_DACL_SECURITY_INFORMATION; | |
811 DWORD error = ::SetNamedSecurityInfo(dir->GetBuffer(), | |
812 SE_FILE_OBJECT, | |
813 si, | |
814 NULL, | |
815 NULL, | |
816 const_cast<ACL*>(dacl.GetPACL()), | |
817 NULL); | |
818 if (error != ERROR_SUCCESS) { | |
819 return HRESULT_FROM_WIN32(error); | |
820 } | |
821 return S_OK; | |
822 } | |
823 | |
824 // Checks for the presence of an environment variable. We are not interested | |
825 // in the value of the variable but only in its presence. | |
826 HRESULT Crash::IsCrashReportProcess(bool* is_crash_report_process) { | |
827 ASSERT1(is_crash_report_process); | |
828 if (::GetEnvironmentVariable(kNoCrashHandlerEnvVariableName, NULL, 0)) { | |
829 *is_crash_report_process = true; | |
830 return S_OK; | |
831 } else { | |
832 DWORD error(::GetLastError()); | |
833 *is_crash_report_process = false; | |
834 return error == ERROR_ENVVAR_NOT_FOUND ? S_OK : HRESULT_FROM_WIN32(error); | |
835 } | |
836 } | |
837 | |
838 HRESULT Crash::Log(uint16 type, | |
839 uint32 id, | |
840 const TCHAR* source, | |
841 const TCHAR* description) { | |
842 ASSERT1(source); | |
843 ASSERT1(description); | |
844 return EventLogger::ReportEvent(source, | |
845 type, | |
846 0, // Category. | |
847 id, | |
848 1, // Number of strings. | |
849 &description, | |
850 0, // Raw data size. | |
851 NULL); // Raw data. | |
852 } | |
853 | |
854 CString Crash::GetProductName(const ParameterMap& parameters) { | |
855 // The crash is logged using the value of 'prod' if available or | |
856 // a default constant string otherwise. | |
857 CString product_name; | |
858 ParameterMap::const_iterator it = parameters.find(_T("prod")); | |
859 const bool is_found = it != parameters.end() && !it->second.empty(); | |
860 return is_found ? it->second.c_str() : kDefaultProductName; | |
861 } | |
862 | |
863 void Crash::UpdateCrashUploadMetrics(bool is_out_of_process, HRESULT hr) { | |
864 switch (hr) { | |
865 case S_OK: | |
866 if (is_out_of_process) { | |
867 ++metric_oop_crashes_uploaded; | |
868 } else { | |
869 ++metric_crashes_uploaded; | |
870 } | |
871 break; | |
872 | |
873 case E_FAIL: | |
874 if (is_out_of_process) { | |
875 ++metric_oop_crashes_failed; | |
876 } else { | |
877 ++metric_crashes_failed; | |
878 } | |
879 break; | |
880 | |
881 case GOOPDATE_E_CRASH_THROTTLED: | |
882 if (is_out_of_process) { | |
883 ++metric_oop_crashes_throttled; | |
884 } else { | |
885 ++metric_crashes_throttled; | |
886 } | |
887 break; | |
888 | |
889 case GOOPDATE_E_CRASH_REJECTED: | |
890 if (is_out_of_process) { | |
891 ++metric_oop_crashes_rejected; | |
892 } else { | |
893 ++metric_crashes_rejected; | |
894 } | |
895 break; | |
896 | |
897 default: | |
898 ASSERT1(false); | |
899 break; | |
900 } | |
901 } | |
902 | |
903 int Crash::CrashNow() { | |
904 #ifdef DEBUG | |
905 CORE_LOG(LEVEL_ERROR, (_T("[Crash::CrashNow]"))); | |
906 int foo = 10; | |
907 int bar = foo - 10; | |
908 int baz = foo / bar; | |
909 return baz; | |
910 #else | |
911 return 0; | |
912 #endif | |
913 } | |
914 | |
915 HRESULT Crash::CreateLowIntegrityDesc(CSecurityDesc* sd) { | |
916 ASSERT1(sd); | |
917 ASSERT1(!sd->GetPSECURITY_DESCRIPTOR()); | |
918 | |
919 if (!vista_util::IsVistaOrLater()) { | |
920 return S_FALSE; | |
921 } | |
922 | |
923 return sd->FromString(LOW_INTEGRITY_SDDL_SACL) ? S_OK : | |
924 HRESULTFromLastError(); | |
925 } | |
926 | |
927 bool Crash::AddPipeSecurityDaclToDesc(bool is_machine, CSecurityDesc* sd) { | |
928 ASSERT1(sd); | |
929 | |
930 CAccessToken current_token; | |
931 if (!current_token.GetEffectiveToken(TOKEN_QUERY)) { | |
932 OPT_LOG(LE, (_T("[Failed to get current thread token]"))); | |
933 return false; | |
934 } | |
935 | |
936 CDacl dacl; | |
937 if (!current_token.GetDefaultDacl(&dacl)) { | |
938 OPT_LOG(LE, (_T("[Failed to get default DACL]"))); | |
939 return false; | |
940 } | |
941 | |
942 if (!is_machine) { | |
943 sd->SetDacl(dacl); | |
944 return true; | |
945 } | |
946 | |
947 if (!dacl.AddAllowedAce(ATL::Sids::Users(), kPipeAccessMask)) { | |
948 OPT_LOG(LE, (_T("[Failed to setup pipe security]"))); | |
949 return false; | |
950 } | |
951 | |
952 if (!dacl.AddDeniedAce(ATL::Sids::Network(), FILE_ALL_ACCESS)) { | |
953 OPT_LOG(LE, (_T("[Failed to setup pipe security]"))); | |
954 return false; | |
955 } | |
956 | |
957 sd->SetDacl(dacl); | |
958 return true; | |
959 } | |
960 | |
961 bool Crash::BuildPipeSecurityAttributes(bool is_machine, | |
962 CSecurityAttributes* sa) { | |
963 ASSERT1(sa); | |
964 | |
965 CSecurityDesc sd; | |
966 HRESULT hr = CreateLowIntegrityDesc(&sd); | |
967 if (FAILED(hr)) { | |
968 OPT_LOG(LE, (_T("[Failed to CreateLowIntegrityDesc][0x%x]"), hr)); | |
969 return false; | |
970 } | |
971 | |
972 if (!AddPipeSecurityDaclToDesc(is_machine, &sd)) { | |
973 return false; | |
974 } | |
975 | |
976 sa->Set(sd); | |
977 | |
978 #ifdef _DEBUG | |
979 // Print SDDL for debugging. | |
980 CString sddl; | |
981 sd.ToString(&sddl, OWNER_SECURITY_INFORMATION | | |
982 GROUP_SECURITY_INFORMATION | | |
983 DACL_SECURITY_INFORMATION | | |
984 SACL_SECURITY_INFORMATION | | |
985 LABEL_SECURITY_INFORMATION); | |
986 CORE_LOG(L1, (_T("[Pipe security SDDL][%s]"), sddl)); | |
987 #endif | |
988 | |
989 return true; | |
990 } | |
991 | |
992 bool Crash::BuildCrashDirSecurityAttributes(CSecurityAttributes* sa) { | |
993 ASSERT1(sa); | |
994 | |
995 CDacl dacl; | |
996 CAccessToken current_token; | |
997 | |
998 if (!current_token.GetEffectiveToken(TOKEN_QUERY)) { | |
999 OPT_LOG(LE, (_T("[Failed to get current thread token]"))); | |
1000 return false; | |
1001 } | |
1002 | |
1003 if (!current_token.GetDefaultDacl(&dacl)) { | |
1004 OPT_LOG(LE, (_T("[Failed to get default DACL]"))); | |
1005 return false; | |
1006 } | |
1007 | |
1008 CSecurityDesc sd; | |
1009 sd.SetDacl(dacl); | |
1010 // Prevent the security settings on the parent folder of CrashReports folder | |
1011 // from being inherited by children of CrashReports folder. | |
1012 sd.SetControl(SE_DACL_PROTECTED, SE_DACL_PROTECTED); | |
1013 sa->Set(sd); | |
1014 | |
1015 #ifdef _DEBUG | |
1016 // Print SDDL for debugging. | |
1017 CString sddl; | |
1018 sd.ToString(&sddl); | |
1019 CORE_LOG(L1, (_T("[Folder security SDDL][%s]"), sddl)); | |
1020 #endif | |
1021 | |
1022 return true; | |
1023 } | |
1024 | |
1025 HRESULT Crash::StartServer() { | |
1026 CORE_LOG(L1, (_T("[Crash::StartServer]"))); | |
1027 ++metric_crash_start_server_total; | |
1028 | |
1029 std::wstring dump_path(crash_dir_); | |
1030 | |
1031 // Append the current user's sid to the pipe name so that machine and | |
1032 // user instances of the crash server open different pipes. | |
1033 CString user_sid; | |
1034 HRESULT hr = user_info::GetProcessUser(NULL, NULL, &user_sid); | |
1035 if (FAILED(hr)) { | |
1036 OPT_LOG(LE, (_T("[user_info::GetProcessUser failed][0x%08x]"), hr)); | |
1037 return hr; | |
1038 } | |
1039 CString pipe_name(kCrashPipeNamePrefix); | |
1040 SafeCStringAppendFormat(&pipe_name, _T("\\%s"), user_sid); | |
1041 | |
1042 CSecurityAttributes pipe_sec_attrs; | |
1043 | |
1044 // * If running as machine, use custom security attributes on the pipe to | |
1045 // allow all users on the local machine to connect to it. Add the low | |
1046 // integrity SACL to account for browser plugins. | |
1047 // * If running as user, the default security descriptor for the | |
1048 // pipe grants full control to the system account, administrators, | |
1049 // and the creator owner. Add the low integrity SACL to account for browser | |
1050 // plugins. | |
1051 if (!BuildPipeSecurityAttributes(is_machine_, &pipe_sec_attrs)) { | |
1052 return GOOPDATE_E_CRASH_SECURITY_FAILED; | |
1053 } | |
1054 | |
1055 scoped_ptr<CrashGenerationServer> crash_server( | |
1056 new CrashGenerationServer(std::wstring(pipe_name), | |
1057 &pipe_sec_attrs, | |
1058 ClientConnectedCallback, NULL, | |
1059 ClientCrashedCallback, NULL, | |
1060 ClientExitedCallback, NULL, | |
1061 true, &dump_path)); | |
1062 | |
1063 if (!crash_server->Start()) { | |
1064 CORE_LOG(LE, (_T("[CrashServer::Start failed]"))); | |
1065 return GOOPDATE_E_CRASH_START_SERVER_FAILED; | |
1066 } | |
1067 | |
1068 crash_server_ = crash_server.release(); | |
1069 | |
1070 ++metric_crash_start_server_succeeded; | |
1071 return S_OK; | |
1072 } | |
1073 | |
1074 void Crash::StopServer() { | |
1075 CORE_LOG(L1, (_T("[Crash::StopServer]"))); | |
1076 delete crash_server_; | |
1077 crash_server_ = NULL; | |
1078 } | |
1079 | |
1080 | |
1081 void _cdecl Crash::ClientConnectedCallback(void* context, | |
1082 const ClientInfo* client_info) { | |
1083 ASSERT1(!context); | |
1084 ASSERT1(client_info); | |
1085 UNREFERENCED_PARAMETER(context); | |
1086 OPT_LOG(L1, (_T("[Client connected][%d]"), client_info->pid())); | |
1087 } | |
1088 | |
1089 void _cdecl Crash::ClientCrashedCallback(void* context, | |
1090 const ClientInfo* client_info, | |
1091 const std::wstring* dump_path) { | |
1092 ASSERT1(!context); | |
1093 ASSERT1(client_info); | |
1094 UNREFERENCED_PARAMETER(context); | |
1095 OPT_LOG(L1, (_T("[Client crashed][%d]"), client_info->pid())); | |
1096 | |
1097 CString crash_filename(dump_path ? dump_path->c_str() : NULL); | |
1098 HRESULT hr = Crash::CrashHandler(is_machine_, *client_info, crash_filename); | |
1099 if (FAILED(hr)) { | |
1100 OPT_LOG(LE, (_T("[CrashHandler failed][0x%08x]"), hr)); | |
1101 } | |
1102 } | |
1103 | |
1104 void _cdecl Crash::ClientExitedCallback(void* context, | |
1105 const ClientInfo* client_info) { | |
1106 ASSERT1(!context); | |
1107 ASSERT1(client_info); | |
1108 UNREFERENCED_PARAMETER(context); | |
1109 OPT_LOG(L1, (_T("[Client exited][%d]"), client_info->pid())); | |
1110 } | |
1111 | |
1112 } // namespace omaha | |
1113 | |
OLD | NEW |