ipc/glue/CrashReporterHost.cpp
author Kershaw Chang <kershaw@mozilla.com>
Fri, 11 Jan 2019 18:57:23 +0000
changeset 453535 c083f11eb58d
parent 453480 3f76ed638d83
child 454068 d54846d01280
permissions -rw-r--r--
Bug 1513057 - P1: Start the new socket process basics (prefs, full xpcom init, logging, no sandboxing) r=mayhemer,dragana Differential Revision: https://phabricator.services.mozilla.com/D14148

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "CrashReporterHost.h"
#include "CrashReporterMetadataShmem.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/recordreplay/ParentIPC.h"
#include "mozilla/Sprintf.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/Telemetry.h"
#include "nsIAsyncShutdown.h"
#include "nsICrashService.h"
#include "nsXULAppAPI.h"

// Consistency checking for nsICrashService constants.  We depend on the
// equivalence between nsICrashService values and GeckoProcessType values
// in the code below.  Making them equal also ensures that if new process
// types are added, people will know they may need to add crash reporting
// support in various places because compilation errors will be triggered here.
static_assert(nsICrashService::PROCESS_TYPE_MAIN == (int)GeckoProcessType_Default,
              "GeckoProcessType enum is out of sync with nsICrashService!");
static_assert(nsICrashService::PROCESS_TYPE_PLUGIN == (int)GeckoProcessType_Plugin,
              "GeckoProcessType enum is out of sync with nsICrashService!");
static_assert(nsICrashService::PROCESS_TYPE_CONTENT == (int)GeckoProcessType_Content,
              "GeckoProcessType enum is out of sync with nsICrashService!");
static_assert(nsICrashService::PROCESS_TYPE_IPDLUNITTEST == (int)GeckoProcessType_IPDLUnitTest,
              "GeckoProcessType enum is out of sync with nsICrashService!");
static_assert(nsICrashService::PROCESS_TYPE_GMPLUGIN == (int)GeckoProcessType_GMPlugin,
              "GeckoProcessType enum is out of sync with nsICrashService!");
static_assert(nsICrashService::PROCESS_TYPE_GPU == (int)GeckoProcessType_GPU,
              "GeckoProcessType enum is out of sync with nsICrashService!");
static_assert(nsICrashService::PROCESS_TYPE_VR == (int)GeckoProcessType_VR,
              "GeckoProcessType enum is out of sync with nsICrashService!");
static_assert(nsICrashService::PROCESS_TYPE_RDD == (int)GeckoProcessType_RDD,
              "GeckoProcessType enum is out of sync with nsICrashService!");
static_assert(nsICrashService::PROCESS_TYPE_SOCKET == (int)GeckoProcessType_Socket,
              "GeckoProcessType enum is out of sync with nsICrashService!");
// Add new static asserts here if you add more process types.
// Update this static assert as well.
static_assert(nsICrashService::PROCESS_TYPE_SOCKET + 1 == (int)GeckoProcessType_End,
              "GeckoProcessType enum is out of sync with nsICrashService!");


