// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This module contains the necessary code to register the Breakpad exception
// handler. This implementation is based on Chrome crash reporting code. See:
//   - src/components/breakpad/app/breakpad_win.cc
//   - src/chrome/installer/setup/setup_main.cc

#include "remoting/base/breakpad.h"

#include <windows.h>
#include <string>

#include "base/atomicops.h"
#include "base/file_version_info.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/process/memory.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/wrapped_window_proc.h"
#include "breakpad/src/client/windows/handler/exception_handler.h"

namespace remoting {
void InitializeCrashReportingForTest(const wchar_t* pipe_name);
}  // namespace remoting

namespace {

const wchar_t kBreakpadProductName[] = L"Chromoting";
const wchar_t kBreakpadVersionEntry[] = L"ver";
const wchar_t kBreakpadVersionDefault[] = L"0.1.0.0";
const wchar_t kBreakpadProdEntry[] = L"prod";
const wchar_t kBreakpadPlatformEntry[] = L"plat";
const wchar_t kBreakpadPlatformWin32[] = L"Win32";

// The protocol for connecting to the out-of-process Breakpad crash
// reporter is different for x86-32 and x86-64: the message sizes
// are different because the message struct contains a pointer.  As
// a result, there are two different named pipes to connect to.  The
// 64-bit one is distinguished with an "-x64" suffix.
#if defined(_WIN64)
const wchar_t kGoogleUpdatePipeName[] =
    L"\\\\.\\pipe\\GoogleCrashServices\\S-1-5-18-x64";
#else
const wchar_t kGoogleUpdatePipeName[] =
    L"\\\\.\\pipe\\GoogleCrashServices\\S-1-5-18";
#endif

using base::subtle::AtomicWord;
using base::subtle::NoBarrier_CompareAndSwap;

class BreakpadWin {
 public:
  BreakpadWin();
  ~BreakpadWin();

  static BreakpadWin* GetInstance();

 private:
  // Returns the Custom information to be used for crash reporting.
  google_breakpad::CustomClientInfo* GetCustomInfo();

  // This callback is executed when the process has crashed and *before*
  // the crash dump is created. To prevent duplicate crash reports we
  // make every thread calling this method, except the very first one,
  // go to sleep.
  static bool OnExceptionCallback(void* context,
                                  EXCEPTION_POINTERS* exinfo,
                                  MDRawAssertionInfo* assertion);

  // Crashes the process after generating a dump for the provided exception.
  // Note that the crash reporter should be initialized before calling this
  // function for it to do anything.
  static int OnWindowProcedureException(EXCEPTION_POINTERS* exinfo);

  // Breakpad's exception handler.
  scoped_ptr<google_breakpad::ExceptionHandler> breakpad_;

  // This flag is used to indicate that an exception is already being handled.
  volatile AtomicWord handling_exception_;

  // The testing hook below allows overriding the crash server pipe name.
  static const wchar_t* pipe_name_;

  friend void ::remoting::InitializeCrashReportingForTest(const wchar_t*);

