chromium/tools/win/chromeexts/commands/crash_info_command.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/win/chromeexts/commands/crash_info_command.h"

#include <windows.h>

#include <dbgeng.h>

#include <cstring>
#include <ctime>
#include <vector>

#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "third_party/crashpad/crashpad/client/annotation.h"
#include "third_party/crashpad/crashpad/minidump/minidump_extensions.h"
#include "third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h"

namespace tools {
namespace win {
namespace chromeexts {

namespace {

template <typename T, size_t N>
constexpr size_t countof(T const (&)[N]) {
  return N;
}

// Get a readable string to represent a process integrity level.
const char* ProcessIntegrityString(ULONG32 integrity_level) {
  if (integrity_level < SECURITY_MANDATORY_LOW_RID) {
    return "Untrusted";
  }
  if (integrity_level < SECURITY_MANDATORY_MEDIUM_RID) {
    return "Low";
  }
  if (integrity_level < SECURITY_MANDATORY_MEDIUM_PLUS_RID) {
    return "Medium";
  }
  if (integrity_level < SECURITY_MANDATORY_HIGH_RID) {
    return "Medium Plus";
  }
  if (integrity_level < SECURITY_MANDATORY_SYSTEM_RID) {
    return "High";
  }
  if (integrity_level < SECURITY_MANDATORY_PROTECTED_PROCESS_RID) {
    return "System";
  }
  return "Protected Process";
}

}  // namespace

CrashInfoCommand::CrashInfoCommand() = default;

CrashInfoCommand::~CrashInfoCommand() = default;

HRESULT CrashInfoCommand::Execute() {
  ULONG debuggee_class;
  ULONG debuggee_qualifier;
  GetDebugClientAs<IDebugControl>()->GetDebuggeeType(&debuggee_class,
                                                     &debuggee_qualifier);

  // If the debug target is a minidump, it may be a crashpad dump.
  bool crashpad_dump = debuggee_class == DEBUG_CLASS_USER_WINDOWS &&
                       debuggee_qualifier == DEBUG_USER_WINDOWS_SMALL_DUMP;

  crashpad::MinidumpCrashpadInfo info;
  HRESULT hr;
  size_t bytes_read;

  if (crashpad_dump) {
    // Read the crashpad stream to verify this is a crashpad dump.
    hr = GetDebugClientAs<IDebugClient5>()->QueryInterface(
        IID_PPV_ARGS(&debug_advanced_));
    if (FAILED(hr)) {
      PrintErrorf("QI for IDebugAdvanced3: %08X\n", hr);
      return hr;
    }

    hr = ReadFromDumpStream(
        crashpad::MinidumpStreamType::kMinidumpStreamTypeCrashpadInfo, 0,
        sizeof(info), &info, &bytes_read);
    if (hr == E_NOINTERFACE) {
      // The request returns E_NOINTERFACE when the requested stream
      // type isn't found.
      crashpad_dump = false;
    } else if (FAILED(hr)) {
      PrintErrorf("Reading crashpad info: %08X\n", hr);
      return hr;
    }
  }

  if (!crashpad_dump) {
    PrintErrorf("This doesn't look like a crashpad dump.\n");
    return S_OK;
  }

  Printf("CrashpadInfo version: %d\n", info.version);
  Printf("Report ID: %s\n", info.report_id.ToString().c_str());
  Printf("Client ID: %s\n", info.client_id.ToString().c_str());

  DisplayMiscInfo();
  DisplayAnnotations();

  return S_OK;
}

void CrashInfoCommand::DisplayAnnotations() {
  // Find the dump file and re-open it directly.  Reading by streams doesn't
  // allow access to the extra data written by crashpad at the end of many
  // streams.
  std::unique_ptr<crashpad::FileReaderInterface> dump_file_reader =
      OpenDumpFileReader();
  if (!dump_file_reader) {
    PrintErrorf("Failed to open dump reader\n");
    return;
  }
  crashpad::ProcessSnapshotMinidump snapshot;
  if (!snapshot.Initialize(dump_file_reader.get())) {
    PrintErrorf("Failed to construct process snapshot\n");
    return;
  }
  Printf("\nDump annotations:\n\n");
  Printf("Process annotations:\n");
  for (const auto& kv : snapshot.AnnotationsSimpleMap()) {
    PrintfWithIndent(1, "%s = %s\n", kv.first.c_str(), kv.second.c_str());
  }
  constexpr char kModuleHeader[] = "Annotations for module: %s\n";
  for (const crashpad::ModuleSnapshot* module : snapshot.Modules()) {
    bool printed_header = false;
    if (module->AnnotationsSimpleMap().size() > 0) {
      Printf(kModuleHeader, module->Name().c_str());
      printed_header = true;
      for (const auto& kv : module->AnnotationsSimpleMap()) {
        PrintfWithIndent(1, "%s = %s\n", kv.first.c_str(), kv.second.c_str());
      }
    }
    if (module->AnnotationsVector().size() > 0) {
      if (!printed_header) {
        Printf(kModuleHeader, module->Name().c_str());
        printed_header = true;
      }
      PrintfWithIndent(1, "vector:\n");
      for (std::string annotation : module->AnnotationsVector()) {
        PrintfWithIndent(2, "%s\n", annotation.c_str());
      }
    }
    if (module->AnnotationObjects().size() > 0) {
      if (!printed_header) {
        Printf(kModuleHeader, module->Name().c_str());
        printed_header = true;
      }
      for (const crashpad::AnnotationSnapshot& annotation :
           module->AnnotationObjects()) {
        PrintfWithIndent(1, "%s = ", annotation.name.c_str());
        if (annotation.type ==
            static_cast<uint16_t>(crashpad::Annotation::Type::kString)) {
          std::string value(
              reinterpret_cast<const char*>(annotation.value.data()),
              annotation.value.size());
          Printf("%s\n", value.c_str());
        } else if (annotation.type >
                   static_cast<uint16_t>(
                       crashpad::Annotation::Type::kUserDefinedStart)) {
          Printf("user defined - type: %d, size: %d\n",
                 annotation.type -
                     static_cast<uint16_t>(
                         crashpad::Annotation::Type::kUserDefinedStart),
                 annotation.value.size());
        }
      }
    }
  }
}

void CrashInfoCommand::DisplayMiscInfo() {
  HRESULT hr;
  size_t bytes_read;
  MINIDUMP_MISC_INFO_5 misc_info;
  memset(&misc_info, 0, sizeof(misc_info));
  hr = ReadFromDumpStream(
      crashpad::MinidumpStreamType::kMinidumpStreamTypeMiscInfo, 0,
      sizeof(misc_info), &misc_info, &bytes_read);
  if (FAILED(hr)) {
    PrintErrorf("Reading misc info: %08X\n", hr);
    return;
  }

  if (misc_info.Flags1 & MINIDUMP_MISC1_PROCESS_ID) {
    Printf("Process ID: %ld\n", misc_info.ProcessId);
  }
  if (misc_info.Flags1 & MINIDUMP_MISC1_PROCESS_TIMES) {
    time_t create_time = misc_info.ProcessCreateTime;
    Printf("Create time: %s", std::ctime(&create_time));
    Printf("User time: %us\n", misc_info.ProcessUserTime);
    Printf("Kernel time: %us\n", misc_info.ProcessKernelTime);
  }
  if (misc_info.Flags1 & MINIDUMP_MISC1_PROCESSOR_POWER_INFO) {
    Printf("ProcessorMaxMhz: %u\n", misc_info.ProcessorMaxMhz);
    Printf("ProcessorCurrentMhz: %u\n", misc_info.ProcessorCurrentMhz);
    Printf("ProcessorMhzLimit: %u\n", misc_info.ProcessorMhzLimit);
    Printf("ProcessorMaxIdleState: %u\n", misc_info.ProcessorMaxIdleState);
    Printf("ProcessorCurrentIdleState: %u\n", misc_info.ProcessorMaxIdleState);
  }
  if (misc_info.Flags1 & MINIDUMP_MISC3_PROCESS_INTEGRITY) {
    Printf("ProcessIntegrityLevel: %s\n",
           ProcessIntegrityString(misc_info.ProcessIntegrityLevel));
  }
  if (misc_info.Flags1 & MINIDUMP_MISC3_PROCESS_EXECUTE_FLAGS) {
    Printf("ProcessExcecuteFlags: 0x%08x\n",
           ProcessIntegrityString(misc_info.ProcessExecuteFlags));
  }
  if (misc_info.Flags1 & MINIDUMP_MISC3_PROTECTED_PROCESS) {
    Printf("ProtectedProcess: 0x%08x\n",
           ProcessIntegrityString(misc_info.ProcessExecuteFlags));
  }
  if (misc_info.Flags1 & MINIDUMP_MISC3_TIMEZONE) {
    Printf("Standard Time Zone: %s\n",
           base::SysWideToNativeMB(
               std::wstring(misc_info.TimeZone.StandardName,
                            countof(misc_info.TimeZone.StandardName)))
               .c_str());
    Printf("Daylight Time Zone: %s\n",
           base::SysWideToNativeMB(
               std::wstring(misc_info.TimeZone.DaylightName,
                            countof(misc_info.TimeZone.DaylightName)))
               .c_str());
  }
  if (misc_info.Flags1 & MINIDUMP_MISC4_BUILDSTRING) {
    Printf("BuildString: %s\n",
           base::SysWideToNativeMB(std::wstring(misc_info.BuildString,
                                                countof(misc_info.BuildString)))
               .c_str());
    Printf("DbgBldStr: %s\n",
           base::SysWideToNativeMB(
               std::wstring(misc_info.DbgBldStr, countof(misc_info.DbgBldStr)))
               .c_str());
  }
  if (misc_info.Flags1 & MINIDUMP_MISC5_PROCESS_COOKIE) {
    Printf("Process Cookie: 0x%08x\n", misc_info.ProcessCookie);
  }
}

std::unique_ptr<crashpad::FileReaderInterface>
CrashInfoCommand::OpenDumpFileReader() {
  ULONG64 wide_dump_file_handle;
  ULONG dump_type;
  HRESULT hr = GetDebugClientAs<IDebugClient5>()->GetDumpFile(
      0, nullptr, 0, nullptr, &wide_dump_file_handle, &dump_type);
  if (FAILED(hr)) {
    PrintErrorf("getting dump file handle: %08x\n", hr);
    return nullptr;
  }

  crashpad::FileHandle dump_file_handle =
      reinterpret_cast<crashpad::FileHandle>(wide_dump_file_handle);
  return std::make_unique<crashpad::WeakFileHandleFileReader>(dump_file_handle);
}

HRESULT CrashInfoCommand::ReadFromDumpStream(uint32_t stream_type,
                                             uint64_t offset,
                                             size_t max_read,
                                             void* bytes,
                                             size_t* bytes_read) {
  *bytes_read = 0;

  DEBUG_READ_USER_MINIDUMP_STREAM request;
  memset(&request, 0, sizeof(request));
  request.StreamType = stream_type;
  request.Offset = offset;
  request.Buffer = bytes;
  request.BufferSize = max_read;

  HRESULT hr =
      debug_advanced_->Request(DEBUG_REQUEST_READ_USER_MINIDUMP_STREAM,
                               &request, sizeof(request), nullptr, 0, nullptr);
  if (SUCCEEDED(hr)) {
    *bytes_read = request.BufferUsed;
  }

  return hr;
}

}  // namespace chromeexts
}  // namespace win
}  // namespace tools