/* -*- 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 "nsExceptionHandler.h"
#include "nsExceptionHandlerUtils.h"
#include "json/json.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsComponentManagerUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "nsDirectoryService.h"
#include "nsString.h"
#include "nsTHashMap.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/Services.h"
#include "nsIObserverService.h"
#include "mozilla/Unused.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Printf.h"
#include "mozilla/RuntimeExceptionModule.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/SyncRunnable.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Unused.h"
#include "nsPrintfCString.h"
#include "nsThreadUtils.h"
#include "nsThread.h"
#include "jsfriendapi.h"
#include "private/pprio.h"
#include "base/process_util.h"
#include "common/basictypes.h"
#include "mozilla/toolkit/crashreporter/mozannotation_client_ffi_generated.h"
#include "mozilla/toolkit/crashreporter/mozannotation_server_ffi_generated.h"
#ifdef MOZ_BACKGROUNDTASKS
# include
"mozilla/BackgroundTasks.h"
#endif
#if defined(XP_WIN)
# ifdef WIN32_LEAN_AND_MEAN
# undef WIN32_LEAN_AND_MEAN
# endif
# include
"nsXULAppAPI.h"
# include
"nsIXULAppInfo.h"
# include
"nsIWindowsRegKey.h"
# include
"breakpad-client/windows/crash_generation/client_info.h"
# include
"breakpad-client/windows/crash_generation/crash_generation_server.h"
# include
"breakpad-client/windows/handler/exception_handler.h"
# include <dbghelp.h>
# include <string.h>
# include
"nsDirectoryServiceUtils.h"
# include
"nsWindowsDllInterceptor.h"
# include
"mozilla/WindowsDllBlocklist.h"
# include
"psapi.h" // For PERFORMANCE_INFORMATION and K32GetPerformanceInfo()
#elif defined(XP_MACOSX)
# include
"breakpad-client/mac/crash_generation/client_info.h"
# include
"breakpad-client/mac/crash_generation/crash_generation_server.h"
# include
"breakpad-client/mac/handler/exception_handler.h"
# include <string>
# include <Carbon/Carbon.h>
# include <CoreFoundation/CoreFoundation.h>
# include <crt_externs.h>
# include <fcntl.h>
# include <mach/mach.h>
# include <mach/vm_statistics.h>
# include <sys/sysctl.h>
# include <sys/types.h>
# include <spawn.h>
# include <unistd.h>
# include
"mac_utils.h"
#elif defined(XP_LINUX)
# include
"nsIINIParser.h"
# include
"common/linux/linux_libc_support.h"
# include
"third_party/lss/linux_syscall_support.h"
# include
"breakpad-client/linux/crash_generation/client_info.h"
# include
"breakpad-client/linux/crash_generation/crash_generation_server.h"
# include
"breakpad-client/linux/handler/exception_handler.h"
# include
"common/linux/eintr_wrapper.h"
# include <fcntl.h>
# include <sys/types.h>
# include
"sys/sysinfo.h"
# include <sys/wait.h>
# include <unistd.h>
#else
# error
"Not yet implemented for this platform"
#endif // defined(XP_WIN)
#ifdef XP_WIN
# include <filesystem>
#endif
#include <fstream>
#include <optional>
#include <stdlib.h>
#include <time.h>
#include <prenv.h>
#include <prio.h>
#include "mozilla/Mutex.h"
#include "nsDebug.h"
#include "nsCRT.h"
#include "nsIFile.h"
#include "mozilla/IOInterposer.h"
#include "mozilla/mozalloc_oom.h"
#if defined(XP_MACOSX)
CFStringRef reporterClientAppID = CFSTR(
"org.mozilla.crashreporter");
#endif
#if defined(MOZ_WIDGET_ANDROID)
# include
"common/linux/file_id.h"
#endif
using google_breakpad::ClientInfo;
using google_breakpad::CrashGenerationServer;
#ifdef XP_LINUX
using google_breakpad::MinidumpDescriptor;
#elif defined(XP_WIN)
using google_breakpad::ExceptionHandler;
#endif
#if defined(MOZ_WIDGET_ANDROID)
using google_breakpad::auto_wasteful_vector;
using google_breakpad::FileID;
using google_breakpad::kDefaultBuildIdSize;
using google_breakpad::PageAllocator;
#endif
using namespace mozilla;
namespace mozilla::phc {
// Global instance that is retrieved by the process generating the crash report
MOZ_GLOBINIT mozilla::phc::AddrInfo gAddrInfo;
}
// namespace mozilla::phc
namespace CrashReporter {
#ifdef XP_WIN
typedef wchar_t XP_CHAR;
typedef std::wstring xpstring;
# define XP_TEXT(x) L
##x
# define CONVERT_XP_CHAR_TO_UTF16(x) x
# define XP_STRLEN(x) wcslen(x)
# define my_strlen strlen
# define my_memchr memchr
# define CRASH_REPORTER_FILENAME u
"crashreporter.exe"_ns
# define XP_PATH_SEPARATOR L
"\\"
# define XP_PATH_SEPARATOR_CHAR L
'\\'
# define XP_PATH_MAX (MAX_PATH + 1)
// "<reporter path>" "<minidump path>"
# define CMDLINE_SIZE ((XP_PATH_MAX * 2) + 6)
# define XP_TTOA(time, buffer) _i64toa((time), (buffer), 10)
# define XP_STOA(size, buffer) _ui64toa((size), (buffer), 10)
#else
typedef char XP_CHAR;
typedef std::string xpstring;
# define XP_TEXT(x) x
# define CONVERT_XP_CHAR_TO_UTF16(x) NS_ConvertUTF8toUTF16(x)
# define CRASH_REPORTER_FILENAME u
"crashreporter"_ns
# define XP_PATH_SEPARATOR
"/"
# define XP_PATH_SEPARATOR_CHAR
'/'
# define XP_PATH_MAX PATH_MAX
# ifdef XP_LINUX
# define XP_STRLEN(x) my_strlen(x)
# define XP_TTOA(time, buffer) \
my_u64tostring(uint64_t(time), (buffer),
sizeof(buffer))
# define XP_STOA(size, buffer) \
my_u64tostring((size), (buffer),
sizeof(buffer))
# else
# define XP_STRLEN(x) strlen(x)
# define XP_TTOA(time, buffer) sprintf(buffer,
"%" PRIu64, uint64_t(time))
# define XP_STOA(size, buffer) sprintf(buffer,
"%zu", size_t(size))
# define my_strlen strlen
# define my_memchr memchr
# define sys_close close
# define sys_fork fork
# define sys_open open
# define sys_read read
# define sys_write write
# endif
#endif // XP_WIN
#if defined(__GNUC__)
# define MAYBE_UNUSED __attribute__((unused))
#else
# define MAYBE_UNUSED
#endif // defined(__GNUC__)
#ifndef XP_LINUX
static const XP_CHAR dumpFileExtension[] = XP_TEXT(
".dmp");
#endif
static const XP_CHAR extraFileExtension[] = XP_TEXT(
".extra");
static const XP_CHAR memoryReportExtension[] = XP_TEXT(
".memory.json.gz");
MOZ_RUNINIT
static std::optional<xpstring> defaultMemoryReportPath = {};
static const char kCrashMainID[] =
"crash.main.3\n";
static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr;
static mozilla::Atomic<
bool> gEncounteredChildException(
false);
MOZ_CONSTINIT
static nsCString gServerURL;
MOZ_RUNINIT
static xpstring pendingDirectory;
MOZ_RUNINIT
static xpstring crashReporterPath;
MOZ_RUNINIT
static xpstring memoryReportPath;
// Where crash events should go.
MOZ_RUNINIT
static xpstring eventsDirectory;
// If this is false, we don't launch the crash reporter
static bool doReport =
true;
// if this is true, we pass the exception on to the OS crash reporter
static bool showOSCrashReporter =
false;
// The time of the last recorded crash, as a time_t value.
static time_t lastCrashTime = 0;
// The pathname of a file to store the crash time in
static XP_CHAR lastCrashTimeFilename[XP_PATH_MAX] = {0};
#if defined(MOZ_WIDGET_ANDROID)
// on Android 4.2 and above there is a user serial number associated
// with the current process that gets lost when we fork so we need to
// explicitly pass it to am
static char* androidUserSerial = nullptr;
// Before Android 8 we needed to use "startservice" to start the crash reporting
// service. After Android 8 we need to use "start-foreground-service"
static const char* androidStartServiceCommand = nullptr;
#endif
// this holds additional data sent via the API
static Mutex* notesFieldLock;
static nsCString* notesField = nullptr;
static bool isGarbageCollecting;
static uint32_t eventloopNestingLevel = 0;
static time_t inactiveStateStart = 0;
static
#if defined(XP_UNIX)
pthread_t
#elif defined(XP_WIN)
// defined(XP_UNIX)
DWORD
#endif // defined(XP_WIN)
gMainThreadId = 0;
// Avoid a race during application termination.
static Mutex* dumpSafetyLock;
static bool isSafeToDump =
false;
// Whether to include heap regions of the crash context.
static bool sIncludeContextHeap =
false;
// OOP crash reporting
static CrashGenerationServer* crashServer;
// chrome process has this
static std::terminate_handler oldTerminateHandler = nullptr;
#if defined(XP_WIN) ||
defined(XP_MACOSX)
static char* childCrashNotifyPipe;
#elif defined(XP_LINUX)
static int serverSocketFd = -1;
static int clientSocketFd = -1;
#endif
// |dumpMapLock| must protect all access to |pidToMinidump|.
static Mutex* dumpMapLock;
struct ChildProcessData :
public nsUint32HashKey {
explicit ChildProcessData(KeyTypePointer aKey)
: nsUint32HashKey(aKey), annotations(nullptr) {}
nsCOMPtr<nsIFile> minidump;
UniquePtr<AnnotationTable> annotations;
};
typedef nsTHashtable<ChildProcessData> ChildMinidumpMap;
static ChildMinidumpMap* pidToMinidump;
static bool OOPInitialized();
void RecordMainThreadId() {
gMainThreadId =
#if defined(XP_UNIX)
pthread_self()
#elif defined(XP_WIN)
// defined(XP_UNIX)
GetCurrentThreadId()
#endif // defined(XP_WIN)
;
}
bool SignalSafeIsMainThread() {
// We can't rely on NS_IsMainThread() because we are in a signal handler, and
// sTLSIsMainThread is a thread local variable and it can be lazy allocated
// i.e., we could hit code path where this variable has not been accessed
// before and needs to be allocated right now, which will lead to spinlock
// deadlock effectively hanging the process, as in bug 1756407.
#if defined(XP_UNIX)
pthread_t th = pthread_self();
return pthread_equal(th, gMainThreadId);
#elif defined(XP_WIN)
// defined(XP_UNIX)
DWORD th = GetCurrentThreadId();
return th == gMainThreadId;
#endif // defined(XP_WIN)
}
#if defined(XP_WIN)
// the following are used to prevent other DLLs reverting the last chance
// exception handler to the windows default. Any attempt to change the
// unhandled exception filter or to reset it is ignored and our crash
// reporter is loaded instead (in case it became unloaded somehow)
typedef LPTOP_LEVEL_EXCEPTION_FILTER(WINAPI* SetUnhandledExceptionFilter_func)(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
static WindowsDllInterceptor::FuncHookType<SetUnhandledExceptionFilter_func>
stub_SetUnhandledExceptionFilter;
static LPTOP_LEVEL_EXCEPTION_FILTER previousUnhandledExceptionFilter = nullptr;
MOZ_RUNINIT
static WindowsDllInterceptor gKernel32Intercept;
static bool gBlockUnhandledExceptionFilter =
true;
static LPTOP_LEVEL_EXCEPTION_FILTER GetUnhandledExceptionFilter() {
// Set a dummy value to get the current filter, then restore
LPTOP_LEVEL_EXCEPTION_FILTER current = SetUnhandledExceptionFilter(nullptr);
SetUnhandledExceptionFilter(current);
return current;
}
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI patched_SetUnhandledExceptionFilter(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) {
if (!gBlockUnhandledExceptionFilter) {
// don't intercept
return stub_SetUnhandledExceptionFilter(lpTopLevelExceptionFilter);
}
if (lpTopLevelExceptionFilter == previousUnhandledExceptionFilter) {
// OK to swap back and forth between the previous filter
previousUnhandledExceptionFilter =
stub_SetUnhandledExceptionFilter(lpTopLevelExceptionFilter);
return previousUnhandledExceptionFilter;
}
// intercept attempts to change the filter
return nullptr;
}
# if defined(HAVE_64BIT_BUILD)
static LPTOP_LEVEL_EXCEPTION_FILTER sUnhandledExceptionFilter = nullptr;
static long JitExceptionHandler(
void* exceptionRecord,
void* context) {
EXCEPTION_POINTERS pointers = {(PEXCEPTION_RECORD)exceptionRecord,
(PCONTEXT)context};
return sUnhandledExceptionFilter(&pointers);
}
static void SetJitExceptionHandler() {
sUnhandledExceptionFilter = GetUnhandledExceptionFilter();
if (sUnhandledExceptionFilter)
js::SetJitExceptionHandler(JitExceptionHandler);
}
# endif
#endif // defined(XP_WIN)
MOZ_RUNINIT
static struct ReservedResources {
#if defined(XP_WIN) && !
defined(HAVE_64BIT_BUILD)
// This should be bigger than xul.dll plus a bit of extra space for
// MinidumpWriteDump allocations.
static const SIZE_T kReserveSize = 0x5000000;
// 80 MB
void* mVirtualMemory;
#endif
ReservedResources()
#if defined(XP_WIN) && !
defined(HAVE_64BIT_BUILD)
: mVirtualMemory(nullptr)
#endif
{
}
} gReservedResources;
static void ReserveResources() {
#if defined(XP_WIN) && !
defined(HAVE_64BIT_BUILD)
// Reserve some VM space. In the event that we crash because VM space is
// being leaked without leaking memory, freeing this space before taking
// the minidump will allow us to collect a minidump. No need to check if
// this allocation succeeded as we don't require it to.
MOZ_ASSERT(gReservedResources.mVirtualMemory == nullptr);
gReservedResources.mVirtualMemory = VirtualAlloc(
nullptr, ReservedResources::kReserveSize, MEM_RESERVE, PAGE_NOACCESS);
#endif
}
static void ReleaseResources() {
#if defined(XP_WIN) && !
defined(HAVE_64BIT_BUILD)
if (gReservedResources.mVirtualMemory) {
VirtualFree(gReservedResources.mVirtualMemory, 0, MEM_RELEASE);
gReservedResources.mVirtualMemory = nullptr;
}
#endif // defined(XP_WIN)
}
#ifdef XP_LINUX
static inline void my_u64tostring(uint64_t aValue,
char* aBuffer,
size_t aBufferLength) {
my_memset(aBuffer, 0, aBufferLength);
my_uitos(aBuffer, aValue, my_uint_len(aValue));
}
#endif
static void CreateFileFromPath(
const xpstring& path, nsIFile** file) {
Unused << NS_NewPathStringLocalFile(
DependentPathString(path.c_str(), path.size()), file);
}
[[nodiscard]]
static std::optional<xpstring> CreatePathFromFile(nsIFile* file) {
AutoPathString path;
#ifdef XP_WIN
nsresult rv = file->GetPath(path);
#else
nsresult rv = file->GetNativePath(path);
#endif
if (NS_FAILED(rv)) {
return {};
}
return xpstring(
static_cast<xpstring::const_pointer>(path.get()),
path.Length());
}
static time_t GetCurrentTimeForCrashTime() {
#ifdef XP_LINUX
struct kernel_timeval tv;
sys_gettimeofday(&tv, nullptr);
return tv.tv_sec;
#else
return time(nullptr);
#endif
}
static XP_CHAR* Concat(XP_CHAR* str,
const XP_CHAR* toAppend, size_t* size) {
size_t appendLen = XP_STRLEN(toAppend);
if (appendLen >= *size) {
appendLen = *size - 1;
}
memcpy(str, toAppend, appendLen *
sizeof(XP_CHAR));
str += appendLen;
*str =
'\0';
*size -= appendLen;
return str;
}
void AnnotateOOMAllocationSize(size_t size) { gOOMAllocationSize = size; }
static size_t gTexturesSize = 0;
void AnnotateTexturesSize(size_t size) { gTexturesSize = size; }
#ifndef XP_WIN
// Like Windows CopyFile for *nix
//
// This function is not declared static even though it's not used outside of
// this file because of an issue in Fennec which prevents breakpad's exception
// handler from invoking the MinidumpCallback function. See bug 1424304.
bool copy_file(
const char* from,
const char* to) {
const int kBufSize = 4096;
int fdfrom = sys_open(from, O_RDONLY, 0);
if (fdfrom < 0) {
return false;
}
bool ok =
false;
int fdto = sys_open(to, O_WRONLY | O_CREAT, 0666);
if (fdto < 0) {
sys_close(fdfrom);
return false;
}
char buf[kBufSize];
while (
true) {
int r = sys_read(fdfrom, buf, kBufSize);
if (r == 0) {
ok =
true;
break;
}
if (r < 0) {
break;
}
char* wbuf = buf;
while (r) {
int w = sys_write(fdto, wbuf, r);
if (w > 0) {
r -= w;
wbuf += w;
}
else if (errno != EINTR) {
break;
}
}
if (r) {
break;
}
}
sys_close(fdfrom);
sys_close(fdto);
return ok;
}
#endif
/**
* The PlatformWriter class provides a tool to create and write to a file that
* is safe to call from within an exception handler. To use it this way the
* file path needs to be provided as a bare C string.
*/
class PlatformWriter {
public:
PlatformWriter() : mBuffer{}, mPos(0), mFD(kInvalidFileHandle) {}
explicit PlatformWriter(
const XP_CHAR* aPath) : PlatformWriter() {
Open(aPath);
}
~PlatformWriter() {
if (Valid()) {
Flush();
#ifdef XP_WIN
CloseHandle(mFD);
#elif defined(XP_UNIX)
sys_close(mFD);
#endif
}
}
void Open(
const XP_CHAR* aPath) {
#ifdef XP_WIN
mFD = CreateFile(aPath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr);
#elif defined(XP_UNIX)
mFD = sys_open(aPath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
#endif
}
void OpenHandle(FileHandle aFD) { mFD = aFD; }
bool Valid() {
return mFD != kInvalidFileHandle; }
void WriteBuffer(
const char* aBuffer, size_t aLen) {
if (!Valid()) {
return;
}
while (aLen-- > 0) {
WriteChar(*aBuffer++);
}
}
void WriteString(
const char* aStr) { WriteBuffer(aStr, my_strlen(aStr)); }
template <
int N>
void WriteLiteral(
const char (&aStr)[N]) {
WriteBuffer(aStr, N - 1);
}
FileHandle FileDesc() {
return mFD; }
private:
PlatformWriter(
const PlatformWriter&) =
delete;
const PlatformWriter&
operator=(
const PlatformWriter&) =
delete;
void WriteChar(
char aChar) {
if (mPos == kBufferSize) {
Flush();
}
mBuffer[mPos++] = aChar;
}
void Flush() {
if (mPos > 0) {
char* buffer = mBuffer;
size_t length = mPos;
while (length > 0) {
#ifdef XP_WIN
DWORD written_bytes = 0;
if (!WriteFile(mFD, buffer, length, &written_bytes, nullptr)) {
break;
}
#elif defined(XP_UNIX)
ssize_t written_bytes = sys_write(mFD, buffer, length);
if (written_bytes < 0) {
if (errno == EAGAIN) {
continue;
}
break;
}
#endif
buffer += written_bytes;
length -= written_bytes;
}
mPos = 0;
}
}
static const size_t kBufferSize = 512;
char mBuffer[kBufferSize];
size_t mPos;
FileHandle mFD;
};
class JSONAnnotationWriter :
public AnnotationWriter {
public:
explicit JSONAnnotationWriter(PlatformWriter& aPlatformWriter)
: mWriter(aPlatformWriter), mEmpty(
true) {
mWriter.WriteBuffer(
"{", 1);
}
~JSONAnnotationWriter() { mWriter.WriteBuffer(
"}", 1); }
void Write(Annotation aAnnotation,
const char* aValue,
size_t aLen = 0) override {
size_t len = aLen ? aLen : my_strlen(aValue);
const char* annotationStr = AnnotationToString(aAnnotation);
if (len && CrashReporter::ShouldIncludeAnnotation(aAnnotation, aValue)) {
WritePrefix();
mWriter.WriteBuffer(annotationStr, my_strlen(annotationStr));
WriteSeparator();
WriteEscapedString(aValue, len);
WriteSuffix();
}
};
void Write(Annotation aAnnotation,
bool aValue) override {
Write(aAnnotation, aValue ?
"1" :
"0", 1);
};
void Write(Annotation aAnnotation, uint64_t aValue) override {
char buffer[32] = {};
XP_STOA(aValue, buffer);
Write(aAnnotation, buffer);
};
private:
void WritePrefix() {
if (mEmpty) {
mWriter.WriteBuffer(
"\"", 1);
mEmpty =
false;
}
else {
mWriter.WriteBuffer(
",\"", 2);
}
}
void WriteSeparator() { mWriter.WriteBuffer(
"\":\
"", 3); }
void WriteSuffix() { mWriter.WriteBuffer(
"\"", 1); }
void WriteEscapedString(
const char* aStr, size_t aLen) {
for (size_t i = 0; i < aLen; i++) {
uint8_t c = aStr[i];
if (c <= 0x1f || c ==
'\\' || c ==
'\"') {
mWriter.WriteBuffer(
"\\u00", 4);
WriteHexDigitAsAsciiChar((c & 0x00f0) >> 4);
WriteHexDigitAsAsciiChar(c & 0x000f);
}
else {
mWriter.WriteBuffer(aStr + i, 1);
}
}
}
void WriteHexDigitAsAsciiChar(uint8_t u) {
char buf[1];
buf[0] =
static_cast<
unsigned>((u < 10) ?
'0' + u :
'a' + (u - 10));
mWriter.WriteBuffer(buf, 1);
}
PlatformWriter& mWriter;
bool mEmpty;
};
class BinaryAnnotationWriter :
public AnnotationWriter {
public:
explicit BinaryAnnotationWriter(PlatformWriter& aPlatformWriter)
: mPlatformWriter(aPlatformWriter) {}
void Write(Annotation aAnnotation,
const char* aValue,
size_t aLen = 0) override {
uint64_t len = aLen ? aLen : my_strlen(aValue);
mPlatformWriter.WriteBuffer((
const char*)&aAnnotation,
sizeof(aAnnotation));
mPlatformWriter.WriteBuffer((
const char*)&len,
sizeof(len));
mPlatformWriter.WriteBuffer(aValue, len);
};
void Write(Annotation aAnnotation, uint64_t aValue) override {
char buffer[32] = {};
XP_STOA(aValue, buffer);
Write(aAnnotation, buffer);
};
private:
PlatformWriter& mPlatformWriter;
};
#ifdef MOZ_PHC
// 21 is the max length of a 64-bit decimal address entry, including the
// trailing comma or '\0'. And then we add another 32 just to be safe.
const size_t phcStringifiedAnnotationSize =
(mozilla::phc::StackTrace::kMaxFrames * 21) + 32;
static void PHCStackTraceToString(
char* aBuffer, size_t aBufferLen,
const phc::StackTrace& aStack) {
char addrString[32];
*aBuffer = 0;
for (size_t i = 0; i < aStack.mLength; i++) {
if (i != 0) {
strcat(aBuffer,
",");
}
XP_STOA(uintptr_t(aStack.mPcs[i]), addrString);
strncat(aBuffer, addrString, aBufferLen);
}
}
// The stack traces are encoded as a comma-separated list of decimal
// (not hexadecimal!) addresses, e.g. "12345678,12345679,12345680".
static void WritePHCStackTrace(AnnotationWriter& aWriter,
const Annotation aName,
const Maybe<phc::StackTrace>& aStack) {
if (aStack.isNothing()) {
return;
}
// 21 is the max length of a 64-bit decimal address entry, including the
// trailing comma or '\0'. And then we add another 32 just to be safe.
char addrsString[phcStringifiedAnnotationSize];
PHCStackTraceToString(addrsString,
sizeof(addrsString), *aStack);
aWriter.Write(aName, addrsString);
}
static void WritePHCAddrInfo(AnnotationWriter& writer,
const phc::AddrInfo* aAddrInfo) {
// Is this a PHC allocation needing special treatment?
if (aAddrInfo && aAddrInfo->mKind != phc::AddrInfo::Kind::Unknown) {
const char* kindString;
switch (aAddrInfo->mKind) {
case phc::AddrInfo::Kind::Unknown:
kindString =
"Unknown(?!)";
break;
case phc::AddrInfo::Kind::NeverAllocatedPage:
kindString =
"NeverAllocatedPage";
break;
case phc::AddrInfo::Kind::InUsePage:
kindString =
"InUsePage(?!)";
break;
case phc::AddrInfo::Kind::FreedPage:
kindString =
"FreedPage";
break;
case phc::AddrInfo::Kind::GuardPage:
kindString =
"GuardPage";
break;
default:
kindString =
"Unmatched(?!)";
break;
}
writer.Write(Annotation::PHCKind, kindString);
writer.Write(Annotation::PHCBaseAddress,
reinterpret_cast<uint64_t>(aAddrInfo->mBaseAddr));
writer.Write(Annotation::PHCUsableSize,
static_cast<uint64_t>(aAddrInfo->mUsableSize));
WritePHCStackTrace(writer, Annotation::PHCAllocStack,
aAddrInfo->mAllocStack);
WritePHCStackTrace(writer, Annotation::PHCFreeStack, aAddrInfo->mFreeStack);
}
}
static void PopulatePHCStackTraceAnnotation(
AnnotationTable& aAnnotations,
const Annotation aName,
const Maybe<phc::StackTrace>& aStack) {
if (aStack.isNothing()) {
return;
}
char addrsString[phcStringifiedAnnotationSize];
PHCStackTraceToString(addrsString,
sizeof(addrsString), *aStack);
aAnnotations[aName] = addrsString;
}
static void PopulatePHCAnnotations(AnnotationTable& aAnnotations,
const phc::AddrInfo* aAddrInfo) {
// Is this a PHC allocation needing special treatment?
if (aAddrInfo && aAddrInfo->mKind != phc::AddrInfo::Kind::Unknown) {
const char* kindString;
switch (aAddrInfo->mKind) {
case phc::AddrInfo::Kind::Unknown:
kindString =
"Unknown(?!)";
break;
case phc::AddrInfo::Kind::NeverAllocatedPage:
kindString =
"NeverAllocatedPage";
break;
case phc::AddrInfo::Kind::InUsePage:
kindString =
"InUsePage(?!)";
break;
case phc::AddrInfo::Kind::FreedPage:
kindString =
"FreedPage";
break;
case phc::AddrInfo::Kind::GuardPage:
kindString =
"GuardPage";
break;
default:
kindString =
"Unmatched(?!)";
break;
}
aAnnotations[Annotation::PHCKind] = kindString;
aAnnotations[Annotation::PHCBaseAddress] =
nsPrintfCString(
"%zu", uintptr_t(aAddrInfo->mBaseAddr));
aAnnotations[Annotation::PHCUsableSize] =
nsPrintfCString(
"%zu", aAddrInfo->mUsableSize);
PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCAllocStack,
aAddrInfo->mAllocStack);
PopulatePHCStackTraceAnnotation(aAnnotations, Annotation::PHCFreeStack,
aAddrInfo->mFreeStack);
}
}
#endif
/**
* If minidump_id is null, we assume that dump_path contains the full
* dump file path.
*/
static void OpenAPIData(PlatformWriter& aWriter,
const XP_CHAR* dump_path,
const XP_CHAR* minidump_id = nullptr) {
static XP_CHAR extraDataPath[XP_PATH_MAX];
size_t size = XP_PATH_MAX;
XP_CHAR* p;
if (minidump_id) {
p = Concat(extraDataPath, dump_path, &size);
p = Concat(p, XP_PATH_SEPARATOR, &size);
p = Concat(p, minidump_id, &size);
}
else {
p = Concat(extraDataPath, dump_path, &size);
// Skip back past the .dmp extension, if any.
if (*(p - 4) == XP_TEXT(
'.')) {
p -= 4;
size += 4;
}
}
Concat(p, extraFileExtension, &size);
aWriter.Open(extraDataPath);
}
#ifdef XP_WIN
static void AnnotateMemoryStatus(AnnotationWriter& aWriter) {
MEMORYSTATUSEX statex;
statex.dwLength =
sizeof(statex);
if (GlobalMemoryStatusEx(&statex)) {
aWriter.Write(Annotation::SystemMemoryUsePercentage,
static_cast<uint64_t>(statex.dwMemoryLoad));
aWriter.Write(Annotation::TotalVirtualMemory, statex.ullTotalVirtual);
aWriter.Write(Annotation::AvailableVirtualMemory, statex.ullAvailVirtual);
aWriter.Write(Annotation::TotalPhysicalMemory, statex.ullTotalPhys);
aWriter.Write(Annotation::AvailablePhysicalMemory, statex.ullAvailPhys);
}
PERFORMANCE_INFORMATION info;
if (K32GetPerformanceInfo(&info,
sizeof(info))) {
aWriter.Write(Annotation::TotalPageFile,
static_cast<uint64_t>(info.CommitLimit * info.PageSize));
aWriter.Write(Annotation::AvailablePageFile,
static_cast<uint64_t>((info.CommitLimit - info.CommitTotal) *
info.PageSize));
}
}
#elif XP_MACOSX
// Extract the total physical memory of the system.
static void WritePhysicalMemoryStatus(AnnotationWriter& aWriter) {
uint64_t physicalMemoryByteSize = 0;
const size_t NAME_LEN = 2;
int name[NAME_LEN] = {
/* Hardware */ CTL_HW,
/* 64-bit physical memory size */ HW_MEMSIZE};
size_t infoByteSize =
sizeof(physicalMemoryByteSize);
if (sysctl(name, NAME_LEN, &physicalMemoryByteSize, &infoByteSize,
/* We do not replace data */ nullptr,
/* We do not replace data */ 0) != -1) {
aWriter.Write(Annotation::TotalPhysicalMemory, physicalMemoryByteSize);
}
}
// Extract available and purgeable physical memory.
static void WriteAvailableMemoryStatus(AnnotationWriter& aWriter) {
auto host = mach_host_self();
vm_statistics64_data_t stats;
unsigned int count = HOST_VM_INFO64_COUNT;
if (host_statistics64(host, HOST_VM_INFO64, (host_info64_t)&stats, &count) ==
KERN_SUCCESS) {
aWriter.Write(Annotation::AvailablePhysicalMemory,
static_cast<uint64_t>(stats.free_count * vm_page_size));
aWriter.Write(Annotation::PurgeablePhysicalMemory,
static_cast<uint64_t>(stats.purgeable_count * vm_page_size));
}
}
// Extract the status of the swap.
static void WriteSwapFileStatus(AnnotationWriter& aWriter) {
const size_t NAME_LEN = 2;
int name[] = {
/* Hardware */ CTL_VM,
/* 64-bit physical memory size */ VM_SWAPUSAGE};
struct xsw_usage swapUsage;
size_t infoByteSize =
sizeof(swapUsage);
if (sysctl(name, NAME_LEN, &swapUsage, &infoByteSize,
/* We do not replace data */ nullptr,
/* We do not replace data */ 0) != -1) {
aWriter.Write(Annotation::AvailableSwapMemory, swapUsage.xsu_avail);
}
}
static void AnnotateMemoryStatus(AnnotationWriter& aWriter) {
WritePhysicalMemoryStatus(aWriter);
WriteAvailableMemoryStatus(aWriter);
WriteSwapFileStatus(aWriter);
}
#elif XP_LINUX
static void AnnotateMemoryStatus(AnnotationWriter& aWriter) {
// We can't simply call `sysinfo` as this requires libc.
// So we need to parse /proc/meminfo.
// We read the entire file to memory prior to parsing
// as it makes the parser code a little bit simpler.
// As /proc/meminfo is synchronized via `proc_create_single`,
// there's no risk of race condition regardless of how we
// read it.
// The buffer in which we're going to load the entire file.
// A typical size for /proc/meminfo is 1KiB, so 4KiB should
// be large enough until further notice.
const size_t BUFFER_SIZE_BYTES = 4096;
char buffer[BUFFER_SIZE_BYTES];
size_t bufferLen = 0;
{
// Read and load into memory.
int fd = sys_open(
"/proc/meminfo", O_RDONLY,
/* chmod */ 0);
if (fd == -1) {
// No /proc/meminfo? Well, fail silently.
return;
}
auto Guard = MakeScopeExit([fd]() { mozilla::Unused << sys_close(fd); });
ssize_t bytesRead = 0;
do {
if ((bytesRead = sys_read(fd, buffer + bufferLen,
BUFFER_SIZE_BYTES - bufferLen)) < 0) {
if ((errno == EAGAIN) || (errno == EINTR)) {
continue;
}
// Cannot read for some reason. Let's give up.
return;
}
bufferLen += bytesRead;
if (bufferLen == BUFFER_SIZE_BYTES) {
// The file is too large, bail out
return;
}
}
while (bytesRead != 0);
}
// Each line of /proc/meminfo looks like
// SomeLabel: number unit
// The last line is empty.
// Let's write a parser.
// Note that we don't care about writing a normative parser, so
// we happily skip whitespaces without checking that it's necessary.
// A stack-allocated structure containing a 0-terminated string.
// We could avoid the memory copies and make it a slice at the cost
// of a slightly more complicated parser. Since we're not in a
// performance-critical section, we didn't.
struct DataBuffer {
DataBuffer() : data{0}, pos(0) {}
// Clear the buffer.
void reset() {
pos = 0;
data[0] = 0;
}
// Append a character.
//
// In case of error (if c is '\0' or the buffer is full), does nothing.
void append(
char c) {
if (c == 0 || pos >=
sizeof(data) - 1) {
return;
}
data[pos++] = c;
data[pos] = 0;
}
// Compare the buffer against a nul-terminated string.
bool operator==(
const char* s)
const {
for (size_t i = 0; i < pos; ++i) {
if (s[i] != data[i]) {
// Note: Since `data` never contains a '0' in positions [0,pos)
// this will bailout once we have reached the end of `s`.
return false;
}
}
return true;
}
// A NUL-terminated string of `pos + 1` chars (the +1 is for the 0).
char data[256];
// Invariant: < 256.
size_t pos;
};
// A DataBuffer holding the string representation of a non-negative number.
struct NumberBuffer : DataBuffer {
// If possible, convert the string into a number.
// Returns `true` in case of success, `false` in case of failure.
bool asNumber(size_t* number) {
int result;
if (!my_strtoui(&result, data)) {
return false;
}
*number = result;
return true;
}
};
// A DataBuffer holding the string representation of a unit. As of this
// writing, we only support unit `kB`, which seems to be the only unit used in
// `/proc/meminfo`.
struct UnitBuffer : DataBuffer {
// If possible, convert the string into a multiplier, e.g. `kB => 1024`.
// Return `true` in case of success, `false` in case of failure.
bool asMultiplier(size_t* multiplier) {
if (*
this ==
"kB") {
*multiplier = 1024;
return true;
}
// Other units don't seem to be specified/used.
return false;
}
};
// The state of the mini-parser.
enum class State {
// Reading the label, including the trailing ':'.
Label,
// Reading the number, ignoring any whitespace.
Number,
// Reading the unit, ignoring any whitespace.
Unit,
};
// A single measure being read from /proc/meminfo, e.g.
// the total physical memory available on the system.
struct Measure {
Measure() : state(State::Label) {}
// Reset the measure for a new read.
void reset() {
state = State::Label;
label.reset();
number.reset();
unit.reset();
}
// Attempt to convert the measure into a number.
// Return `true` if both the number and the multiplier could be
// converted, `false` otherwise.
// In case of overflow, produces the maximal possible `size_t`.
bool asValue(size_t* result) {
size_t numberAsSize = 0;
if (!number.asNumber(&numberAsSize)) {
return false;
}
size_t unitAsMultiplier = 0;
if (!unit.asMultiplier(&unitAsMultiplier)) {
return false;
}
if (numberAsSize * unitAsMultiplier >= numberAsSize) {
*result = numberAsSize * unitAsMultiplier;
}
else {
// Overflow. Unlikely, but just in case, let's return
// the maximal possible value.
*result = size_t(-1);
}
return true;
}
// The label being read, e.g. `MemFree`. Does not include the trailing ':'.
DataBuffer label;
// The number being read, e.g. "1024".
NumberBuffer number;
// The unit being read, e.g. "kB".
UnitBuffer unit;
// What we're reading at the moment.
State state;
};
// A value we wish to store for later processing.
// e.g. to compute `AvailablePageFile`, we need to
// store `CommitLimit` and `Committed_AS`.
struct ValueStore {
ValueStore() : value(0), found(
false) {}
size_t value;
bool found;
};
ValueStore commitLimit;
ValueStore committedAS;
ValueStore memTotal;
ValueStore swapTotal;
// The current measure.
Measure measure;
for (size_t pos = 0; pos < size_t(bufferLen); ++pos) {
const char c = buffer[pos];
switch (measure.state) {
case State::Label:
if (c ==
':') {
// We have finished reading the label.
measure.state = State::Number;
}
else {
measure.label.append(c);
}
break;
case State::Number:
if (c ==
' ') {
// Ignore whitespace
}
else if (
'0' <= c && c <=
'9') {
// Accumulate numbers.
measure.number.append(c);
}
else {
// We have jumped to the unit.
measure.unit.append(c);
measure.state = State::Unit;
}
break;
case State::Unit:
if (c ==
' ') {
// Ignore whitespace
}
else if (c ==
'\n') {
// Flush line.
// - If this one of the measures we're interested in, write it.
// - Once we're done, reset the parser.
auto Guard = MakeScopeExit([&measure]() { measure.reset(); });
struct PointOfInterest {
// The label we're looking for, e.g. "MemTotal".
const char* label;
// If non-nullptr, store the value at this address.
ValueStore* dest;
// If other than Annotation::Count, write the value for this
// annotation.
Annotation annotation;
};
const PointOfInterest POINTS_OF_INTEREST[] = {
{
"MemTotal", &memTotal, Annotation::TotalPhysicalMemory},
{
"MemFree", nullptr, Annotation::AvailablePhysicalMemory},
{
"MemAvailable", nullptr, Annotation::AvailableVirtualMemory},
{
"SwapFree", nullptr, Annotation::AvailableSwapMemory},
{
"SwapTotal", &swapTotal, Annotation::Count},
{
"CommitLimit", &commitLimit, Annotation::Count},
{
"Committed_AS", &committedAS, Annotation::Count},
};
for (
const auto& pointOfInterest : POINTS_OF_INTEREST) {
if (measure.label == pointOfInterest.label) {
size_t value;
if (measure.asValue(&value)) {
if (pointOfInterest.dest != nullptr) {
pointOfInterest.dest->found =
true;
pointOfInterest.dest->value = value;
}
if (pointOfInterest.annotation != Annotation::Count) {
aWriter.Write(pointOfInterest.annotation,
static_cast<uint64_t>(value));
}
}
break;
}
}
// Otherwise, ignore.
}
else {
measure.unit.append(c);
}
break;
}
}
if (commitLimit.found && committedAS.found) {
// If available, attempt to determine the available virtual memory.
// As `commitLimit` is not guaranteed to be larger than `committedAS`,
// we return `0` in case the commit limit has already been exceeded.
uint64_t availablePageFile = (committedAS.value <= commitLimit.value)
? (commitLimit.value - committedAS.value)
: 0;
aWriter.Write(Annotation::AvailablePageFile, availablePageFile);
}
if (memTotal.found && swapTotal.found) {
// If available, attempt to determine the available virtual memory.
aWriter.Write(Annotation::TotalPageFile,
static_cast<uint64_t>(memTotal.value + swapTotal.value));
}
}
#else
static void AnnotateMemoryStatus(AnnotationTable&) {
// No memory data for other platforms yet.
}
#endif // XP_WIN || XP_MACOSX || XP_LINUX || else
#if !
defined(MOZ_WIDGET_ANDROID)
/**
* Launches the program specified in aProgramPath with aMinidumpPath as its
* sole argument.
*
* @param aProgramPath The path of the program to be launched
* @param aMinidumpPath The path of the minidump file, passed as an argument
* to the launched program
*/
static bool LaunchProgram(
const XP_CHAR* aProgramPath,
const XP_CHAR* aMinidumpPath) {
# ifdef XP_WIN
XP_CHAR cmdLine[CMDLINE_SIZE];
XP_CHAR* p;
size_t size = CMDLINE_SIZE;
p = Concat(cmdLine, L
"\"", &size);
p = Concat(p, aProgramPath, &size);
p = Concat(p, L
"\" \
"", &size);
p = Concat(p, aMinidumpPath, &size);
Concat(p, L
"\"", &size);
PROCESS_INFORMATION pi = {};
STARTUPINFO si = {};
si.cb =
sizeof(si);
// If CreateProcess() fails don't do anything.
if (CreateProcess(
/* lpApplicationName */ nullptr, (LPWSTR)cmdLine,
/* lpProcessAttributes */ nullptr, /* lpThreadAttributes */ nullptr,
/* bInheritHandles */ FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_BREAKAWAY_FROM_JOB,
/* lpEnvironment */ nullptr, /* lpCurrentDirectory */ nullptr, &si,
&pi)) {
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
# elif
defined(XP_MACOSX)
pid_t pid = 0;
char*
const my_argv[] = {
const_cast<
char*>(aProgramPath),
const_cast<
char*>(aMinidumpPath), nullptr};
char** env = nullptr;
char*** nsEnv = _NSGetEnviron();
if (nsEnv) {
env = *nsEnv;
}
int rv = posix_spawnp(&pid, my_argv[0], nullptr, nullptr, my_argv, env);
if (rv != 0) {
return false;
}
# else // !XP_MACOSX
pid_t pid = sys_fork();
if (pid == -1) {
return false;
}
else if (pid == 0) {
Unused << execl(aProgramPath, aProgramPath, aMinidumpPath, nullptr);
_
exit(1);
}
# endif
// XP_MACOSX
return true;
}
#else
/**
* Launch the crash reporter activity on Android
*
* @param aProgramPath The path of the program to be launched
* @param aMinidumpPath The path to the crash minidump file
*/
static bool LaunchCrashHandlerService(
const XP_CHAR* aProgramPath,
const XP_CHAR* aMinidumpPath) {
static XP_CHAR extrasPath[XP_PATH_MAX];
size_t size = XP_PATH_MAX;
XP_CHAR* p = Concat(extrasPath, aMinidumpPath, &size);
p = Concat(p - 3,
"extra", &size);
pid_t pid = sys_fork();
if (pid == -1)
return false;
else if (pid == 0) {
// Invoke the crash handler service using am
if (androidUserSerial) {
Unused << execlp(
"/system/bin/am",
"/system/bin/am",
androidStartServiceCommand,
"--user", androidUserSerial,
"-a",
"org.mozilla.gecko.ACTION_CRASHED",
"-n",
aProgramPath,
"--es",
"minidumpPath", aMinidumpPath,
"--es",
"extrasPath", extrasPath,
"--ez",
"fatal",
"true",
"--es",
"processType",
"MAIN", (
char*)0);
}
else {
Unused << execlp(
"/system/bin/am",
"/system/bin/am", androidStartServiceCommand,
"-a",
"org.mozilla.gecko.ACTION_CRASHED",
"-n", aProgramPath,
"--es",
"minidumpPath", aMinidumpPath,
"--es",
"extrasPath", extrasPath,
"--ez",
"fatal",
"true",
"--es",
"processType",
"MAIN", (
char*)0);
}
_
exit(1);
}
else {
// We need to wait on the 'am start' command above to finish, otherwise
// everything will be killed by the ActivityManager as soon as the signal
// handler exits
int status;
Unused << HANDLE_EINTR(sys_waitpid(pid, &status, __WALL));
}
return true;
}
#endif
static void WriteAnnotations(AnnotationWriter& aWriter,
const AnnotationTable& aAnnotations) {
for (
auto key : MakeEnumeratedRange(Annotation::Count)) {
const nsCString& value = aAnnotations[key];
if (!value.IsEmpty()) {
aWriter.Write(key, value.get(), value.Length());
}
}
}
static void WriteSynthesizedAnnotations(AnnotationWriter& aWriter) {
AnnotateMemoryStatus(aWriter);
}
static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
const phc::AddrInfo* addrInfo,
time_t crashTime) {
JSONAnnotationWriter writer(pw);
for (
auto key : MakeEnumeratedRange(Annotation::Count)) {
AnnotationContents contents = {};
size_t address =
mozannotation_get_contents(
static_cast<uint32_t>(key), &contents);
if (address != 0) {
switch (TypeOfAnnotation(key)) {
case AnnotationType::String:
switch (contents.tag) {
case AnnotationContents::Tag::NSCStringPointer: {
const nsCString* string =
reinterpret_cast<
const nsCString*>(address);
writer.Write(key, string->Data(), string->Length());
}
break;
case AnnotationContents::Tag::CStringPointer:
address = *(
reinterpret_cast<size_t*>(address));
if (address == 0) {
break;
}
// FALLTHROUGH
case AnnotationContents::Tag::CString: {
writer.Write(key,
reinterpret_cast<
const char*>(address));
}
break;
case AnnotationContents::Tag::ByteBuffer:
writer.Write(key,
reinterpret_cast<
const char*>(address),
static_cast<size_t>(contents.byte_buffer._0));
break;
case AnnotationContents::Tag::OwnedByteBuffer:
writer.Write(key,
reinterpret_cast<
const char*>(address),
static_cast<size_t>(contents.owned_byte_buffer._0));
break;
case AnnotationContents::Tag::Empty:
break;
}
break;
case AnnotationType::Boolean:
writer.Write(key, *
reinterpret_cast<
const bool*>(address));
break;
case AnnotationType::U32:
writer.Write(key,
static_cast<uint64_t>(
*
reinterpret_cast<uint32_t*>(address)));
break;
case AnnotationType::U64:
writer.Write(key, *
reinterpret_cast<uint64_t*>(address));
break;
case AnnotationType::USize:
writer.Write(
key,
static_cast<uint64_t>(*
reinterpret_cast<size_t*>(address)));
break;
}
}
}
WriteSynthesizedAnnotations(writer);
writer.Write(Annotation::CrashTime, uint64_t(crashTime));
if (inactiveStateStart) {
writer.Write(Annotation::LastInteractionDuration,
static_cast<uint64_t>(crashTime - inactiveStateStart));
}
double uptimeTS = (TimeStamp::NowLoRes() - TimeStamp::ProcessCreation())
.ToSecondsSigDigits();
char uptimeTSString[64] = {};
SimpleNoCLibDtoA(uptimeTS, uptimeTSString,
sizeof(uptimeTSString));
writer.Write(Annotation::UptimeTS, uptimeTSString);
// calculate time since last crash (if possible).
if (lastCrashTime != 0) {
uint64_t timeSinceLastCrash = crashTime - lastCrashTime;
writer.Write(Annotation::SecondsSinceLastCrash, timeSinceLastCrash);
}
#if defined(XP_WIN) &&
defined(HAS_DLL_BLOCKLIST)
// HACK: The DLL blocklist code will manually write its annotations as JSON
DllBlocklist_WriteNotes();
#endif // defined(XP_WIN) && defined(HAS_DLL_BLOCKLIST)
#ifdef MOZ_PHC
WritePHCAddrInfo(writer, addrInfo);
#endif
}
static void WriteCrashEventFile(time_t crashTime,
const char* crashTimeString,
const phc::AddrInfo* addrInfo,
#ifdef XP_LINUX
const MinidumpDescriptor& descriptor
#else
const XP_CHAR* minidump_id
#endif
) {
if (BackgroundTasks::IsBackgroundTaskMode()) {
// Do not generate a crash event file if the main process was running a
// background task, as the crash won't be visible to the user.
return;
}
// Minidump IDs are UUIDs (36) + NULL.
static char id_ascii[37] = {};
#ifdef XP_LINUX
const char* index = strrchr(descriptor.path(),
'/');
MOZ_ASSERT(index);
MOZ_ASSERT(strlen(index) == 1 + 36 + 4);
// "/" + UUID + ".dmp"
for (uint32_t i = 0; i < 36; i++) {
id_ascii[i] = *(index + 1 + i);
}
#else
MOZ_ASSERT(XP_STRLEN(minidump_id) == 36);
for (uint32_t i = 0; i < 36; i++) {
id_ascii[i] = *((
char*)(minidump_id + i));
}
#endif
PlatformWriter eventFile;
if (!eventsDirectory.empty()) {
static XP_CHAR crashEventPath[XP_PATH_MAX];
size_t size = XP_PATH_MAX;
XP_CHAR* p;
p = Concat(crashEventPath, eventsDirectory.c_str(), &size);
p = Concat(p, XP_PATH_SEPARATOR, &size);
#ifdef XP_LINUX
Concat(p, id_ascii, &size);
#else
Concat(p, minidump_id, &size);
#endif
eventFile.Open(crashEventPath);
eventFile.WriteLiteral(kCrashMainID);
eventFile.WriteString(crashTimeString);
eventFile.WriteLiteral(
"\n");
eventFile.WriteString(id_ascii);
eventFile.WriteLiteral(
"\n");
WriteAnnotationsForMainProcessCrash(eventFile, addrInfo, crashTime);
}
}
// Callback invoked from breakpad's exception handler, this writes out the
// last annotations after a crash occurs and launches the crash reporter client.
//
// This function is not declared static even though it's not used outside of
// this file because of an issue in Fennec which prevents breakpad's exception
// handler from invoking it. See bug 1424304.
bool MinidumpCallback(
#ifdef XP_LINUX
const MinidumpDescriptor& descriptor,
#else
const XP_CHAR* dump_path,
const XP_CHAR* minidump_id,
#endif
void* context,
#ifdef XP_WIN
EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion,
#endif
const phc::AddrInfo* addrInfo,
bool succeeded) {
bool returnValue = showOSCrashReporter ?
false : succeeded;
static XP_CHAR minidumpPath[XP_PATH_MAX];
size_t size = XP_PATH_MAX;
XP_CHAR* p;
#ifndef XP_LINUX
p = Concat(minidumpPath, dump_path, &size);
p = Concat(p, XP_PATH_SEPARATOR, &size);
p = Concat(p, minidump_id, &size);
Concat(p, dumpFileExtension, &size);
#else
Concat(minidumpPath, descriptor.path(), &size);
#endif
static XP_CHAR memoryReportLocalPath[XP_PATH_MAX];
size = XP_PATH_MAX;
#ifndef XP_LINUX
p = Concat(memoryReportLocalPath, dump_path, &size);
p = Concat(p, XP_PATH_SEPARATOR, &size);
p = Concat(p, minidump_id, &size);
#else
p = Concat(memoryReportLocalPath, descriptor.path(), &size);
// Skip back past the .dmp extension
p -= 4;
#endif
Concat(p, memoryReportExtension, &size);
if (!memoryReportPath.empty()) {
#ifdef XP_WIN
CopyFile(memoryReportPath.c_str(), memoryReportLocalPath,
false);
#else
copy_file(memoryReportPath.c_str(), memoryReportLocalPath);
#endif
}
time_t crashTime = GetCurrentTimeForCrashTime();
char crashTimeString[32];
XP_TTOA(crashTime, crashTimeString);
// write crash time to file
if (lastCrashTimeFilename[0] != 0) {
PlatformWriter lastCrashFile(lastCrashTimeFilename);
lastCrashFile.WriteString(crashTimeString);
}
WriteCrashEventFile(crashTime, crashTimeString, addrInfo,
#ifdef XP_LINUX
descriptor
#else
minidump_id
#endif
);
{
PlatformWriter apiData;
#ifdef XP_LINUX
OpenAPIData(apiData, descriptor.path());
#else
OpenAPIData(apiData, dump_path, minidump_id);
#endif
WriteAnnotationsForMainProcessCrash(apiData, addrInfo, crashTime);
}
if (doReport && isSafeToDump && !BackgroundTasks::IsBackgroundTaskMode()) {
// We launch the crash reporter client/dialog only if we've been explicitly
// asked to report crashes and if we weren't already trying to unset the
// exception handler (which is indicated by isSafeToDump being false).
#if defined(MOZ_WIDGET_ANDROID)
// Android
returnValue =
LaunchCrashHandlerService(crashReporterPath.c_str(), minidumpPath);
#else // Windows, Mac, Linux, etc...
returnValue = LaunchProgram(crashReporterPath.c_str(), minidumpPath);
#endif
}
#ifdef XP_WIN
TerminateProcess(GetCurrentProcess(), 1);
#endif
return returnValue;
}
#if defined(XP_MACOSX) ||
defined(__ANDROID__) ||
defined(XP_LINUX)
static size_t EnsureTrailingSlash(XP_CHAR* aBuf, size_t aBufLen) {
size_t len = XP_STRLEN(aBuf);
if ((len + 1) < aBufLen && len > 0 &&
aBuf[len - 1] != XP_PATH_SEPARATOR_CHAR) {
aBuf[len] = XP_PATH_SEPARATOR_CHAR;
++len;
aBuf[len] = 0;
}
return len;
}
#endif
#if defined(XP_WIN)
static size_t BuildTempPath(
wchar_t* aBuf, size_t aBufLen) {
// first figure out buffer size
DWORD pathLen = GetTempPath(0, nullptr);
if (pathLen == 0 || pathLen >= aBufLen) {
return 0;
}
return GetTempPath(pathLen, aBuf);
}
static size_t BuildTempPath(char16_t* aBuf, size_t aBufLen) {
return BuildTempPath(
reinterpret_cast<
wchar_t*>(aBuf), aBufLen);
}
#elif defined(XP_MACOSX)
static size_t BuildTempPath(
char* aBuf, size_t aBufLen) {
if (aBufLen < PATH_MAX) {
return 0;
}
FSRef fsRef;
OSErr err =
FSFindFolder(kUserDomain, kTemporaryFolderType, kCreateFolder, &fsRef);
if (err != noErr) {
return 0;
}
OSStatus status = FSRefMakePath(&fsRef, (UInt8*)aBuf, PATH_MAX);
if (status != noErr) {
return 0;
}
return EnsureTrailingSlash(aBuf, aBufLen);
}
#elif defined(__ANDROID__)
static size_t BuildTempPath(
char* aBuf, size_t aBufLen) {
// GeckoAppShell sets this in the environment
const char* tempenv = PR_GetEnv(
"TMPDIR");
if (!tempenv) {
return false;
}
size_t size = aBufLen;
Concat(aBuf, tempenv, &size);
return EnsureTrailingSlash(aBuf, aBufLen);
}
#elif defined(XP_UNIX)
static size_t BuildTempPath(
char* aBuf, size_t aBufLen) {
const char* tempenv = PR_GetEnv(
"TMPDIR");
const char* tmpPath =
"/tmp/";
if (!tempenv) {
tempenv = tmpPath;
}
size_t size = aBufLen;
Concat(aBuf, tempenv, &size);
return EnsureTrailingSlash(aBuf, aBufLen);
}
#else
# error
"Implement this for your platform"
#endif
template <
typename CharT, size_t N>
static size_t BuildTempPath(CharT (&aBuf)[N]) {
static_assert(N >= XP_PATH_MAX,
"char array length is too small");
return BuildTempPath(&aBuf[0], N);
}
template <
typename PathStringT>
static bool BuildTempPath(PathStringT& aResult) {
aResult.SetLength(XP_PATH_MAX);
size_t actualLen = BuildTempPath(aResult.BeginWriting(), XP_PATH_MAX);
if (!actualLen) {
return false;
}
aResult.SetLength(actualLen);
return true;
}
#ifdef XP_WIN
static bool IsCrashingException(EXCEPTION_POINTERS* exinfo) {
if (!exinfo) {
return true;
}
PEXCEPTION_RECORD e = (PEXCEPTION_RECORD)exinfo->ExceptionRecord;
switch (e->ExceptionCode) {
case STATUS_FLOAT_DENORMAL_OPERAND:
case STATUS_FLOAT_DIVIDE_BY_ZERO:
case STATUS_FLOAT_INEXACT_RESULT:
case STATUS_FLOAT_INVALID_OPERATION:
case STATUS_FLOAT_OVERFLOW:
case STATUS_FLOAT_STACK_CHECK:
case STATUS_FLOAT_UNDERFLOW:
case STATUS_FLOAT_MULTIPLE_FAULTS:
case STATUS_FLOAT_MULTIPLE_TRAPS:
return false;
// Don't write minidump, continue exception search
default:
return true;
}
}
#endif // XP_WIN
// Do various actions to prepare the child process for minidump generation.
// This includes disabling the I/O interposer and DLL blocklist which both
// would get in the way. We also free the resources we have reserved, such as
// address space on 32-bit Windows builds and file descriptors on Linux so that
// they're available to the minidump generation code.
static void PrepareForMinidump() {
mozilla::IOInterposer::Disable();
ReleaseResources();
#if defined(XP_WIN)
# if defined(DEBUG) &&
defined(HAS_DLL_BLOCKLIST)
DllBlocklist_Shutdown();
# endif
#endif // XP_WIN
}
#ifdef XP_WIN
/**
* Filters out floating point exceptions which are handled by nsSigHandlers.cpp
* and should not be handled as crashes.
*/
static ExceptionHandler::FilterResult Filter(
void* context,
EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion) {
if (!IsCrashingException(exinfo)) {
return ExceptionHandler::FilterResult::ContinueSearch;
}
PrepareForMinidump();
return ExceptionHandler::FilterResult::HandleException;
}
static ExceptionHandler::FilterResult ChildFilter(
void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion) {
if (!IsCrashingException(exinfo)) {
return ExceptionHandler::FilterResult::ContinueSearch;
}
if (gEncounteredChildException.exchange(
true)) {
return ExceptionHandler::FilterResult::AbortWithoutMinidump;
}
PrepareForMinidump();
return ExceptionHandler::FilterResult::HandleException;
}
static MINIDUMP_TYPE GetMinidumpType() {
MINIDUMP_TYPE minidump_type =
static_cast<MINIDUMP_TYPE>(
MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules |
MiniDumpWithHandleData);
# ifdef NIGHTLY_BUILD
minidump_type =
static_cast<MINIDUMP_TYPE>(
minidump_type |
// This is Nightly only because this doubles the size of minidumps based
// on the experimental data.
MiniDumpWithProcessThreadData |
// This allows us to examine heap objects referenced from stack objects
// at the cost of further doubling the size of minidumps.
MiniDumpWithIndirectlyReferencedMemory);
# endif
const char* e = PR_GetEnv(
"MOZ_CRASHREPORTER_FULLDUMP");
if (e && *e) {
minidump_type = MiniDumpWithFullMemory;
}
return minidump_type;
}
#else
static bool Filter(
void* context) {
PrepareForMinidump();
return true;
}
static bool ChildFilter(
void* context) {
if (gEncounteredChildException.exchange(
true)) {
return false;
}
PrepareForMinidump();
return true;
}
#endif // !defined(XP_WIN)
static bool ChildMinidumpCallback(
#if defined(XP_WIN)
const wchar_t* dump_path,
const wchar_t* minidump_id,
#elif defined(XP_LINUX)
const MinidumpDescriptor& descriptor,
#else // defined(XP_MACOSX)
const char* dump_dir,
const char* minidump_id,
#endif
void* context,
#if defined(XP_WIN)
EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion,
#endif // defined(XP_WIN)
const mozilla::phc::AddrInfo* addr_info,
bool succeeded) {
return succeeded;
}
static bool ShouldReport() {
// this environment variable prevents us from launching
// the crash reporter client
const char* envvar = PR_GetEnv(
"MOZ_CRASHREPORTER_NO_REPORT");
if (envvar && *envvar) {
return false;
}
envvar = PR_GetEnv(
"MOZ_CRASHREPORTER_FULLDUMP");
if (envvar && *envvar) {
return false;
}
return true;
}
static void TerminateHandler() { MOZ_CRASH(
"Unhandled exception"); }
#if !
defined(MOZ_WIDGET_ANDROID)
// Locate the specified executable and store its path as a native string in
// the |aPath| so we can later invoke it from within the exception handler.
static nsresult LocateExecutable(nsIFile* aXREDirectory,
const nsAString& aName,
PathString& aPath) {
nsCOMPtr<nsIFile> exePath;
nsresult rv = aXREDirectory->Clone(getter_AddRefs(exePath));
NS_ENSURE_SUCCESS(rv, rv);
# ifdef XP_MACOSX
exePath->SetNativeLeafName(
"MacOS"_ns);
exePath->Append(u
"crashreporter.app"_ns);
exePath->Append(u
"Contents"_ns);
exePath->Append(u
"MacOS"_ns);
# endif
exePath->Append(aName);
aPath = exePath->NativePath();
return NS_OK;
}
#endif // !defined(MOZ_WIDGET_ANDROID)
static void InitializeAppNotes() {
notesFieldLock =
new Mutex(
"notesFieldLock");
notesField =
new nsCString();
}
// Register crash annotations that are present in both main and child processes
static void RegisterAnnotations() {
mozannotation_register_cstring_ptr(
static_cast<uint32_t>(Annotation::MozCrashReason), &gMozCrashReason);
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
mozannotation_register_cstring(
static_cast<uint32_t>(Annotation::MainThreadRunnableName),
&nsThread::sMainThreadRunnableName[0]);
#endif
mozannotation_register_bytebuffer(
static_cast<uint32_t>(Annotation::EventLoopNestingLevel),
&eventloopNestingLevel,
sizeof(uint32_t));
mozannotation_register_nscstring(
static_cast<uint32_t>(Annotation::Notes),
notesField);
mozannotation_register_bytebuffer(
static_cast<uint32_t>(Annotation::OOMAllocationSize), &gOOMAllocationSize,
sizeof(size_t));
mozannotation_register_bytebuffer(
static_cast<uint32_t>(Annotation::IsGarbageCollecting),
&isGarbageCollecting,
sizeof(
bool));
mozannotation_register_nscstring(
static_cast<uint32_t>(Annotation::ServerURL),
&gServerURL);
mozannotation_register_bytebuffer(
static_cast<uint32_t>(Annotation::TextureUsage), &gTexturesSize,
sizeof(size_t));
#if defined(XP_WIN) &&
defined(HAS_DLL_BLOCKLIST)
mozannotation_register_bytebuffer(
static_cast<uint32_t>(Annotation::BlocklistInitFailed),
DllBlocklist_GetBlocklistInitFailedPointer(),
sizeof(
bool));
mozannotation_register_bytebuffer(
static_cast<uint32_t>(Annotation::User32BeforeBlocklist),
DllBlocklist_GetUser32BeforeBlocklistPointer(),
sizeof(
bool));
mozannotation_register_cstring(
static_cast<uint32_t>(Annotation::BlockedDllList),
DllBlocklist_GetBlocklistWriterData());
#endif // defined(XP_WIN) && defined(HAS_DLL_BLOCKLIST)
}
static void TeardownAppNotes() {
delete notesFieldLock;
notesFieldLock = nullptr;
delete notesField;
notesField = nullptr;
}
nsresult SetExceptionHandler(nsIFile* aXREDirectory,
bool force
/*=false*/) {
if (gExceptionHandler)
return NS_ERROR_ALREADY_INITIALIZED;
#if defined(DEBUG)
// In debug builds, disable the crash reporter by default, and allow to
// enable it with the MOZ_CRASHREPORTER environment variable.
const char* envvar = PR_GetEnv(
"MOZ_CRASHREPORTER");
if ((!envvar || !*envvar) && !force)
return NS_OK;
#else
// In other builds, enable the crash reporter by default, and allow
// disabling it with the MOZ_CRASHREPORTER_DISABLE environment variable.
const char* envvar = PR_GetEnv(
"MOZ_CRASHREPORTER_DISABLE");
if (envvar && *envvar && !force)
return NS_OK;
#endif
// this environment variable prevents us from launching
// the crash reporter client
doReport = ShouldReport();
RegisterRuntimeExceptionModule();
InitializeAppNotes();
RegisterAnnotations();
#if !
defined(MOZ_WIDGET_ANDROID)
// Locate the crash reporter executable
PathString crashReporterPath_temp;
nsresult rv = LocateExecutable(aXREDirectory, CRASH_REPORTER_FILENAME,
crashReporterPath_temp);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
crashReporterPath = crashReporterPath_temp.get();
#else
// On Android, we launch a service defined via MOZ_ANDROID_CRASH_HANDLER
const char* androidCrashHandler = PR_GetEnv(
"MOZ_ANDROID_CRASH_HANDLER");
if (androidCrashHandler) {
crashReporterPath = xpstring(androidCrashHandler);
}
else {
NS_WARNING(
"No Android crash handler set");
}
const char* deviceAndroidVersion =
PR_GetEnv(
"MOZ_ANDROID_DEVICE_SDK_VERSION");
if (deviceAndroidVersion != nullptr) {
const int deviceSdkVersion = atol(deviceAndroidVersion);
if (deviceSdkVersion >= 26) {
androidStartServiceCommand = (
char*)
"start-foreground-service";
}
else {
androidStartServiceCommand = (
char*)
"startservice";
}
}
#endif // !defined(MOZ_WIDGET_ANDROID)
// get temp path to use for minidump path
PathString tempPath;
if (!BuildTempPath(tempPath)) {
return NS_ERROR_FAILURE;
}
ReserveResources();
#ifdef XP_WIN
// Pre-load psapi.dll to prevent it from being loaded during exception
// handling.
::LoadLibraryW(L
"psapi.dll");
#endif // XP_WIN
#ifdef MOZ_WIDGET_ANDROID
androidUserSerial = getenv(
"MOZ_ANDROID_USER_SERIAL_NUMBER");
#endif
// Initialize the flag and mutex used to avoid dump processing
// once browser termination has begun.
NS_ASSERTION(!dumpSafetyLock,
"Shouldn't have a lock yet");
// Do not deallocate this lock while it is still possible for
// isSafeToDump to be tested on another thread.
dumpSafetyLock =
new Mutex(
"dumpSafetyLock");
MutexAutoLock lock(*dumpSafetyLock);
isSafeToDump =
true;
// now set the exception handler
#ifdef XP_LINUX
MinidumpDescriptor descriptor(tempPath.get());
#endif
#ifdef XP_WIN
previousUnhandledExceptionFilter = GetUnhandledExceptionFilter();
#endif
gExceptionHandler =
new google_breakpad::ExceptionHandler(
#ifdef XP_LINUX
descriptor,
#elif defined(XP_WIN)
std::wstring(tempPath.get()),
#else
tempPath.get(),
#endif
Filter, MinidumpCallback, nullptr,
#ifdef XP_WIN
google_breakpad::ExceptionHandler::HANDLER_ALL, GetMinidumpType(),
(
const wchar_t*)nullptr, nullptr);
#else
true
# ifdef XP_MACOSX
,
nullptr
# endif
# ifdef XP_LINUX
,
-1
# endif
);
#endif // XP_WIN
if (!gExceptionHandler)
return NS_ERROR_OUT_OF_MEMORY;
#ifdef XP_WIN
gExceptionHandler->set_handle_debug_exceptions(
true);
// Initially set sIncludeContextHeap to true for debugging startup crashes
// even if the controlling pref value is false.
SetIncludeContextHeap(
true);
# if defined(HAVE_64BIT_BUILD)
// Tell JS about the new filter before we disable SetUnhandledExceptionFilter
SetJitExceptionHandler();
# endif
RecordMainThreadId();
// protect the crash reporter from being unloaded
gBlockUnhandledExceptionFilter =
true;
gKernel32Intercept.Init(
"kernel32.dll");
DebugOnly<
bool> ok = stub_SetUnhandledExceptionFilter.Set(
gKernel32Intercept,
"SetUnhandledExceptionFilter",
&patched_SetUnhandledExceptionFilter);
# ifdef DEBUG
if (!ok)
printf_stderr(
"SetUnhandledExceptionFilter hook failed; crash reporter is "
"vulnerable.\n");
# endif
#endif
// store application start time
RecordAnnotationU64(Annotation::StartupTime,
static_cast<uint64_t>(time(nullptr)));
#if defined(XP_MACOSX)
// On OS X, many testers like to see the OS crash reporting dialog
// since it offers immediate stack traces. We allow them to set
// a default to pass exceptions to the OS handler.
Boolean keyExistsAndHasValidFormat =
false;
Boolean prefValue = ::CFPreferencesGetAppBooleanValue(
CFSTR(
"OSCrashReporter"), kCFPreferencesCurrentApplication,
&keyExistsAndHasValidFormat);
if (keyExistsAndHasValidFormat) showOSCrashReporter = prefValue;
#endif
oldTerminateHandler = std::set_terminate(&TerminateHandler);
return NS_OK;
}
bool GetEnabled() {
return gExceptionHandler != nullptr; }
bool GetMinidumpPath(nsAString& aPath) {
if (!gExceptionHandler)
return false;
#ifndef XP_LINUX
aPath = CONVERT_XP_CHAR_TO_UTF16(gExceptionHandler->dump_path().c_str());
#else
aPath = CONVERT_XP_CHAR_TO_UTF16(
--> --------------------
--> maximum size reached
--> --------------------