namespace mozilla {
namespace ipc {

CrashReporterHost::CrashReporterHost(GeckoProcessType aProcessType,
                                     const Shmem& aShmem,
                                     CrashReporter::ThreadId aThreadId)
    : mProcessType(aProcessType),
      mShmem(aShmem),
      mThreadId(aThreadId),
      mStartTime(::time(nullptr)),
      mFinalized(false) {}

bool CrashReporterHost::GenerateCrashReport(base::ProcessId aPid) {
  if (!TakeCrashedChildMinidump(aPid, nullptr)) {
    return false;
  }
  return FinalizeCrashReport();
}

RefPtr<nsIFile> CrashReporterHost::TakeCrashedChildMinidump(
    base::ProcessId aPid, uint32_t* aOutSequence) {
  MOZ_ASSERT(!HasMinidump());

  RefPtr<nsIFile> crashDump;
  if (!XRE_TakeMinidumpForChild(aPid, getter_AddRefs(crashDump),
                                aOutSequence)) {
    return nullptr;
  }
  if (!AdoptMinidump(crashDump)) {
    return nullptr;
  }
  return crashDump.get();
}

bool CrashReporterHost::AdoptMinidump(nsIFile* aFile) {
  return CrashReporter::GetIDFromMinidump(aFile, mDumpID);
}

int32_t CrashReporterHost::GetCrashType(
    const CrashReporter::AnnotationTable& aAnnotations) {
  // RecordReplayHang is set in the middleman content process, so check
  // aAnnotations.
  if (aAnnotations[CrashReporter::Annotation::RecordReplayHang].EqualsLiteral(
          "1")) {
    return nsICrashService::CRASH_TYPE_HANG;
  }

  // PluginHang is set in the parent process, so check mExtraAnnotations.
  if (mExtraAnnotations[CrashReporter::Annotation::PluginHang].EqualsLiteral(
          "1")) {
    return nsICrashService::CRASH_TYPE_HANG;
  }

  return nsICrashService::CRASH_TYPE_CRASH;
}

bool CrashReporterHost::FinalizeCrashReport() {
  MOZ_ASSERT(!mFinalized);
  MOZ_ASSERT(HasMinidump());

  CrashReporter::AnnotationTable annotations;

  nsAutoCString type;
  // The gecko media plugin and normal plugin processes are lumped together
  // as a historical artifact.
  if (mProcessType == GeckoProcessType_GMPlugin) {
    type.AssignLiteral("plugin");
  } else {
    // This check will pick up some cases that will never happen (e.g. IPDL
    // unit tests), but that's OK.
    switch (mProcessType) {
#define GECKO_PROCESS_TYPE(enum_name, string_name, xre_name) \
      case GeckoProcessType_##enum_name: \
        type.AssignLiteral(string_name); \
        break;
#include "mozilla/GeckoProcessTypes.h"
#undef GECKO_PROCESS_TYPE
    default:
      NS_ERROR("unknown process type");
      break;
    }
  }
  annotations[CrashReporter::Annotation::ProcessType] = type;

  char startTime[32];
  SprintfLiteral(startTime, "%lld", static_cast<long long>(mStartTime));
  annotations[CrashReporter::Annotation::StartupTime] =
      nsDependentCString(startTime);

  // We might not have shmem (for example, when running crashreporter tests).
  if (mShmem.IsReadable()) {
    CrashReporterMetadataShmem::ReadAppNotes(mShmem, annotations);
  }
  CrashReporter::AppendExtraData(mDumpID, mExtraAnnotations);
  CrashReporter::AppendExtraData(mDumpID, annotations);

  int32_t crashType = GetCrashType(annotations);
  NotifyCrashService(mProcessType, crashType, mDumpID);

  mFinalized = true;
  return true;
}

/* static */ void CrashReporterHost::NotifyCrashService(
    GeckoProcessType aProcessType, int32_t aCrashType,
    const nsString& aChildDumpID) {
  if (!NS_IsMainThread()) {
    RefPtr<Runnable> runnable = NS_NewRunnableFunction(
        "ipc::CrashReporterHost::NotifyCrashService", [&]() -> void {
          CrashReporterHost::NotifyCrashService(aProcessType, aCrashType,
                                                aChildDumpID);
        });
    RefPtr<nsIThread> mainThread = do_GetMainThread();
    SyncRunnable::DispatchToThread(mainThread, runnable);
    return;
  }

  MOZ_ASSERT(!aChildDumpID.IsEmpty());

  nsCOMPtr<nsICrashService> crashService =
      do_GetService("@mozilla.org/crashservice;1");
  if (!crashService) {
    return;
  }

  int32_t processType;
  nsCString telemetryKey;

  switch (aProcessType) {
    case GeckoProcessType_IPDLUnitTest:
    case GeckoProcessType_Default:
      NS_ERROR("unknown process type");
      return;
    default:
      processType = (int)aProcessType;
      break;
  }

  if (aProcessType == GeckoProcessType_Plugin &&
      aCrashType == nsICrashService::CRASH_TYPE_HANG) {
    telemetryKey.AssignLiteral("pluginhang");
  } else {
    switch (aProcessType) {
#define GECKO_PROCESS_TYPE(enum_name, string_name, xre_name) \
      case GeckoProcessType_##enum_name:                     \
        telemetryKey.AssignLiteral(string_name);             \
        break;
#include "mozilla/GeckoProcessTypes.h"
#undef GECKO_PROCESS_TYPE
      // We can't really hit this, thanks to the above switch, but having it here
      // will placate the compiler.
      default:
        NS_ERROR("unknown process type");
        return;
    }
  }

  RefPtr<Promise> promise;
  crashService->AddCrash(processType, aCrashType, aChildDumpID,
                         getter_AddRefs(promise));
  Telemetry::Accumulate(Telemetry::SUBPROCESS_CRASHES_WITH_DUMP, telemetryKey,
                        1);
}

void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
                                      bool aValue) {
  mExtraAnnotations[aKey] =
      aValue ? NS_LITERAL_CSTRING("1") : NS_LITERAL_CSTRING("0");
}

void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
                                      int aValue) {
  nsAutoCString valueString;
  valueString.AppendInt(aValue);
  mExtraAnnotations[aKey] = valueString;
}

void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
                                      unsigned int aValue) {
  nsAutoCString valueString;
  valueString.AppendInt(aValue);
  mExtraAnnotations[aKey] = valueString;
}

void CrashReporterHost::AddAnnotation(CrashReporter::Annotation aKey,
                                      const nsCString& aValue) {
  mExtraAnnotations[aKey] = aValue;
}

}  // namespace ipc
}  // namespace mozilla