Home | History | Annotate | Download | only in tools
      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.
      4 
      5 #include "components/crash/tools/crash_service.h"
      6 
      7 #include <windows.h>
      8 
      9 #include <sddl.h>
     10 #include <fstream>
     11 #include <map>
     12 
     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"
     20 
     21 namespace breakpad {
     22 
     23 namespace {
     24 
     25 const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
     26 
     27 const wchar_t kCrashReportURL[] = L"https://clients2.google.com/cr/report";
     28 const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt";
     29 
     30 typedef std::map<std::wstring, std::wstring> CrashMap;
     31 
     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();
     35 
     36   for (uintptr_t i = 0; i < info.count; ++i) {
     37     (*map)[info.entries[i].name] = info.entries[i].value;
     38   }
     39 
     40   (*map)[L"rept"] = reporter_tag;
     41 
     42   return !map->empty();
     43 }
     44 
     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)
     49     return false;
     50   file_path.resize(last_dot);
     51   file_path += L".txt";
     52 
     53   std::wofstream file(file_path.c_str(),
     54       std::ios_base::out | std::ios_base::app | std::ios::binary);
     55   if (!file.is_open())
     56     return false;
     57 
     58   CrashMap::const_iterator pos;
     59   for (pos = map.begin(); pos != map.end(); ++pos) {
     60     std::wstring line = pos->first;
     61     line += L':';
     62     line += pos->second;
     63     line += L'\n';
     64     file.write(line.c_str(), static_cast<std::streamsize>(line.length()));
     65   }
     66   return true;
     67 }
     68 
     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) {
     73   switch (message) {
     74     case WM_CLOSE:
     75     case WM_ENDSESSION:
     76     case WM_DESTROY:
     77       PostQuitMessage(0);
     78       break;
     79     default:
     80       return DefWindowProc(hwnd, message, wparam, lparam);
     81   }
     82   return 0;
     83 }
     84 
     85 // This is the main and only application window.
     86 HWND g_top_window = NULL;
     87 
     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   ATOM atom = ::RegisterClassExW(&wcx);
     96   DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED;
     97 
     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);
    103   if (!window)
    104     return false;
    105 
    106   ::UpdateWindow(window);
    107   VLOG(1) << "window handle is " << window;
    108   g_top_window = window;
    109   return true;
    110 }
    111 
    112 // Simple helper class to keep the process alive until the current request
    113 // finishes.
    114 class ProcessingLock {
    115  public:
    116   ProcessingLock() {
    117     ::InterlockedIncrement(&op_count_);
    118   }
    119   ~ProcessingLock() {
    120     ::InterlockedDecrement(&op_count_);
    121   }
    122   static bool IsWorking() {
    123     return (op_count_ != 0);
    124   }
    125  private:
    126   static volatile LONG op_count_;
    127 };
    128 
    129 volatile LONG ProcessingLock::op_count_ = 0;
    130 
    131 // This structure contains the information that the worker thread needs to
    132 // send a crash dump to the server.
    133 struct DumpJobInfo {
    134   DWORD pid;
    135   CrashService* self;
    136   CrashMap map;
    137   std::wstring dump_path;
    138 
    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) {
    142   }
    143 };
    144 
    145 }  // namespace
    146 
    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";
    153 
    154 CrashService::CrashService()
    155     : sender_(NULL),
    156       dumper_(NULL),
    157       requests_handled_(0),
    158       requests_sent_(0),
    159       clients_connected_(0),
    160       clients_terminated_(0) {
    161 }
    162 
    163 CrashService::~CrashService() {
    164   base::AutoLock lock(sending_);
    165   delete dumper_;
    166   delete sender_;
    167 }
    168 
    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;
    173 
    174   std::wstring pipe_name = kTestPipeName;
    175   int max_reports = -1;
    176 
    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);
    180 
    181   CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
    182 
    183   base::FilePath dumps_path_to_use = dumps_path;
    184 
    185   if (cmd_line.HasSwitch(kDumpsDir)) {
    186     dumps_path_to_use =
    187         base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir));
    188   }
    189 
    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());
    193 
    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);
    197 
    198 #ifdef _WIN64
    199   pipe_name += L"-x64";
    200 #endif
    201 
    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);
    206   }
    207 
    208   SECURITY_ATTRIBUTES security_attributes = {0};
    209   SECURITY_ATTRIBUTES* security_attributes_actual = NULL;
    210 
    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);
    216 
    217     security_attributes.nLength = sizeof(security_attributes);
    218     security_attributes.lpSecurityDescriptor = security_descriptor;
    219     security_attributes.bInheritHandle = FALSE;
    220 
    221     security_attributes_actual = &security_attributes;
    222   }
    223 
    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,
    229                                       NULL, NULL,
    230                                       true, &dumps_path_to_use.value());
    231 
    232   if (!dumper_) {
    233     LOG(ERROR) << "could not create dumper";
    234     if (security_attributes.lpSecurityDescriptor)
    235       LocalFree(security_attributes.lpSecurityDescriptor);
    236     return false;
    237   }
    238 
    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);
    244     return false;
    245   }
    246 
    247   reporter_tag_ = L"crash svc";
    248   if (cmd_line.HasSwitch(kReporterTag))
    249     reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag);
    250 
    251   // Log basic information.
    252   VLOG(1) << "pipe name is " << pipe_name
    253           << "\ndumps at " << dumps_path_to_use.value();
    254 
    255   if (sender_) {
    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_;
    260   }
    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);
    266     return false;
    267   }
    268 
    269   if (security_attributes.lpSecurityDescriptor)
    270     LocalFree(security_attributes.lpSecurityDescriptor);
    271 
    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);
    281 
    282   return true;
    283 }
    284 
    285 void CrashService::OnClientConnected(void* context,
    286     const google_breakpad::ClientInfo* client_info) {
    287   ProcessingLock lock;
    288   VLOG(1) << "client start. pid = " << client_info->pid();
    289   CrashService* self = static_cast<CrashService*>(context);
    290   ::InterlockedIncrement(&self->clients_connected_);
    291 }
    292 
    293 void CrashService::OnClientExited(void* context,
    294     const google_breakpad::ClientInfo* client_info) {
    295   ProcessingLock lock;
    296   VLOG(1) << "client end. pid = " << client_info->pid();
    297   CrashService* self = static_cast<CrashService*>(context);
    298   ::InterlockedIncrement(&self->clients_terminated_);
    299 
    300   if (!self->sender_)
    301     return;
    302 
    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_)
    307     return;
    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
    311     // chance to start.
    312     ::Sleep(1000);
    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);
    319     }
    320   }
    321 }
    322 
    323 void CrashService::OnClientDumpRequest(void* context,
    324     const google_breakpad::ClientInfo* client_info,
    325     const std::wstring* file_path) {
    326   ProcessingLock lock;
    327 
    328   if (!file_path) {
    329     LOG(ERROR) << "dump with no file path";
    330     return;
    331   }
    332   if (!client_info) {
    333     LOG(ERROR) << "dump with no client info";
    334     return;
    335   }
    336 
    337   CrashService* self = static_cast<CrashService*>(context);
    338   if (!self) {
    339     LOG(ERROR) << "dump with no context";
    340     return;
    341   }
    342 
    343   CrashMap map;
    344   CustomInfoToMap(client_info, self->reporter_tag_, &map);
    345 
    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;
    356   }
    357 
    358   DWORD pid = client_info->pid();
    359   VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value();
    360 
    361   if (!WriteCustomInfoToFile(dump_location.value(), map)) {
    362     LOG(ERROR) << "could not write custom info file";
    363   }
    364 
    365   if (!self->sender_)
    366     return;
    367 
    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";
    375   }
    376 }
    377 
    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) {
    381   if (!context)
    382     return 0;
    383 
    384   DumpJobInfo* info = static_cast<DumpJobInfo*>(context);
    385 
    386   std::wstring report_id = L"<unsent>";
    387 
    388   const DWORD kOneMinute = 60*1000;
    389   const DWORD kOneHour = 60*kOneMinute;
    390 
    391   const DWORD kSleepSchedule[] = {
    392       24*kOneHour,
    393       8*kOneHour,
    394       4*kOneHour,
    395       kOneHour,
    396       15*kOneMinute,
    397       0};
    398 
    399   int retry_round = arraysize(kSleepSchedule) - 1;
    400 
    401   do {
    402     ::Sleep(kSleepSchedule[retry_round]);
    403     {
    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>";
    414           break;
    415         case google_breakpad::RESULT_REJECTED:
    416           report_id = L"<rejected>";
    417           ++info->self->requests_handled_;
    418           retry_round = 0;
    419           break;
    420         case google_breakpad::RESULT_SUCCEEDED:
    421           ++info->self->requests_sent_;
    422           ++info->self->requests_handled_;
    423           retry_round = 0;
    424           break;
    425         case google_breakpad::RESULT_THROTTLED:
    426           report_id = L"<throttled>";
    427           break;
    428         default:
    429           report_id = L"<unknown>";
    430           break;
    431       };
    432     }
    433 
    434     VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id;
    435     --retry_round;
    436   } while (retry_round >= 0);
    437 
    438   if (!::DeleteFileW(info->dump_path.c_str()))
    439     LOG(WARNING) << "could not delete " << info->dump_path;
    440 
    441   delete info;
    442   return 0;
    443 }
    444 
    445 int CrashService::ProcessingLoop() {
    446   MSG msg;
    447   while (GetMessage(&msg, NULL, 0, 0)) {
    448     TranslateMessage(&msg);
    449     DispatchMessage(&msg);
    450   }
    451 
    452   VLOG(1) << "session ending..";
    453   while (ProcessingLock::IsWorking()) {
    454     ::Sleep(50);
    455   }
    456 
    457   VLOG(1) << "clients connected :" << clients_connected_
    458           << "\nclients terminated :" << clients_terminated_
    459           << "\ndumps serviced :" << requests_handled_
    460           << "\ndumps reported :" << requests_sent_;
    461 
    462   return static_cast<int>(msg.wParam);
    463 }
    464 
    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)";
    468 
    469   DWORD error = ERROR_SUCCESS;
    470   PSECURITY_DESCRIPTOR sec_desc = NULL;
    471 
    472   PACL sacl = NULL;
    473   BOOL sacl_present = FALSE;
    474   BOOL sacl_defaulted = FALSE;
    475 
    476   if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.c_str(),
    477                                                              SDDL_REVISION,
    478                                                              &sec_desc, NULL)) {
    479     if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
    480                                     &sacl_defaulted)) {
    481       return sec_desc;
    482     }
    483   }
    484 
    485   return NULL;
    486 }
    487 
    488 }  // namespace breakpad
    489