OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/app/breakpad_win.h" |
| 6 |
| 7 #include <windows.h> |
| 8 #include <tchar.h> |
| 9 #include <vector> |
| 10 |
| 11 #include "base/base_switches.h" |
| 12 #include "base/command_line.h" |
| 13 #include "base/file_util.h" |
| 14 #include "base/file_version_info.h" |
| 15 #include "base/registry.h" |
| 16 #include "base/string_util.h" |
| 17 #include "base/win_util.h" |
| 18 #include "chrome/app/google_update_client.h" |
| 19 #include "chrome/common/env_vars.h" |
| 20 #include "chrome/installer/util/install_util.h" |
| 21 #include "chrome/installer/util/google_update_settings.h" |
| 22 #include "breakpad/src/client/windows/handler/exception_handler.h" |
| 23 |
| 24 namespace { |
| 25 |
| 26 const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\"; |
| 27 const wchar_t kChromePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices"; |
| 28 |
| 29 // This is the well known SID for the system principal. |
| 30 const wchar_t kSystemPrincipalSid[] =L"S-1-5-18"; |
| 31 |
| 32 google_breakpad::ExceptionHandler* g_breakpad = NULL; |
| 33 |
| 34 std::vector<wchar_t*>* url_chunks = NULL; |
| 35 |
| 36 // Dumps the current process memory. |
| 37 extern "C" void __declspec(dllexport) __cdecl DumpProcess() { |
| 38 if (g_breakpad) |
| 39 g_breakpad->WriteMinidump(); |
| 40 } |
| 41 |
| 42 // Returns the custom info structure based on the dll in parameter and the |
| 43 // process type. |
| 44 static google_breakpad::CustomClientInfo* custom_info = NULL; |
| 45 google_breakpad::CustomClientInfo* GetCustomInfo(const std::wstring& dll_path, |
| 46 const std::wstring& type) { |
| 47 scoped_ptr<FileVersionInfo> |
| 48 version_info(FileVersionInfo::CreateFileVersionInfo(dll_path)); |
| 49 |
| 50 std::wstring version, product; |
| 51 if (version_info.get()) { |
| 52 // Get the information from the file. |
| 53 product = version_info->product_short_name(); |
| 54 version = version_info->product_version(); |
| 55 if (!version_info->is_official_build()) |
| 56 version.append(L"-devel"); |
| 57 } else { |
| 58 // No version info found. Make up the values. |
| 59 product = L"Chrome"; |
| 60 version = L"0.0.0.0-devel"; |
| 61 } |
| 62 |
| 63 google_breakpad::CustomInfoEntry ver_entry(L"ver", version.c_str()); |
| 64 google_breakpad::CustomInfoEntry prod_entry(L"prod", product.c_str()); |
| 65 google_breakpad::CustomInfoEntry plat_entry(L"plat", L"Win32"); |
| 66 google_breakpad::CustomInfoEntry type_entry(L"ptype", type.c_str()); |
| 67 |
| 68 if (type == L"renderer") { |
| 69 // If we're a renderer create entries for the URL. Currently we only allow |
| 70 // each chunk to be 64 characters, which isn't enough for a URL. As a hack |
| 71 // we create 8 entries and split the URL across the entries. |
| 72 google_breakpad::CustomInfoEntry url1(L"url-chunk-1", L""); |
| 73 google_breakpad::CustomInfoEntry url2(L"url-chunk-2", L""); |
| 74 google_breakpad::CustomInfoEntry url3(L"url-chunk-3", L""); |
| 75 google_breakpad::CustomInfoEntry url4(L"url-chunk-4", L""); |
| 76 google_breakpad::CustomInfoEntry url5(L"url-chunk-5", L""); |
| 77 google_breakpad::CustomInfoEntry url6(L"url-chunk-6", L""); |
| 78 google_breakpad::CustomInfoEntry url7(L"url-chunk-7", L""); |
| 79 google_breakpad::CustomInfoEntry url8(L"url-chunk-8", L""); |
| 80 |
| 81 static google_breakpad::CustomInfoEntry entries[] = |
| 82 { ver_entry, prod_entry, plat_entry, type_entry, url1, url2, url3, |
| 83 url4, url5, url6, url7, url8 }; |
| 84 |
| 85 std::vector<wchar_t*>* tmp_url_chunks = new std::vector<wchar_t*>(8); |
| 86 for (size_t i = 0; i < 8; ++i) |
| 87 (*tmp_url_chunks)[i] = entries[4 + i].value; |
| 88 url_chunks = tmp_url_chunks; |
| 89 |
| 90 static google_breakpad::CustomClientInfo custom_info = {entries, |
| 91 arraysize(entries)}; |
| 92 |
| 93 return &custom_info; |
| 94 } |
| 95 static google_breakpad::CustomInfoEntry entries[] = {ver_entry, |
| 96 prod_entry, |
| 97 plat_entry, |
| 98 type_entry}; |
| 99 |
| 100 static google_breakpad::CustomClientInfo custom_info = {entries, |
| 101 arraysize(entries)}; |
| 102 |
| 103 return &custom_info; |
| 104 } |
| 105 |
| 106 // Contains the information needed by the worker thread. |
| 107 struct CrashReporterInfo { |
| 108 google_breakpad::CustomClientInfo* custom_info; |
| 109 std::wstring dll_path; |
| 110 std::wstring process_type; |
| 111 }; |
| 112 |
| 113 // This callback is executed when the browser process has crashed, after |
| 114 // the crash dump has been created. We need to minimize the amount of work |
| 115 // done here since we have potentially corrupted process. Our job is to |
| 116 // spawn another instance of chrome which will show a 'chrome has crashed' |
| 117 // dialog. This code needs to live in the exe and thus has no access to |
| 118 // facilities such as the i18n helpers. |
| 119 bool DumpDoneCallback(const wchar_t*, const wchar_t*, void*, |
| 120 EXCEPTION_POINTERS* ex_info, |
| 121 MDRawAssertionInfo*, bool) { |
| 122 // We set CHROME_CRASHED env var. If the CHROME_RESTART is present. |
| 123 // This signals the child process to show the 'chrome has crashed' dialog. |
| 124 if (!::GetEnvironmentVariableW(env_vars::kRestartInfo, NULL, 0)) |
| 125 return true; |
| 126 ::SetEnvironmentVariableW(env_vars::kShowRestart, L"1"); |
| 127 // Now we just start chrome browser with the same command line. |
| 128 STARTUPINFOW si = {sizeof(si)}; |
| 129 PROCESS_INFORMATION pi; |
| 130 if (::CreateProcessW(NULL, ::GetCommandLineW(), NULL, NULL, FALSE, |
| 131 CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &si, &pi)) { |
| 132 ::CloseHandle(pi.hProcess); |
| 133 ::CloseHandle(pi.hThread); |
| 134 } |
| 135 // After this return we will be terminated. The actual return value is |
| 136 // not used at all. |
| 137 return true; |
| 138 } |
| 139 |
| 140 // Previous unhandled filter. Will be called if not null when we |
| 141 // intercept a crash. |
| 142 LPTOP_LEVEL_EXCEPTION_FILTER previous_filter = NULL; |
| 143 |
| 144 // Exception filter used when breakpad is not enabled. We just display |
| 145 // the "Do you want to restart" message and then we call the previous filter. |
| 146 long WINAPI ChromeExceptionFilter(EXCEPTION_POINTERS* info) { |
| 147 DumpDoneCallback(NULL, NULL, NULL, info, NULL, false); |
| 148 |
| 149 if (previous_filter) |
| 150 return previous_filter(info); |
| 151 |
| 152 return EXCEPTION_EXECUTE_HANDLER; |
| 153 } |
| 154 |
| 155 extern "C" void __declspec(dllexport) __cdecl SetActiveRendererURL( |
| 156 const wchar_t* url_cstring) { |
| 157 DCHECK(url_cstring); |
| 158 if (!url_chunks) |
| 159 return; |
| 160 |
| 161 std::wstring url(url_cstring); |
| 162 size_t num_chunks = url_chunks->size(); |
| 163 size_t chunk_index = 0; |
| 164 size_t url_size = url.size(); |
| 165 |
| 166 // Split the url across all the chunks. |
| 167 for (size_t url_offset = 0; |
| 168 chunk_index < num_chunks && url_offset < url_size; ++chunk_index) { |
| 169 size_t current_chunk_size = std::min(url_size - url_offset, |
| 170 static_cast<size_t>( |
| 171 google_breakpad::CustomInfoEntry::kValueMaxLength - 1)); |
| 172 url._Copy_s((*url_chunks)[chunk_index], |
| 173 google_breakpad::CustomInfoEntry::kValueMaxLength, |
| 174 current_chunk_size, url_offset); |
| 175 (*url_chunks)[chunk_index][current_chunk_size] = L'\0'; |
| 176 url_offset += current_chunk_size; |
| 177 } |
| 178 |
| 179 // And null terminate any unneeded chunks. |
| 180 for (; chunk_index < num_chunks; ++chunk_index) |
| 181 (*url_chunks)[chunk_index][0] = L'\0'; |
| 182 } |
| 183 |
| 184 } // namespace |
| 185 |
| 186 // This function is executed by the child process that DumpDoneCallback() |
| 187 // spawned and basically just shows the 'chrome has crashed' dialog if |
| 188 // the CHROME_CRASHED environment variable is present. |
| 189 bool ShowRestartDialogIfCrashed(bool* exit_now) { |
| 190 if (!::GetEnvironmentVariableW(env_vars::kShowRestart, NULL, 0)) |
| 191 return false; |
| 192 |
| 193 DWORD len = ::GetEnvironmentVariableW(env_vars::kRestartInfo, NULL, 0); |
| 194 if (!len) |
| 195 return true; |
| 196 |
| 197 wchar_t* restart_data = new wchar_t[len + 1]; |
| 198 ::GetEnvironmentVariableW(env_vars::kRestartInfo, restart_data, len); |
| 199 restart_data[len] = 0; |
| 200 // The CHROME_RESTART var contains the dialog strings separated by '|'. |
| 201 // See PrepareRestartOnCrashEnviroment() function for details. |
| 202 std::vector<std::wstring> dlg_strings; |
| 203 SplitString(restart_data, L'|', &dlg_strings); |
| 204 delete[] restart_data; |
| 205 if (dlg_strings.size() < 3) |
| 206 return true; |
| 207 |
| 208 // If the UI layout is right-to-left, we need to pass the appropriate MB_XXX |
| 209 // flags so that an RTL message box is displayed. |
| 210 UINT flags = MB_OKCANCEL | MB_ICONWARNING; |
| 211 if (dlg_strings[2] == env_vars::kRtlLocale) |
| 212 flags |= MB_RIGHT | MB_RTLREADING; |
| 213 |
| 214 // Show the dialog now. It is ok if another chrome is started by the |
| 215 // user since we have not initialized the databases. |
| 216 *exit_now = (IDOK != ::MessageBoxW(NULL, dlg_strings[1].c_str(), |
| 217 dlg_strings[0].c_str(), flags)); |
| 218 return true; |
| 219 } |
| 220 |
| 221 static DWORD __stdcall InitCrashReporterThread(void* param) { |
| 222 CrashReporterInfo* info = reinterpret_cast<CrashReporterInfo*>(param); |
| 223 |
| 224 // GetCustomInfo can take a few milliseconds to get the file information, so |
| 225 // we do it here so it can run in a separate thread. |
| 226 info->custom_info = GetCustomInfo(info->dll_path, info->process_type); |
| 227 |
| 228 const CommandLine& command = *CommandLine::ForCurrentProcess(); |
| 229 bool full_dump = command.HasSwitch(switches::kFullMemoryCrashReport); |
| 230 bool use_crash_service = command.HasSwitch(switches::kNoErrorDialogs) || |
| 231 GetEnvironmentVariable(L"CHROME_HEADLESS", NULL, 0); |
| 232 |
| 233 google_breakpad::ExceptionHandler::MinidumpCallback callback = NULL; |
| 234 |
| 235 if (info->process_type == L"browser") { |
| 236 // We install the post-dump callback only for the browser process. It |
| 237 // spawns a new browser process. |
| 238 callback = &DumpDoneCallback; |
| 239 } |
| 240 |
| 241 std::wstring pipe_name; |
| 242 if (use_crash_service) { |
| 243 // Crash reporting is done by crash_service.exe. |
| 244 pipe_name = kChromePipeName; |
| 245 } else { |
| 246 // We want to use the Google Update crash reporting. We need to check if the |
| 247 // user allows it first. |
| 248 if (!GoogleUpdateSettings::GetCollectStatsConsent()) { |
| 249 // The user did not allow Google Update to send crashes, we need to use |
| 250 // our default crash handler instead, but only for the browser process. |
| 251 if (callback) |
| 252 InitDefaultCrashCallback(); |
| 253 return 0; |
| 254 } |
| 255 |
| 256 // Build the pipe name. It can be either: |
| 257 // System-wide install: "NamedPipe\GoogleCrashServices\S-1-5-18" |
| 258 // Per-user install: "NamedPipe\GoogleCrashServices\<user SID>" |
| 259 std::wstring user_sid; |
| 260 if (InstallUtil::IsPerUserInstall(info->dll_path.c_str())) { |
| 261 if (!win_util::GetUserSidString(&user_sid)) { |
| 262 delete info; |
| 263 return -1; |
| 264 } |
| 265 } else { |
| 266 user_sid = kSystemPrincipalSid; |
| 267 } |
| 268 |
| 269 pipe_name = kGoogleUpdatePipeName; |
| 270 pipe_name += user_sid; |
| 271 } |
| 272 |
| 273 // Get the alternate dump directory. We use the temp path. |
| 274 wchar_t temp_dir[MAX_PATH] = {0}; |
| 275 ::GetTempPathW(MAX_PATH, temp_dir); |
| 276 |
| 277 MINIDUMP_TYPE dump_type = full_dump ? MiniDumpWithFullMemory : MiniDumpNormal; |
| 278 |
| 279 g_breakpad = new google_breakpad::ExceptionHandler(temp_dir, NULL, callback, |
| 280 NULL, google_breakpad::ExceptionHandler::HANDLER_ALL, |
| 281 dump_type, pipe_name.c_str(), info->custom_info); |
| 282 |
| 283 if (!g_breakpad->IsOutOfProcess()) { |
| 284 // The out-of-process handler is unavailable. |
| 285 ::SetEnvironmentVariable(env_vars::kNoOOBreakpad, |
| 286 info->process_type.c_str()); |
| 287 } else { |
| 288 // Tells breakpad to handle breakpoint and single step exceptions. |
| 289 // This might break JIT debuggers, but at least it will always |
| 290 // generate a crashdump for these exceptions. |
| 291 g_breakpad->set_handle_debug_exceptions(true); |
| 292 } |
| 293 |
| 294 delete info; |
| 295 return 0; |
| 296 } |
| 297 |
| 298 void InitDefaultCrashCallback() { |
| 299 previous_filter = SetUnhandledExceptionFilter(ChromeExceptionFilter); |
| 300 } |
| 301 |
| 302 void InitCrashReporterWithDllPath(const std::wstring& dll_path) { |
| 303 const CommandLine& command = *CommandLine::ForCurrentProcess(); |
| 304 if (!command.HasSwitch(switches::kDisableBreakpad)) { |
| 305 // Disable the message box for assertions. |
| 306 _CrtSetReportMode(_CRT_ASSERT, 0); |
| 307 |
| 308 // Query the custom_info now because if we do it in the thread it's going to |
| 309 // fail in the sandbox. The thread will delete this object. |
| 310 CrashReporterInfo* info = new CrashReporterInfo; |
| 311 info->process_type = command.GetSwitchValue(switches::kProcessType); |
| 312 if (info->process_type.empty()) |
| 313 info->process_type = L"browser"; |
| 314 |
| 315 info->dll_path = dll_path; |
| 316 |
| 317 // If this is not the browser, we can't be sure that we will be able to |
| 318 // initialize the crash_handler in another thread, so we run it right away. |
| 319 // This is important to keep the thread for the browser process because |
| 320 // it may take some times to initialize the crash_service process. We use |
| 321 // the Windows worker pool to make better reuse of the thread. |
| 322 if (info->process_type != L"browser") { |
| 323 InitCrashReporterThread(info); |
| 324 } else { |
| 325 if (QueueUserWorkItem( |
| 326 &InitCrashReporterThread, info, WT_EXECUTELONGFUNCTION) == 0) { |
| 327 // We failed to queue to the worker pool, initialize in this thread. |
| 328 InitCrashReporterThread(info); |
| 329 } |
| 330 } |
| 331 } |
| 332 } |
OLD | NEW |