1 // Copyright (c) 2012 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.
5 #include "chrome/tools/crash_service/crash_service.h"
13 #include "base/command_line.h"
14 #include "base/file_util.h"
15 #include "base/logging.h"
16 #include "base/path_service.h"
17 #include "base/win/windows_version.h"
18 #include "breakpad/src/client/windows/crash_generation/client_info.h"
19 #include "breakpad/src/client/windows/crash_generation/crash_generation_server.h"
20 #include "breakpad/src/client/windows/sender/crash_report_sender.h"
21 #include "chrome/common/chrome_constants.h"
22 #include "chrome/common/chrome_paths.h"
26 const wchar_t kTestPipeName
[] = L
"\\\\.\\pipe\\ChromeCrashServices";
28 const wchar_t kCrashReportURL
[] = L
"https://clients2.google.com/cr/report";
29 const wchar_t kCheckPointFile
[] = L
"crash_checkpoint.txt";
31 typedef std::map
<std::wstring
, std::wstring
> CrashMap
;
33 bool CustomInfoToMap(const google_breakpad::ClientInfo
* client_info
,
34 const std::wstring
& reporter_tag
, CrashMap
* map
) {
35 google_breakpad::CustomClientInfo info
= client_info
->GetCustomInfo();
37 for (uintptr_t i
= 0; i
< info
.count
; ++i
) {
38 (*map
)[info
.entries
[i
].name
] = info
.entries
[i
].value
;
41 (*map
)[L
"rept"] = reporter_tag
;
46 bool WriteCustomInfoToFile(const std::wstring
& dump_path
, const CrashMap
& map
) {
47 std::wstring
file_path(dump_path
);
48 size_t last_dot
= file_path
.rfind(L
'.');
49 if (last_dot
== std::wstring::npos
)
51 file_path
.resize(last_dot
);
54 std::wofstream
file(file_path
.c_str(),
55 std::ios_base::out
| std::ios_base::app
| std::ios::binary
);
59 CrashMap::const_iterator pos
;
60 for (pos
= map
.begin(); pos
!= map
.end(); ++pos
) {
61 std::wstring line
= pos
->first
;
65 file
.write(line
.c_str(), static_cast<std::streamsize
>(line
.length()));
70 // The window procedure task is to handle when a) the user logs off.
71 // b) the system shuts down or c) when the user closes the window.
72 LRESULT __stdcall
CrashSvcWndProc(HWND hwnd
, UINT message
,
73 WPARAM wparam
, LPARAM lparam
) {
81 return DefWindowProc(hwnd
, message
, wparam
, lparam
);
86 // This is the main and only application window.
87 HWND g_top_window
= NULL
;
89 bool CreateTopWindow(HINSTANCE instance
, bool visible
) {
90 WNDCLASSEXW wcx
= {0};
91 wcx
.cbSize
= sizeof(wcx
);
92 wcx
.style
= CS_HREDRAW
| CS_VREDRAW
;
93 wcx
.lpfnWndProc
= CrashSvcWndProc
;
94 wcx
.hInstance
= instance
;
95 wcx
.lpszClassName
= L
"crash_svc_class";
96 ATOM atom
= ::RegisterClassExW(&wcx
);
97 DWORD style
= visible
? WS_POPUPWINDOW
| WS_VISIBLE
: WS_OVERLAPPED
;
99 // The window size is zero but being a popup window still shows in the
100 // task bar and can be closed using the system menu or using task manager.
101 HWND window
= CreateWindowExW(0, wcx
.lpszClassName
, L
"crash service", style
,
102 CW_USEDEFAULT
, CW_USEDEFAULT
, 0, 0,
103 NULL
, NULL
, instance
, NULL
);
107 ::UpdateWindow(window
);
108 VLOG(1) << "window handle is " << window
;
109 g_top_window
= window
;
113 // Simple helper class to keep the process alive until the current request
115 class ProcessingLock
{
118 ::InterlockedIncrement(&op_count_
);
121 ::InterlockedDecrement(&op_count_
);
123 static bool IsWorking() {
124 return (op_count_
!= 0);
127 static volatile LONG op_count_
;
130 volatile LONG
ProcessingLock::op_count_
= 0;
132 // This structure contains the information that the worker thread needs to
133 // send a crash dump to the server.
138 std::wstring dump_path
;
140 DumpJobInfo(DWORD process_id
, CrashService
* service
,
141 const CrashMap
& crash_map
, const std::wstring
& path
)
142 : pid(process_id
), self(service
), map(crash_map
), dump_path(path
) {
148 // Command line switches:
149 const char CrashService::kMaxReports
[] = "max-reports";
150 const char CrashService::kNoWindow
[] = "no-window";
151 const char CrashService::kReporterTag
[] = "reporter";
152 const char CrashService::kDumpsDir
[] = "dumps-dir";
153 const char CrashService::kPipeName
[] = "pipe-name";
155 CrashService::CrashService(const std::wstring
& report_dir
)
156 : report_path_(report_dir
),
159 requests_handled_(0),
161 clients_connected_(0),
162 clients_terminated_(0) {
163 chrome::RegisterPathProvider();
166 CrashService::~CrashService() {
167 base::AutoLock
lock(sending_
);
173 bool CrashService::Initialize(const std::wstring
& command_line
) {
174 using google_breakpad::CrashReportSender
;
175 using google_breakpad::CrashGenerationServer
;
177 std::wstring pipe_name
= kTestPipeName
;
178 int max_reports
= -1;
180 // The checkpoint file allows CrashReportSender to enforce the the maximum
181 // reports per day quota. Does not seem to serve any other purpose.
182 base::FilePath checkpoint_path
= report_path_
.Append(kCheckPointFile
);
184 // The dumps path is typically : '<user profile>\Local settings\
185 // Application data\Goggle\Chrome\Crash Reports' and the report path is
186 // Application data\Google\Chrome\Reported Crashes.txt
187 base::FilePath user_data_dir
;
188 if (!PathService::Get(chrome::DIR_USER_DATA
, &user_data_dir
)) {
189 LOG(ERROR
) << "could not get DIR_USER_DATA";
192 report_path_
= user_data_dir
.Append(chrome::kCrashReportLog
);
194 CommandLine cmd_line
= CommandLine::FromString(command_line
);
196 base::FilePath dumps_path
;
197 if (cmd_line
.HasSwitch(kDumpsDir
)) {
198 dumps_path
= base::FilePath(cmd_line
.GetSwitchValueNative(kDumpsDir
));
200 if (!PathService::Get(chrome::DIR_CRASH_DUMPS
, &dumps_path
)) {
201 LOG(ERROR
) << "could not get DIR_CRASH_DUMPS";
206 // We can override the send reports quota with a command line switch.
207 if (cmd_line
.HasSwitch(kMaxReports
))
208 max_reports
= _wtoi(cmd_line
.GetSwitchValueNative(kMaxReports
).c_str());
210 // Allow the global pipe name to be overridden for better testability.
211 if (cmd_line
.HasSwitch(kPipeName
))
212 pipe_name
= cmd_line
.GetSwitchValueNative(kPipeName
);
215 pipe_name
+= L
"-x64";
218 if (max_reports
> 0) {
219 // Create the http sender object.
220 sender_
= new CrashReportSender(checkpoint_path
.value());
222 LOG(ERROR
) << "could not create sender";
225 sender_
->set_max_reports_per_day(max_reports
);
228 SECURITY_ATTRIBUTES security_attributes
= {0};
229 SECURITY_ATTRIBUTES
* security_attributes_actual
= NULL
;
231 if (base::win::GetVersion() >= base::win::VERSION_VISTA
) {
232 SECURITY_DESCRIPTOR
* security_descriptor
=
233 reinterpret_cast<SECURITY_DESCRIPTOR
*>(
234 GetSecurityDescriptorForLowIntegrity());
235 DCHECK(security_descriptor
!= NULL
);
237 security_attributes
.nLength
= sizeof(security_attributes
);
238 security_attributes
.lpSecurityDescriptor
= security_descriptor
;
239 security_attributes
.bInheritHandle
= FALSE
;
241 security_attributes_actual
= &security_attributes
;
244 // Create the OOP crash generator object.
245 dumper_
= new CrashGenerationServer(pipe_name
, security_attributes_actual
,
246 &CrashService::OnClientConnected
, this,
247 &CrashService::OnClientDumpRequest
, this,
248 &CrashService::OnClientExited
, this,
250 true, &dumps_path
.value());
253 LOG(ERROR
) << "could not create dumper";
254 if (security_attributes
.lpSecurityDescriptor
)
255 LocalFree(security_attributes
.lpSecurityDescriptor
);
259 if (!CreateTopWindow(::GetModuleHandleW(NULL
),
260 !cmd_line
.HasSwitch(kNoWindow
))) {
261 LOG(ERROR
) << "could not create window";
262 if (security_attributes
.lpSecurityDescriptor
)
263 LocalFree(security_attributes
.lpSecurityDescriptor
);
267 reporter_tag_
= L
"crash svc";
268 if (cmd_line
.HasSwitch(kReporterTag
))
269 reporter_tag_
= cmd_line
.GetSwitchValueNative(kReporterTag
);
271 // Log basic information.
272 VLOG(1) << "pipe name is " << pipe_name
273 << "\ndumps at " << dumps_path
.value()
274 << "\nreports at " << report_path_
.value();
277 VLOG(1) << "checkpoint is " << checkpoint_path
.value()
278 << "\nserver is " << kCrashReportURL
279 << "\nmaximum " << sender_
->max_reports_per_day() << " reports/day"
280 << "\nreporter is " << reporter_tag_
;
282 // Start servicing clients.
283 if (!dumper_
->Start()) {
284 LOG(ERROR
) << "could not start dumper";
285 if (security_attributes
.lpSecurityDescriptor
)
286 LocalFree(security_attributes
.lpSecurityDescriptor
);
290 if (security_attributes
.lpSecurityDescriptor
)
291 LocalFree(security_attributes
.lpSecurityDescriptor
);
293 // This is throwaway code. We don't need to sync with the browser process
294 // once Google Update is updated to a version supporting OOP crash handling.
295 // Create or open an event to signal the browser process that the crash
296 // service is initialized.
297 HANDLE running_event
=
298 ::CreateEventW(NULL
, TRUE
, TRUE
, L
"g_chrome_crash_svc");
299 // If the browser already had the event open, the CreateEvent call did not
300 // signal it. We need to do it manually.
301 ::SetEvent(running_event
);
306 void CrashService::OnClientConnected(void* context
,
307 const google_breakpad::ClientInfo
* client_info
) {
309 VLOG(1) << "client start. pid = " << client_info
->pid();
310 CrashService
* self
= static_cast<CrashService
*>(context
);
311 ::InterlockedIncrement(&self
->clients_connected_
);
314 void CrashService::OnClientExited(void* context
,
315 const google_breakpad::ClientInfo
* client_info
) {
317 VLOG(1) << "client end. pid = " << client_info
->pid();
318 CrashService
* self
= static_cast<CrashService
*>(context
);
319 ::InterlockedIncrement(&self
->clients_terminated_
);
324 // When we are instructed to send reports we need to exit if there are
325 // no more clients to service. The next client that runs will start us.
326 // Only chrome.exe starts crash_service with a non-zero max_reports.
327 if (self
->clients_connected_
> self
->clients_terminated_
)
329 if (self
->sender_
->max_reports_per_day() > 0) {
330 // Wait for the other thread to send crashes, if applicable. The sender
331 // thread takes the sending_ lock, so the sleep is just to give it a
334 base::AutoLock
lock(self
->sending_
);
335 // Some people can restart chrome very fast, check again if we have
336 // a new client before exiting for real.
337 if (self
->clients_connected_
== self
->clients_terminated_
) {
338 VLOG(1) << "zero clients. exiting";
339 ::PostMessage(g_top_window
, WM_CLOSE
, 0, 0);
344 void CrashService::OnClientDumpRequest(void* context
,
345 const google_breakpad::ClientInfo
* client_info
,
346 const std::wstring
* file_path
) {
350 LOG(ERROR
) << "dump with no file path";
354 LOG(ERROR
) << "dump with no client info";
358 CrashService
* self
= static_cast<CrashService
*>(context
);
360 LOG(ERROR
) << "dump with no context";
365 CustomInfoToMap(client_info
, self
->reporter_tag_
, &map
);
367 // Move dump file to the directory under client breakpad dump location.
368 base::FilePath dump_location
= base::FilePath(*file_path
);
369 CrashMap::const_iterator it
= map
.find(L
"breakpad-dump-location");
370 if (it
!= map
.end()) {
371 base::FilePath alternate_dump_location
= base::FilePath(it
->second
);
372 file_util::CreateDirectoryW(alternate_dump_location
);
373 alternate_dump_location
= alternate_dump_location
.Append(
374 dump_location
.BaseName());
375 base::Move(dump_location
, alternate_dump_location
);
376 dump_location
= alternate_dump_location
;
379 DWORD pid
= client_info
->pid();
380 VLOG(1) << "dump for pid = " << pid
<< " is " << dump_location
.value();
382 if (!WriteCustomInfoToFile(dump_location
.value(), map
)) {
383 LOG(ERROR
) << "could not write custom info file";
389 // Send the crash dump using a worker thread. This operation has retry
390 // logic in case there is no internet connection at the time.
391 DumpJobInfo
* dump_job
= new DumpJobInfo(pid
, self
, map
,
392 dump_location
.value());
393 if (!::QueueUserWorkItem(&CrashService::AsyncSendDump
,
394 dump_job
, WT_EXECUTELONGFUNCTION
)) {
395 LOG(ERROR
) << "could not queue job";
399 // We are going to try sending the report several times. If we can't send,
400 // we sleep from one minute to several hours depending on the retry round.
401 unsigned long CrashService::AsyncSendDump(void* context
) {
405 DumpJobInfo
* info
= static_cast<DumpJobInfo
*>(context
);
407 std::wstring report_id
= L
"<unsent>";
409 const DWORD kOneMinute
= 60*1000;
410 const DWORD kOneHour
= 60*kOneMinute
;
412 const DWORD kSleepSchedule
[] = {
420 int retry_round
= arraysize(kSleepSchedule
) - 1;
423 ::Sleep(kSleepSchedule
[retry_round
]);
425 // Take the server lock while sending. This also prevent early
426 // termination of the service object.
427 base::AutoLock
lock(info
->self
->sending_
);
428 VLOG(1) << "trying to send report for pid = " << info
->pid
;
429 google_breakpad::ReportResult send_result
430 = info
->self
->sender_
->SendCrashReport(kCrashReportURL
, info
->map
,
431 info
->dump_path
, &report_id
);
432 switch (send_result
) {
433 case google_breakpad::RESULT_FAILED
:
434 report_id
= L
"<network issue>";
436 case google_breakpad::RESULT_REJECTED
:
437 report_id
= L
"<rejected>";
438 ++info
->self
->requests_handled_
;
441 case google_breakpad::RESULT_SUCCEEDED
:
442 ++info
->self
->requests_sent_
;
443 ++info
->self
->requests_handled_
;
446 case google_breakpad::RESULT_THROTTLED
:
447 report_id
= L
"<throttled>";
450 report_id
= L
"<unknown>";
455 VLOG(1) << "dump for pid =" << info
->pid
<< " crash2 id =" << report_id
;
457 } while (retry_round
>= 0);
459 if (!::DeleteFileW(info
->dump_path
.c_str()))
460 LOG(WARNING
) << "could not delete " << info
->dump_path
;
466 int CrashService::ProcessingLoop() {
468 while (GetMessage(&msg
, NULL
, 0, 0)) {
469 TranslateMessage(&msg
);
470 DispatchMessage(&msg
);
473 VLOG(1) << "session ending..";
474 while (ProcessingLock::IsWorking()) {
478 VLOG(1) << "clients connected :" << clients_connected_
479 << "\nclients terminated :" << clients_terminated_
480 << "\ndumps serviced :" << requests_handled_
481 << "\ndumps reported :" << requests_sent_
;
483 return static_cast<int>(msg
.wParam
);
486 PSECURITY_DESCRIPTOR
CrashService::GetSecurityDescriptorForLowIntegrity() {
487 // Build the SDDL string for the label.
488 std::wstring sddl
= L
"S:(ML;;NW;;;S-1-16-4096)";
490 DWORD error
= ERROR_SUCCESS
;
491 PSECURITY_DESCRIPTOR sec_desc
= NULL
;
494 BOOL sacl_present
= FALSE
;
495 BOOL sacl_defaulted
= FALSE
;
497 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl
.c_str(),
500 if (::GetSecurityDescriptorSacl(sec_desc
, &sacl_present
, &sacl
,