  DISALLOW_COPY_AND_ASSIGN(BreakpadWin);
};

// |LazyInstance| is used to guarantee that the exception handler will be
// initialized exactly once.
// N.B. LazyInstance does not allow this to be a static member of the class.
static base::LazyInstance<BreakpadWin>::Leaky g_instance =
    LAZY_INSTANCE_INITIALIZER;

const wchar_t* BreakpadWin::pipe_name_ = kGoogleUpdatePipeName;

BreakpadWin::BreakpadWin() : handling_exception_(0) {
  // Disable the message box for assertions.
  _CrtSetReportMode(_CRT_ASSERT, 0);

  // Get the alternate dump directory. We use the temp path.
  // N.B. We don't use base::GetTempDir() here to avoid running more code then
  //      necessary before crashes can be properly reported.
  wchar_t temp_directory[MAX_PATH + 1] = { 0 };
  DWORD length = GetTempPath(MAX_PATH, temp_directory);
  if (length == 0)
    return;

  // Minidump with stacks, PEB, TEBs and unloaded module list.
  MINIDUMP_TYPE dump_type = static_cast<MINIDUMP_TYPE>(
      MiniDumpWithProcessThreadData |
      MiniDumpWithUnloadedModules);
  breakpad_.reset(
      new google_breakpad::ExceptionHandler(
          temp_directory, &OnExceptionCallback, NULL, NULL,
          google_breakpad::ExceptionHandler::HANDLER_ALL, dump_type,
          pipe_name_, GetCustomInfo()));

  if (breakpad_->IsOutOfProcess()) {
    // Tells breakpad to handle breakpoint and single step exceptions.
    breakpad_->set_handle_debug_exceptions(true);
  }

  // Catch exceptions thrown from a window procedure.
  base::win::WinProcExceptionFilter exception_filter =
      base::win::SetWinProcExceptionFilter(&OnWindowProcedureException);
  CHECK(!exception_filter);
}

BreakpadWin::~BreakpadWin() {
  // This object should be leaked so that crashes occurred during the process
  // shutdown will be caught.
  NOTREACHED();
}

// static
BreakpadWin* BreakpadWin::GetInstance() {
  return &g_instance.Get();
}

// Returns the Custom information to be used for crash reporting.
google_breakpad::CustomClientInfo* BreakpadWin::GetCustomInfo() {
  HMODULE binary = base::GetModuleFromAddress(
      reinterpret_cast<void*>(&remoting::InitializeCrashReporting));
  scoped_ptr<FileVersionInfo> version_info(
      FileVersionInfo::CreateFileVersionInfoForModule(binary));

  static wchar_t version[64];
  if (version_info.get()) {
    wcscpy_s(version, version_info->product_version().c_str());
  } else {
    wcscpy_s(version, kBreakpadVersionDefault);
  }

  static google_breakpad::CustomInfoEntry ver_entry(
      kBreakpadVersionEntry, version);
  static google_breakpad::CustomInfoEntry prod_entry(
      kBreakpadProdEntry, kBreakpadProductName);
  static google_breakpad::CustomInfoEntry plat_entry(
      kBreakpadPlatformEntry, kBreakpadPlatformWin32);
  static google_breakpad::CustomInfoEntry entries[] = {
      ver_entry, prod_entry, plat_entry  };
  static google_breakpad::CustomClientInfo custom_info = {
      entries, arraysize(entries) };
  return &custom_info;
}

// static
bool BreakpadWin::OnExceptionCallback(void* /* context */,
                                      EXCEPTION_POINTERS* /* exinfo */,
                                      MDRawAssertionInfo* /* assertion */) {
  BreakpadWin* self = BreakpadWin::GetInstance();
  if (NoBarrier_CompareAndSwap(&self->handling_exception_, 0, 1) != 0) {
    // Capture every thread except the first one in the sleep. We don't
    // want multiple threads to concurrently report exceptions.
    ::Sleep(INFINITE);
  }
  return true;
}

// static
int BreakpadWin::OnWindowProcedureException(EXCEPTION_POINTERS* exinfo) {
  BreakpadWin* self = BreakpadWin::GetInstance();
  if (self->breakpad_.get() != NULL) {
    self->breakpad_->WriteMinidumpForException(exinfo);
    TerminateProcess(GetCurrentProcess(),
                     exinfo->ExceptionRecord->ExceptionCode);
  }
  return EXCEPTION_CONTINUE_SEARCH;
}

}  // namespace

namespace remoting {

void InitializeCrashReporting() {
  // Touch the object to make sure it is initialized.
  BreakpadWin::GetInstance();
}

void InitializeCrashReportingForTest(const wchar_t* pipe_name) {
  BreakpadWin::pipe_name_ = pipe_name;
  InitializeCrashReporting();
}

}  // namespace remoting
