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.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 InitCrashReporter(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 |