910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/base/crash/breakpad_utils.h"

#include <utility>

#include "base/base_paths.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "remoting/base/version.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

namespace remoting {

// This class is allowlisted in thread_restrictions.h.
class ScopedAllowBlockingForCrashReporting : public base::ScopedAllowBlocking {
};

namespace {

const base::BasePathKey kBasePathKey =
#if BUILDFLAG(IS_WIN)
    // We can't use %TEMP% for Windows because our processes run as SYSTEM,
    // Local Service, and the user. SYSTEM processes will write to %WINDIR%\Temp
    // which we don't want to do and if a crash occurs before login, there isn't
    // a user temp env var to query. Because of these issues, we store the crash
    // dumps, which are usually < 100KB, in the install folder so they will get
    // cleaned up when the user uninstalls or we push an update.
    base::BasePathKey::DIR_ASSETS;
#else
    base::BasePathKey::DIR_TEMP;
#endif

const base::FilePath::CharType kMinidumpsPath[] =
#if BUILDFLAG(IS_WIN)
    FILE_PATH_LITERAL("minidumps");
#else
    FILE_PATH_LITERAL("chromoting/minidumps");
#endif

const base::FilePath::CharType kTempExtension[] = FILE_PATH_LITERAL("temp");
const base::FilePath::CharType kJsonExtension[] = FILE_PATH_LITERAL("json");

}  // namespace

const char kBreakpadProductVersionKey[] = "product_version";
const char kBreakpadProcessStartTimeKey[] = "process_start_time";
const char kBreakpadProcessIdKey[] = "process_id";
const char kBreakpadProcessNameKey[] = "process_name";
const char kBreakpadProcessUptimeKey[] = "process_uptime";

#if BUILDFLAG(IS_WIN)

const wchar_t kCrashServerPipeName[] =
    L"\\\\.\\pipe\\RemotingCrashService\\S-1-5-18";

base::win::ScopedHandle GetClientHandleForCrashServerPipe() {
  const ACCESS_MASK kPipeAccessMask = FILE_READ_ATTRIBUTES | FILE_READ_DATA |
                                      FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA |
                                      SYNCHRONIZE;
  const DWORD kPipeFlagsAndAttributes =
      SECURITY_IDENTIFICATION | SECURITY_SQOS_PRESENT;

  SECURITY_ATTRIBUTES security_attributes = {0};
  security_attributes.bInheritHandle = true;

  base::win::ScopedHandle handle(
      CreateFile(kCrashServerPipeName, kPipeAccessMask,
                 /*dwShareMode=*/0, &security_attributes, OPEN_EXISTING,
                 kPipeFlagsAndAttributes,
                 /*hTemplateFile=*/nullptr));
  if (!handle.get()) {
    PLOG(ERROR) << "Failed to open named pipe to crash server.";
  }

  return handle;
}

#endif  // BUILDFLAG(IS_WIN)

base::FilePath GetMinidumpDirectoryPath() {
  base::FilePath base_path;
  if (base::PathService::Get(kBasePathKey, &base_path)) {
    return base_path.Append(kMinidumpsPath);
  }

  LOG(ERROR) << "Failed to retrieve a directory for crash reporting.";
  return base::FilePath();
}

bool CreateMinidumpDirectoryIfNeeded(const base::FilePath& minidump_directory) {
  if (!base::DirectoryExists(minidump_directory) &&
      !base::CreateDirectory(minidump_directory)) {
    LOG(ERROR) << "Failed to create minidump directory: " << minidump_directory;
    return false;
  }
  return true;
}

bool WriteMetadataForMinidump(const base::FilePath& minidump_file_path,
                              base::Value::Dict metadata) {
  auto metadata_file_contents = base::WriteJson(metadata);
  if (!metadata_file_contents.has_value()) {
    LOG(ERROR) << "Failed to convert metadata to JSON.";
    return false;
  }

  ScopedAllowBlockingForCrashReporting scoped_allow_blocking;
  auto temp_metadata_file_path =
      base::FilePath(minidump_file_path).ReplaceExtension(kTempExtension);
  if (!base::WriteFile(temp_metadata_file_path, *metadata_file_contents)) {
    LOG(ERROR) << "Failed to write crash dump metadata to temp file.";
    return false;
  }

  auto metadata_file_path =
      temp_metadata_file_path.ReplaceExtension(kJsonExtension);
  if (!base::Move(temp_metadata_file_path, metadata_file_path)) {
    LOG(ERROR) << "Failed to rename temp metadata file.";
    return false;
  }

  return true;
}

BreakpadHelper::BreakpadHelper() = default;
BreakpadHelper::~BreakpadHelper() = default;

bool BreakpadHelper::Initialize(const base::FilePath& minidump_directory) {
  CHECK(!initialized_);

  const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  // This includes both executable name and the path such as:
  // /opt/google/chrome-remote-desktop/chrome-remote-desktop-host
  //   or
  // C:\\Program Files (x86)\\Chromoting\\127.0.6498.0\\remoting_host.exe
  process_name_ = cmd_line->GetProgram();

  if (!CreateMinidumpDirectoryIfNeeded(minidump_directory)) {
    return false;
  }

  initialized_ = true;
  return initialized_;
}

void BreakpadHelper::OnException() {
  // Shared by in-proc and out-of-proc exception handlers.
  if (handling_exception_.exchange(true)) {
    base::PlatformThread::Sleep(base::TimeDelta::Max());
  }
}

bool BreakpadHelper::OnMinidumpGenerated(
    const base::FilePath& minidump_file_path) {
  CHECK(initialized_);

  if (!handling_exception_.exchange(true)) {
    // Log a warning that the caller should be using OnException() to indicate
    // when exception processing has started. Don't block so the current
    // exception is handled.
    LOG(WARNING) << "OnException() not called in filter callback.";
  }

  auto process_uptime = base::Time::NowFromSystemTime() - process_start_time_;
  auto metadata =
      base::Value::Dict()
          .Set(kBreakpadProcessIdKey, static_cast<int>(process_id_))
          .Set(kBreakpadProcessNameKey, process_name_.MaybeAsASCII())
          .Set(kBreakpadProcessStartTimeKey,
               base::NumberToString(process_start_time_.ToTimeT()))
          .Set(kBreakpadProcessUptimeKey,
               base::NumberToString(process_uptime.InMilliseconds()))
          .Set(kBreakpadProductVersionKey, REMOTING_VERSION_STRING);

  bool metadata_written =
      WriteMetadataForMinidump(minidump_file_path, std::move(metadata));
  handling_exception_.exchange(false);
  return metadata_written;
}

}  // namespace remoting