OLD | NEW |
| (Empty) |
1 // Copyright 2003-2009 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 // Debug functions | |
17 | |
18 #include "omaha/base/debug.h" | |
19 | |
20 #include <dbghelp.h> | |
21 #include <wtsapi32.h> | |
22 #include <atlstr.h> | |
23 #ifdef _DEBUG | |
24 #include <atlcom.h> | |
25 #define STRSAFE_NO_DEPRECATE | |
26 #include <strsafe.h> | |
27 #endif | |
28 #include <stdlib.h> | |
29 #include <signal.h> | |
30 #include "base/basictypes.h" | |
31 #include "base/scoped_ptr.h" | |
32 #include "omaha/base/app_util.h" | |
33 #include "omaha/base/clipboard.h" | |
34 #include "omaha/base/commontypes.h" | |
35 #include "omaha/base/constants.h" | |
36 #include "omaha/base/const_addresses.h" | |
37 #include "omaha/base/const_config.h" | |
38 #include "omaha/base/const_debug.h" | |
39 #include "omaha/base/const_timeouts.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/reg_key.h" | |
45 #include "omaha/base/safe_format.h" | |
46 #include "omaha/base/scope_guard.h" | |
47 #include "omaha/base/scoped_ptr_address.h" | |
48 #include "omaha/base/string.h" | |
49 #include "omaha/base/system.h" | |
50 #include "omaha/base/synchronized.h" | |
51 #include "omaha/base/time.h" | |
52 #include "omaha/base/utils.h" | |
53 #include "omaha/base/vistautil.h" | |
54 #include "omaha/base/vista_utils.h" | |
55 | |
56 namespace omaha { | |
57 | |
58 #ifdef _DEBUG | |
59 #define kSprintfBuffers (100) // number of buffers for SPRINTF | |
60 #else | |
61 #define kSprintfBuffers (3) // number of buffers for SPRINTF | |
62 #endif | |
63 | |
64 // pad SPRINTF buffer to check for overruns | |
65 #if SHIPPING | |
66 #define kSprintfBufferOverrunPadding 0 | |
67 #else // !SHIPPING | |
68 #ifdef DEBUG | |
69 #define kSprintfBufferOverrunPadding 20000 | |
70 #else | |
71 #define kSprintfBufferOverrunPadding 1024 | |
72 #endif // DEBUG | |
73 #endif // SHIPPING | |
74 | |
75 #ifdef _DEBUG | |
76 const TCHAR* const kErrorRequestToSendFormat = | |
77 _T("*** Please hit Ignore to continue and send error information to the ") | |
78 _T("%s team ***\n*** These details have been pasted to the clipboard ***"); | |
79 | |
80 // Max length of report summary string. | |
81 const int kMaxReportSummaryLen = 1024 * 100; | |
82 #endif // DEBUG | |
83 | |
84 #define kReportIdsLock kLockPrefix \ | |
85 _T("Report_Ids_Lock_57146B01-6A07-4b8d-A1D8-0C3AFC3B2F9B") | |
86 | |
87 SELECTANY bool g_always_assert = false; | |
88 SELECTANY TCHAR *g_additional_status_ping_info = NULL; | |
89 | |
90 #define kSprintfMaxLen (1024 + 2) // max length that wvsprintf writes is 1024 | |
91 static bool g_initialized_sprintf = false; | |
92 static volatile LONG g_sprintf_interlock = 0; | |
93 static int g_current_sprintf_buffer = 0; | |
94 static TCHAR *g_sprintf_buffer = NULL; | |
95 static TCHAR *g_sprintf_buffers[kSprintfBuffers]; | |
96 SELECTANY volatile LONG g_debugassertrecursioncheck = 0; | |
97 static int g_total_reports = 0; | |
98 | |
99 SELECTANY ReportIds g_report_ids; | |
100 | |
101 // Builds a full path name out of the given filename. If the filename is | |
102 // a relative path, it is appended to the debug directory. Otherwise, if the | |
103 // filename is a full path, it returns it as the full debug filename. | |
104 static CString MakeFullDebugFilename(const TCHAR *filename) { | |
105 CString full_name; | |
106 if (lstrlen(filename) <= 2 || filename[1] != _T(':')) { | |
107 full_name = GetDebugDirectory(); | |
108 full_name += L"\\"; | |
109 } | |
110 full_name += filename; | |
111 return full_name; | |
112 } | |
113 | |
114 | |
115 // Displays the assert box. Due to session isolation, MB_SERVICE_NOTIFICATION | |
116 // flag does not work for Vista services. In this case, use WTS to display | |
117 // a message box in the active console session. | |
118 void ShowAssertDialog(const TCHAR *message, const TCHAR *title) { | |
119 int ret = 0; | |
120 OSVERSIONINFOEX osviex = {sizeof(OSVERSIONINFOEX), 0}; | |
121 const bool is_vista_or_greater = | |
122 ::GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&osviex)) && | |
123 osviex.dwMajorVersion >= 6; | |
124 bool is_system_process = false; | |
125 if (is_vista_or_greater && | |
126 SUCCEEDED(IsSystemProcess(&is_system_process)) && | |
127 is_system_process) { | |
128 DWORD session_id = System::WTSGetActiveConsoleSessionId(); | |
129 if (session_id == kInvalidSessionId) { | |
130 session_id = WTS_CURRENT_SESSION; | |
131 } | |
132 DWORD response = 0; | |
133 ::WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, | |
134 session_id, | |
135 const_cast<TCHAR*>(title), | |
136 _tcslen(title) * sizeof(TCHAR), | |
137 const_cast<TCHAR*>(message), | |
138 _tcslen(message) * sizeof(TCHAR), | |
139 MB_ABORTRETRYIGNORE | MB_ICONERROR, | |
140 0, | |
141 &response, | |
142 true); | |
143 ret = response; | |
144 } else { | |
145 ret = ::MessageBoxW(NULL, | |
146 message, | |
147 title, | |
148 MB_ABORTRETRYIGNORE | | |
149 MB_ICONERROR | | |
150 MB_SERVICE_NOTIFICATION); | |
151 } | |
152 | |
153 switch (ret) { | |
154 case IDABORT: | |
155 // Terminate the process if the user chose 'Abort'. Calling ExitProcess | |
156 // here results in calling the destructors for static objects which can | |
157 // result in deadlocks. | |
158 raise(SIGABRT); | |
159 break; | |
160 | |
161 case IDRETRY: | |
162 // Break if the user chose "Retry". | |
163 __debugbreak(); | |
164 break; | |
165 default: | |
166 // By default we ignore the message. | |
167 break; | |
168 } | |
169 } | |
170 | |
171 DebugObserver* g_debug_observer = NULL; | |
172 | |
173 // replaces the debug observer, returns the previous value. | |
174 DebugObserver* SetDebugObserver(DebugObserver* observer) { | |
175 DebugObserver* old_value = g_debug_observer; | |
176 g_debug_observer = observer; | |
177 return old_value; | |
178 } | |
179 | |
180 DebugObserver* PeekDebugObserver() { | |
181 return g_debug_observer; | |
182 } | |
183 | |
184 int SehSendMinidump(unsigned int code, | |
185 struct _EXCEPTION_POINTERS *ep, | |
186 time64 time_between_minidumps) { | |
187 if (code == EXCEPTION_BREAKPOINT) | |
188 return EXCEPTION_CONTINUE_SEARCH; | |
189 | |
190 if (::IsDebuggerPresent()) | |
191 return EXCEPTION_CONTINUE_SEARCH; | |
192 | |
193 OutputDebugString(L"**SehSendMinidump**\r\n"); | |
194 | |
195 if (g_debug_observer) { | |
196 return g_debug_observer->SehSendMinidump(code, ep, time_between_minidumps); | |
197 } | |
198 | |
199 return EXCEPTION_EXECUTE_HANDLER; | |
200 } | |
201 | |
202 #if defined(_DEBUG) || defined(ASSERT_IN_RELEASE) | |
203 CallInterceptor<DebugAssertFunctionType> debug_assert_interceptor; | |
204 | |
205 // Replaces the debug assert function; returns the old value. | |
206 DebugAssertFunctionType* ReplaceDebugAssertFunction( | |
207 DebugAssertFunctionType* replacement) { | |
208 return debug_assert_interceptor.ReplaceFunction(replacement); | |
209 } | |
210 | |
211 void OnAssert(const char *expr, const TCHAR *msg, | |
212 const char *filename, int32 linenumber) { | |
213 if (g_debug_observer) { | |
214 g_debug_observer->OnAssert(expr, msg, filename, linenumber); | |
215 } | |
216 } | |
217 #endif | |
218 | |
219 #if defined(_DEBUG) | |
220 CString OnDebugReport(uint32 id, | |
221 bool is_report, | |
222 ReportType type, | |
223 const char *expr, | |
224 const TCHAR *message, | |
225 const char *filename, | |
226 int32 linenumber, | |
227 DebugReportKind debug_report_kind) { | |
228 CString trace; | |
229 if (g_debug_observer) { | |
230 trace = g_debug_observer->OnDebugReport(id, is_report, | |
231 type, expr, message, filename, | |
232 linenumber, debug_report_kind); | |
233 } | |
234 return trace; | |
235 } | |
236 | |
237 void SendExceptionReport(const TCHAR *log_file, const TCHAR *filename, int line, | |
238 const TCHAR *type, uint32 id, bool offline) { | |
239 if (g_debug_observer) { | |
240 g_debug_observer->SendExceptionReport(log_file, filename, line, | |
241 type, id, offline); | |
242 } | |
243 } | |
244 #endif | |
245 | |
246 | |
247 #ifdef _DEBUG // won't compile since _CrtDbgReport isn't defined. | |
248 | |
249 #include <crtdbg.h> // NOLINT | |
250 static CString g_report_summary; | |
251 | |
252 // dump summary of reports on exit | |
253 SELECTANY ReportSummaryGenerator g_report_summary_generator; | |
254 | |
255 ReportSummaryGenerator::~ReportSummaryGenerator() { | |
256 DumpReportSummary(); | |
257 } | |
258 | |
259 void ReportSummaryGenerator::DumpReportSummary() { | |
260 if (g_total_reports) { | |
261 ::OutputDebugString(L"REPORT SUMMARY:\r\n"); | |
262 ::OutputDebugString(SPRINTF(L"%d total reports\r\n", g_total_reports)); | |
263 ::OutputDebugString(g_report_summary); | |
264 } else { | |
265 ::OutputDebugString(L"NO REPORTS!!\r\n"); | |
266 } | |
267 } | |
268 | |
269 TCHAR *ReportSummaryGenerator::GetReportSummary() { | |
270 TCHAR *s = new TCHAR[kMaxReportSummaryLen]; | |
271 if (s) { | |
272 s[0] = 0; | |
273 if (g_total_reports) { | |
274 SafeStrCat(s, L"REPORT SUMMARY:\r\n\r\n", kMaxReportSummaryLen); | |
275 SafeStrCat(s, | |
276 SPRINTF(L"%d total reports\r\n\r\n", g_total_reports), | |
277 kMaxReportSummaryLen); | |
278 SafeStrCat(s, | |
279 g_report_summary. | |
280 Left(kMaxReportSummaryLen - lstrlen(s) - 1).GetString(), | |
281 kMaxReportSummaryLen); | |
282 CString report_string = g_report_ids.DebugReportString(); | |
283 ReplaceCString(report_string, L"&", L"\r\n"); | |
284 SafeStrCat(s, | |
285 report_string. | |
286 Left(kMaxReportSummaryLen - lstrlen(s) - 1).GetString(), | |
287 kMaxReportSummaryLen); | |
288 } else { | |
289 SafeStrCat(s, L"NO REPORTS!!\r\n", kMaxReportSummaryLen); | |
290 } | |
291 } | |
292 | |
293 return s; | |
294 } | |
295 | |
296 static CAtlMap<CString, uint32> g_reports_done; | |
297 | |
298 #endif // _DEBUG | |
299 | |
300 #ifdef _DEBUG | |
301 | |
302 void TraceError(DWORD error) { | |
303 HLOCAL mem = NULL; | |
304 ::FormatMessage( | |
305 FORMAT_MESSAGE_ALLOCATE_BUFFER | | |
306 FORMAT_MESSAGE_FROM_SYSTEM | | |
307 FORMAT_MESSAGE_IGNORE_INSERTS, | |
308 static_cast<LPVOID>(_AtlBaseModule.GetResourceInstance()), | |
309 error, | |
310 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language | |
311 reinterpret_cast<TCHAR*>(&mem), | |
312 0, | |
313 NULL); | |
314 | |
315 TCHAR* str = reinterpret_cast<TCHAR*>(::LocalLock(mem)); | |
316 ::OutputDebugString(str); | |
317 REPORT(false, R_ERROR, (str), 3968294226); | |
318 ::LocalFree(mem); | |
319 } | |
320 | |
321 // ban ASSERT/VERIFY/REPORT to prevent recursion | |
322 #undef ASSERT | |
323 #undef VERIFY | |
324 #undef REPORT | |
325 | |
326 // TODO(omaha): fix static initialization order below. | |
327 // The initialization order of static variables is not deterministic per C++ | |
328 // standard and it depends completely on the compiler implementation. | |
329 // For VC++ compiler we are using, it seems to work as expected. | |
330 // One real fix is to put all definitons of static variables inside a class and | |
331 // define a boolean variable in this class to indicate that all necessary | |
332 // static initializations have been done. | |
333 // | |
334 // The follow definition is used to detect whether we get the exception | |
335 // during initializing static variables. If this is the case, DebugReport() | |
336 // will not function and will throw an exception because some of its refering | |
337 // static variables are not initialized yet (i.e. g_reports_done). | |
338 const int kTestInitStaticVariablesDoneValue = 1234; | |
339 struct TestInitStaticVariablesDone { | |
340 int value; | |
341 TestInitStaticVariablesDone() : value(kTestInitStaticVariablesDoneValue) {} | |
342 }; | |
343 static TestInitStaticVariablesDone test_var; | |
344 | |
345 bool DebugReport(unsigned int id, | |
346 ReportType type, | |
347 const char *expr, | |
348 const TCHAR *message, | |
349 const char *filename, | |
350 int linenumber, | |
351 DebugReportKind debug_report_kind) { | |
352 int recursion_count = ::InterlockedIncrement(&g_debugassertrecursioncheck); | |
353 ON_SCOPE_EXIT(::InterlockedDecrement, &g_debugassertrecursioncheck); | |
354 if (recursion_count > 1) { | |
355 ::OutputDebugString(_T("recursive debugreport skipped\n")); | |
356 return 1; | |
357 } | |
358 | |
359 if (debug_assert_interceptor.interceptor()) { | |
360 // call replacement function (typically used for unit tests) | |
361 // Note that I'm doing this inside the in_assert block for paranoia; | |
362 // it's not really necessary and perhaps the wrong choice. | |
363 debug_assert_interceptor.interceptor()(expr, CT2A(message), filename, | |
364 linenumber); | |
365 return true; | |
366 } | |
367 | |
368 | |
369 // Check whether we have already finished initializing all static variables | |
370 // needed for executing DebugReport(). If not, bail out. | |
371 if (test_var.value != kTestInitStaticVariablesDoneValue) { | |
372 CString debug_msg; | |
373 SafeCStringFormat(&debug_msg, _T("%hs:%d - %s - %S"), | |
374 filename, linenumber, message, expr); | |
375 debug_msg.Append(_T("\n\nException occurs while initializing ") | |
376 _T("static variables needed for DebugReport")); | |
377 ShowAssertDialog(debug_msg, _T("DebugReport")); | |
378 return true; | |
379 } | |
380 | |
381 bool is_assert = debug_report_kind == DEBUGREPORT_ASSERT; | |
382 bool is_report = debug_report_kind == DEBUGREPORT_REPORT; | |
383 bool is_abort = debug_report_kind == DEBUGREPORT_ABORT; | |
384 | |
385 if (is_report) | |
386 g_total_reports++; | |
387 | |
388 g_report_ids.ReleaseReport(id); | |
389 | |
390 if (type == R_FATAL) { | |
391 if (is_report) { | |
392 // Treat as ASSERT | |
393 is_report = false; | |
394 is_assert = true; | |
395 } | |
396 } | |
397 | |
398 bool always_assert = g_always_assert; | |
399 | |
400 if (always_assert) { | |
401 is_report = false; | |
402 is_assert = true; | |
403 } | |
404 | |
405 if (!message) { | |
406 message = _T(""); | |
407 } | |
408 | |
409 // log to debugger | |
410 TCHAR *debug_string; | |
411 // ::OutputDebugString(DEBUG_LOG_SEPARATOR); | |
412 ::OutputDebugString(is_report ? _T("REPORT: ") : | |
413 (is_assert ? _T("ASSERT: ") : _T("ABORT: "))); | |
414 | |
415 CFixedStringT<CString, 1024> proc_name = app_util::GetAppName(); | |
416 | |
417 // last %s now %s skip %d | |
418 const TCHAR* format = message && *message ? | |
419 _T("[%hs:%d][%hs][%s]") : _T("[%hs:%d][%hs]"); | |
420 debug_string = SPRINTF(format, filename, linenumber, expr, message); | |
421 | |
422 // String_Int64ToString(g_last_report_time, 10), | |
423 // String_Int64ToString(time, 10), skip_report)); | |
424 | |
425 // ::OutputDebugString(DEBUG_LOG_SEPARATOR); | |
426 // ::OutputDebugString(_T("\n")); | |
427 | |
428 #ifdef LOGGING | |
429 // Log the reports via the logging system to all loggers. | |
430 CString what = is_report ? _T("REPORT") : | |
431 is_assert ? _T("ASSERT") : _T("ABORT"); | |
432 LC_LOG(LC_LOGGING, LEVEL_ERROR, (_T("[%s]%s"), what, debug_string)); | |
433 #else | |
434 ::OutputDebugString(debug_string); | |
435 ::OutputDebugString(_T("\n")); | |
436 #endif | |
437 | |
438 // skip sending strack trace for duplicate reports | |
439 CString report_id; | |
440 SafeCStringFormat(&report_id, _T("%hs:%d"), filename, linenumber); | |
441 | |
442 uint32 prev_reports = 0; | |
443 if (g_reports_done.Lookup(report_id, prev_reports) && is_report) { | |
444 prev_reports++; | |
445 g_reports_done.SetAt(report_id, prev_reports); | |
446 ::OutputDebugString(SPRINTF(_T("skipping duplicate report %s %d\n"), | |
447 report_id.GetString(), | |
448 prev_reports)); | |
449 return 1; | |
450 } | |
451 | |
452 prev_reports++; | |
453 g_reports_done.SetAt(report_id, prev_reports); | |
454 | |
455 g_report_summary.Append(debug_string); | |
456 g_report_summary.Append(L" ("); | |
457 g_report_summary.Append(itostr(id)); | |
458 g_report_summary.Append(L")"); | |
459 g_report_summary.Append(L"\r\n"); | |
460 | |
461 // ::OutputDebugString(_T("log to file\n")); | |
462 | |
463 // log to file | |
464 CString path_name(MakeFullDebugFilename(kCiDebugLogFile)); | |
465 HANDLE h = CreateFile(path_name, | |
466 GENERIC_WRITE | GENERIC_READ, | |
467 FILE_SHARE_READ | FILE_SHARE_WRITE, | |
468 NULL, | |
469 OPEN_ALWAYS, | |
470 FILE_ATTRIBUTE_NORMAL, | |
471 NULL); | |
472 | |
473 HANDLE assert_file = INVALID_HANDLE_VALUE; | |
474 if (is_assert) { | |
475 path_name = MakeFullDebugFilename(kCiAssertOccurredFile); | |
476 assert_file = CreateFile(path_name, | |
477 GENERIC_WRITE, | |
478 0, | |
479 0, | |
480 OPEN_ALWAYS, | |
481 FILE_FLAG_WRITE_THROUGH, | |
482 NULL); | |
483 } | |
484 | |
485 HANDLE abort_file = INVALID_HANDLE_VALUE; | |
486 if (is_abort) { | |
487 path_name = MakeFullDebugFilename(kCiAbortOccurredFile); | |
488 abort_file = CreateFile(path_name, | |
489 GENERIC_WRITE, | |
490 0, | |
491 0, | |
492 OPEN_ALWAYS, | |
493 FILE_FLAG_WRITE_THROUGH, | |
494 NULL); | |
495 } | |
496 | |
497 if (h != INVALID_HANDLE_VALUE || | |
498 assert_file != INVALID_HANDLE_VALUE || | |
499 abort_file != INVALID_HANDLE_VALUE) { | |
500 // more convenient for now to have this in UTF8 | |
501 char *utf8_buffer = new char[(lstrlen(debug_string)*2) + 1]; | |
502 if (utf8_buffer) { | |
503 int conv_bytes = WideCharToMultiByte(CP_UTF8, | |
504 0, | |
505 debug_string, | |
506 lstrlen(debug_string), | |
507 utf8_buffer, | |
508 (lstrlen(debug_string) * 2) + 1, | |
509 NULL, | |
510 NULL); | |
511 | |
512 if (conv_bytes) { | |
513 DWORD bytes_written; | |
514 BOOL result; | |
515 | |
516 if (h != INVALID_HANDLE_VALUE) { | |
517 SetFilePointer(h, 0, NULL, FILE_END); | |
518 result = ::WriteFile(h, | |
519 (LPCVOID)utf8_buffer, | |
520 conv_bytes, | |
521 &bytes_written, | |
522 NULL); | |
523 result = ::WriteFile(h, | |
524 (LPCVOID)DEBUG_LOG_SEPARATOR_CHAR, | |
525 strlen(DEBUG_LOG_SEPARATOR_CHAR), | |
526 &bytes_written, | |
527 NULL); | |
528 } | |
529 if (assert_file != INVALID_HANDLE_VALUE) { | |
530 result = ::WriteFile(assert_file, | |
531 (LPCVOID)utf8_buffer, | |
532 conv_bytes, | |
533 &bytes_written, | |
534 NULL); | |
535 } | |
536 if (abort_file != INVALID_HANDLE_VALUE) { | |
537 result = ::WriteFile(abort_file, | |
538 (LPCVOID)utf8_buffer, | |
539 conv_bytes, | |
540 &bytes_written, | |
541 NULL); | |
542 } | |
543 } | |
544 | |
545 delete [] utf8_buffer; | |
546 } | |
547 } | |
548 | |
549 if (h != INVALID_HANDLE_VALUE) { | |
550 ::CloseHandle(h); | |
551 } | |
552 if (assert_file != INVALID_HANDLE_VALUE) { | |
553 ::CloseHandle(assert_file); | |
554 } | |
555 if (abort_file != INVALID_HANDLE_VALUE) { | |
556 ::CloseHandle(abort_file); | |
557 } | |
558 | |
559 CString stack_trace = OnDebugReport(id, is_report, type, expr, message, | |
560 filename, linenumber, debug_report_kind); | |
561 | |
562 if (is_report) { | |
563 return 1; | |
564 } | |
565 | |
566 ::OutputDebugString(L"show assert dialog\r\n"); | |
567 ::OutputDebugString(stack_trace.GetString()); | |
568 | |
569 CString process_path; | |
570 GetModuleFileName(NULL, &process_path); | |
571 | |
572 static TCHAR clipboard_string[4096] = {0}; | |
573 lstrcpyn(clipboard_string, | |
574 SPRINTF(L"%ls (pid=%i)\r\n%hs:%d\r\n\r\n%hs\r\n%s\r\n\r\n", | |
575 process_path, | |
576 ::GetCurrentProcessId(), | |
577 filename, | |
578 linenumber, | |
579 expr, | |
580 message), | |
581 arraysize(clipboard_string)); | |
582 stack_trace = stack_trace.Left( | |
583 arraysize(clipboard_string) - lstrlen(clipboard_string) - 1); | |
584 SafeStrCat(clipboard_string, stack_trace, arraysize(clipboard_string)); | |
585 SetClipboard(clipboard_string); | |
586 | |
587 stack_trace = stack_trace.Left(kMaxStackTraceDialogLen); | |
588 | |
589 CString assert_text; | |
590 SafeCStringFormat(&assert_text, | |
591 _T("Assertion (%ls) failed!\r\n\r\nProcess: %d ") | |
592 _T("(0x%08X)\r\nThread %d (0x%08X)\r\nProgram: %ls\r\n") | |
593 _T("Version: %s\r\nFile: %hs\r\nLine: %d\r\n\r\n"), | |
594 debug_report_kind == DEBUGREPORT_ASSERT ? | |
595 L"Assert" : (debug_report_kind == DEBUGREPORT_ABORT ? | |
596 L"Abort" : L"Report"), | |
597 ::GetCurrentProcessId(), ::GetCurrentProcessId(), ::GetCurrentThreadId(), | |
598 ::GetCurrentThreadId(), process_path, omaha::GetVersionString(), filename, | |
599 linenumber); | |
600 | |
601 CString error_request_to_send; | |
602 SafeCStringFormat(&error_request_to_send, kErrorRequestToSendFormat, kAppName)
; | |
603 | |
604 if (lstrlen(message) > 0) { | |
605 SafeCStringAppendFormat(&assert_text, | |
606 _T("Expression: %hs\r\nMessage: %s\r\n\r\n%s\r\n\r\n%s"), | |
607 expr, | |
608 message, | |
609 stack_trace, | |
610 error_request_to_send); | |
611 } else { | |
612 SafeCStringAppendFormat(&assert_text, | |
613 _T("Expression: %hs\r\n\r\n%s\r\n\r\n%s"), | |
614 expr, stack_trace, error_request_to_send); | |
615 } | |
616 | |
617 ShowAssertDialog(assert_text, CString(filename)); | |
618 return 1; | |
619 } | |
620 | |
621 #endif // #ifdef _DEBUG | |
622 | |
623 | |
624 ReportIds::ReportIds() { | |
625 data_.report_counts_num = 0; | |
626 NamedObjectAttributes lock_attr; | |
627 GetNamedObjectAttributes(kReportIdsLock, | |
628 vista_util::IsUserAdmin(), | |
629 &lock_attr); | |
630 InitializeWithSecAttr(lock_attr.name, &lock_attr.sa); | |
631 } | |
632 | |
633 ReportIds::~ReportIds() { | |
634 // don't attempt to write out reports from low integrity mode. | |
635 // | |
636 // TODO(omaha): save reports from a low integrity process (specifically IE | |
637 // which does some extra special magic to thwart this) -- | |
638 // possible by launch a process or a broker, etc. | |
639 if (vista::IsProcessProtected()) { | |
640 return; | |
641 } | |
642 | |
643 if (data_.report_counts_num != 0) { | |
644 // Back the report IDs to the registry | |
645 __mutexBlock(this) { | |
646 ReportData *reports_in_config = NULL; | |
647 if (LoadReportData(&reports_in_config)) { | |
648 MergeReports(reports_in_config, &data_); | |
649 SaveReportData(reports_in_config); | |
650 | |
651 byte *data = reinterpret_cast<byte*>(reports_in_config); | |
652 delete [] data; | |
653 } else { | |
654 // There's no data in the registry, so just fill it up with this | |
655 // component's data. | |
656 SaveReportData(&data_); | |
657 } | |
658 } | |
659 } | |
660 } | |
661 | |
662 const TCHAR* const GetRegKeyShared() { | |
663 return vista_util::IsUserAdmin() ? _T("HKLM\\") kCiRegKeyShared : | |
664 _T("HKCU\\") kCiRegKeyShared; | |
665 } | |
666 | |
667 void ReportIds::ResetReportsAfterPing() { | |
668 // We will lose reports from TRS between the time DebugReportString was called | |
669 // and now. We'll also lose reports from non TRS components if they exit in | |
670 // between this time. Not important. | |
671 data_.report_counts_num = 0; | |
672 __mutexBlock(this) { | |
673 RegKey::DeleteValue(GetRegKeyShared(), kRegValueReportIds); | |
674 } | |
675 } | |
676 | |
677 void ReportIds::MergeReports(ReportData *data1, const ReportData *data2) { | |
678 // Loop through each report ID from data2. If we find it already, increment | |
679 // the report's count in data1. Otherwise, if there's enough space, add the | |
680 // report ID to data1. | |
681 uint32 i, j; | |
682 for (i = 0; i < data2->report_counts_num; ++i) { | |
683 bool duplicate_report = false; | |
684 for (j = 0; j < data1->report_counts_num; ++j) { | |
685 if (data1->report_ids[j] == data2->report_ids[i]) { | |
686 // uint16 is promoted to int. | |
687 data1->report_counts[j] = static_cast<uint16>( | |
688 data1->report_counts[j] + data2->report_counts[i]); | |
689 duplicate_report = true; | |
690 } | |
691 } | |
692 | |
693 if (!duplicate_report && j < kMaxUniqueReports) { | |
694 data1->report_ids[j] = data2->report_ids[i]; | |
695 data1->report_counts[j] = data2->report_counts[i]; | |
696 data1->report_counts_num++; | |
697 } | |
698 } | |
699 } | |
700 | |
701 bool ReportIds::LoadReportData(ReportData **data) { | |
702 DWORD byte_count = 0; | |
703 *data = NULL; | |
704 HRESULT hr = RegKey::GetValue(GetRegKeyShared(), | |
705 kRegValueReportIds, | |
706 reinterpret_cast<byte**>(data), | |
707 &byte_count); | |
708 if (SUCCEEDED(hr)) { | |
709 if (byte_count == sizeof(ReportData)) { | |
710 return true; | |
711 } else { | |
712 delete[] data; | |
713 data = NULL; | |
714 return false; | |
715 } | |
716 } else { | |
717 return false; | |
718 } | |
719 } | |
720 | |
721 void ReportIds::SaveReportData(ReportData *data) { | |
722 if (data->report_counts_num) { | |
723 RegKey::SetValue(GetRegKeyShared(), | |
724 kRegValueReportIds, | |
725 reinterpret_cast<byte*>(data), | |
726 sizeof(ReportData)); | |
727 } | |
728 } | |
729 | |
730 bool ReportIds::ReleaseReport(uint32 id) { | |
731 uint32 max = data_.report_counts_num; | |
732 | |
733 // If two threads call simultaneously, might miss one of an existing report | |
734 // here; not important. | |
735 | |
736 uint32 i = 0; | |
737 for (i = 0; i < max; ++i) { | |
738 if (data_.report_ids[i] == id) { | |
739 data_.report_counts[i]++; | |
740 return true; | |
741 } | |
742 } | |
743 | |
744 // If two threads call simultaneously, might overwrite first of another | |
745 // report; not important. | |
746 | |
747 if (i < kMaxUniqueReports) { | |
748 data_.report_ids[i] = id; | |
749 data_.report_counts[i] = 1; | |
750 data_.report_counts_num = i + 1; // Set only after setting ids and count; | |
751 // don't use ++ | |
752 } | |
753 | |
754 #ifdef _DEBUG | |
755 OutputDebugString(SPRINTF(_T("release report %u\n"), id)); | |
756 #endif | |
757 // must return true (return value of REPORT) | |
758 return true; | |
759 } | |
760 | |
761 // caller deletes the string | |
762 TCHAR *ReportIds::DebugReportString() { | |
763 TCHAR *s = new TCHAR[(kMaxUniqueReports * kMaxReportCountString) + 1]; | |
764 if (!s) { return NULL; } | |
765 s[0] = '\0'; | |
766 | |
767 #if 0 | |
768 // this version if we use a hash table for the report counts: | |
769 uint32 id; | |
770 uint16 count; | |
771 hr = g_reports->First(&found, &id, &count); | |
772 while (SUCCEEDED(hr) && found) { | |
773 if (count) { | |
774 SafeCStringAppendFormat(&status_info, _T("%d=%d"), | |
775 id, | |
776 static_cast<uint32>(count)); | |
777 } | |
778 | |
779 hr = g_reports->Next(&found, &id, &count); | |
780 } | |
781 #endif | |
782 | |
783 // The registry will contain the REPORTs from other components, so get that | |
784 // list and merge it with TRS'. | |
785 __mutexBlock(this) { | |
786 ReportData *reports_in_config = NULL; | |
787 if (LoadReportData(&reports_in_config)) { | |
788 MergeReports(&data_, reports_in_config); | |
789 | |
790 byte *data = reinterpret_cast<byte*>(reports_in_config); | |
791 delete[] data; | |
792 } | |
793 } | |
794 | |
795 TCHAR *current_pos = s; | |
796 for (uint32 i = 0; i < data_.report_counts_num; i++) { | |
797 if (data_.report_counts[i]) { | |
798 // should be no chance of overflow, ok to use wsprintf | |
799 int n = wsprintf(current_pos, | |
800 _T("%u:%u,"), | |
801 data_.report_ids[i], | |
802 static_cast<uint32>(data_.report_counts[i])); | |
803 current_pos += n; | |
804 } | |
805 } | |
806 | |
807 return s; | |
808 } | |
809 | |
810 // A simple helper function whose sole purpose is | |
811 // to isolate the dtor from SPRINTF which uses try/except. | |
812 // app_util::GetAppName returns a CString and dtor's | |
813 // aren't allowed in functions with try/except when | |
814 // the /EHsc flag is set. | |
815 void FillInSprintfErrorString(const TCHAR * format, TCHAR * error_string) { | |
816 wsprintf(error_string, L"SPRINTF buffer overrun %ls %hs %ls", | |
817 app_util::GetAppName(), omaha::GetVersionString(), format); | |
818 } | |
819 | |
820 // following is currently included in release build | |
821 // return string from format+arglist; for debugging | |
822 TCHAR * __cdecl SPRINTF(const TCHAR * format, ...) { | |
823 while (::InterlockedCompareExchange(&g_sprintf_interlock, 1, 0) == 1) { | |
824 // while (::InterlockedIncrement(&g_sprintf_interlock)>1) { | |
825 // ::InterlockedDecrement(&g_sprintf_interlock); | |
826 // Don't process APCs here. | |
827 // Can lead to infinite recursion, for example: filecap->logging->filecap... | |
828 Sleep(0); | |
829 } | |
830 | |
831 g_current_sprintf_buffer++; | |
832 if (g_current_sprintf_buffer >= kSprintfBuffers) { | |
833 g_current_sprintf_buffer = 0; | |
834 } | |
835 | |
836 TCHAR *sprintf_buf = NULL; | |
837 | |
838 if (!g_initialized_sprintf) { // initialize buffers | |
839 g_sprintf_buffer = new TCHAR[ | |
840 ((kSprintfMaxLen + 1) * kSprintfBuffers) + | |
841 kSprintfBufferOverrunPadding]; | |
842 TCHAR* buffer = g_sprintf_buffer; | |
843 if (!buffer) { goto cleanup; } | |
844 for (int i = 0; i < kSprintfBuffers; ++i) { | |
845 g_sprintf_buffers[i] = buffer; | |
846 buffer += kSprintfMaxLen + 1; | |
847 } | |
848 | |
849 #if !SHIPPING | |
850 for (int i = ((kSprintfMaxLen+1) * kSprintfBuffers); | |
851 i < ((kSprintfMaxLen + 1) * kSprintfBuffers) + | |
852 kSprintfBufferOverrunPadding; | |
853 ++i) { | |
854 g_sprintf_buffer[i] = 1; | |
855 } | |
856 #endif | |
857 | |
858 // InitializeCriticalSection(&g_sprintf_critical_section); | |
859 g_initialized_sprintf = true; | |
860 } | |
861 | |
862 sprintf_buf = g_sprintf_buffers[g_current_sprintf_buffer]; | |
863 | |
864 // EnterCriticalSection(&g_sprintf_critical_section); | |
865 | |
866 __try { | |
867 // create the formatted CString | |
868 va_list vl; | |
869 va_start(vl, format); | |
870 #ifdef DEBUG | |
871 StringCbVPrintfW(sprintf_buf, kSprintfMaxLen, format, vl); | |
872 #else | |
873 wvsprintfW(sprintf_buf, format, vl); | |
874 #endif | |
875 va_end(vl); | |
876 | |
877 #if !SHIPPING | |
878 for (int i = ((kSprintfMaxLen+1) * kSprintfBuffers); | |
879 i < ((kSprintfMaxLen+1) * kSprintfBuffers) + | |
880 kSprintfBufferOverrunPadding; | |
881 ++i) { | |
882 if (g_sprintf_buffer[i] != 1) { | |
883 TCHAR error_string[1024]; | |
884 FillInSprintfErrorString(format, error_string); | |
885 MessageBox(NULL, | |
886 error_string, | |
887 error_string, | |
888 MB_OK | MB_SETFOREGROUND | MB_TOPMOST); | |
889 break; | |
890 } | |
891 } | |
892 #endif | |
893 } | |
894 __except(EXCEPTION_EXECUTE_HANDLER) { | |
895 lstrcpyn(sprintf_buf, _T("sprintf failure"), kSprintfMaxLen); | |
896 } | |
897 | |
898 // LeaveCriticalSection(&g_sprintf_critical_section); | |
899 | |
900 cleanup: | |
901 | |
902 ::InterlockedDecrement(&g_sprintf_interlock); | |
903 return sprintf_buf; | |
904 } | |
905 | |
906 #if 0 | |
907 TCHAR * __cdecl SPRINTF(const TCHAR * format, ...) { | |
908 ASSERT(format, (L"")); | |
909 | |
910 g_current_sprintf_buffer++; | |
911 if (g_current_sprintf_buffer >= kSprintfBuffers) { | |
912 g_current_sprintf_buffer = 0; | |
913 } | |
914 | |
915 TCHAR *sprintf_buf = sprintf_buffers[g_current_sprintf_buffer]; | |
916 CFixedStringT<CString, kSprintfMaxLen> out; | |
917 | |
918 va_list argptr; | |
919 va_start(argptr, format); | |
920 out.FormatV(format, argptr); | |
921 va_end(argptr); | |
922 | |
923 // copy to fixed return buffers | |
924 SafeStrCat(sprintf_buf, | |
925 out.GetBufferSetLength(kSprintfMaxLen), | |
926 g_current_sprintf_buffer); | |
927 sprintf_buf[kSprintfMaxLen] = '\0'; | |
928 | |
929 return sprintf_buf; | |
930 } | |
931 #endif | |
932 | |
933 // Cleanup allocated memory | |
934 class SprintfCleaner { | |
935 public: | |
936 SprintfCleaner() {} | |
937 | |
938 ~SprintfCleaner() { | |
939 while (::InterlockedCompareExchange(&g_sprintf_interlock, 1, 0) == 1) { | |
940 Sleep(0); | |
941 } | |
942 | |
943 if (g_initialized_sprintf) { | |
944 delete[] g_sprintf_buffer; | |
945 for (int i = 0; i < kSprintfBuffers; ++i) { | |
946 g_sprintf_buffers[i] = NULL; | |
947 } | |
948 g_initialized_sprintf = false; | |
949 } | |
950 | |
951 ::InterlockedDecrement(&g_sprintf_interlock); | |
952 } | |
953 | |
954 private: | |
955 DISALLOW_EVIL_CONSTRUCTORS(SprintfCleaner); | |
956 }; | |
957 | |
958 static SprintfCleaner cleaner; | |
959 | |
960 // This is for our testers to find asserts in release mode. | |
961 #if !defined(_DEBUG) && defined(ASSERT_IN_RELEASE) | |
962 bool ReleaseAssert(const char *expr, | |
963 const TCHAR *msg, | |
964 const char *filename, | |
965 int32 linenumber) { | |
966 ASSERT(filename, (L"")); | |
967 ASSERT(msg, (L"")); | |
968 ASSERT(expr, (L"")); | |
969 | |
970 if (debug_assert_interceptor.interceptor()) { | |
971 // call replacement function (typically used for unit tests) | |
972 // Note that I'm doing this inside the in_assert block for paranoia; | |
973 // it's not really necessary and perhaps the wrong choice. | |
974 debug_assert_interceptor.interceptor()(expr, | |
975 CT2CA(msg), | |
976 filename, | |
977 linenumber); | |
978 return true; | |
979 } | |
980 | |
981 OnAssert(expr, msg, filename, linenumber); | |
982 | |
983 // Also put up a message box. | |
984 TCHAR error_string[1024] = {0}; | |
985 wsprintf(error_string, | |
986 L"App: %ls\r\n" | |
987 L"Expr: %hs\r\n" | |
988 L"File: %hs\r\n" | |
989 L"Line: %d\r\n" | |
990 L"Version: %hs\r\n" | |
991 L"Message: ", | |
992 app_util::GetAppName(), | |
993 expr, | |
994 filename, | |
995 linenumber, | |
996 VER_TIMESTAMP_STR_FILE); | |
997 SafeStrCat(error_string, msg, arraysize(error_string)); | |
998 SafeStrCat(error_string, | |
999 L"\r\n\r\n*** This message has been copied to the clipboard. ***", | |
1000 arraysize(error_string)); | |
1001 SetClipboard(error_string); | |
1002 | |
1003 TCHAR title_string[1024]; | |
1004 wsprintf(title_string, L"%s ASSERT %s %hs", | |
1005 kAppName, app_util::GetAppName(), VER_TIMESTAMP_STR_FILE); | |
1006 MessageBox(NULL, | |
1007 error_string, | |
1008 title_string, | |
1009 MB_OK | MB_SETFOREGROUND | MB_TOPMOST); | |
1010 return true; | |
1011 } | |
1012 #endif | |
1013 | |
1014 #if defined(_DEBUG) | |
1015 void DebugAbort(const TCHAR *msg, | |
1016 const char* filename, | |
1017 int32 linenumber, | |
1018 bool do_abort) { | |
1019 DebugReport(0, R_FATAL, "", msg, filename, linenumber, DEBUGREPORT_ABORT); | |
1020 if (do_abort) { | |
1021 abort(); | |
1022 } | |
1023 } | |
1024 #else | |
1025 void ReleaseAbort(const TCHAR *msg, | |
1026 const char* filename, | |
1027 int32 linenumber, | |
1028 bool do_abort) { | |
1029 // Send info to the server. | |
1030 #if defined(ASSERT_IN_RELEASE) | |
1031 OnAssert("", msg, filename, linenumber); | |
1032 #endif | |
1033 | |
1034 // Also put up a message box. | |
1035 TCHAR error_string[1024] = {0}; | |
1036 wsprintf(error_string, | |
1037 L"App: %ls\r\n" | |
1038 L"File: %hs\r\n" | |
1039 L"Line: %d\r\n" | |
1040 L"Version: %hs\r\n" | |
1041 L"Message: ", | |
1042 app_util::GetAppName(), | |
1043 filename, | |
1044 linenumber, | |
1045 omaha::GetVersionString()); | |
1046 SafeStrCat(error_string, msg, arraysize(error_string)); | |
1047 SafeStrCat(error_string, | |
1048 L"\r\n\r\n*** This message has been copied to the clipboard. ***", | |
1049 arraysize(error_string)); | |
1050 SetClipboard(error_string); | |
1051 | |
1052 TCHAR title_string[1024]; | |
1053 wsprintf(title_string, | |
1054 L"%s ABORT %s %hs", | |
1055 kAppName, | |
1056 app_util::GetAppName(), | |
1057 omaha::GetVersionString()); | |
1058 MessageBox(NULL, | |
1059 error_string, | |
1060 title_string, | |
1061 MB_OK | MB_SETFOREGROUND | MB_TOPMOST); | |
1062 | |
1063 if (do_abort) { | |
1064 abort(); | |
1065 } | |
1066 } | |
1067 #endif | |
1068 | |
1069 | |
1070 #ifdef _DEBUG | |
1071 | |
1072 void DumpInterface(IUnknown* unknown) { | |
1073 if (!unknown) | |
1074 return; | |
1075 | |
1076 OutputDebugString(_T("------------------------------------------------\r\n")); | |
1077 | |
1078 // Open the HKCR\Interfaces key where the IIDs of marshalable interfaces | |
1079 // are stored. | |
1080 RegKey key; | |
1081 if (SUCCEEDED(key.Open(HKEY_CLASSES_ROOT, _T("Interface"), KEY_READ))) { | |
1082 TCHAR name[_MAX_PATH + 1] = {0}; | |
1083 DWORD name_size = _MAX_PATH; | |
1084 DWORD index = 0; | |
1085 FILETIME last_written; | |
1086 | |
1087 // | |
1088 // Enumerate through the IIDs and see if the object supports it | |
1089 // by calling QueryInterface. | |
1090 // | |
1091 while (::RegEnumKeyEx(key.Key(), | |
1092 index++, | |
1093 name, | |
1094 &name_size, | |
1095 NULL, | |
1096 NULL, | |
1097 NULL, | |
1098 &last_written) == ERROR_SUCCESS) { | |
1099 // Convert the string to an IID | |
1100 IID iid; | |
1101 HRESULT hr = StringToGuidSafe(name, &iid); | |
1102 | |
1103 CComPtr<IUnknown> test; | |
1104 if (unknown->QueryInterface(iid, | |
1105 reinterpret_cast<void**>(&test)) == S_OK) { | |
1106 // | |
1107 // The object supports this interface. | |
1108 // See if we can get a human readable name for the interface | |
1109 // If not, the name buffer already contains the string | |
1110 // representation of the IID, which we'll use as a fallback. | |
1111 // | |
1112 RegKey sub_key; | |
1113 if (sub_key.Open(key.Key(), name, KEY_READ) == S_OK) { | |
1114 scoped_array<TCHAR> display; | |
1115 // If this fails, we should still have the IID | |
1116 if (sub_key.GetValue(NULL, address(display)) == S_OK) | |
1117 lstrcpyn(name, display.get(), _MAX_PATH); | |
1118 } | |
1119 | |
1120 CString fmt; | |
1121 SafeCStringFormat(&fmt, _T(" %s\r\n"), name); | |
1122 OutputDebugString(fmt); | |
1123 } | |
1124 | |
1125 ZeroMemory(name, arraysize(name)); | |
1126 name_size = _MAX_PATH; | |
1127 } | |
1128 } | |
1129 | |
1130 OutputDebugString(_T("------------------------------------------------\r\n")); | |
1131 } | |
1132 #endif | |
1133 | |
1134 // TODO(omaha): the implementation below is using CStrings so it is not very | |
1135 // conservative in terms of memory allocations. | |
1136 int SehNoMinidump(unsigned int code, struct _EXCEPTION_POINTERS *, | |
1137 const char *filename, int32 linenumber, bool show_message) { | |
1138 if (code == EXCEPTION_BREAKPOINT) | |
1139 return EXCEPTION_CONTINUE_SEARCH; | |
1140 | |
1141 uint32 latest_cl = 0; | |
1142 #ifdef VERSION_LATEST_CL | |
1143 latest_cl = VERSION_LATEST_CL; | |
1144 #endif | |
1145 | |
1146 if (show_message) { | |
1147 TCHAR message[1025] = {0}; | |
1148 wsprintf(message, | |
1149 _T("Exception %x in %s %s %u\r\n\r\n%hs:%d\r\n"), | |
1150 code, | |
1151 app_util::GetAppName(), | |
1152 omaha::GetVersionString(), | |
1153 latest_cl, | |
1154 filename, | |
1155 linenumber); | |
1156 | |
1157 SetClipboard(message); | |
1158 uint32 type = MB_ABORTRETRYIGNORE | | |
1159 MB_ICONERROR | | |
1160 MB_SERVICE_NOTIFICATION | | |
1161 MB_SETFOREGROUND | | |
1162 MB_TOPMOST; | |
1163 int ret = ::MessageBox(NULL, message, _T("Exception"), type); | |
1164 switch (ret) { | |
1165 case IDABORT: | |
1166 // Kamikaze if the user chose 'abort' | |
1167 ::ExitProcess(static_cast<UINT>(-1)); | |
1168 break; | |
1169 | |
1170 case IDRETRY: | |
1171 // Break if the user chose "retry" | |
1172 __debugbreak(); | |
1173 break; | |
1174 | |
1175 default: | |
1176 // By default we ignore the message | |
1177 break; | |
1178 } | |
1179 } | |
1180 return EXCEPTION_EXECUTE_HANDLER; | |
1181 } | |
1182 | |
1183 CString GetDebugDirectory() { | |
1184 CString debug_dir; | |
1185 CString system_drive = GetEnvironmentVariableAsString(_T("SystemDrive")); | |
1186 if (!system_drive.IsEmpty()) { | |
1187 debug_dir += system_drive; | |
1188 debug_dir += L"\\"; | |
1189 } | |
1190 debug_dir += kCiDebugDirectory; | |
1191 return debug_dir; | |
1192 } | |
1193 | |
1194 } // namespace omaha | |
1195 | |
OLD | NEW |