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 "components/crash/tools/crash_service.h"
13 #include "base/command_line.h"
14 #include "base/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/win/windows_version.h"
17 #include "breakpad/src/client/windows/crash_generation/client_info.h"
18 #include "breakpad/src/client/windows/crash_generation/crash_generation_server.h"
19 #include "breakpad/src/client/windows/sender/crash_report_sender.h"
25 const wchar_t kTestPipeName
[] = L
"\\\\.\\pipe\\ChromeCrashServices";
27 const wchar_t kCrashReportURL
[] = L
"https://clients2.google.com/cr/report";
28 const wchar_t kCheckPointFile
[] = L
"crash_checkpoint.txt";
30 typedef std::map
<std::wstring
, std::wstring
> CrashMap
;
32 bool CustomInfoToMap(const google_breakpad::ClientInfo
* client_info
,
33 const std::wstring
& reporter_tag
, CrashMap
* map
) {
34 google_breakpad::CustomClientInfo info
= client_info
->GetCustomInfo();
36 for (uintptr_t i
= 0; i
< info
.count
; ++i
) {
37 (*map
)[info
.entries
[i
].name
] = info
.entries
[i
].value
;
40 (*map
)[L
"rept"] = reporter_tag
;
45 bool WriteCustomInfoToFile(const std::wstring
& dump_path
, const CrashMap
& map
) {
46 std::wstring
file_path(dump_path
);
47 size_t last_dot
= file_path
.rfind(L
'.');
48 if (last_dot
== std::wstring::npos
)
50 file_path
.resize(last_dot
);
53 std::wofstream
file(file_path
.c_str(),
54 std::ios_base::out
| std::ios_base::app
| std::ios::binary
);
58 CrashMap::const_iterator pos
;
59 for (pos
= map
.begin(); pos
!= map
.end(); ++pos
) {
60 std::wstring line
= pos
->first
;
64 file
.write(line
.c_str(), static_cast<std::streamsize
>(line
.length()));
69 // The window procedure task is to handle when a) the user logs off.
70 // b) the system shuts down or c) when the user closes the window.
71 LRESULT __stdcall
CrashSvcWndProc(HWND hwnd
, UINT message
,
72 WPARAM wparam
, LPARAM lparam
) {
80 return DefWindowProc(hwnd
, message
, wparam
, lparam
);
85 // This is the main and only application window.
86 HWND g_top_window
= NULL
;
88 bool CreateTopWindow(HINSTANCE instance
, bool visible
) {
89 WNDCLASSEXW wcx
= {0};
90 wcx
.cbSize
= sizeof(wcx
);
91 wcx
.style
= CS_HREDRAW
| CS_VREDRAW
;
92 wcx
.lpfnWndProc
= CrashSvcWndProc
;
93 wcx
.hInstance
= instance
;
94 wcx
.lpszClassName
= L
"crash_svc_class";
95 ::RegisterClassExW(&wcx
);
96 DWORD style
= visible
? WS_POPUPWINDOW
| WS_VISIBLE
: WS_OVERLAPPED
;
98 // The window size is zero but being a popup window still shows in the
99 // task bar and can be closed using the system menu or using task manager.
100 HWND window
= CreateWindowExW(0, wcx
.lpszClassName
, L
"crash service", style
,
101 CW_USEDEFAULT
, CW_USEDEFAULT
, 0, 0,
102 NULL
, NULL
, instance
, NULL
);
106 ::UpdateWindow(window
);
107 VLOG(1) << "window handle is " << window
;
108 g_top_window
= window
;
112 // Simple helper class to keep the process alive until the current request
114 class ProcessingLock
{
117 ::InterlockedIncrement(&op_count_
);
120 ::InterlockedDecrement(&op_count_
);
122 static bool IsWorking() {
123 return (op_count_
!= 0);
126 static volatile LONG op_count_
;
129 volatile LONG
ProcessingLock::op_count_
= 0;
131 // This structure contains the information that the worker thread needs to
132 // send a crash dump to the server.
137 std::wstring dump_path
;
139 DumpJobInfo(DWORD process_id
, CrashService
* service
,
140 const CrashMap
& crash_map
, const std::wstring
& path
)
141 : pid(process_id
), self(service
), map(crash_map
), dump_path(path
) {
147 // Command line switches:
148 const char CrashService::kMaxReports
[] = "max-reports";
149 const char CrashService::kNoWindow
[] = "no-window";
150 const char CrashService::kReporterTag
[] = "reporter";
151 const char CrashService::kDumpsDir
[] = "dumps-dir";
152 const char CrashService::kPipeName
[] = "pipe-name";
154 CrashService::CrashService()
157 requests_handled_(0),
159 clients_connected_(0),
160 clients_terminated_(0) {
163 CrashService::~CrashService() {
164 base::AutoLock
lock(sending_
);
169 bool CrashService::Initialize(const base::FilePath
& operating_dir
,
170 const base::FilePath
& dumps_path
) {
171 using google_breakpad::CrashReportSender
;
172 using google_breakpad::CrashGenerationServer
;
174 std::wstring pipe_name
= kTestPipeName
;
175 int max_reports
= -1;
177 // The checkpoint file allows CrashReportSender to enforce the the maximum
178 // reports per day quota. Does not seem to serve any other purpose.
179 base::FilePath checkpoint_path
= operating_dir
.Append(kCheckPointFile
);
181 base::CommandLine
& cmd_line
= *base::CommandLine::ForCurrentProcess();
183 base::FilePath dumps_path_to_use
= dumps_path
;
185 if (cmd_line
.HasSwitch(kDumpsDir
)) {
187 base::FilePath(cmd_line
.GetSwitchValueNative(kDumpsDir
));
190 // We can override the send reports quota with a command line switch.
191 if (cmd_line
.HasSwitch(kMaxReports
))
192 max_reports
= _wtoi(cmd_line
.GetSwitchValueNative(kMaxReports
).c_str());
194 // Allow the global pipe name to be overridden for better testability.
195 if (cmd_line
.HasSwitch(kPipeName
))
196 pipe_name
= cmd_line
.GetSwitchValueNative(kPipeName
);
199 pipe_name
+= L
"-x64";
202 if (max_reports
> 0) {
203 // Create the http sender object.
204 sender_
= new CrashReportSender(checkpoint_path
.value());
205 sender_
->set_max_reports_per_day(max_reports
);
208 SECURITY_ATTRIBUTES security_attributes
= {0};
209 SECURITY_ATTRIBUTES
* security_attributes_actual
= NULL
;
211 if (base::win::GetVersion() >= base::win::VERSION_VISTA
) {
212 SECURITY_DESCRIPTOR
* security_descriptor
=
213 reinterpret_cast<SECURITY_DESCRIPTOR
*>(
214 GetSecurityDescriptorForLowIntegrity());
215 DCHECK(security_descriptor
!= NULL
);
217 security_attributes
.nLength
= sizeof(security_attributes
);
218 security_attributes
.lpSecurityDescriptor
= security_descriptor
;
219 security_attributes
.bInheritHandle
= FALSE
;
221 security_attributes_actual
= &security_attributes
;
224 // Create the OOP crash generator object.
225 dumper_
= new CrashGenerationServer(pipe_name
, security_attributes_actual
,
226 &CrashService::OnClientConnected
, this,
227 &CrashService::OnClientDumpRequest
, this,
228 &CrashService::OnClientExited
, this,
230 true, &dumps_path_to_use
.value());
233 LOG(ERROR
) << "could not create dumper";
234 if (security_attributes
.lpSecurityDescriptor
)
235 LocalFree(security_attributes
.lpSecurityDescriptor
);
239 if (!CreateTopWindow(::GetModuleHandleW(NULL
),
240 !cmd_line
.HasSwitch(kNoWindow
))) {
241 LOG(ERROR
) << "could not create window";
242 if (security_attributes
.lpSecurityDescriptor
)
243 LocalFree(security_attributes
.lpSecurityDescriptor
);
247 reporter_tag_
= L
"crash svc";
248 if (cmd_line
.HasSwitch(kReporterTag
))
249 reporter_tag_
= cmd_line
.GetSwitchValueNative(kReporterTag
);
251 // Log basic information.
252 VLOG(1) << "pipe name is " << pipe_name
253 << "\ndumps at " << dumps_path_to_use
.value();
256 VLOG(1) << "checkpoint is " << checkpoint_path
.value()
257 << "\nserver is " << kCrashReportURL
258 << "\nmaximum " << sender_
->max_reports_per_day() << " reports/day"
259 << "\nreporter is " << reporter_tag_
;
261 // Start servicing clients.
262 if (!dumper_
->Start()) {
263 LOG(ERROR
) << "could not start dumper";
264 if (security_attributes
.lpSecurityDescriptor
)
265 LocalFree(security_attributes
.lpSecurityDescriptor
);
269 if (security_attributes
.lpSecurityDescriptor
)
270 LocalFree(security_attributes
.lpSecurityDescriptor
);
272 // This is throwaway code. We don't need to sync with the browser process
273 // once Google Update is updated to a version supporting OOP crash handling.
274 // Create or open an event to signal the browser process that the crash
275 // service is initialized.
276 HANDLE running_event
=
277 ::CreateEventW(NULL
, TRUE
, TRUE
, L
"g_chrome_crash_svc");
278 // If the browser already had the event open, the CreateEvent call did not
279 // signal it. We need to do it manually.
280 ::SetEvent(running_event
);
285 void CrashService::OnClientConnected(void* context
,
286 const google_breakpad::ClientInfo
* client_info
) {
288 VLOG(1) << "client start. pid = " << client_info
->pid();
289 CrashService
* self
= static_cast<CrashService
*>(context
);
290 ::InterlockedIncrement(&self
->clients_connected_
);
293 void CrashService::OnClientExited(void* context
,
294 const google_breakpad::ClientInfo
* client_info
) {
296 VLOG(1) << "client end. pid = " << client_info
->pid();
297 CrashService
* self
= static_cast<CrashService
*>(context
);
298 ::InterlockedIncrement(&self
->clients_terminated_
);
303 // When we are instructed to send reports we need to exit if there are
304 // no more clients to service. The next client that runs will start us.
305 // Only chrome.exe starts crash_service with a non-zero max_reports.
306 if (self
->clients_connected_
> self
->clients_terminated_
)
308 if (self
->sender_
->max_reports_per_day() > 0) {
309 // Wait for the other thread to send crashes, if applicable. The sender
310 // thread takes the sending_ lock, so the sleep is just to give it a
313 base::AutoLock
lock(self
->sending_
);
314 // Some people can restart chrome very fast, check again if we have
315 // a new client before exiting for real.
316 if (self
->clients_connected_
== self
->clients_terminated_
) {
317 VLOG(1) << "zero clients. exiting";
318 ::PostMessage(g_top_window
, WM_CLOSE
, 0, 0);
323 void CrashService::OnClientDumpRequest(void* context
,
324 const google_breakpad::ClientInfo
* client_info
,
325 const std::wstring
* file_path
) {
329 LOG(ERROR
) << "dump with no file path";
333 LOG(ERROR
) << "dump with no client info";
337 CrashService
* self
= static_cast<CrashService
*>(context
);
339 LOG(ERROR
) << "dump with no context";
344 CustomInfoToMap(client_info
, self
->reporter_tag_
, &map
);
346 // Move dump file to the directory under client breakpad dump location.
347 base::FilePath dump_location
= base::FilePath(*file_path
);
348 CrashMap::const_iterator it
= map
.find(L
"breakpad-dump-location");
349 if (it
!= map
.end()) {
350 base::FilePath alternate_dump_location
= base::FilePath(it
->second
);
351 base::CreateDirectoryW(alternate_dump_location
);
352 alternate_dump_location
= alternate_dump_location
.Append(
353 dump_location
.BaseName());
354 base::Move(dump_location
, alternate_dump_location
);
355 dump_location
= alternate_dump_location
;
358 DWORD pid
= client_info
->pid();
359 VLOG(1) << "dump for pid = " << pid
<< " is " << dump_location
.value();
361 if (!WriteCustomInfoToFile(dump_location
.value(), map
)) {
362 LOG(ERROR
) << "could not write custom info file";
368 // Send the crash dump using a worker thread. This operation has retry
369 // logic in case there is no internet connection at the time.
370 DumpJobInfo
* dump_job
= new DumpJobInfo(pid
, self
, map
,
371 dump_location
.value());
372 if (!::QueueUserWorkItem(&CrashService::AsyncSendDump
,
373 dump_job
, WT_EXECUTELONGFUNCTION
)) {
374 LOG(ERROR
) << "could not queue job";
378 // We are going to try sending the report several times. If we can't send,
379 // we sleep from one minute to several hours depending on the retry round.
380 unsigned long CrashService::AsyncSendDump(void* context
) {
384 DumpJobInfo
* info
= static_cast<DumpJobInfo
*>(context
);
386 std::wstring report_id
= L
"<unsent>";
388 const DWORD kOneMinute
= 60*1000;
389 const DWORD kOneHour
= 60*kOneMinute
;
391 const DWORD kSleepSchedule
[] = {
399 int retry_round
= arraysize(kSleepSchedule
) - 1;
402 ::Sleep(kSleepSchedule
[retry_round
]);
404 // Take the server lock while sending. This also prevent early
405 // termination of the service object.
406 base::AutoLock
lock(info
->self
->sending_
);
407 VLOG(1) << "trying to send report for pid = " << info
->pid
;
408 google_breakpad::ReportResult send_result
409 = info
->self
->sender_
->SendCrashReport(kCrashReportURL
, info
->map
,
410 info
->dump_path
, &report_id
);
411 switch (send_result
) {
412 case google_breakpad::RESULT_FAILED
:
413 report_id
= L
"<network issue>";
415 case google_breakpad::RESULT_REJECTED
:
416 report_id
= L
"<rejected>";
417 ++info
->self
->requests_handled_
;
420 case google_breakpad::RESULT_SUCCEEDED
:
421 ++info
->self
->requests_sent_
;
422 ++info
->self
->requests_handled_
;
425 case google_breakpad::RESULT_THROTTLED
:
426 report_id
= L
"<throttled>";
429 report_id
= L
"<unknown>";
434 VLOG(1) << "dump for pid =" << info
->pid
<< " crash2 id =" << report_id
;
436 } while (retry_round
>= 0);
438 if (!::DeleteFileW(info
->dump_path
.c_str()))
439 LOG(WARNING
) << "could not delete " << info
->dump_path
;
445 int CrashService::ProcessingLoop() {
447 while (GetMessage(&msg
, NULL
, 0, 0)) {
448 TranslateMessage(&msg
);
449 DispatchMessage(&msg
);
452 VLOG(1) << "session ending..";
453 while (ProcessingLock::IsWorking()) {
457 VLOG(1) << "clients connected :" << clients_connected_
458 << "\nclients terminated :" << clients_terminated_
459 << "\ndumps serviced :" << requests_handled_
460 << "\ndumps reported :" << requests_sent_
;
462 return static_cast<int>(msg
.wParam
);
465 PSECURITY_DESCRIPTOR
CrashService::GetSecurityDescriptorForLowIntegrity() {
466 // Build the SDDL string for the label.
467 std::wstring sddl
= L
"S:(ML;;NW;;;S-1-16-4096)";
469 PSECURITY_DESCRIPTOR sec_desc
= NULL
;
472 BOOL sacl_present
= FALSE
;
473 BOOL sacl_defaulted
= FALSE
;
475 if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl
.c_str(),
478 if (::GetSecurityDescriptorSacl(sec_desc
, &sacl_present
, &sacl
,
487 } // namespace breakpad