diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/crashreporter/nsExceptionHandler.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/crashreporter/nsExceptionHandler.cpp')
-rw-r--r-- | toolkit/crashreporter/nsExceptionHandler.cpp | 4147 |
1 files changed, 4147 insertions, 0 deletions
diff --git a/toolkit/crashreporter/nsExceptionHandler.cpp b/toolkit/crashreporter/nsExceptionHandler.cpp new file mode 100644 index 000000000..1e0743192 --- /dev/null +++ b/toolkit/crashreporter/nsExceptionHandler.cpp @@ -0,0 +1,4147 @@ +/* -*- 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 "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDataHashtable.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/dom/CrashReporterChild.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "mozilla/Unused.h" +#include "mozilla/Sprintf.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/TimeStamp.h" + +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "jsfriendapi.h" + +#if defined(XP_WIN32) +#ifdef WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#endif + +#include "nsXULAppAPI.h" +#include "nsIXULAppInfo.h" +#include "nsIWindowsRegKey.h" +#include "client/windows/crash_generation/client_info.h" +#include "client/windows/crash_generation/crash_generation_server.h" +#include "client/windows/handler/exception_handler.h" +#include <dbghelp.h> +#include <string.h> +#include "nsDirectoryServiceUtils.h" + +#include "nsWindowsDllInterceptor.h" +#elif defined(XP_MACOSX) +#include "client/mac/crash_generation/client_info.h" +#include "client/mac/crash_generation/crash_generation_server.h" +#include "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 <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 "client/linux/crash_generation/client_info.h" +#include "client/linux/crash_generation/crash_generation_server.h" +#include "client/linux/handler/exception_handler.h" +#include "common/linux/eintr_wrapper.h" +#include <fcntl.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#elif defined(XP_SOLARIS) +#include "client/solaris/handler/exception_handler.h" +#include <fcntl.h> +#include <sys/types.h> +#include <unistd.h> +#else +#error "Not yet implemented for this platform" +#endif // defined(XP_WIN32) + +#ifdef MOZ_CRASHREPORTER_INJECTOR +#include "InjectCrashReporter.h" +using mozilla::InjectCrashRunnable; +#endif + +#include <stdlib.h> +#include <time.h> +#include <prenv.h> +#include <prio.h> +#include <prmem.h> +#include "mozilla/Mutex.h" +#include "nsDebug.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "prprf.h" +#include <map> +#include <vector> + +#include "mozilla/double-conversion.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/mozalloc_oom.h" +#include "mozilla/WindowsDllBlocklist.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::CrashGenerationServer; +using google_breakpad::ClientInfo; +#ifdef XP_LINUX +using google_breakpad::MinidumpDescriptor; +#endif +#if defined(MOZ_WIDGET_ANDROID) +using google_breakpad::auto_wasteful_vector; +using google_breakpad::FileID; +using google_breakpad::PageAllocator; +#endif +using namespace mozilla; +using mozilla::dom::CrashReporterChild; +using mozilla::dom::PCrashReporterChild; +using mozilla::ipc::CrashReporterClient; + +namespace CrashReporter { + +#ifdef XP_WIN32 +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 CRASH_REPORTER_FILENAME "crashreporter.exe" +#define PATH_SEPARATOR "\\" +#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) +#ifdef _USE_32BIT_TIME_T +#define XP_TTOA(time, buffer, base) ltoa(time, buffer, base) +#else +#define XP_TTOA(time, buffer, base) _i64toa(time, buffer, base) +#endif +#define XP_STOA(size, buffer, base) _ui64toa(size, buffer, base) +#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 "crashreporter" +#define PATH_SEPARATOR "/" +#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, base) my_inttostring(time, buffer, sizeof(buffer)) +#define XP_STOA(size, buffer, base) my_inttostring(size, buffer, sizeof(buffer)) +#else +#define XP_STRLEN(x) strlen(x) +#define XP_TTOA(time, buffer, base) sprintf(buffer, "%ld", time) +#define XP_STOA(size, buffer, base) sprintf(buffer, "%zu", (size_t) size) +#define my_strlen strlen +#define sys_close close +#define sys_fork fork +#define sys_open open +#define sys_read read +#define sys_write write +#endif +#endif // XP_WIN32 + +#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 childCrashAnnotationBaseName[] = XP_TEXT("GeckoChildCrash"); +static const XP_CHAR extraFileExtension[] = XP_TEXT(".extra"); +static const XP_CHAR memoryReportExtension[] = XP_TEXT(".memory.json.gz"); +static xpstring *defaultMemoryReportPath = nullptr; + +// A whitelist of crash annotations which do not contain sensitive data +// and are saved in the crash record and sent with Firefox Health Report. +static char const * const kCrashEventAnnotations[] = { + "AsyncShutdownTimeout", + "BuildID", + "ProductID", + "ProductName", + "ReleaseChannel", + "SecondsSinceLastCrash", + "ShutdownProgress", + "StartupCrash", + "TelemetryEnvironment", + "Version", + // The following entries are not normal annotations but are included + // in the crash record/FHR: + // "ContainsMemoryReport" + // "EventLoopNestingLevel" + // "IsGarbageCollecting" + // "AvailablePageFile" + // "AvailableVirtualMemory" + // "SystemMemoryUsePercentage" + // "OOMAllocationSize" + // "TotalPageFile" + // "TotalPhysicalMemory" + // "TotalVirtualMemory" + // "MozCrashReason" +}; + +static const char kCrashMainID[] = "crash.main.2\n"; + +static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr; + +static XP_CHAR* pendingDirectory; +static XP_CHAR* crashReporterPath; +static XP_CHAR* memoryReportPath; + +// Where crash events should go. +static XP_CHAR* eventsDirectory; +static char* eventsEnv = nullptr; + +// The current telemetry session ID to write to the event file +static char* currentSessionId = nullptr; + +// If this is false, we don't launch the crash reporter +static bool doReport = true; + +// If this is true, we don't have a crash reporter +static bool headlessClient = false; + +// 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}; + +// A marker file to hold the path to the last dump written, which +// will be checked on startup. +static XP_CHAR crashMarkerFilename[XP_PATH_MAX] = {0}; + +// Whether we've already looked for the marker file. +static bool lastRunCrashID_checked = false; +// The minidump ID contained in the marker file. +static nsString* lastRunCrashID = nullptr; + +#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; +#endif + +// these are just here for readability +static const char kTimeSinceLastCrashParameter[] = "SecondsSinceLastCrash="; +static const int kTimeSinceLastCrashParameterLen = + sizeof(kTimeSinceLastCrashParameter)-1; + +// this holds additional data sent via the API +static Mutex* crashReporterAPILock; +static Mutex* notesFieldLock; +static AnnotationTable* crashReporterAPIData_Hash; +static nsCString* crashReporterAPIData = nullptr; +static nsCString* crashEventAPIData = nullptr; +static nsCString* notesField = nullptr; +static bool isGarbageCollecting; +static uint32_t eventloopNestingLevel = 0; + +// Avoid a race during application termination. +static Mutex* dumpSafetyLock; +static bool isSafeToDump = false; + +// OOP crash reporting +static CrashGenerationServer* crashServer; // chrome process has this + +static std::terminate_handler oldTerminateHandler = nullptr; + +#if (defined(XP_MACOSX) || defined(XP_WIN)) +// This field is valid in both chrome and content processes. +static xpstring* childProcessTmpDir = nullptr; +#endif + +# if defined(XP_WIN) || defined(XP_MACOSX) +// If crash reporting is disabled, we hand out this "null" pipe to the +// child process and don't attempt to connect to a parent server. +static const char kNullNotifyPipe[] = "-"; +static char* childCrashNotifyPipe; + +# elif defined(XP_LINUX) +static int serverSocketFd = -1; +static int clientSocketFd = -1; +static const int kMagicChildCrashReportFd = 4; + +# endif + +// |dumpMapLock| must protect all access to |pidToMinidump|. +static Mutex* dumpMapLock; +struct ChildProcessData : public nsUint32HashKey +{ + explicit ChildProcessData(KeyTypePointer aKey) + : nsUint32HashKey(aKey) + , sequence(0) +#ifdef MOZ_CRASHREPORTER_INJECTOR + , callback(nullptr) +#endif + { } + + nsCOMPtr<nsIFile> minidump; + // Each crashing process is assigned an increasing sequence number to + // indicate which process crashed first. + uint32_t sequence; +#ifdef MOZ_CRASHREPORTER_INJECTOR + InjectorCrashCallback* callback; +#endif +}; + +typedef nsTHashtable<ChildProcessData> ChildMinidumpMap; +static ChildMinidumpMap* pidToMinidump; +static uint32_t crashSequence; +static bool OOPInitialized(); + +#ifdef MOZ_CRASHREPORTER_INJECTOR +static nsIThread* sInjectorThread; + +class ReportInjectedCrash : public Runnable +{ +public: + explicit ReportInjectedCrash(uint32_t pid) : mPID(pid) { } + + NS_IMETHOD Run(); + +private: + uint32_t mPID; +}; +#endif // MOZ_CRASHREPORTER_INJECTOR + +// Crashreporter annotations that we don't send along in subprocess reports. +static const char* kSubprocessBlacklist[] = { + "FramePoisonBase", + "FramePoisonSize", + "StartupCrash", + "StartupTime", + "URL" +}; + +// If annotations are attempted before the crash reporter is enabled, +// they queue up here. +class DelayedNote; +nsTArray<nsAutoPtr<DelayedNote> >* gDelayedAnnotations; + +#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 SetUnhandledExceptionFilter_func stub_SetUnhandledExceptionFilter = 0; +static LPTOP_LEVEL_EXCEPTION_FILTER previousUnhandledExceptionFilter = nullptr; +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; +} + +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); +} + +/** + * 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. + * + * This size is bigger than xul.dll plus some extra for MinidumpWriteDump + * allocations. + */ +static const SIZE_T kReserveSize = 0x4000000; // 64 MB +static void* gBreakpadReservedVM; +#endif + +#ifdef XP_MACOSX +static cpu_type_t pref_cpu_types[2] = { +#if defined(__i386__) + CPU_TYPE_X86, +#elif defined(__x86_64__) + CPU_TYPE_X86_64, +#elif defined(__ppc__) + CPU_TYPE_POWERPC, +#endif + CPU_TYPE_ANY }; + +static posix_spawnattr_t spawnattr; +#endif + +#if defined(MOZ_WIDGET_ANDROID) +// Android builds use a custom library loader, +// so the embedding will provide a list of shared +// libraries that are mapped into anonymous mappings. +typedef struct { + std::string name; + uintptr_t start_address; + size_t length; + size_t file_offset; +} mapping_info; +static std::vector<mapping_info> library_mappings; +typedef std::map<uint32_t,google_breakpad::MappingList> MappingMap; +#endif +} + +// Format a non-negative double to a string, without using C-library functions, +// which need to be avoided (.e.g. bug 1240160, comment 10). Leave the utility +// non-file static so that we can gtest it. Return false if we failed to +// get the formatting done correctly. +bool SimpleNoCLibDtoA(double aValue, char* aBuffer, int aBufferLength) +{ + // aBufferLength is the size of the buffer. Be paranoid. + aBuffer[aBufferLength-1] = '\0'; + + if (aValue < 0) { + return false; + } + + int length, point, i; + bool sign; + bool ok = true; + double_conversion::DoubleToStringConverter::DoubleToAscii( + aValue, + double_conversion::DoubleToStringConverter::SHORTEST, + 8, + aBuffer, + aBufferLength, + &sign, + &length, + &point); + + // length does not account for the 0 terminator. + if (length > point && (length+1) < (aBufferLength-1)) { + // We have to insert a decimal point. Not worried about adding a leading zero + // in the < 1 (point == 0) case. + aBuffer[length+1] = '\0'; + for (i=length; i>point; i-=1) { + aBuffer[i] = aBuffer[i-1]; + } + aBuffer[i] = '.'; // Not worried about locales + } else if (length < point) { + // Trailing zeros scenario + for (i=length; i<point; i+=1) { + if (i >= aBufferLength-2) { + ok = false; + } + aBuffer[i] = '0'; + } + aBuffer[i] = '\0'; + } + return ok; +} + +namespace CrashReporter { + +#ifdef XP_LINUX +inline void +my_inttostring(intmax_t t, char* buffer, size_t buffer_length) +{ + my_memset(buffer, 0, buffer_length); + my_uitos(buffer, t, my_uint_len(t)); +} +#endif + +#ifdef XP_WIN +static void +CreateFileFromPath(const xpstring& path, nsIFile** file) +{ + NS_NewLocalFile(nsDependentString(path.c_str()), false, file); +} + +static void +CreateFileFromPath(const wchar_t* path, nsIFile** file) +{ + CreateFileFromPath(std::wstring(path), file); +} + +static xpstring* +CreatePathFromFile(nsIFile* file) +{ + nsAutoString path; + nsresult rv = file->GetPath(path); + if (NS_FAILED(rv)) { + return nullptr; + } + return new xpstring(static_cast<wchar_t*>(path.get()), path.Length()); +} +#else +static void +CreateFileFromPath(const xpstring& path, nsIFile** file) +{ + NS_NewNativeLocalFile(nsDependentCString(path.c_str()), false, file); +} + +MAYBE_UNUSED static xpstring* +CreatePathFromFile(nsIFile* file) +{ + nsAutoCString path; + nsresult rv = file->GetNativePath(path); + if (NS_FAILED(rv)) { + return nullptr; + } + return new xpstring(path.get(), path.Length()); +} +#endif + +static XP_CHAR* +Concat(XP_CHAR* str, const XP_CHAR* toAppend, int* size) +{ + int 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; +} + +static size_t gOOMAllocationSize = 0; + +void AnnotateOOMAllocationSize(size_t size) +{ + gOOMAllocationSize = size; +} + +static size_t gTexturesSize = 0; + +void AnnotateTexturesSize(size_t size) +{ + gTexturesSize = size; +} + +static size_t gNumOfPendingIPC = 0; +static uint32_t gTopPendingIPCCount = 0; +static const char* gTopPendingIPCName = nullptr; +static uint32_t gTopPendingIPCType = 0; + +void AnnotatePendingIPC(size_t aNumOfPendingIPC, + uint32_t aTopPendingIPCCount, + const char* aTopPendingIPCName, + uint32_t aTopPendingIPCType) +{ + gNumOfPendingIPC = aNumOfPendingIPC; + gTopPendingIPCCount = aTopPendingIPCCount; + gTopPendingIPCName = aTopPendingIPCName; + gTopPendingIPCType = aTopPendingIPCType; +} + +#ifndef XP_WIN +// Like Windows CopyFile for *nix +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 + +#ifdef XP_WIN + +class PlatformWriter +{ +public: + PlatformWriter() + : mHandle(INVALID_HANDLE_VALUE) + { } + + explicit PlatformWriter(const wchar_t* path) + : PlatformWriter() + { + Open(path); + } + + ~PlatformWriter() { + if (Valid()) { + CloseHandle(mHandle); + } + } + + void Open(const wchar_t* path) { + mHandle = CreateFile(path, GENERIC_WRITE, 0, + nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, + nullptr); + } + + bool Valid() { + return mHandle != INVALID_HANDLE_VALUE; + } + + void WriteBuffer(const char* buffer, size_t len) + { + if (!Valid()) { + return; + } + DWORD nBytes; + WriteFile(mHandle, buffer, len, &nBytes, nullptr); + } + + HANDLE Handle() { + return mHandle; + } + +private: + HANDLE mHandle; +}; + +#elif defined(XP_UNIX) + +class PlatformWriter +{ +public: + PlatformWriter() + : mFD(-1) + { } + + explicit PlatformWriter(const char* path) + : PlatformWriter() + { + Open(path); + } + + ~PlatformWriter() { + if (Valid()) { + sys_close(mFD); + } + } + + void Open(const char* path) { + mFD = sys_open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); + } + + bool Valid() { + return mFD != -1; + } + + void WriteBuffer(const char* buffer, size_t len) { + if (!Valid()) { + return; + } + Unused << sys_write(mFD, buffer, len); + } + +private: + int mFD; +}; + +#else +#error "Need implementation of PlatformWrite for this platform" +#endif + +template<int N> +void +WriteLiteral(PlatformWriter& pw, const char (&str)[N]) +{ + pw.WriteBuffer(str, N - 1); +} + +static void +WriteString(PlatformWriter& pw, const char* str) { +#ifdef XP_LINUX + size_t len = my_strlen(str); +#else + size_t len = strlen(str); +#endif + + pw.WriteBuffer(str, len); +} + +template<int N> +static void +WriteAnnotation(PlatformWriter& pw, const char (&name)[N], + const char* value) { + WriteLiteral(pw, name); + WriteLiteral(pw, "="); + WriteString(pw, value); + WriteLiteral(pw, "\n"); +}; + +/** + * 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]; + int 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 +void +WriteGlobalMemoryStatus(PlatformWriter* apiData, PlatformWriter* eventFile) +{ + char buffer[128]; + + // Try to get some information about memory. + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + if (GlobalMemoryStatusEx(&statex)) { + +#define WRITE_STATEX_FIELD(field, name, conversionFunc) \ + conversionFunc(statex.field, buffer, 10); \ + if (apiData) { \ + WriteAnnotation(*apiData, name, buffer); \ + } \ + if (eventFile) { \ + WriteAnnotation(*eventFile, name, buffer); \ + } + + WRITE_STATEX_FIELD(dwMemoryLoad, "SystemMemoryUsePercentage", ltoa); + WRITE_STATEX_FIELD(ullTotalVirtual, "TotalVirtualMemory", _ui64toa); + WRITE_STATEX_FIELD(ullAvailVirtual, "AvailableVirtualMemory", _ui64toa); + WRITE_STATEX_FIELD(ullTotalPageFile, "TotalPageFile", _ui64toa); + WRITE_STATEX_FIELD(ullAvailPageFile, "AvailablePageFile", _ui64toa); + WRITE_STATEX_FIELD(ullTotalPhys, "TotalPhysicalMemory", _ui64toa); + WRITE_STATEX_FIELD(ullAvailPhys, "AvailablePhysicalMemory", _ui64toa); + +#undef WRITE_STATEX_FIELD + } +} +#endif + +bool MinidumpCallback( +#ifdef XP_LINUX + const MinidumpDescriptor& descriptor, +#else + const XP_CHAR* dump_path, + const XP_CHAR* minidump_id, +#endif + void* context, +#ifdef XP_WIN32 + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, +#endif + bool succeeded) +{ + bool returnValue = showOSCrashReporter ? false : succeeded; + + static XP_CHAR minidumpPath[XP_PATH_MAX]; + int 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) { +#ifdef XP_WIN + CopyFile(memoryReportPath, memoryReportLocalPath, false); +#else + copy_file(memoryReportPath, memoryReportLocalPath); +#endif + } + + if (headlessClient) { + // Leave a marker indicating that there was a crash. + PlatformWriter markerFile(crashMarkerFilename); +#if defined(XP_WIN) + markerFile.WriteBuffer(reinterpret_cast<const char*>(minidumpPath), + 2*wcslen(minidumpPath)); +#elif defined(XP_UNIX) + markerFile.WriteBuffer(minidumpPath, my_strlen(minidumpPath)); +#endif + } + + char oomAllocationSizeBuffer[32] = ""; + if (gOOMAllocationSize) { + XP_STOA(gOOMAllocationSize, oomAllocationSizeBuffer, 10); + } + + char texturesSizeBuffer[32] = ""; + if (gTexturesSize) { + XP_STOA(gTexturesSize, texturesSizeBuffer, 10); + } + + char numOfPendingIPCBuffer[32] = ""; + char topPendingIPCCountBuffer[32] = ""; + char topPendingIPCTypeBuffer[11] = "0x"; + if (gNumOfPendingIPC) { + XP_STOA(gNumOfPendingIPC, numOfPendingIPCBuffer, 10); + if (gTopPendingIPCCount) { + XP_STOA(gTopPendingIPCCount, topPendingIPCCountBuffer, 10); + } + if (gTopPendingIPCType) { + XP_STOA(gTopPendingIPCType, &topPendingIPCTypeBuffer[2], 16); + } + } + + // calculate time since last crash (if possible), and store + // the time of this crash. + time_t crashTime; +#ifdef XP_LINUX + struct kernel_timeval tv; + sys_gettimeofday(&tv, nullptr); + crashTime = tv.tv_sec; +#else + crashTime = time(nullptr); +#endif + time_t timeSinceLastCrash = 0; + // stringified versions of the above + char crashTimeString[32]; + char timeSinceLastCrashString[32]; + + XP_TTOA(crashTime, crashTimeString, 10); + if (lastCrashTime != 0) { + timeSinceLastCrash = crashTime - lastCrashTime; + XP_TTOA(timeSinceLastCrash, timeSinceLastCrashString, 10); + } + // write crash time to file + if (lastCrashTimeFilename[0] != 0) { + PlatformWriter lastCrashFile(lastCrashTimeFilename); + WriteString(lastCrashFile, crashTimeString); + } + + bool ignored = false; + double uptimeTS = (TimeStamp::NowLoRes()- + TimeStamp::ProcessCreation(ignored)).ToSecondsSigDigits(); + char uptimeTSString[64]; + SimpleNoCLibDtoA(uptimeTS, uptimeTSString, sizeof(uptimeTSString)); + + // Write crash event file. + + // 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 apiData; + PlatformWriter eventFile; + + if (eventsDirectory) { + static XP_CHAR crashEventPath[XP_PATH_MAX]; + int size = XP_PATH_MAX; + XP_CHAR* p; + p = Concat(crashEventPath, eventsDirectory, &size); + p = Concat(p, XP_PATH_SEPARATOR, &size); +#ifdef XP_LINUX + p = Concat(p, id_ascii, &size); +#else + p = Concat(p, minidump_id, &size); +#endif + + eventFile.Open(crashEventPath); + WriteLiteral(eventFile, kCrashMainID); + WriteString(eventFile, crashTimeString); + WriteLiteral(eventFile, "\n"); + WriteString(eventFile, id_ascii); + WriteLiteral(eventFile, "\n"); + if (currentSessionId) { + WriteAnnotation(eventFile, "TelemetrySessionId", currentSessionId); + } + if (crashEventAPIData) { + eventFile.WriteBuffer(crashEventAPIData->get(), crashEventAPIData->Length()); + } + } + + if (!crashReporterAPIData->IsEmpty()) { + // write out API data +#ifdef XP_LINUX + OpenAPIData(apiData, descriptor.path()); +#else + OpenAPIData(apiData, dump_path, minidump_id); +#endif + apiData.WriteBuffer(crashReporterAPIData->get(), crashReporterAPIData->Length()); + } + WriteAnnotation(apiData, "CrashTime", crashTimeString); + WriteAnnotation(eventFile, "CrashTime", crashTimeString); + + WriteAnnotation(apiData, "UptimeTS", uptimeTSString); + WriteAnnotation(eventFile, "UptimeTS", uptimeTSString); + + if (timeSinceLastCrash != 0) { + WriteAnnotation(apiData, "SecondsSinceLastCrash", + timeSinceLastCrashString); + WriteAnnotation(eventFile, "SecondsSinceLastCrash", + timeSinceLastCrashString); + } + if (isGarbageCollecting) { + WriteAnnotation(apiData, "IsGarbageCollecting", + isGarbageCollecting ? "1" : "0"); + WriteAnnotation(eventFile, "IsGarbageCollecting", + isGarbageCollecting ? "1" : "0"); + } + + char buffer[128]; + + if (eventloopNestingLevel > 0) { + XP_STOA(eventloopNestingLevel, buffer, 10); + WriteAnnotation(apiData, "EventLoopNestingLevel", buffer); + WriteAnnotation(eventFile, "EventLoopNestingLevel", buffer); + } + +#ifdef XP_WIN + if (gBreakpadReservedVM) { + _ui64toa(uintptr_t(gBreakpadReservedVM), buffer, 10); + WriteAnnotation(apiData, "BreakpadReserveAddress", buffer); + _ui64toa(kReserveSize, buffer, 10); + WriteAnnotation(apiData, "BreakpadReserveSize", buffer); + } + +#ifdef HAS_DLL_BLOCKLIST + if (apiData.Valid()) { + DllBlocklist_WriteNotes(apiData.Handle()); + DllBlocklist_WriteNotes(eventFile.Handle()); + } +#endif + WriteGlobalMemoryStatus(&apiData, &eventFile); +#endif // XP_WIN + + if (gMozCrashReason) { + WriteAnnotation(apiData, "MozCrashReason", gMozCrashReason); + WriteAnnotation(eventFile, "MozCrashReason", gMozCrashReason); + } + + if (oomAllocationSizeBuffer[0]) { + WriteAnnotation(apiData, "OOMAllocationSize", oomAllocationSizeBuffer); + WriteAnnotation(eventFile, "OOMAllocationSize", oomAllocationSizeBuffer); + } + + if (texturesSizeBuffer[0]) { + WriteAnnotation(apiData, "TextureUsage", texturesSizeBuffer); + WriteAnnotation(eventFile, "TextureUsage", texturesSizeBuffer); + } + + if (numOfPendingIPCBuffer[0]) { + WriteAnnotation(apiData, "NumberOfPendingIPC", numOfPendingIPCBuffer); + WriteAnnotation(eventFile, "NumberOfPendingIPC", numOfPendingIPCBuffer); + if (topPendingIPCCountBuffer[0]) { + WriteAnnotation(apiData, "TopPendingIPCCount", topPendingIPCCountBuffer); + WriteAnnotation(eventFile, "TopPendingIPCCount", topPendingIPCCountBuffer); + } + if (gTopPendingIPCName) { + WriteAnnotation(apiData, "TopPendingIPCName", gTopPendingIPCName); + WriteAnnotation(eventFile, "TopPendingIPCName", gTopPendingIPCName); + } + if (topPendingIPCTypeBuffer[2]) { + WriteAnnotation(apiData, "TopPendingIPCType", topPendingIPCTypeBuffer); + WriteAnnotation(eventFile, "TopPendingIPCType", topPendingIPCTypeBuffer); + } + } + + if (memoryReportPath) { + WriteLiteral(apiData, "ContainsMemoryReport=1\n"); + WriteLiteral(eventFile, "ContainsMemoryReport=1\n"); + } + } + +#ifdef XP_WIN + if (!doReport) { + TerminateProcess(GetCurrentProcess(), 1); + return returnValue; + } + + XP_CHAR cmdLine[CMDLINE_SIZE]; + size = CMDLINE_SIZE; + p = Concat(cmdLine, L"\"", &size); + p = Concat(p, crashReporterPath, &size); + p = Concat(p, L"\" \"", &size); + p = Concat(p, minidumpPath, &size); + Concat(p, L"\"", &size); + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWNORMAL; + ZeroMemory(&pi, sizeof(pi)); + + if (CreateProcess(nullptr, (LPWSTR)cmdLine, nullptr, nullptr, FALSE, 0, + nullptr, nullptr, &si, &pi)) { + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + } + // we're not really in a position to do anything if the CreateProcess fails + TerminateProcess(GetCurrentProcess(), 1); +#elif defined(XP_UNIX) + if (!doReport) { + return returnValue; + } + +#ifdef XP_MACOSX + char* const my_argv[] = { + crashReporterPath, + minidumpPath, + nullptr + }; + + char **env = nullptr; + char ***nsEnv = _NSGetEnviron(); + if (nsEnv) + env = *nsEnv; + int result = posix_spawnp(nullptr, + my_argv[0], + nullptr, + &spawnattr, + my_argv, + env); + + if (result != 0) + return false; + +#else // !XP_MACOSX + pid_t pid = sys_fork(); + + if (pid == -1) + return false; + else if (pid == 0) { +#if !defined(MOZ_WIDGET_ANDROID) + // need to clobber this, as libcurl might load NSS, + // and we want it to load the system NSS. + unsetenv("LD_LIBRARY_PATH"); + Unused << execl(crashReporterPath, + crashReporterPath, minidumpPath, (char*)0); +#else + // Invoke the reportCrash activity using am + if (androidUserSerial) { + Unused << execlp("/system/bin/am", + "/system/bin/am", + "start", + "--user", androidUserSerial, + "-a", "org.mozilla.gecko.reportCrash", + "-n", crashReporterPath, + "--es", "minidumpPath", minidumpPath, + "--ez", "minidumpSuccess", succeeded ? "true" : "false", + (char*)0); + } else { + Unused << execlp("/system/bin/am", + "/system/bin/am", + "start", + "-a", "org.mozilla.gecko.reportCrash", + "-n", crashReporterPath, + "--es", "minidumpPath", minidumpPath, + "--ez", "minidumpSuccess", succeeded ? "true" : "false", + (char*)0); + } +#endif + _exit(1); +#ifdef MOZ_WIDGET_ANDROID + } 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)); +#endif + } +#endif // XP_MACOSX +#endif // XP_UNIX + + 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_WIN32) + +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 or Gonk's init.rc sets this in the environment + const char *tempenv = PR_GetEnv("TMPDIR"); + if (!tempenv) { + return false; + } + int size = (int)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; + } + int size = (int)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; +} + +static void +PrepareChildExceptionTimeAnnotations() +{ + MOZ_ASSERT(!XRE_IsParentProcess()); + static XP_CHAR tempPath[XP_PATH_MAX] = {0}; + + // Get the temp path + int charsAvailable = XP_PATH_MAX; + XP_CHAR* p = tempPath; +#if (defined(XP_MACOSX) || defined(XP_WIN)) + if (!childProcessTmpDir || childProcessTmpDir->empty()) { + return; + } + p = Concat(p, childProcessTmpDir->c_str(), &charsAvailable); + // Ensure that this path ends with a path separator + if (p > tempPath && *(p - 1) != XP_PATH_SEPARATOR_CHAR) { + p = Concat(p, XP_PATH_SEPARATOR, &charsAvailable); + } +#else + size_t tempPathLen = BuildTempPath(tempPath); + if (!tempPathLen) { + return; + } + p += tempPathLen; + charsAvailable -= tempPathLen; +#endif + + // Generate and append the file name + p = Concat(p, childCrashAnnotationBaseName, &charsAvailable); + XP_CHAR pidBuffer[32] = XP_TEXT(""); +#if defined(XP_WIN32) + _ui64tow(GetCurrentProcessId(), pidBuffer, 10); +#else + XP_STOA(getpid(), pidBuffer, 10); +#endif + p = Concat(p, pidBuffer, &charsAvailable); + + // Now open the file... + PlatformWriter apiData; + OpenAPIData(apiData, tempPath); + + // ...and write out any annotations. These must be escaped if necessary + // (but don't call EscapeAnnotation here, because it touches the heap). +#ifdef XP_WIN + WriteGlobalMemoryStatus(&apiData, nullptr); +#endif + + char oomAllocationSizeBuffer[32] = ""; + if (gOOMAllocationSize) { + XP_STOA(gOOMAllocationSize, oomAllocationSizeBuffer, 10); + } + + if (oomAllocationSizeBuffer[0]) { + WriteAnnotation(apiData, "OOMAllocationSize", oomAllocationSizeBuffer); + } + + if (gMozCrashReason) { + WriteAnnotation(apiData, "MozCrashReason", gMozCrashReason); + } + + char numOfPendingIPCBuffer[32] = ""; + char topPendingIPCCountBuffer[32] = ""; + char topPendingIPCTypeBuffer[11] = "0x"; + if (gNumOfPendingIPC) { + XP_STOA(gNumOfPendingIPC, numOfPendingIPCBuffer, 10); + if (gTopPendingIPCCount) { + XP_STOA(gTopPendingIPCCount, topPendingIPCCountBuffer, 10); + } + if (gTopPendingIPCType) { + XP_STOA(gTopPendingIPCType, &topPendingIPCTypeBuffer[2], 16); + } + } + + if (numOfPendingIPCBuffer[0]) { + WriteAnnotation(apiData, "NumberOfPendingIPC", numOfPendingIPCBuffer); + if (topPendingIPCCountBuffer[0]) { + WriteAnnotation(apiData, "TopPendingIPCCount", topPendingIPCCountBuffer); + } + if (gTopPendingIPCName) { + WriteAnnotation(apiData, "TopPendingIPCName", gTopPendingIPCName); + } + if (topPendingIPCTypeBuffer[2]) { + WriteAnnotation(apiData, "TopPendingIPCType", topPendingIPCTypeBuffer); + } + } +} + +#ifdef XP_WIN +static void +ReserveBreakpadVM() +{ + if (!gBreakpadReservedVM) { + gBreakpadReservedVM = VirtualAlloc(nullptr, kReserveSize, MEM_RESERVE, + PAGE_NOACCESS); + } +} + +static void +FreeBreakpadVM() +{ + if (gBreakpadReservedVM) { + VirtualFree(gBreakpadReservedVM, 0, MEM_RELEASE); + } +} + +/** + * Filters out floating point exceptions which are handled by nsSigHandlers.cpp + * and should not be handled as crashes. + * + * Also calls FreeBreakpadVM if appropriate. + */ +static bool FPEFilter(void* context, EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion) +{ + if (!exinfo) { + mozilla::IOInterposer::Disable(); + FreeBreakpadVM(); + 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 + } + mozilla::IOInterposer::Disable(); + FreeBreakpadVM(); + return true; +} + +static bool +ChildFPEFilter(void* context, EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion) +{ + bool result = FPEFilter(context, exinfo, assertion); + if (result) { + PrepareChildExceptionTimeAnnotations(); + } + return result; +} + +MINIDUMP_TYPE GetMinidumpType() +{ + MINIDUMP_TYPE minidump_type = MiniDumpNormal; + + // Try to determine what version of dbghelp.dll we're using. + // MinidumpWithFullMemoryInfo is only available in 6.1.x or newer. + + DWORD version_size = GetFileVersionInfoSizeW(L"dbghelp.dll", nullptr); + if (version_size > 0) { + std::vector<BYTE> buffer(version_size); + if (GetFileVersionInfoW(L"dbghelp.dll", + 0, + version_size, + &buffer[0])) { + UINT len; + VS_FIXEDFILEINFO* file_info; + VerQueryValue(&buffer[0], L"\\", (void**)&file_info, &len); + WORD major = HIWORD(file_info->dwFileVersionMS), + minor = LOWORD(file_info->dwFileVersionMS), + revision = HIWORD(file_info->dwFileVersionLS); + if (major > 6 || (major == 6 && minor > 1) || + (major == 6 && minor == 1 && revision >= 7600)) { + minidump_type = MiniDumpWithFullMemoryInfo; + } + } + } + + const char* e = PR_GetEnv("MOZ_CRASHREPORTER_FULLDUMP"); + if (e && *e) { + minidump_type = MiniDumpWithFullMemory; + } + + return minidump_type; +} + +#endif // XP_WIN + +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 bool +Filter(void* context) +{ + mozilla::IOInterposer::Disable(); + return true; +} + +static bool +ChildFilter(void* context) +{ + bool result = Filter(context); + if (result) { + PrepareChildExceptionTimeAnnotations(); + } + return result; +} + +void TerminateHandler() +{ + MOZ_CRASH("Unhandled exception"); +} + +nsresult SetExceptionHandler(nsIFile* aXREDirectory, + bool force/*=false*/) +{ + if (gExceptionHandler) + return NS_ERROR_ALREADY_INITIALIZED; + +#if !defined(DEBUG) || defined(MOZ_WIDGET_GONK) + // In non-debug builds, enable the crash reporter by default, and allow + // disabling it with the MOZ_CRASHREPORTER_DISABLE environment variable. + // Also enable it by default in debug gonk builds as it is difficult to + // set environment on startup. + const char *envvar = PR_GetEnv("MOZ_CRASHREPORTER_DISABLE"); + if (envvar && *envvar && !force) + return NS_OK; +#else + // 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; +#endif + +#if defined(MOZ_WIDGET_GONK) + doReport = false; + headlessClient = true; +#elif defined(XP_WIN) + doReport = ShouldReport(); +#else + // this environment variable prevents us from launching + // the crash reporter client + doReport = ShouldReport(); +#endif + + // allocate our strings + crashReporterAPIData = new nsCString(); + crashEventAPIData = new nsCString(); + + NS_ASSERTION(!crashReporterAPILock, "Shouldn't have a lock yet"); + crashReporterAPILock = new Mutex("crashReporterAPILock"); + NS_ASSERTION(!notesFieldLock, "Shouldn't have a lock yet"); + notesFieldLock = new Mutex("notesFieldLock"); + + crashReporterAPIData_Hash = + new nsDataHashtable<nsCStringHashKey,nsCString>(); + NS_ENSURE_TRUE(crashReporterAPIData_Hash, NS_ERROR_OUT_OF_MEMORY); + + notesField = new nsCString(); + NS_ENSURE_TRUE(notesField, NS_ERROR_OUT_OF_MEMORY); + + if (!headlessClient) { + // locate crashreporter executable + nsCOMPtr<nsIFile> exePath; + nsresult rv = aXREDirectory->Clone(getter_AddRefs(exePath)); + NS_ENSURE_SUCCESS(rv, rv); + +#if defined(XP_MACOSX) + exePath->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS")); + exePath->Append(NS_LITERAL_STRING("crashreporter.app")); + exePath->Append(NS_LITERAL_STRING("Contents")); + exePath->Append(NS_LITERAL_STRING("MacOS")); +#endif + + exePath->AppendNative(NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME)); + +#ifdef XP_WIN32 + nsString crashReporterPath_temp; + + exePath->GetPath(crashReporterPath_temp); + crashReporterPath = reinterpret_cast<wchar_t*>(ToNewUnicode(crashReporterPath_temp)); +#elif !defined(__ANDROID__) + nsCString crashReporterPath_temp; + + exePath->GetNativePath(crashReporterPath_temp); + crashReporterPath = ToNewCString(crashReporterPath_temp); +#else + // On Android, we launch using the application package name instead of a + // filename, so use the dynamically set MOZ_ANDROID_PACKAGE_NAME, or fall + // back to the static ANDROID_PACKAGE_NAME. + const char* androidPackageName = PR_GetEnv("MOZ_ANDROID_PACKAGE_NAME"); + if (androidPackageName != nullptr) { + nsCString package(androidPackageName); + package.Append("/org.mozilla.gecko.CrashReporter"); + crashReporterPath = ToNewCString(package); + } else { + nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporter"); + crashReporterPath = ToNewCString(package); + } +#endif + } + + // get temp path to use for minidump path +#if defined(XP_WIN32) + nsString tempPath; +#else + nsCString tempPath; +#endif + if (!BuildTempPath(tempPath)) { + return NS_ERROR_FAILURE; + } + +#ifdef XP_MACOSX + // Initialize spawn attributes, since this calls malloc. + if (posix_spawnattr_init(&spawnattr) != 0) { + return NS_ERROR_FAILURE; + } + + // Set spawn attributes. + size_t attr_count = ArrayLength(pref_cpu_types); + size_t attr_ocount = 0; + if (posix_spawnattr_setbinpref_np(&spawnattr, + attr_count, + pref_cpu_types, + &attr_ocount) != 0 || + attr_ocount != attr_count) { + posix_spawnattr_destroy(&spawnattr); + return NS_ERROR_FAILURE; + } +#endif + +#ifdef XP_WIN32 + ReserveBreakpadVM(); +#endif // XP_WIN32 + +#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 + +#ifdef XP_WIN + FPEFilter, +#else + Filter, +#endif + MinidumpCallback, + nullptr, +#ifdef XP_WIN32 + 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_WIN32 + + if (!gExceptionHandler) + return NS_ERROR_OUT_OF_MEMORY; + +#ifdef XP_WIN + gExceptionHandler->set_handle_debug_exceptions(true); + +#ifdef _WIN64 + // Tell JS about the new filter before we disable SetUnhandledExceptionFilter + sUnhandledExceptionFilter = GetUnhandledExceptionFilter(); + if (sUnhandledExceptionFilter) + js::SetJitExceptionHandler(JitExceptionHandler); +#endif + + // protect the crash reporter from being unloaded + gBlockUnhandledExceptionFilter = true; + gKernel32Intercept.Init("kernel32.dll"); + bool ok = gKernel32Intercept.AddHook("SetUnhandledExceptionFilter", + reinterpret_cast<intptr_t>(patched_SetUnhandledExceptionFilter), + (void**) &stub_SetUnhandledExceptionFilter); + +#ifdef DEBUG + if (!ok) + printf_stderr ("SetUnhandledExceptionFilter hook failed; crash reporter is vulnerable.\n"); +#endif +#endif + + // store application start time + char timeString[32]; + time_t startupTime = time(nullptr); + XP_TTOA(startupTime, timeString, 10); + AnnotateCrashReport(NS_LITERAL_CSTRING("StartupTime"), + nsDependentCString(timeString)); + +#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 + +#if defined(MOZ_WIDGET_ANDROID) + for (unsigned int i = 0; i < library_mappings.size(); i++) { + PageAllocator allocator; + auto_wasteful_vector<uint8_t, sizeof(MDGUID)> guid(&allocator); + FileID::ElfFileIdentifierFromMappedFile( + (void const *)library_mappings[i].start_address, guid); + gExceptionHandler->AddMappingInfo(library_mappings[i].name, + guid.data(), + library_mappings[i].start_address, + library_mappings[i].length, + library_mappings[i].file_offset); + } +#endif + + mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize); + + 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( + gExceptionHandler->minidump_descriptor().directory().c_str()); +#endif + return true; +} + +nsresult SetMinidumpPath(const nsAString& aPath) +{ + if (!gExceptionHandler) + return NS_ERROR_NOT_INITIALIZED; + +#ifdef XP_WIN32 + gExceptionHandler->set_dump_path(std::wstring(char16ptr_t(aPath.BeginReading()))); +#elif defined(XP_LINUX) + gExceptionHandler->set_minidump_descriptor( + MinidumpDescriptor(NS_ConvertUTF16toUTF8(aPath).BeginReading())); +#else + gExceptionHandler->set_dump_path(NS_ConvertUTF16toUTF8(aPath).BeginReading()); +#endif + return NS_OK; +} + +static nsresult +WriteDataToFile(nsIFile* aFile, const nsACString& data) +{ + PRFileDesc* fd; + nsresult rv = aFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 00600, &fd); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_OK; + if (PR_Write(fd, data.Data(), data.Length()) == -1) { + rv = NS_ERROR_FAILURE; + } + PR_Close(fd); + return rv; +} + +static nsresult +GetFileContents(nsIFile* aFile, nsACString& data) +{ + PRFileDesc* fd; + nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_OK; + int32_t filesize = PR_Available(fd); + if (filesize <= 0) { + rv = NS_ERROR_FILE_NOT_FOUND; + } + else { + data.SetLength(filesize); + if (PR_Read(fd, data.BeginWriting(), filesize) == -1) { + rv = NS_ERROR_FAILURE; + } + } + PR_Close(fd); + return rv; +} + +// Function typedef for initializing a piece of data that we +// don't already have. +typedef nsresult (*InitDataFunc)(nsACString&); + +// Attempt to read aFile's contents into aContents, if aFile +// does not exist, create it and initialize its contents +// by calling aInitFunc for the data. +static nsresult +GetOrInit(nsIFile* aDir, const nsACString& filename, + nsACString& aContents, InitDataFunc aInitFunc) +{ + bool exists; + + nsCOMPtr<nsIFile> dataFile; + nsresult rv = aDir->Clone(getter_AddRefs(dataFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dataFile->AppendNative(filename); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dataFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exists) { + if (aInitFunc) { + // get the initial value and write it to the file + rv = aInitFunc(aContents); + NS_ENSURE_SUCCESS(rv, rv); + rv = WriteDataToFile(dataFile, aContents); + } + else { + // didn't pass in an init func + rv = NS_ERROR_FAILURE; + } + } + else { + // just get the file's contents + rv = GetFileContents(dataFile, aContents); + } + + return rv; +} + +// Init the "install time" data. We're taking an easy way out here +// and just setting this to "the time when this version was first run". +static nsresult +InitInstallTime(nsACString& aInstallTime) +{ + time_t t = time(nullptr); + char buf[16]; + SprintfLiteral(buf, "%ld", t); + aInstallTime = buf; + + return NS_OK; +} + +// Ensure a directory exists and create it if missing. +static nsresult +EnsureDirectoryExists(nsIFile* dir) +{ + nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700); + + if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) { + return rv; + } + + return NS_OK; +} + +// Annotate the crash report with a Unique User ID and time +// since install. Also do some prep work for recording +// time since last crash, which must be calculated at +// crash time. +// If any piece of data doesn't exist, initialize it first. +nsresult SetupExtraData(nsIFile* aAppDataDirectory, + const nsACString& aBuildID) +{ + nsCOMPtr<nsIFile> dataDirectory; + nsresult rv = aAppDataDirectory->Clone(getter_AddRefs(dataDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dataDirectory->AppendNative(NS_LITERAL_CSTRING("Crash Reports")); + NS_ENSURE_SUCCESS(rv, rv); + + EnsureDirectoryExists(dataDirectory); + +#if defined(XP_WIN32) + nsAutoString dataDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_DATA_DIRECTORY=")); + + nsAutoString dataDirectoryPath; + rv = dataDirectory->GetPath(dataDirectoryPath); + NS_ENSURE_SUCCESS(rv, rv); + + dataDirEnv.Append(dataDirectoryPath); + + _wputenv(dataDirEnv.get()); +#else + // Save this path in the environment for the crash reporter application. + nsAutoCString dataDirEnv("MOZ_CRASHREPORTER_DATA_DIRECTORY="); + + nsAutoCString dataDirectoryPath; + rv = dataDirectory->GetNativePath(dataDirectoryPath); + NS_ENSURE_SUCCESS(rv, rv); + + dataDirEnv.Append(dataDirectoryPath); + + char* env = ToNewCString(dataDirEnv); + NS_ENSURE_TRUE(env, NS_ERROR_OUT_OF_MEMORY); + + PR_SetEnv(env); +#endif + + nsAutoCString data; + if(NS_SUCCEEDED(GetOrInit(dataDirectory, + NS_LITERAL_CSTRING("InstallTime") + aBuildID, + data, InitInstallTime))) + AnnotateCrashReport(NS_LITERAL_CSTRING("InstallTime"), data); + + // this is a little different, since we can't init it with anything, + // since it's stored at crash time, and we can't annotate the + // crash report with the stored value, since we really want + // (now - LastCrash), so we just get a value if it exists, + // and store it in a time_t value. + if(NS_SUCCEEDED(GetOrInit(dataDirectory, NS_LITERAL_CSTRING("LastCrash"), + data, nullptr))) { + lastCrashTime = (time_t)atol(data.get()); + } + + // not really the best place to init this, but I have the path I need here + nsCOMPtr<nsIFile> lastCrashFile; + rv = dataDirectory->Clone(getter_AddRefs(lastCrashFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = lastCrashFile->AppendNative(NS_LITERAL_CSTRING("LastCrash")); + NS_ENSURE_SUCCESS(rv, rv); + memset(lastCrashTimeFilename, 0, sizeof(lastCrashTimeFilename)); + +#if defined(XP_WIN32) + nsAutoString filename; + rv = lastCrashFile->GetPath(filename); + NS_ENSURE_SUCCESS(rv, rv); + + if (filename.Length() < XP_PATH_MAX) + wcsncpy(lastCrashTimeFilename, filename.get(), filename.Length()); +#else + nsAutoCString filename; + rv = lastCrashFile->GetNativePath(filename); + NS_ENSURE_SUCCESS(rv, rv); + + if (filename.Length() < XP_PATH_MAX) + strncpy(lastCrashTimeFilename, filename.get(), filename.Length()); +#endif + + if (headlessClient) { + nsCOMPtr<nsIFile> markerFile; + rv = dataDirectory->Clone(getter_AddRefs(markerFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = markerFile->AppendNative(NS_LITERAL_CSTRING("LastCrashFilename")); + NS_ENSURE_SUCCESS(rv, rv); + memset(crashMarkerFilename, 0, sizeof(crashMarkerFilename)); + +#if defined(XP_WIN32) + nsAutoString markerFilename; + rv = markerFile->GetPath(markerFilename); + NS_ENSURE_SUCCESS(rv, rv); + + if (markerFilename.Length() < XP_PATH_MAX) + wcsncpy(crashMarkerFilename, markerFilename.get(), + markerFilename.Length()); +#else + nsAutoCString markerFilename; + rv = markerFile->GetNativePath(markerFilename); + NS_ENSURE_SUCCESS(rv, rv); + + if (markerFilename.Length() < XP_PATH_MAX) + strncpy(crashMarkerFilename, markerFilename.get(), + markerFilename.Length()); +#endif + } + + return NS_OK; +} + +static void OOPDeinit(); + +nsresult UnsetExceptionHandler() +{ + if (isSafeToDump) { + MutexAutoLock lock(*dumpSafetyLock); + isSafeToDump = false; + } + +#ifdef XP_WIN + // allow SetUnhandledExceptionFilter + gBlockUnhandledExceptionFilter = false; +#endif + + delete gExceptionHandler; + + // do this here in the unlikely case that we succeeded in allocating + // our strings but failed to allocate gExceptionHandler. + delete crashReporterAPIData_Hash; + crashReporterAPIData_Hash = nullptr; + + delete crashReporterAPILock; + crashReporterAPILock = nullptr; + + delete notesFieldLock; + notesFieldLock = nullptr; + + delete crashReporterAPIData; + crashReporterAPIData = nullptr; + + delete crashEventAPIData; + crashEventAPIData = nullptr; + + delete notesField; + notesField = nullptr; + + delete lastRunCrashID; + lastRunCrashID = nullptr; + + if (pendingDirectory) { + free(pendingDirectory); + pendingDirectory = nullptr; + } + + if (crashReporterPath) { + free(crashReporterPath); + crashReporterPath = nullptr; + } + + if (eventsDirectory) { + free(eventsDirectory); + eventsDirectory = nullptr; + } + + if (currentSessionId) { + free(currentSessionId); + currentSessionId = nullptr; + } + + if (memoryReportPath) { + free(memoryReportPath); + memoryReportPath = nullptr; + } + +#ifdef XP_MACOSX + posix_spawnattr_destroy(&spawnattr); +#endif + + if (!gExceptionHandler) + return NS_ERROR_NOT_INITIALIZED; + + gExceptionHandler = nullptr; + + OOPDeinit(); + + delete dumpSafetyLock; + dumpSafetyLock = nullptr; + + std::set_terminate(oldTerminateHandler); + + return NS_OK; +} + +static void ReplaceChar(nsCString& str, const nsACString& character, + const nsACString& replacement) +{ + nsCString::const_iterator iter, end; + + str.BeginReading(iter); + str.EndReading(end); + + while (FindInReadable(character, iter, end)) { + nsCString::const_iterator start; + str.BeginReading(start); + int32_t pos = end - start; + str.Replace(pos - 1, 1, replacement); + + str.BeginReading(iter); + iter.advance(pos + replacement.Length() - 1); + str.EndReading(end); + } +} + +static bool DoFindInReadable(const nsACString& str, const nsACString& value) +{ + nsACString::const_iterator start, end; + str.BeginReading(start); + str.EndReading(end); + + return FindInReadable(value, start, end); +} + +static bool +IsInWhitelist(const nsACString& key) +{ + for (size_t i = 0; i < ArrayLength(kCrashEventAnnotations); ++i) { + if (key.EqualsASCII(kCrashEventAnnotations[i])) { + return true; + } + } + return false; +} + +// This function is miscompiled with MSVC 2005/2008 when PGO is on. +#ifdef _MSC_VER +#pragma optimize("", off) +#endif +static nsresult +EscapeAnnotation(const nsACString& key, const nsACString& data, nsCString& escapedData) +{ + if (DoFindInReadable(key, NS_LITERAL_CSTRING("=")) || + DoFindInReadable(key, NS_LITERAL_CSTRING("\n"))) + return NS_ERROR_INVALID_ARG; + + if (DoFindInReadable(data, NS_LITERAL_CSTRING("\0"))) + return NS_ERROR_INVALID_ARG; + + escapedData = data; + + // escape backslashes + ReplaceChar(escapedData, NS_LITERAL_CSTRING("\\"), + NS_LITERAL_CSTRING("\\\\")); + // escape newlines + ReplaceChar(escapedData, NS_LITERAL_CSTRING("\n"), + NS_LITERAL_CSTRING("\\n")); + return NS_OK; +} +#ifdef _MSC_VER +#pragma optimize("", on) +#endif + +class DelayedNote +{ + public: + DelayedNote(const nsACString& aKey, const nsACString& aData) + : mKey(aKey), mData(aData), mType(Annotation) {} + + explicit DelayedNote(const nsACString& aData) + : mData(aData), mType(AppNote) {} + + void Run() + { + if (mType == Annotation) { + AnnotateCrashReport(mKey, mData); + } else { + AppendAppNotesToCrashReport(mData); + } + } + + private: + nsCString mKey; + nsCString mData; + enum AnnotationType { Annotation, AppNote } mType; +}; + +static void +EnqueueDelayedNote(DelayedNote* aNote) +{ + if (!gDelayedAnnotations) { + gDelayedAnnotations = new nsTArray<nsAutoPtr<DelayedNote> >(); + } + gDelayedAnnotations->AppendElement(aNote); +} + +class CrashReporterHelperRunnable : public Runnable { +public: + explicit CrashReporterHelperRunnable(const nsACString& aKey, + const nsACString& aData) + : mKey(aKey) + , mData(aData) + , mAppendAppNotes(false) + {} + explicit CrashReporterHelperRunnable(const nsACString& aData) + : mKey() + , mData(aData) + , mAppendAppNotes(true) + {} + + NS_IMETHOD Run() override; + +private: + nsCString mKey; + nsCString mData; + bool mAppendAppNotes; +}; + +nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data) +{ + if (!GetEnabled()) + return NS_ERROR_NOT_INITIALIZED; + + nsCString escapedData; + nsresult rv = EscapeAnnotation(key, data, escapedData); + if (NS_FAILED(rv)) + return rv; + + if (!XRE_IsParentProcess()) { + // The newer CrashReporterClient can be used from any thread. + if (RefPtr<CrashReporterClient> client = CrashReporterClient::GetSingleton()) { + client->AnnotateCrashReport(nsCString(key), escapedData); + return NS_OK; + } + + // Otherwise, we have to handle this on the main thread since we will go + // through IPDL. + if (!NS_IsMainThread()) { + // Child process needs to handle this in the main thread: + nsCOMPtr<nsIRunnable> r = new CrashReporterHelperRunnable(key, data); + NS_DispatchToMainThread(r); + return NS_OK; + } + + MOZ_ASSERT(NS_IsMainThread()); + PCrashReporterChild* reporter = CrashReporterChild::GetCrashReporter(); + if (!reporter) { + EnqueueDelayedNote(new DelayedNote(key, data)); + return NS_OK; + } + if (!reporter->SendAnnotateCrashReport(nsCString(key), escapedData)) + return NS_ERROR_FAILURE; + return NS_OK; + } + + MutexAutoLock lock(*crashReporterAPILock); + + crashReporterAPIData_Hash->Put(key, escapedData); + + // now rebuild the file contents + crashReporterAPIData->Truncate(0); + crashEventAPIData->Truncate(0); + for (auto it = crashReporterAPIData_Hash->Iter(); !it.Done(); it.Next()) { + const nsACString& key = it.Key(); + nsCString entry = it.Data(); + if (!entry.IsEmpty()) { + NS_NAMED_LITERAL_CSTRING(kEquals, "="); + NS_NAMED_LITERAL_CSTRING(kNewline, "\n"); + nsAutoCString line = key + kEquals + entry + kNewline; + + crashReporterAPIData->Append(line); + if (IsInWhitelist(key)) { + crashEventAPIData->Append(line); + } + } + } + + return NS_OK; +} + +nsresult RemoveCrashReportAnnotation(const nsACString& key) +{ + return AnnotateCrashReport(key, NS_LITERAL_CSTRING("")); +} + +nsresult SetGarbageCollecting(bool collecting) +{ + if (!GetEnabled()) + return NS_ERROR_NOT_INITIALIZED; + + isGarbageCollecting = collecting; + + return NS_OK; +} + +void SetEventloopNestingLevel(uint32_t level) +{ + eventloopNestingLevel = level; +} + +nsresult AppendAppNotesToCrashReport(const nsACString& data) +{ + if (!GetEnabled()) + return NS_ERROR_NOT_INITIALIZED; + + if (DoFindInReadable(data, NS_LITERAL_CSTRING("\0"))) + return NS_ERROR_INVALID_ARG; + + if (!XRE_IsParentProcess()) { + // Since we don't go through AnnotateCrashReport in the parent process, + // we must ensure that the data is escaped and valid before the parent + // sees it. + nsCString escapedData; + nsresult rv = EscapeAnnotation(NS_LITERAL_CSTRING("Notes"), data, escapedData); + if (NS_FAILED(rv)) + return rv; + + if (RefPtr<CrashReporterClient> client = CrashReporterClient::GetSingleton()) { + client->AppendAppNotes(escapedData); + return NS_OK; + } + + if (!NS_IsMainThread()) { + // Child process needs to handle this in the main thread: + nsCOMPtr<nsIRunnable> r = new CrashReporterHelperRunnable(data); + NS_DispatchToMainThread(r); + return NS_OK; + } + + MOZ_ASSERT(NS_IsMainThread()); + PCrashReporterChild* reporter = CrashReporterChild::GetCrashReporter(); + if (!reporter) { + EnqueueDelayedNote(new DelayedNote(data)); + return NS_OK; + } + + if (!reporter->SendAppendAppNotes(escapedData)) + return NS_ERROR_FAILURE; + return NS_OK; + } + + MutexAutoLock lock(*notesFieldLock); + + notesField->Append(data); + return AnnotateCrashReport(NS_LITERAL_CSTRING("Notes"), *notesField); +} + +nsresult CrashReporterHelperRunnable::Run() +{ + // We expect this to be in the child process' main thread. If it isn't, + // something is happening we didn't design for. + MOZ_ASSERT(!XRE_IsParentProcess()); + MOZ_ASSERT(NS_IsMainThread()); + + // Don't just leave the assert, paranoid about infinite recursion + if (NS_IsMainThread()) { + if (mAppendAppNotes) { + return AppendAppNotesToCrashReport(mData); + } else { + return AnnotateCrashReport(mKey, mData); + } + } + return NS_ERROR_FAILURE; +} + +// Returns true if found, false if not found. +bool GetAnnotation(const nsACString& key, nsACString& data) +{ + if (!gExceptionHandler) + return false; + + nsAutoCString entry; + if (!crashReporterAPIData_Hash->Get(key, &entry)) + return false; + + data = entry; + return true; +} + +nsresult RegisterAppMemory(void* ptr, size_t length) +{ + if (!GetEnabled()) + return NS_ERROR_NOT_INITIALIZED; + +#if defined(XP_LINUX) || defined(XP_WIN32) + gExceptionHandler->RegisterAppMemory(ptr, length); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +nsresult UnregisterAppMemory(void* ptr) +{ + if (!GetEnabled()) + return NS_ERROR_NOT_INITIALIZED; + +#if defined(XP_LINUX) || defined(XP_WIN32) + gExceptionHandler->UnregisterAppMemory(ptr); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +bool GetServerURL(nsACString& aServerURL) +{ + if (!gExceptionHandler) + return false; + + return GetAnnotation(NS_LITERAL_CSTRING("ServerURL"), aServerURL); +} + +nsresult SetServerURL(const nsACString& aServerURL) +{ + // store server URL with the API data + // the client knows to handle this specially + return AnnotateCrashReport(NS_LITERAL_CSTRING("ServerURL"), + aServerURL); +} + +nsresult +SetRestartArgs(int argc, char** argv) +{ + if (!gExceptionHandler) + return NS_OK; + + int i; + nsAutoCString envVar; + char *env; + char *argv0 = getenv("MOZ_APP_LAUNCHER"); + for (i = 0; i < argc; i++) { + envVar = "MOZ_CRASHREPORTER_RESTART_ARG_"; + envVar.AppendInt(i); + envVar += "="; + if (argv0 && i == 0) { + // Is there a request to suppress default binary launcher? + envVar += argv0; + } else { + envVar += argv[i]; + } + + // PR_SetEnv() wants the string to be available for the lifetime + // of the app, so dup it here + env = ToNewCString(envVar); + if (!env) + return NS_ERROR_OUT_OF_MEMORY; + + PR_SetEnv(env); + } + + // make sure the arg list is terminated + envVar = "MOZ_CRASHREPORTER_RESTART_ARG_"; + envVar.AppendInt(i); + envVar += "="; + + // PR_SetEnv() wants the string to be available for the lifetime + // of the app, so dup it here + env = ToNewCString(envVar); + if (!env) + return NS_ERROR_OUT_OF_MEMORY; + + PR_SetEnv(env); + + // make sure we save the info in XUL_APP_FILE for the reporter + const char *appfile = PR_GetEnv("XUL_APP_FILE"); + if (appfile && *appfile) { + envVar = "MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE="; + envVar += appfile; + env = ToNewCString(envVar); + PR_SetEnv(env); + } + + return NS_OK; +} + +#ifdef XP_WIN32 +nsresult WriteMinidumpForException(EXCEPTION_POINTERS* aExceptionInfo) +{ + if (!gExceptionHandler) + return NS_ERROR_NOT_INITIALIZED; + + return gExceptionHandler->WriteMinidumpForException(aExceptionInfo) ? NS_OK : NS_ERROR_FAILURE; +} +#endif + +#ifdef XP_LINUX +bool WriteMinidumpForSigInfo(int signo, siginfo_t* info, void* uc) +{ + if (!gExceptionHandler) { + // Crash reporting is disabled. + return false; + } + return gExceptionHandler->HandleSignal(signo, info, uc); +} +#endif + +#ifdef XP_MACOSX +nsresult AppendObjCExceptionInfoToAppNotes(void *inException) +{ + nsAutoCString excString; + GetObjCExceptionInfo(inException, excString); + AppendAppNotesToCrashReport(excString); + return NS_OK; +} +#endif + +/* + * Combined code to get/set the crash reporter submission pref on + * different platforms. + */ +static nsresult PrefSubmitReports(bool* aSubmitReports, bool writePref) +{ + nsresult rv; +#if defined(XP_WIN32) + /* + * NOTE! This needs to stay in sync with the preference checking code + * in toolkit/crashreporter/client/crashreporter_win.cpp + */ + nsCOMPtr<nsIXULAppInfo> appinfo = + do_GetService("@mozilla.org/xre/app-info;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString appVendor, appName; + rv = appinfo->GetVendor(appVendor); + NS_ENSURE_SUCCESS(rv, rv); + rv = appinfo->GetName(appName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIWindowsRegKey> regKey + (do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString regPath; + + regPath.AppendLiteral("Software\\"); + + // We need to ensure the registry keys are created so we can properly + // write values to it + + // Create appVendor key + if(!appVendor.IsEmpty()) { + regPath.Append(appVendor); + regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_ConvertUTF8toUTF16(regPath), + nsIWindowsRegKey::ACCESS_SET_VALUE); + regPath.Append('\\'); + } + + // Create appName key + regPath.Append(appName); + regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_ConvertUTF8toUTF16(regPath), + nsIWindowsRegKey::ACCESS_SET_VALUE); + regPath.Append('\\'); + + // Create Crash Reporter key + regPath.AppendLiteral("Crash Reporter"); + regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_ConvertUTF8toUTF16(regPath), + nsIWindowsRegKey::ACCESS_SET_VALUE); + + // If we're saving the pref value, just write it to ROOT_KEY_CURRENT_USER + // and we're done. + if (writePref) { + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_ConvertUTF8toUTF16(regPath), + nsIWindowsRegKey::ACCESS_SET_VALUE); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t value = *aSubmitReports ? 1 : 0; + rv = regKey->WriteIntValue(NS_LITERAL_STRING("SubmitCrashReport"), value); + regKey->Close(); + return rv; + } + + // We're reading the pref value, so we need to first look under + // ROOT_KEY_LOCAL_MACHINE to see if it's set there, and then fall back to + // ROOT_KEY_CURRENT_USER. If it's not set in either place, the pref defaults + // to "true". + uint32_t value; + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, + NS_ConvertUTF8toUTF16(regPath), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_SUCCEEDED(rv)) { + rv = regKey->ReadIntValue(NS_LITERAL_STRING("SubmitCrashReport"), &value); + regKey->Close(); + if (NS_SUCCEEDED(rv)) { + *aSubmitReports = !!value; + return NS_OK; + } + } + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, + NS_ConvertUTF8toUTF16(regPath), + nsIWindowsRegKey::ACCESS_QUERY_VALUE); + if (NS_FAILED(rv)) { + *aSubmitReports = true; + return NS_OK; + } + + rv = regKey->ReadIntValue(NS_LITERAL_STRING("SubmitCrashReport"), &value); + // default to true on failure + if (NS_FAILED(rv)) { + value = 1; + rv = NS_OK; + } + regKey->Close(); + + *aSubmitReports = !!value; + return NS_OK; +#elif defined(XP_MACOSX) + rv = NS_OK; + if (writePref) { + CFPropertyListRef cfValue = (CFPropertyListRef)(*aSubmitReports ? kCFBooleanTrue : kCFBooleanFalse); + ::CFPreferencesSetAppValue(CFSTR("submitReport"), + cfValue, + reporterClientAppID); + if (!::CFPreferencesAppSynchronize(reporterClientAppID)) + rv = NS_ERROR_FAILURE; + } + else { + *aSubmitReports = true; + Boolean keyExistsAndHasValidFormat = false; + Boolean prefValue = ::CFPreferencesGetAppBooleanValue(CFSTR("submitReport"), + reporterClientAppID, + &keyExistsAndHasValidFormat); + if (keyExistsAndHasValidFormat) + *aSubmitReports = !!prefValue; + } + return rv; +#elif defined(XP_UNIX) + /* + * NOTE! This needs to stay in sync with the preference checking code + * in toolkit/crashreporter/client/crashreporter_linux.cpp + */ + nsCOMPtr<nsIFile> reporterINI; + rv = NS_GetSpecialDirectory("UAppData", getter_AddRefs(reporterINI)); + NS_ENSURE_SUCCESS(rv, rv); + reporterINI->AppendNative(NS_LITERAL_CSTRING("Crash Reports")); + reporterINI->AppendNative(NS_LITERAL_CSTRING("crashreporter.ini")); + + bool exists; + rv = reporterINI->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) { + if (!writePref) { + // If reading the pref, default to true if .ini doesn't exist. + *aSubmitReports = true; + return NS_OK; + } + // Create the file so the INI processor can write to it. + rv = reporterINI->Create(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIINIParserFactory> iniFactory = + do_GetService("@mozilla.org/xpcom/ini-processor-factory;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIINIParser> iniParser; + rv = iniFactory->CreateINIParser(reporterINI, + getter_AddRefs(iniParser)); + NS_ENSURE_SUCCESS(rv, rv); + + // If we're writing the pref, just set and we're done. + if (writePref) { + nsCOMPtr<nsIINIParserWriter> iniWriter = do_QueryInterface(iniParser); + NS_ENSURE_TRUE(iniWriter, NS_ERROR_FAILURE); + + rv = iniWriter->SetString(NS_LITERAL_CSTRING("Crash Reporter"), + NS_LITERAL_CSTRING("SubmitReport"), + *aSubmitReports ? NS_LITERAL_CSTRING("1") : + NS_LITERAL_CSTRING("0")); + NS_ENSURE_SUCCESS(rv, rv); + rv = iniWriter->WriteFile(nullptr, 0); + return rv; + } + + nsAutoCString submitReportValue; + rv = iniParser->GetString(NS_LITERAL_CSTRING("Crash Reporter"), + NS_LITERAL_CSTRING("SubmitReport"), + submitReportValue); + + // Default to "true" if the pref can't be found. + if (NS_FAILED(rv)) + *aSubmitReports = true; + else if (submitReportValue.EqualsASCII("0")) + *aSubmitReports = false; + else + *aSubmitReports = true; + + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +nsresult GetSubmitReports(bool* aSubmitReports) +{ + return PrefSubmitReports(aSubmitReports, false); +} + +nsresult SetSubmitReports(bool aSubmitReports) +{ + nsresult rv; + + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + if (!obsServ) { + return NS_ERROR_FAILURE; + } + + rv = PrefSubmitReports(&aSubmitReports, true); + if (NS_FAILED(rv)) { + return rv; + } + + obsServ->NotifyObservers(nullptr, "submit-reports-pref-changed", nullptr); + return NS_OK; +} + +static void +SetCrashEventsDir(nsIFile* aDir) +{ + nsCOMPtr<nsIFile> eventsDir = aDir; + + const char *env = PR_GetEnv("CRASHES_EVENTS_DIR"); + if (env && *env) { + NS_NewNativeLocalFile(nsDependentCString(env), + false, getter_AddRefs(eventsDir)); + EnsureDirectoryExists(eventsDir); + } + + if (eventsDirectory) { + free(eventsDirectory); + } + +#ifdef XP_WIN + nsString path; + eventsDir->GetPath(path); + eventsDirectory = reinterpret_cast<wchar_t*>(ToNewUnicode(path)); + + // Save the path in the environment for the crash reporter application. + nsAutoString eventsDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_EVENTS_DIRECTORY=")); + eventsDirEnv.Append(path); + _wputenv(eventsDirEnv.get()); +#else + nsCString path; + eventsDir->GetNativePath(path); + eventsDirectory = ToNewCString(path); + + // Save the path in the environment for the crash reporter application. + nsAutoCString eventsDirEnv("MOZ_CRASHREPORTER_EVENTS_DIRECTORY="); + eventsDirEnv.Append(path); + + // PR_SetEnv() wants the string to be available for the lifetime + // of the app, so dup it here. + char* oldEventsEnv = eventsEnv; + eventsEnv = ToNewCString(eventsDirEnv); + PR_SetEnv(eventsEnv); + + if (oldEventsEnv) { + free(oldEventsEnv); + } +#endif +} + +void +SetProfileDirectory(nsIFile* aDir) +{ + nsCOMPtr<nsIFile> dir; + aDir->Clone(getter_AddRefs(dir)); + + dir->Append(NS_LITERAL_STRING("crashes")); + EnsureDirectoryExists(dir); + dir->Append(NS_LITERAL_STRING("events")); + EnsureDirectoryExists(dir); + SetCrashEventsDir(dir); +} + +void +SetUserAppDataDirectory(nsIFile* aDir) +{ + nsCOMPtr<nsIFile> dir; + aDir->Clone(getter_AddRefs(dir)); + + dir->Append(NS_LITERAL_STRING("Crash Reports")); + EnsureDirectoryExists(dir); + dir->Append(NS_LITERAL_STRING("events")); + EnsureDirectoryExists(dir); + SetCrashEventsDir(dir); +} + +void +UpdateCrashEventsDir() +{ + const char *env = PR_GetEnv("CRASHES_EVENTS_DIR"); + if (env && *env) { + SetCrashEventsDir(nullptr); + } + + nsCOMPtr<nsIFile> eventsDir; + nsresult rv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(eventsDir)); + if (NS_SUCCEEDED(rv)) { + SetProfileDirectory(eventsDir); + return; + } + + rv = NS_GetSpecialDirectory("UAppData", getter_AddRefs(eventsDir)); + if (NS_SUCCEEDED(rv)) { + SetUserAppDataDirectory(eventsDir); + return; + } + + NS_WARNING("Couldn't get the user appdata directory. Crash events may not be produced."); +} + +bool GetCrashEventsDir(nsAString& aPath) +{ + if (!eventsDirectory) { + return false; + } + + aPath = CONVERT_XP_CHAR_TO_UTF16(eventsDirectory); + return true; +} + +void +SetMemoryReportFile(nsIFile* aFile) +{ + if (!gExceptionHandler) { + return; + } +#ifdef XP_WIN + nsString path; + aFile->GetPath(path); + memoryReportPath = reinterpret_cast<wchar_t*>(ToNewUnicode(path)); +#else + nsCString path; + aFile->GetNativePath(path); + memoryReportPath = ToNewCString(path); +#endif +} + +nsresult +GetDefaultMemoryReportFile(nsIFile** aFile) +{ + nsCOMPtr<nsIFile> defaultMemoryReportFile; + if (!defaultMemoryReportPath) { + nsresult rv = NS_GetSpecialDirectory(NS_APP_PROFILE_DIR_STARTUP, + getter_AddRefs(defaultMemoryReportFile)); + if (NS_FAILED(rv)) { + return rv; + } + defaultMemoryReportFile->AppendNative(NS_LITERAL_CSTRING("memory-report.json.gz")); + defaultMemoryReportPath = CreatePathFromFile(defaultMemoryReportFile); + if (!defaultMemoryReportPath) { + return NS_ERROR_FAILURE; + } + } else { + CreateFileFromPath(*defaultMemoryReportPath, + getter_AddRefs(defaultMemoryReportFile)); + if (!defaultMemoryReportFile) { + return NS_ERROR_FAILURE; + } + } + defaultMemoryReportFile.forget(aFile); + return NS_OK; +} + +void +SetTelemetrySessionId(const nsACString& id) +{ + if (!gExceptionHandler) { + return; + } + if (currentSessionId) { + free(currentSessionId); + } + currentSessionId = ToNewCString(id); +} + +static void +FindPendingDir() +{ + if (pendingDirectory) + return; + + nsCOMPtr<nsIFile> pendingDir; + nsresult rv = NS_GetSpecialDirectory("UAppData", getter_AddRefs(pendingDir)); + if (NS_FAILED(rv)) { + NS_WARNING("Couldn't get the user appdata directory, crash dumps will go in an unusual location"); + } + else { + pendingDir->Append(NS_LITERAL_STRING("Crash Reports")); + pendingDir->Append(NS_LITERAL_STRING("pending")); + +#ifdef XP_WIN + nsString path; + pendingDir->GetPath(path); + pendingDirectory = reinterpret_cast<wchar_t*>(ToNewUnicode(path)); +#else + nsCString path; + pendingDir->GetNativePath(path); + pendingDirectory = ToNewCString(path); +#endif + } +} + +// The "pending" dir is Crash Reports/pending, from which minidumps +// can be submitted. Because this method may be called off the main thread, +// we store the pending directory as a path. +static bool +GetPendingDir(nsIFile** dir) +{ + // MOZ_ASSERT(OOPInitialized()); + if (!pendingDirectory) { + return false; + } + + nsCOMPtr<nsIFile> pending = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + if (!pending) { + NS_WARNING("Can't set up pending directory during shutdown."); + return false; + } +#ifdef XP_WIN + pending->InitWithPath(nsDependentString(pendingDirectory)); +#else + pending->InitWithNativePath(nsDependentCString(pendingDirectory)); +#endif + pending.swap(*dir); + return true; +} + +// The "limbo" dir is where minidumps go to wait for something else to +// use them. If we're |ShouldReport()|, then the "something else" is +// a minidump submitter, and they're coming from the +// Crash Reports/pending/ dir. Otherwise, we don't know what the +// "somthing else" is, but the minidumps stay in [profile]/minidumps/ +// limbo. +static bool +GetMinidumpLimboDir(nsIFile** dir) +{ + if (ShouldReport()) { + return GetPendingDir(dir); + } + else { +#ifndef XP_LINUX + CreateFileFromPath(gExceptionHandler->dump_path(), dir); +#else + CreateFileFromPath(gExceptionHandler->minidump_descriptor().directory(), + dir); +#endif + return nullptr != *dir; + } +} + +void +DeleteMinidumpFilesForID(const nsAString& id) +{ + nsCOMPtr<nsIFile> minidumpFile; + GetMinidumpForID(id, getter_AddRefs(minidumpFile)); + bool exists = false; + if (minidumpFile && NS_SUCCEEDED(minidumpFile->Exists(&exists)) && exists) { + nsCOMPtr<nsIFile> childExtraFile; + GetExtraFileForMinidump(minidumpFile, getter_AddRefs(childExtraFile)); + if (childExtraFile) { + childExtraFile->Remove(false); + } + minidumpFile->Remove(false); + } +} + +bool +GetMinidumpForID(const nsAString& id, nsIFile** minidump) +{ + if (!GetMinidumpLimboDir(minidump)) + return false; + (*minidump)->Append(id + NS_LITERAL_STRING(".dmp")); + return true; +} + +bool +GetIDFromMinidump(nsIFile* minidump, nsAString& id) +{ + if (minidump && NS_SUCCEEDED(minidump->GetLeafName(id))) { + id.Replace(id.Length() - 4, 4, NS_LITERAL_STRING("")); + return true; + } + return false; +} + +bool +GetExtraFileForID(const nsAString& id, nsIFile** extraFile) +{ + if (!GetMinidumpLimboDir(extraFile)) + return false; + (*extraFile)->Append(id + NS_LITERAL_STRING(".extra")); + return true; +} + +bool +GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile) +{ + nsAutoString leafName; + nsresult rv = minidump->GetLeafName(leafName); + if (NS_FAILED(rv)) + return false; + + nsCOMPtr<nsIFile> extraF; + rv = minidump->Clone(getter_AddRefs(extraF)); + if (NS_FAILED(rv)) + return false; + + leafName.Replace(leafName.Length() - 3, 3, + NS_LITERAL_STRING("extra")); + rv = extraF->SetLeafName(leafName); + if (NS_FAILED(rv)) + return false; + + *extraFile = nullptr; + extraF.swap(*extraFile); + return true; +} + +bool +AppendExtraData(const nsAString& id, const AnnotationTable& data) +{ + nsCOMPtr<nsIFile> extraFile; + if (!GetExtraFileForID(id, getter_AddRefs(extraFile))) + return false; + return AppendExtraData(extraFile, data); +} + +//----------------------------------------------------------------------------- +// Helpers for AppendExtraData() +// +struct Blacklist { + Blacklist() : mItems(nullptr), mLen(0) { } + Blacklist(const char** items, int len) : mItems(items), mLen(len) { } + + bool Contains(const nsACString& key) const { + for (int i = 0; i < mLen; ++i) + if (key.EqualsASCII(mItems[i])) + return true; + return false; + } + + const char** mItems; + const int mLen; +}; + +static void +WriteAnnotation(PRFileDesc* fd, const nsACString& key, const nsACString& value) +{ + PR_Write(fd, key.BeginReading(), key.Length()); + PR_Write(fd, "=", 1); + PR_Write(fd, value.BeginReading(), value.Length()); + PR_Write(fd, "\n", 1); +} + +template<int N> +void +WriteLiteral(PRFileDesc* fd, const char (&str)[N]) +{ + PR_Write(fd, str, N - 1); +} + +static bool +WriteExtraData(nsIFile* extraFile, + const AnnotationTable& data, + const Blacklist& blacklist, + bool writeCrashTime=false, + bool truncate=false) +{ + PRFileDesc* fd; + int truncOrAppend = truncate ? PR_TRUNCATE : PR_APPEND; + nsresult rv = + extraFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | truncOrAppend, + 0600, &fd); + if (NS_FAILED(rv)) + return false; + + for (auto iter = data.ConstIter(); !iter.Done(); iter.Next()) { + // Skip entries in the blacklist. + const nsACString& key = iter.Key(); + if (blacklist.Contains(key)) { + continue; + } + WriteAnnotation(fd, key, iter.Data()); + } + + if (writeCrashTime) { + time_t crashTime = time(nullptr); + char crashTimeString[32]; + XP_TTOA(crashTime, crashTimeString, 10); + + WriteAnnotation(fd, + nsDependentCString("CrashTime"), + nsDependentCString(crashTimeString)); + + bool ignored = false; + double uptimeTS = (TimeStamp::NowLoRes()- + TimeStamp::ProcessCreation(ignored)).ToSecondsSigDigits(); + char uptimeTSString[64]; + SimpleNoCLibDtoA(uptimeTS, uptimeTSString, sizeof(uptimeTSString)); + + WriteAnnotation(fd, + nsDependentCString("UptimeTS"), + nsDependentCString(uptimeTSString)); + } + + if (memoryReportPath) { + WriteLiteral(fd, "ContainsMemoryReport=1\n"); + } + + PR_Close(fd); + return true; +} + +bool +AppendExtraData(nsIFile* extraFile, const AnnotationTable& data) +{ + return WriteExtraData(extraFile, data, Blacklist()); +} + +static bool +GetExtraFileForChildPid(uint32_t aPid, nsIFile** aExtraFile) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + nsCOMPtr<nsIFile> extraFile; + nsresult rv; + +#if defined(XP_WIN) || defined(XP_MACOSX) + if (!childProcessTmpDir) { + return false; + } + CreateFileFromPath(*childProcessTmpDir, getter_AddRefs(extraFile)); + if (!extraFile) { + return false; + } +#elif defined(XP_UNIX) + rv = NS_NewLocalFile(NS_LITERAL_STRING("/tmp"), false, + getter_AddRefs(extraFile)); + if (NS_FAILED(rv)) { + return false; + } +#else +#error "Implement this for your platform" +#endif + + nsAutoString leafName; +#if defined(XP_WIN) + leafName.AppendPrintf("%S%u%S", childCrashAnnotationBaseName, aPid, + extraFileExtension); +#else + leafName.AppendPrintf("%s%u%s", childCrashAnnotationBaseName, aPid, + extraFileExtension); +#endif + + rv = extraFile->Append(leafName); + if (NS_FAILED(rv)) { + return false; + } + + extraFile.forget(aExtraFile); + return true; +} + +static bool +IsDataEscaped(char* aData) +{ + if (strchr(aData, '\n')) { + // There should not be any newlines + return false; + } + char* pos = aData; + while ((pos = strchr(pos, '\\'))) { + if (*(pos + 1) != '\\') { + return false; + } + // Add 2 to account for the second pos + pos += 2; + } + return true; +} + +static void +ReadAndValidateExceptionTimeAnnotations(FILE*& aFd, + AnnotationTable& aAnnotations) +{ + char line[0x1000]; + while (fgets(line, sizeof(line), aFd)) { + char* data = strchr(line, '='); + if (!data) { + // bad data? Abort! + break; + } + // Move past the '=' + *data = 0; + ++data; + size_t dataLen = strlen(data); + // Chop off any trailing newline + if (dataLen > 0 && data[dataLen - 1] == '\n') { + data[dataLen - 1] = 0; + --dataLen; + } + // There should not be any newlines in the key + if (strchr(line, '\n')) { + break; + } + // Data should have been escaped by the child + if (!IsDataEscaped(data)) { + break; + } + // Looks good, save the (line,data) pair + aAnnotations.Put(nsDependentCString(line), + nsDependentCString(data, dataLen)); + } +} + +/** + * NOTE: One side effect of this function is that it deletes the + * GeckoChildCrash<pid>.extra file if it exists, once processed. + */ +static bool +WriteExtraForMinidump(nsIFile* minidump, + uint32_t pid, + const Blacklist& blacklist, + nsIFile** extraFile) +{ + nsCOMPtr<nsIFile> extra; + if (!GetExtraFileForMinidump(minidump, getter_AddRefs(extra))) { + return false; + } + + if (!WriteExtraData(extra, *crashReporterAPIData_Hash, + blacklist, + true /*write crash time*/, + true /*truncate*/)) { + return false; + } + + nsCOMPtr<nsIFile> exceptionTimeExtra; + FILE* fd; + if (pid && GetExtraFileForChildPid(pid, getter_AddRefs(exceptionTimeExtra)) && + NS_SUCCEEDED(exceptionTimeExtra->OpenANSIFileDesc("r", &fd))) { + AnnotationTable exceptionTimeAnnotations; + ReadAndValidateExceptionTimeAnnotations(fd, exceptionTimeAnnotations); + fclose(fd); + if (!AppendExtraData(extra, exceptionTimeAnnotations)) { + return false; + } + } + if (exceptionTimeExtra) { + exceptionTimeExtra->Remove(false); + } + + extra.forget(extraFile); + + return true; +} + +// It really only makes sense to call this function when +// ShouldReport() is true. +// Uses dumpFile's filename to generate memoryReport's filename (same name with +// a different extension) +static bool +MoveToPending(nsIFile* dumpFile, nsIFile* extraFile, nsIFile* memoryReport) +{ + nsCOMPtr<nsIFile> pendingDir; + if (!GetPendingDir(getter_AddRefs(pendingDir))) + return false; + + if (NS_FAILED(dumpFile->MoveTo(pendingDir, EmptyString()))) { + return false; + } + + if (extraFile && NS_FAILED(extraFile->MoveTo(pendingDir, EmptyString()))) { + return false; + } + + if (memoryReport) { + nsAutoString leafName; + nsresult rv = dumpFile->GetLeafName(leafName); + if (NS_FAILED(rv)) { + return false; + } + // Generate the correct memory report filename from the dumpFile's name + leafName.Replace(leafName.Length() - 4, 4, + static_cast<nsString>(CONVERT_XP_CHAR_TO_UTF16(memoryReportExtension))); + if (NS_FAILED(memoryReport->MoveTo(pendingDir, leafName))) { + return false; + } + } + + return true; +} + +static void +OnChildProcessDumpRequested(void* aContext, +#ifdef XP_MACOSX + const ClientInfo& aClientInfo, + const xpstring& aFilePath +#else + const ClientInfo* aClientInfo, + const xpstring* aFilePath +#endif + ) +{ + nsCOMPtr<nsIFile> minidump; + nsCOMPtr<nsIFile> extraFile; + + // Hold the mutex until the current dump request is complete, to + // prevent UnsetExceptionHandler() from pulling the rug out from + // under us. + MutexAutoLock lock(*dumpSafetyLock); + if (!isSafeToDump) + return; + + CreateFileFromPath( +#ifdef XP_MACOSX + aFilePath, +#else + *aFilePath, +#endif + getter_AddRefs(minidump)); + + uint32_t pid = +#ifdef XP_MACOSX + aClientInfo.pid(); +#else + aClientInfo->pid(); +#endif + + if (!WriteExtraForMinidump(minidump, pid, + Blacklist(kSubprocessBlacklist, + ArrayLength(kSubprocessBlacklist)), + getter_AddRefs(extraFile))) + return; + + if (ShouldReport()) { + nsCOMPtr<nsIFile> memoryReport; + if (memoryReportPath) { + CreateFileFromPath(memoryReportPath, getter_AddRefs(memoryReport)); + MOZ_ASSERT(memoryReport); + } + MoveToPending(minidump, extraFile, memoryReport); + } + + { + +#ifdef MOZ_CRASHREPORTER_INJECTOR + bool runCallback; +#endif + { + MutexAutoLock lock(*dumpMapLock); + ChildProcessData* pd = pidToMinidump->PutEntry(pid); + MOZ_ASSERT(!pd->minidump); + pd->minidump = minidump; + pd->sequence = ++crashSequence; +#ifdef MOZ_CRASHREPORTER_INJECTOR + runCallback = nullptr != pd->callback; +#endif + } +#ifdef MOZ_CRASHREPORTER_INJECTOR + if (runCallback) + NS_DispatchToMainThread(new ReportInjectedCrash(pid)); +#endif + } +} + +static bool +OOPInitialized() +{ + return pidToMinidump != nullptr; +} + +void +OOPInit() +{ + class ProxyToMainThread : public Runnable + { + public: + NS_IMETHOD Run() override { + OOPInit(); + return NS_OK; + } + }; + if (!NS_IsMainThread()) { + // This logic needs to run on the main thread + nsCOMPtr<nsIThread> mainThread = do_GetMainThread(); + mozilla::SyncRunnable::DispatchToThread(mainThread, new ProxyToMainThread()); + return; + } + + if (OOPInitialized()) + return; + + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(gExceptionHandler != nullptr, + "attempt to initialize OOP crash reporter before in-process crashreporter!"); + +#if (defined(XP_WIN) || defined(XP_MACOSX)) + nsCOMPtr<nsIFile> tmpDir; +# if defined(MOZ_CONTENT_SANDBOX) + nsresult rv = NS_GetSpecialDirectory(NS_APP_CONTENT_PROCESS_TEMP_DIR, + getter_AddRefs(tmpDir)); + if (NS_FAILED(rv) && PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { + // Temporary hack for xpcshell, will be fixed in bug 1257098 + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir)); + } + if (NS_SUCCEEDED(rv)) { + childProcessTmpDir = CreatePathFromFile(tmpDir); + } +# else + if (NS_SUCCEEDED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + getter_AddRefs(tmpDir)))) { + childProcessTmpDir = CreatePathFromFile(tmpDir); + } +# endif // defined(MOZ_CONTENT_SANDBOX) +#endif // (defined(XP_WIN) || defined(XP_MACOSX)) + +#if defined(XP_WIN) + childCrashNotifyPipe = + PR_smprintf("\\\\.\\pipe\\gecko-crash-server-pipe.%i", + static_cast<int>(::GetCurrentProcessId())); + + const std::wstring dumpPath = gExceptionHandler->dump_path(); + crashServer = new CrashGenerationServer( + std::wstring(NS_ConvertASCIItoUTF16(childCrashNotifyPipe).get()), + nullptr, // default security attributes + nullptr, nullptr, // we don't care about process connect here + OnChildProcessDumpRequested, nullptr, + nullptr, nullptr, // we don't care about process exit here + nullptr, nullptr, // we don't care about upload request here + true, // automatically generate dumps + &dumpPath); + +#elif defined(XP_LINUX) + if (!CrashGenerationServer::CreateReportChannel(&serverSocketFd, + &clientSocketFd)) + NS_RUNTIMEABORT("can't create crash reporter socketpair()"); + + const std::string dumpPath = + gExceptionHandler->minidump_descriptor().directory(); + crashServer = new CrashGenerationServer( + serverSocketFd, + OnChildProcessDumpRequested, nullptr, + nullptr, nullptr, // we don't care about process exit here + true, + &dumpPath); + +#elif defined(XP_MACOSX) + childCrashNotifyPipe = + PR_smprintf("gecko-crash-server-pipe.%i", + static_cast<int>(getpid())); + const std::string dumpPath = gExceptionHandler->dump_path(); + + crashServer = new CrashGenerationServer( + childCrashNotifyPipe, + nullptr, + nullptr, + OnChildProcessDumpRequested, nullptr, + nullptr, nullptr, + true, // automatically generate dumps + dumpPath); +#endif + + if (!crashServer->Start()) + NS_RUNTIMEABORT("can't start crash reporter server()"); + + pidToMinidump = new ChildMinidumpMap(); + + dumpMapLock = new Mutex("CrashReporter::dumpMapLock"); + + FindPendingDir(); + UpdateCrashEventsDir(); +} + +static void +OOPDeinit() +{ + if (!OOPInitialized()) { + NS_WARNING("OOPDeinit() without successful OOPInit()"); + return; + } + +#ifdef MOZ_CRASHREPORTER_INJECTOR + if (sInjectorThread) { + sInjectorThread->Shutdown(); + NS_RELEASE(sInjectorThread); + } +#endif + + delete crashServer; + crashServer = nullptr; + + delete dumpMapLock; + dumpMapLock = nullptr; + + delete pidToMinidump; + pidToMinidump = nullptr; + +#if defined(XP_WIN) + PR_Free(childCrashNotifyPipe); + childCrashNotifyPipe = nullptr; +#endif +} + +#if defined(XP_WIN) || defined(XP_MACOSX) +// Parent-side API for children +const char* +GetChildNotificationPipe() +{ + if (!GetEnabled()) + return kNullNotifyPipe; + + MOZ_ASSERT(OOPInitialized()); + + return childCrashNotifyPipe; +} +#endif + +#ifdef MOZ_CRASHREPORTER_INJECTOR +void +InjectCrashReporterIntoProcess(DWORD processID, InjectorCrashCallback* cb) +{ + if (!GetEnabled()) + return; + + if (!OOPInitialized()) + OOPInit(); + + if (!sInjectorThread) { + if (NS_FAILED(NS_NewThread(&sInjectorThread))) + return; + } + + { + MutexAutoLock lock(*dumpMapLock); + ChildProcessData* pd = pidToMinidump->PutEntry(processID); + MOZ_ASSERT(!pd->minidump && !pd->callback); + pd->callback = cb; + } + + nsCOMPtr<nsIRunnable> r = new InjectCrashRunnable(processID); + sInjectorThread->Dispatch(r, nsIEventTarget::DISPATCH_NORMAL); +} + +NS_IMETHODIMP +ReportInjectedCrash::Run() +{ + // Crash reporting may have been disabled after this method was dispatched + if (!OOPInitialized()) + return NS_OK; + + InjectorCrashCallback* cb; + { + MutexAutoLock lock(*dumpMapLock); + ChildProcessData* pd = pidToMinidump->GetEntry(mPID); + if (!pd || !pd->callback) + return NS_OK; + + MOZ_ASSERT(pd->minidump); + + cb = pd->callback; + } + + cb->OnCrash(mPID); + return NS_OK; +} + +void +UnregisterInjectorCallback(DWORD processID) +{ + if (!OOPInitialized()) + return; + + MutexAutoLock lock(*dumpMapLock); + pidToMinidump->RemoveEntry(processID); +} + +#endif // MOZ_CRASHREPORTER_INJECTOR + +bool +CheckForLastRunCrash() +{ + if (lastRunCrashID) + return true; + + // The exception handler callback leaves the filename of the + // last minidump in a known file. + nsCOMPtr<nsIFile> lastCrashFile; + CreateFileFromPath(crashMarkerFilename, + getter_AddRefs(lastCrashFile)); + + bool exists; + if (NS_FAILED(lastCrashFile->Exists(&exists)) || !exists) { + return false; + } + + nsAutoCString lastMinidump_contents; + if (NS_FAILED(GetFileContents(lastCrashFile, lastMinidump_contents))) { + return false; + } + lastCrashFile->Remove(false); + +#ifdef XP_WIN + // Ugly but effective. + nsDependentString lastMinidump( + reinterpret_cast<const char16_t*>(lastMinidump_contents.get())); +#else + nsAutoCString lastMinidump = lastMinidump_contents; +#endif + nsCOMPtr<nsIFile> lastMinidumpFile; + CreateFileFromPath(lastMinidump.get(), + getter_AddRefs(lastMinidumpFile)); + + if (!lastMinidumpFile || NS_FAILED(lastMinidumpFile->Exists(&exists)) || !exists) { + return false; + } + + nsCOMPtr<nsIFile> lastExtraFile; + if (!GetExtraFileForMinidump(lastMinidumpFile, + getter_AddRefs(lastExtraFile))) { + return false; + } + + nsCOMPtr<nsIFile> memoryReportFile; + nsresult rv = GetDefaultMemoryReportFile(getter_AddRefs(memoryReportFile)); + if (NS_FAILED(rv) || NS_FAILED(memoryReportFile->Exists(&exists)) || !exists) { + memoryReportFile = nullptr; + } + + FindPendingDir(); + + // Move {dump,extra,memory} to pending folder + if (!MoveToPending(lastMinidumpFile, lastExtraFile, memoryReportFile)) { + return false; + } + + lastRunCrashID = new nsString(); + return GetIDFromMinidump(lastMinidumpFile, *lastRunCrashID); +} + +bool +GetLastRunCrashID(nsAString& id) +{ + if (!lastRunCrashID_checked) { + CheckForLastRunCrash(); + lastRunCrashID_checked = true; + } + + if (!lastRunCrashID) { + return false; + } + + id = *lastRunCrashID; + return true; +} + +#if defined(XP_WIN) || defined(XP_MACOSX) +void +InitChildProcessTmpDir() +{ + MOZ_ASSERT(!XRE_IsParentProcess()); + // When retrieved by the child process, this will always resolve to the + // correct directory regardless of sandbox level. + nsCOMPtr<nsIFile> tmpDir; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir)); + if (NS_SUCCEEDED(rv)) { + childProcessTmpDir = CreatePathFromFile(tmpDir); + } +} +#endif // defined(XP_WIN) || defined(XP_MACOSX) + +#if defined(XP_WIN) +// Child-side API +bool +SetRemoteExceptionHandler(const nsACString& crashPipe) +{ + // crash reporting is disabled + if (crashPipe.Equals(kNullNotifyPipe)) + return true; + + MOZ_ASSERT(!gExceptionHandler, "crash client already init'd"); + + gExceptionHandler = new google_breakpad:: + ExceptionHandler(L"", + ChildFPEFilter, + nullptr, // no minidump callback + nullptr, // no callback context + google_breakpad::ExceptionHandler::HANDLER_ALL, + GetMinidumpType(), + NS_ConvertASCIItoUTF16(crashPipe).get(), + nullptr); + gExceptionHandler->set_handle_debug_exceptions(true); + + mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize); + + oldTerminateHandler = std::set_terminate(&TerminateHandler); + + // we either do remote or nothing, no fallback to regular crash reporting + return gExceptionHandler->IsOutOfProcess(); +} + +//-------------------------------------------------- +#elif defined(XP_LINUX) + +// Parent-side API for children +bool +CreateNotificationPipeForChild(int* childCrashFd, int* childCrashRemapFd) +{ + if (!GetEnabled()) { + *childCrashFd = -1; + *childCrashRemapFd = -1; + return true; + } + + MOZ_ASSERT(OOPInitialized()); + + *childCrashFd = clientSocketFd; + *childCrashRemapFd = kMagicChildCrashReportFd; + + return true; +} + +// Child-side API +bool +SetRemoteExceptionHandler() +{ + MOZ_ASSERT(!gExceptionHandler, "crash client already init'd"); + + // MinidumpDescriptor requires a non-empty path. + google_breakpad::MinidumpDescriptor path("."); + + gExceptionHandler = new google_breakpad:: + ExceptionHandler(path, + ChildFilter, + nullptr, // no minidump callback + nullptr, // no callback context + true, // install signal handlers + kMagicChildCrashReportFd); + + if (gDelayedAnnotations) { + for (uint32_t i = 0; i < gDelayedAnnotations->Length(); i++) { + gDelayedAnnotations->ElementAt(i)->Run(); + } + delete gDelayedAnnotations; + } + + mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize); + + oldTerminateHandler = std::set_terminate(&TerminateHandler); + + // we either do remote or nothing, no fallback to regular crash reporting + return gExceptionHandler->IsOutOfProcess(); +} + +//-------------------------------------------------- +#elif defined(XP_MACOSX) +// Child-side API +bool +SetRemoteExceptionHandler(const nsACString& crashPipe) +{ + // crash reporting is disabled + if (crashPipe.Equals(kNullNotifyPipe)) + return true; + + MOZ_ASSERT(!gExceptionHandler, "crash client already init'd"); + + gExceptionHandler = new google_breakpad:: + ExceptionHandler("", + ChildFilter, + nullptr, // no minidump callback + nullptr, // no callback context + true, // install signal handlers + crashPipe.BeginReading()); + + mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize); + + oldTerminateHandler = std::set_terminate(&TerminateHandler); + + // we either do remote or nothing, no fallback to regular crash reporting + return gExceptionHandler->IsOutOfProcess(); +} +#endif // XP_WIN + + +bool +TakeMinidumpForChild(uint32_t childPid, nsIFile** dump, uint32_t* aSequence) +{ + if (!GetEnabled()) + return false; + + MutexAutoLock lock(*dumpMapLock); + + ChildProcessData* pd = pidToMinidump->GetEntry(childPid); + if (!pd) + return false; + + NS_IF_ADDREF(*dump = pd->minidump); + if (aSequence) { + *aSequence = pd->sequence; + } + + pidToMinidump->RemoveEntry(pd); + + return !!*dump; +} + +//----------------------------------------------------------------------------- +// CreatePairedMinidumps() and helpers +// + +void +RenameAdditionalHangMinidump(nsIFile* minidump, nsIFile* childMinidump, + const nsACString& name) +{ + nsCOMPtr<nsIFile> directory; + childMinidump->GetParent(getter_AddRefs(directory)); + if (!directory) + return; + + nsAutoCString leafName; + childMinidump->GetNativeLeafName(leafName); + + // turn "<id>.dmp" into "<id>-<name>.dmp + leafName.Insert(NS_LITERAL_CSTRING("-") + name, leafName.Length() - 4); + + if (NS_FAILED(minidump->MoveToNative(directory, leafName))) { + NS_WARNING("RenameAdditionalHangMinidump failed to move minidump."); + } +} + +static bool +PairedDumpCallback( +#ifdef XP_LINUX + const MinidumpDescriptor& descriptor, +#else + const XP_CHAR* dump_path, + const XP_CHAR* minidump_id, +#endif + void* context, +#ifdef XP_WIN32 + EXCEPTION_POINTERS* /*unused*/, + MDRawAssertionInfo* /*unused*/, +#endif + bool succeeded) +{ + nsCOMPtr<nsIFile>& minidump = *static_cast< nsCOMPtr<nsIFile>* >(context); + + xpstring dump; +#ifdef XP_LINUX + dump = descriptor.path(); +#else + dump = dump_path; + dump += XP_PATH_SEPARATOR; + dump += minidump_id; + dump += dumpFileExtension; +#endif + + CreateFileFromPath(dump, getter_AddRefs(minidump)); + return true; +} + +static bool +PairedDumpCallbackExtra( +#ifdef XP_LINUX + const MinidumpDescriptor& descriptor, +#else + const XP_CHAR* dump_path, + const XP_CHAR* minidump_id, +#endif + void* context, +#ifdef XP_WIN32 + EXCEPTION_POINTERS* /*unused*/, + MDRawAssertionInfo* /*unused*/, +#endif + bool succeeded) +{ + PairedDumpCallback( +#ifdef XP_LINUX + descriptor, +#else + dump_path, minidump_id, +#endif + context, +#ifdef XP_WIN32 + nullptr, nullptr, +#endif + succeeded); + + nsCOMPtr<nsIFile>& minidump = *static_cast< nsCOMPtr<nsIFile>* >(context); + + nsCOMPtr<nsIFile> extra; + return WriteExtraForMinidump(minidump, 0, Blacklist(), getter_AddRefs(extra)); +} + +ThreadId +CurrentThreadId() +{ +#if defined(XP_WIN) + return ::GetCurrentThreadId(); +#elif defined(XP_LINUX) + return sys_gettid(); +#elif defined(XP_MACOSX) + // Just return an index, since Mach ports can't be directly serialized + thread_act_port_array_t threads_for_task; + mach_msg_type_number_t thread_count; + + if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) + return -1; + + for (unsigned int i = 0; i < thread_count; ++i) { + if (threads_for_task[i] == mach_thread_self()) + return i; + } + abort(); +#else +# error "Unsupported platform" +#endif +} + +#ifdef XP_MACOSX +static mach_port_t +GetChildThread(ProcessHandle childPid, ThreadId childBlamedThread) +{ + mach_port_t childThread = MACH_PORT_NULL; + thread_act_port_array_t threads_for_task; + mach_msg_type_number_t thread_count; + + if (task_threads(childPid, &threads_for_task, &thread_count) + == KERN_SUCCESS && childBlamedThread < thread_count) { + childThread = threads_for_task[childBlamedThread]; + } + + return childThread; +} +#endif + +bool TakeMinidump(nsIFile** aResult, bool aMoveToPending) +{ + if (!GetEnabled()) + return false; + + AutoIOInterposerDisable disableIOInterposition; + + xpstring dump_path; +#ifndef XP_LINUX + dump_path = gExceptionHandler->dump_path(); +#else + dump_path = gExceptionHandler->minidump_descriptor().directory(); +#endif + + // capture the dump + if (!google_breakpad::ExceptionHandler::WriteMinidump( + dump_path, +#ifdef XP_MACOSX + true, +#endif + PairedDumpCallback, + static_cast<void*>(aResult) +#ifdef XP_WIN32 + , GetMinidumpType() +#endif + )) { + return false; + } + + if (aMoveToPending) { + MoveToPending(*aResult, nullptr, nullptr); + } + return true; +} + +bool +CreateMinidumpsAndPair(ProcessHandle aTargetPid, + ThreadId aTargetBlamedThread, + const nsACString& aIncomingPairName, + nsIFile* aIncomingDumpToPair, + nsIFile** aMainDumpOut) +{ + if (!GetEnabled()) { + return false; + } + + AutoIOInterposerDisable disableIOInterposition; + +#ifdef XP_MACOSX + mach_port_t targetThread = GetChildThread(aTargetPid, aTargetBlamedThread); +#else + ThreadId targetThread = aTargetBlamedThread; +#endif + + xpstring dump_path; +#ifndef XP_LINUX + dump_path = gExceptionHandler->dump_path(); +#else + dump_path = gExceptionHandler->minidump_descriptor().directory(); +#endif + + // dump the target + nsCOMPtr<nsIFile> targetMinidump; + if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( + aTargetPid, + targetThread, + dump_path, + PairedDumpCallbackExtra, + static_cast<void*>(&targetMinidump) +#ifdef XP_WIN32 + , GetMinidumpType() +#endif + )) + return false; + + nsCOMPtr<nsIFile> targetExtra; + GetExtraFileForMinidump(targetMinidump, getter_AddRefs(targetExtra)); + + // If aIncomingDumpToPair isn't valid, create a dump of this process. + nsCOMPtr<nsIFile> incomingDump; + if (aIncomingDumpToPair == nullptr) { + if (!google_breakpad::ExceptionHandler::WriteMinidump( + dump_path, +#ifdef XP_MACOSX + true, +#endif + PairedDumpCallback, + static_cast<void*>(&incomingDump) +#ifdef XP_WIN32 + , GetMinidumpType() +#endif + )) { + targetMinidump->Remove(false); + targetExtra->Remove(false); + return false; + } + } else { + incomingDump = aIncomingDumpToPair; + } + + RenameAdditionalHangMinidump(incomingDump, targetMinidump, aIncomingPairName); + + if (ShouldReport()) { + MoveToPending(targetMinidump, targetExtra, nullptr); + MoveToPending(incomingDump, nullptr, nullptr); + } + + targetMinidump.forget(aMainDumpOut); + + return true; +} + +bool +CreateAdditionalChildMinidump(ProcessHandle childPid, + ThreadId childBlamedThread, + nsIFile* parentMinidump, + const nsACString& name) +{ + if (!GetEnabled()) + return false; + +#ifdef XP_MACOSX + mach_port_t childThread = GetChildThread(childPid, childBlamedThread); +#else + ThreadId childThread = childBlamedThread; +#endif + + xpstring dump_path; +#ifndef XP_LINUX + dump_path = gExceptionHandler->dump_path(); +#else + dump_path = gExceptionHandler->minidump_descriptor().directory(); +#endif + + // dump the child + nsCOMPtr<nsIFile> childMinidump; + if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( + childPid, + childThread, + dump_path, + PairedDumpCallback, + static_cast<void*>(&childMinidump) +#ifdef XP_WIN32 + , GetMinidumpType() +#endif + )) { + return false; + } + + RenameAdditionalHangMinidump(childMinidump, parentMinidump, name); + + return true; +} + +bool +UnsetRemoteExceptionHandler() +{ + std::set_terminate(oldTerminateHandler); + delete gExceptionHandler; + gExceptionHandler = nullptr; + return true; +} + +#if defined(MOZ_WIDGET_ANDROID) +void AddLibraryMapping(const char* library_name, + uintptr_t start_address, + size_t mapping_length, + size_t file_offset) +{ + if (!gExceptionHandler) { + mapping_info info; + info.name = library_name; + info.start_address = start_address; + info.length = mapping_length; + info.file_offset = file_offset; + library_mappings.push_back(info); + } + else { + PageAllocator allocator; + auto_wasteful_vector<uint8_t, sizeof(MDGUID)> guid(&allocator); + FileID::ElfFileIdentifierFromMappedFile((void const *)start_address, guid); + gExceptionHandler->AddMappingInfo(library_name, + guid.data(), + start_address, + mapping_length, + file_offset); + } +} +#endif + +} // namespace CrashReporter |