michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsExceptionHandler.h" michael@0: #include "nsDataHashtable.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/dom/CrashReporterChild.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/unused.h" michael@0: michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: #if defined(XP_WIN32) michael@0: #ifdef WIN32_LEAN_AND_MEAN michael@0: #undef WIN32_LEAN_AND_MEAN michael@0: #endif michael@0: michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsIXULAppInfo.h" michael@0: #include "nsIWindowsRegKey.h" michael@0: #include "client/windows/crash_generation/client_info.h" michael@0: #include "client/windows/crash_generation/crash_generation_server.h" michael@0: #include "client/windows/handler/exception_handler.h" michael@0: #include michael@0: #include michael@0: #include "nsDirectoryServiceUtils.h" michael@0: michael@0: #include "nsWindowsDllInterceptor.h" michael@0: #elif defined(XP_MACOSX) michael@0: #include "client/mac/crash_generation/client_info.h" michael@0: #include "client/mac/crash_generation/crash_generation_server.h" michael@0: #include "client/mac/handler/exception_handler.h" michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "mac_utils.h" michael@0: #elif defined(XP_LINUX) michael@0: #include "nsIINIParser.h" michael@0: #include "common/linux/linux_libc_support.h" michael@0: #include "third_party/lss/linux_syscall_support.h" michael@0: #include "client/linux/crash_generation/client_info.h" michael@0: #include "client/linux/crash_generation/crash_generation_server.h" michael@0: #include "client/linux/handler/exception_handler.h" michael@0: #include michael@0: #include michael@0: #include michael@0: #elif defined(XP_SOLARIS) michael@0: #include "client/solaris/handler/exception_handler.h" michael@0: #include michael@0: #include michael@0: #include michael@0: #else michael@0: #error "Not yet implemented for this platform" michael@0: #endif // defined(XP_WIN32) michael@0: michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: #include "InjectCrashReporter.h" michael@0: using mozilla::InjectCrashRunnable; michael@0: #endif michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "mozilla/Mutex.h" michael@0: #include "nsDebug.h" michael@0: #include "nsCRT.h" michael@0: #include "nsIFile.h" michael@0: #include "prprf.h" michael@0: #include michael@0: #include michael@0: michael@0: #include "mozilla/IOInterposer.h" michael@0: #include "mozilla/mozalloc_oom.h" michael@0: #include "mozilla/WindowsDllBlocklist.h" michael@0: michael@0: #if defined(XP_MACOSX) michael@0: CFStringRef reporterClientAppID = CFSTR("org.mozilla.crashreporter"); michael@0: #endif michael@0: #if defined(MOZ_WIDGET_ANDROID) michael@0: #include "common/linux/file_id.h" michael@0: #endif michael@0: michael@0: using google_breakpad::CrashGenerationServer; michael@0: using google_breakpad::ClientInfo; michael@0: #ifdef XP_LINUX michael@0: using google_breakpad::MinidumpDescriptor; michael@0: #endif michael@0: using namespace mozilla; michael@0: using mozilla::dom::CrashReporterChild; michael@0: using mozilla::dom::PCrashReporterChild; michael@0: michael@0: namespace CrashReporter { michael@0: michael@0: #ifdef XP_WIN32 michael@0: typedef wchar_t XP_CHAR; michael@0: typedef std::wstring xpstring; michael@0: #define CONVERT_XP_CHAR_TO_UTF16(x) x michael@0: #define XP_STRLEN(x) wcslen(x) michael@0: #define my_strlen strlen michael@0: #define CRASH_REPORTER_FILENAME "crashreporter.exe" michael@0: #define PATH_SEPARATOR "\\" michael@0: #define XP_PATH_SEPARATOR L"\\" michael@0: // sort of arbitrary, but MAX_PATH is kinda small michael@0: #define XP_PATH_MAX 4096 michael@0: // "" "" michael@0: #define CMDLINE_SIZE ((XP_PATH_MAX * 2) + 6) michael@0: #ifdef _USE_32BIT_TIME_T michael@0: #define XP_TTOA(time, buffer, base) ltoa(time, buffer, base) michael@0: #else michael@0: #define XP_TTOA(time, buffer, base) _i64toa(time, buffer, base) michael@0: #endif michael@0: #define XP_STOA(size, buffer, base) _ui64toa(size, buffer, base) michael@0: #else michael@0: typedef char XP_CHAR; michael@0: typedef std::string xpstring; michael@0: #define CONVERT_XP_CHAR_TO_UTF16(x) NS_ConvertUTF8toUTF16(x) michael@0: #define CRASH_REPORTER_FILENAME "crashreporter" michael@0: #define PATH_SEPARATOR "/" michael@0: #define XP_PATH_SEPARATOR "/" michael@0: #define XP_PATH_MAX PATH_MAX michael@0: #ifdef XP_LINUX michael@0: #define XP_STRLEN(x) my_strlen(x) michael@0: #define XP_TTOA(time, buffer, base) my_inttostring(time, buffer, sizeof(buffer)) michael@0: #define XP_STOA(size, buffer, base) my_inttostring(size, buffer, sizeof(buffer)) michael@0: #else michael@0: #define XP_STRLEN(x) strlen(x) michael@0: #define XP_TTOA(time, buffer, base) sprintf(buffer, "%ld", time) michael@0: #define XP_STOA(size, buffer, base) sprintf(buffer, "%zu", size) michael@0: #define my_strlen strlen michael@0: #define sys_close close michael@0: #define sys_fork fork michael@0: #define sys_open open michael@0: #define sys_write write michael@0: #endif michael@0: #endif // XP_WIN32 michael@0: michael@0: static const XP_CHAR dumpFileExtension[] = {'.', 'd', 'm', 'p', michael@0: '\0'}; // .dmp michael@0: static const XP_CHAR extraFileExtension[] = {'.', 'e', 'x', 't', michael@0: 'r', 'a', '\0'}; // .extra michael@0: michael@0: static const char kCrashMainID[] = "crash.main.1\n"; michael@0: michael@0: static google_breakpad::ExceptionHandler* gExceptionHandler = nullptr; michael@0: michael@0: static XP_CHAR* pendingDirectory; michael@0: static XP_CHAR* crashReporterPath; michael@0: michael@0: // Where crash events should go. michael@0: static XP_CHAR* eventsDirectory; michael@0: michael@0: // If this is false, we don't launch the crash reporter michael@0: static bool doReport = true; michael@0: michael@0: // If this is true, we don't have a crash reporter michael@0: static bool headlessClient = false; michael@0: michael@0: // if this is true, we pass the exception on to the OS crash reporter michael@0: static bool showOSCrashReporter = false; michael@0: michael@0: // The time of the last recorded crash, as a time_t value. michael@0: static time_t lastCrashTime = 0; michael@0: // The pathname of a file to store the crash time in michael@0: static XP_CHAR lastCrashTimeFilename[XP_PATH_MAX] = {0}; michael@0: michael@0: // A marker file to hold the path to the last dump written, which michael@0: // will be checked on startup. michael@0: static XP_CHAR crashMarkerFilename[XP_PATH_MAX] = {0}; michael@0: michael@0: // Whether we've already looked for the marker file. michael@0: static bool lastRunCrashID_checked = false; michael@0: // The minidump ID contained in the marker file. michael@0: static nsString* lastRunCrashID = nullptr; michael@0: michael@0: #if defined(MOZ_WIDGET_ANDROID) michael@0: // on Android 4.2 and above there is a user serial number associated michael@0: // with the current process that gets lost when we fork so we need to michael@0: // explicitly pass it to am michael@0: static char* androidUserSerial = nullptr; michael@0: #endif michael@0: michael@0: // these are just here for readability michael@0: static const char kCrashTimeParameter[] = "CrashTime="; michael@0: static const int kCrashTimeParameterLen = sizeof(kCrashTimeParameter)-1; michael@0: michael@0: static const char kTimeSinceLastCrashParameter[] = "SecondsSinceLastCrash="; michael@0: static const int kTimeSinceLastCrashParameterLen = michael@0: sizeof(kTimeSinceLastCrashParameter)-1; michael@0: michael@0: static const char kSysMemoryParameter[] = "SystemMemoryUsePercentage="; michael@0: static const int kSysMemoryParameterLen = sizeof(kSysMemoryParameter)-1; michael@0: michael@0: static const char kTotalVirtualMemoryParameter[] = "TotalVirtualMemory="; michael@0: static const int kTotalVirtualMemoryParameterLen = michael@0: sizeof(kTotalVirtualMemoryParameter)-1; michael@0: michael@0: static const char kAvailableVirtualMemoryParameter[] = "AvailableVirtualMemory="; michael@0: static const int kAvailableVirtualMemoryParameterLen = michael@0: sizeof(kAvailableVirtualMemoryParameter)-1; michael@0: michael@0: static const char kOOMAllocationSizeParameter[] = "OOMAllocationSize="; michael@0: static const int kOOMAllocationSizeParameterLen = michael@0: sizeof(kOOMAllocationSizeParameter)-1; michael@0: michael@0: static const char kTotalPageFileParameter[] = "TotalPageFile="; michael@0: static const int kTotalPageFileParameterLen = michael@0: sizeof(kTotalPageFileParameter)-1; michael@0: michael@0: static const char kAvailablePageFileParameter[] = "AvailablePageFile="; michael@0: static const int kAvailablePageFileParameterLen = michael@0: sizeof(kAvailablePageFileParameter)-1; michael@0: michael@0: static const char kTotalPhysicalMemoryParameter[] = "TotalPhysicalMemory="; michael@0: static const int kTotalPhysicalMemoryParameterLen = michael@0: sizeof(kTotalPhysicalMemoryParameter)-1; michael@0: michael@0: static const char kAvailablePhysicalMemoryParameter[] = "AvailablePhysicalMemory="; michael@0: static const int kAvailablePhysicalMemoryParameterLen = michael@0: sizeof(kAvailablePhysicalMemoryParameter)-1; michael@0: michael@0: static const char kIsGarbageCollectingParameter[] = "IsGarbageCollecting="; michael@0: static const int kIsGarbageCollectingParameterLen = michael@0: sizeof(kIsGarbageCollectingParameter)-1; michael@0: michael@0: static const char kEventLoopNestingLevelParameter[] = "EventLoopNestingLevel="; michael@0: static const int kEventLoopNestingLevelParameterLen = michael@0: sizeof(kEventLoopNestingLevelParameter)-1; michael@0: michael@0: #ifdef XP_WIN michael@0: static const char kBreakpadReserveAddressParameter[] = "BreakpadReserveAddress="; michael@0: static const int kBreakpadReserveAddressParameterLen = michael@0: sizeof(kBreakpadReserveAddressParameter)-1; michael@0: michael@0: static const char kBreakpadReserveSizeParameter[] = "BreakpadReserveSize="; michael@0: static const int kBreakpadReserveSizeParameterLen = michael@0: sizeof(kBreakpadReserveSizeParameter)-1; michael@0: #endif michael@0: michael@0: // this holds additional data sent via the API michael@0: static Mutex* crashReporterAPILock; michael@0: static Mutex* notesFieldLock; michael@0: static AnnotationTable* crashReporterAPIData_Hash; michael@0: static nsCString* crashReporterAPIData = nullptr; michael@0: static nsCString* notesField = nullptr; michael@0: static bool isGarbageCollecting; michael@0: static uint32_t eventloopNestingLevel = 0; michael@0: michael@0: // Avoid a race during application termination. michael@0: static Mutex* dumpSafetyLock; michael@0: static bool isSafeToDump = false; michael@0: michael@0: // OOP crash reporting michael@0: static CrashGenerationServer* crashServer; // chrome process has this michael@0: michael@0: # if defined(XP_WIN) || defined(XP_MACOSX) michael@0: // If crash reporting is disabled, we hand out this "null" pipe to the michael@0: // child process and don't attempt to connect to a parent server. michael@0: static const char kNullNotifyPipe[] = "-"; michael@0: static char* childCrashNotifyPipe; michael@0: michael@0: # elif defined(XP_LINUX) michael@0: static int serverSocketFd = -1; michael@0: static int clientSocketFd = -1; michael@0: static const int kMagicChildCrashReportFd = 4; michael@0: michael@0: # endif michael@0: michael@0: // |dumpMapLock| must protect all access to |pidToMinidump|. michael@0: static Mutex* dumpMapLock; michael@0: struct ChildProcessData : public nsUint32HashKey michael@0: { michael@0: ChildProcessData(KeyTypePointer aKey) michael@0: : nsUint32HashKey(aKey) michael@0: , sequence(0) michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: , callback(nullptr) michael@0: #endif michael@0: { } michael@0: michael@0: nsCOMPtr minidump; michael@0: // Each crashing process is assigned an increasing sequence number to michael@0: // indicate which process crashed first. michael@0: uint32_t sequence; michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: InjectorCrashCallback* callback; michael@0: #endif michael@0: }; michael@0: michael@0: typedef nsTHashtable ChildMinidumpMap; michael@0: static ChildMinidumpMap* pidToMinidump; michael@0: static uint32_t crashSequence; michael@0: static bool OOPInitialized(); michael@0: michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: static nsIThread* sInjectorThread; michael@0: michael@0: class ReportInjectedCrash : public nsRunnable michael@0: { michael@0: public: michael@0: ReportInjectedCrash(uint32_t pid) : mPID(pid) { } michael@0: michael@0: NS_IMETHOD Run(); michael@0: michael@0: private: michael@0: uint32_t mPID; michael@0: }; michael@0: #endif // MOZ_CRASHREPORTER_INJECTOR michael@0: michael@0: // Crashreporter annotations that we don't send along in subprocess michael@0: // reports michael@0: static const char* kSubprocessBlacklist[] = { michael@0: "FramePoisonBase", michael@0: "FramePoisonSize", michael@0: "StartupTime", michael@0: "URL" michael@0: }; michael@0: michael@0: // If annotations are attempted before the crash reporter is enabled, michael@0: // they queue up here. michael@0: class DelayedNote; michael@0: nsTArray >* gDelayedAnnotations; michael@0: michael@0: #if defined(XP_WIN) michael@0: // the following are used to prevent other DLLs reverting the last chance michael@0: // exception handler to the windows default. Any attempt to change the michael@0: // unhandled exception filter or to reset it is ignored and our crash michael@0: // reporter is loaded instead (in case it became unloaded somehow) michael@0: typedef LPTOP_LEVEL_EXCEPTION_FILTER (WINAPI *SetUnhandledExceptionFilter_func) michael@0: (LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); michael@0: static SetUnhandledExceptionFilter_func stub_SetUnhandledExceptionFilter = 0; michael@0: static LPTOP_LEVEL_EXCEPTION_FILTER previousUnhandledExceptionFilter = nullptr; michael@0: static WindowsDllInterceptor gKernel32Intercept; michael@0: static bool gBlockUnhandledExceptionFilter = true; michael@0: michael@0: static void NotePreviousUnhandledExceptionFilter() michael@0: { michael@0: // Set a dummy value to get the previous filter, then restore michael@0: previousUnhandledExceptionFilter = SetUnhandledExceptionFilter(nullptr); michael@0: SetUnhandledExceptionFilter(previousUnhandledExceptionFilter); michael@0: } michael@0: michael@0: static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI michael@0: patched_SetUnhandledExceptionFilter (LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) michael@0: { michael@0: if (!gBlockUnhandledExceptionFilter) { michael@0: // don't intercept michael@0: return stub_SetUnhandledExceptionFilter(lpTopLevelExceptionFilter); michael@0: } michael@0: michael@0: if (lpTopLevelExceptionFilter == previousUnhandledExceptionFilter) { michael@0: // OK to swap back and forth between the previous filter michael@0: previousUnhandledExceptionFilter = michael@0: stub_SetUnhandledExceptionFilter(lpTopLevelExceptionFilter); michael@0: return previousUnhandledExceptionFilter; michael@0: } michael@0: michael@0: // intercept attempts to change the filter michael@0: return nullptr; michael@0: } michael@0: michael@0: /** michael@0: * Reserve some VM space. In the event that we crash because VM space is michael@0: * being leaked without leaking memory, freeing this space before taking michael@0: * the minidump will allow us to collect a minidump. michael@0: * michael@0: * This size is bigger than xul.dll plus some extra for MinidumpWriteDump michael@0: * allocations. michael@0: */ michael@0: static const SIZE_T kReserveSize = 0x2800000; // 40 MB michael@0: static void* gBreakpadReservedVM; michael@0: #endif michael@0: michael@0: #ifdef XP_MACOSX michael@0: static cpu_type_t pref_cpu_types[2] = { michael@0: #if defined(__i386__) michael@0: CPU_TYPE_X86, michael@0: #elif defined(__x86_64__) michael@0: CPU_TYPE_X86_64, michael@0: #elif defined(__ppc__) michael@0: CPU_TYPE_POWERPC, michael@0: #endif michael@0: CPU_TYPE_ANY }; michael@0: michael@0: static posix_spawnattr_t spawnattr; michael@0: #endif michael@0: michael@0: #if defined(MOZ_WIDGET_ANDROID) michael@0: // Android builds use a custom library loader, michael@0: // so the embedding will provide a list of shared michael@0: // libraries that are mapped into anonymous mappings. michael@0: typedef struct { michael@0: std::string name; michael@0: uintptr_t start_address; michael@0: size_t length; michael@0: size_t file_offset; michael@0: } mapping_info; michael@0: static std::vector library_mappings; michael@0: typedef std::map MappingMap; michael@0: #endif michael@0: michael@0: #ifdef XP_LINUX michael@0: inline void michael@0: my_inttostring(intmax_t t, char* buffer, size_t buffer_length) michael@0: { michael@0: my_memset(buffer, 0, buffer_length); michael@0: my_uitos(buffer, t, my_uint_len(t)); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: static void michael@0: CreateFileFromPath(const xpstring& path, nsIFile** file) michael@0: { michael@0: NS_NewLocalFile(nsDependentString(path.c_str()), false, file); michael@0: } michael@0: #else michael@0: static void michael@0: CreateFileFromPath(const xpstring& path, nsIFile** file) michael@0: { michael@0: NS_NewNativeLocalFile(nsDependentCString(path.c_str()), false, file); michael@0: } michael@0: #endif michael@0: michael@0: static XP_CHAR* michael@0: Concat(XP_CHAR* str, const XP_CHAR* toAppend, int* size) michael@0: { michael@0: int appendLen = XP_STRLEN(toAppend); michael@0: if (appendLen >= *size) appendLen = *size - 1; michael@0: michael@0: memcpy(str, toAppend, appendLen * sizeof(XP_CHAR)); michael@0: str += appendLen; michael@0: *str = '\0'; michael@0: *size -= appendLen; michael@0: michael@0: return str; michael@0: } michael@0: michael@0: static size_t gOOMAllocationSize = 0; michael@0: michael@0: void AnnotateOOMAllocationSize(size_t size) michael@0: { michael@0: gOOMAllocationSize = size; michael@0: } michael@0: michael@0: bool MinidumpCallback( michael@0: #ifdef XP_LINUX michael@0: const MinidumpDescriptor& descriptor, michael@0: #else michael@0: const XP_CHAR* dump_path, michael@0: const XP_CHAR* minidump_id, michael@0: #endif michael@0: void* context, michael@0: #ifdef XP_WIN32 michael@0: EXCEPTION_POINTERS* exinfo, michael@0: MDRawAssertionInfo* assertion, michael@0: #endif michael@0: bool succeeded) michael@0: { michael@0: bool returnValue = showOSCrashReporter ? false : succeeded; michael@0: michael@0: static XP_CHAR minidumpPath[XP_PATH_MAX]; michael@0: int size = XP_PATH_MAX; michael@0: XP_CHAR* p; michael@0: #ifndef XP_LINUX michael@0: p = Concat(minidumpPath, dump_path, &size); michael@0: p = Concat(p, XP_PATH_SEPARATOR, &size); michael@0: p = Concat(p, minidump_id, &size); michael@0: Concat(p, dumpFileExtension, &size); michael@0: #else michael@0: Concat(minidumpPath, descriptor.path(), &size); michael@0: #endif michael@0: michael@0: static XP_CHAR extraDataPath[XP_PATH_MAX]; michael@0: size = XP_PATH_MAX; michael@0: #ifndef XP_LINUX michael@0: p = Concat(extraDataPath, dump_path, &size); michael@0: p = Concat(p, XP_PATH_SEPARATOR, &size); michael@0: p = Concat(p, minidump_id, &size); michael@0: #else michael@0: p = Concat(extraDataPath, descriptor.path(), &size); michael@0: // Skip back past the .dmp extension. michael@0: p -= 4; michael@0: #endif michael@0: Concat(p, extraFileExtension, &size); michael@0: michael@0: if (headlessClient) { michael@0: // Leave a marker indicating that there was a crash. michael@0: #if defined(XP_WIN32) michael@0: HANDLE hFile = CreateFile(crashMarkerFilename, GENERIC_WRITE, 0, michael@0: nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, michael@0: nullptr); michael@0: if(hFile != INVALID_HANDLE_VALUE) { michael@0: DWORD nBytes; michael@0: WriteFile(hFile, minidumpPath, 2*wcslen(minidumpPath), &nBytes, nullptr); michael@0: CloseHandle(hFile); michael@0: } michael@0: #elif defined(XP_UNIX) michael@0: int fd = sys_open(crashMarkerFilename, michael@0: O_WRONLY | O_CREAT | O_TRUNC, michael@0: 0600); michael@0: if (fd != -1) { michael@0: unused << sys_write(fd, minidumpPath, my_strlen(minidumpPath)); michael@0: sys_close(fd); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: char oomAllocationSizeBuffer[32]; michael@0: int oomAllocationSizeBufferLen = 0; michael@0: if (gOOMAllocationSize) { michael@0: XP_STOA(gOOMAllocationSize, oomAllocationSizeBuffer, 10); michael@0: oomAllocationSizeBufferLen = my_strlen(oomAllocationSizeBuffer); michael@0: } michael@0: michael@0: // calculate time since last crash (if possible), and store michael@0: // the time of this crash. michael@0: time_t crashTime; michael@0: #ifdef XP_LINUX michael@0: struct kernel_timeval tv; michael@0: sys_gettimeofday(&tv, nullptr); michael@0: crashTime = tv.tv_sec; michael@0: #else michael@0: crashTime = time(nullptr); michael@0: #endif michael@0: time_t timeSinceLastCrash = 0; michael@0: // stringified versions of the above michael@0: char crashTimeString[32]; michael@0: int crashTimeStringLen = 0; michael@0: char timeSinceLastCrashString[32]; michael@0: int timeSinceLastCrashStringLen = 0; michael@0: michael@0: XP_TTOA(crashTime, crashTimeString, 10); michael@0: crashTimeStringLen = my_strlen(crashTimeString); michael@0: if (lastCrashTime != 0) { michael@0: timeSinceLastCrash = crashTime - lastCrashTime; michael@0: XP_TTOA(timeSinceLastCrash, timeSinceLastCrashString, 10); michael@0: timeSinceLastCrashStringLen = my_strlen(timeSinceLastCrashString); michael@0: } michael@0: // write crash time to file michael@0: if (lastCrashTimeFilename[0] != 0) { michael@0: #if defined(XP_WIN32) michael@0: HANDLE hFile = CreateFile(lastCrashTimeFilename, GENERIC_WRITE, 0, michael@0: nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, michael@0: nullptr); michael@0: if(hFile != INVALID_HANDLE_VALUE) { michael@0: DWORD nBytes; michael@0: WriteFile(hFile, crashTimeString, crashTimeStringLen, &nBytes, nullptr); michael@0: CloseHandle(hFile); michael@0: } michael@0: #elif defined(XP_UNIX) michael@0: int fd = sys_open(lastCrashTimeFilename, michael@0: O_WRONLY | O_CREAT | O_TRUNC, michael@0: 0600); michael@0: if (fd != -1) { michael@0: unused << sys_write(fd, crashTimeString, crashTimeStringLen); michael@0: sys_close(fd); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // Write crash event file. michael@0: michael@0: // Minidump IDs are UUIDs (36) + NULL. michael@0: static char id_ascii[37]; michael@0: #ifdef XP_LINUX michael@0: const char * index = strrchr(descriptor.path(), '/'); michael@0: MOZ_ASSERT(index); michael@0: MOZ_ASSERT(strlen(index) == 1 + 36 + 4); // "/" + UUID + ".dmp" michael@0: for (uint32_t i = 0; i < 36; i++) { michael@0: id_ascii[i] = *(index + 1 + i); michael@0: } michael@0: #else michael@0: MOZ_ASSERT(XP_STRLEN(minidump_id) == 36); michael@0: for (uint32_t i = 0; i < 36; i++) { michael@0: id_ascii[i] = *((char *)(minidump_id + i)); michael@0: } michael@0: #endif michael@0: michael@0: if (eventsDirectory) { michael@0: static XP_CHAR crashEventPath[XP_PATH_MAX]; michael@0: int size = XP_PATH_MAX; michael@0: XP_CHAR* p; michael@0: p = Concat(crashEventPath, eventsDirectory, &size); michael@0: p = Concat(p, XP_PATH_SEPARATOR, &size); michael@0: #ifdef XP_LINUX michael@0: p = Concat(p, id_ascii, &size); michael@0: #else michael@0: p = Concat(p, minidump_id, &size); michael@0: #endif michael@0: michael@0: #if defined(XP_WIN32) michael@0: HANDLE hFile = CreateFile(crashEventPath, GENERIC_WRITE, 0, michael@0: nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, michael@0: nullptr); michael@0: if (hFile != INVALID_HANDLE_VALUE) { michael@0: DWORD nBytes; michael@0: WriteFile(hFile, kCrashMainID, sizeof(kCrashMainID) - 1, &nBytes, michael@0: nullptr); michael@0: WriteFile(hFile, crashTimeString, crashTimeStringLen, &nBytes, nullptr); michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: WriteFile(hFile, id_ascii, strlen(id_ascii), &nBytes, nullptr); michael@0: CloseHandle(hFile); michael@0: } michael@0: #elif defined(XP_UNIX) michael@0: int fd = sys_open(crashEventPath, michael@0: O_WRONLY | O_CREAT | O_TRUNC, michael@0: 0600); michael@0: if (fd != -1) { michael@0: unused << sys_write(fd, kCrashMainID, sizeof(kCrashMainID) - 1); michael@0: unused << sys_write(fd, crashTimeString, crashTimeStringLen); michael@0: unused << sys_write(fd, "\n", 1); michael@0: unused << sys_write(fd, id_ascii, strlen(id_ascii)); michael@0: sys_close(fd); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: #if defined(XP_WIN32) michael@0: if (!crashReporterAPIData->IsEmpty()) { michael@0: // write out API data michael@0: HANDLE hFile = CreateFile(extraDataPath, GENERIC_WRITE, 0, michael@0: nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, michael@0: nullptr); michael@0: if(hFile != INVALID_HANDLE_VALUE) { michael@0: DWORD nBytes; michael@0: WriteFile(hFile, crashReporterAPIData->get(), michael@0: crashReporterAPIData->Length(), &nBytes, nullptr); michael@0: WriteFile(hFile, kCrashTimeParameter, kCrashTimeParameterLen, michael@0: &nBytes, nullptr); michael@0: WriteFile(hFile, crashTimeString, crashTimeStringLen, &nBytes, nullptr); michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: if (timeSinceLastCrash != 0) { michael@0: WriteFile(hFile, kTimeSinceLastCrashParameter, michael@0: kTimeSinceLastCrashParameterLen, &nBytes, nullptr); michael@0: WriteFile(hFile, timeSinceLastCrashString, timeSinceLastCrashStringLen, michael@0: &nBytes, nullptr); michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: } michael@0: if (isGarbageCollecting) { michael@0: WriteFile(hFile, kIsGarbageCollectingParameter, kIsGarbageCollectingParameterLen, michael@0: &nBytes, nullptr); michael@0: WriteFile(hFile, isGarbageCollecting ? "1" : "0", 1, &nBytes, nullptr); michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: } michael@0: michael@0: char buffer[128]; michael@0: int bufferLen; michael@0: michael@0: if (eventloopNestingLevel > 0) { michael@0: WriteFile(hFile, kEventLoopNestingLevelParameter, kEventLoopNestingLevelParameterLen, michael@0: &nBytes, nullptr); michael@0: _ultoa(eventloopNestingLevel, buffer, 10); michael@0: WriteFile(hFile, buffer, strlen(buffer), &nBytes, nullptr); michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: } michael@0: michael@0: if (gBreakpadReservedVM) { michael@0: WriteFile(hFile, kBreakpadReserveAddressParameter, kBreakpadReserveAddressParameterLen, &nBytes, nullptr); michael@0: _ui64toa(uintptr_t(gBreakpadReservedVM), buffer, 10); michael@0: WriteFile(hFile, buffer, strlen(buffer), &nBytes, nullptr); michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: WriteFile(hFile, kBreakpadReserveSizeParameter, kBreakpadReserveSizeParameterLen, &nBytes, nullptr); michael@0: _ui64toa(kReserveSize, buffer, 10); michael@0: WriteFile(hFile, buffer, strlen(buffer), &nBytes, nullptr); michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: } michael@0: michael@0: #ifdef HAS_DLL_BLOCKLIST michael@0: DllBlocklist_WriteNotes(hFile); michael@0: #endif michael@0: michael@0: // Try to get some information about memory. michael@0: MEMORYSTATUSEX statex; michael@0: statex.dwLength = sizeof(statex); michael@0: if (GlobalMemoryStatusEx(&statex)) { michael@0: michael@0: #define WRITE_STATEX_FIELD(field, paramName, conversionFunc) \ michael@0: WriteFile(hFile, k##paramName##Parameter, \ michael@0: k##paramName##ParameterLen, &nBytes, nullptr); \ michael@0: conversionFunc(statex.field, buffer, 10); \ michael@0: bufferLen = strlen(buffer); \ michael@0: WriteFile(hFile, buffer, bufferLen, &nBytes, nullptr); \ michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: michael@0: WRITE_STATEX_FIELD(dwMemoryLoad, SysMemory, ltoa); michael@0: WRITE_STATEX_FIELD(ullTotalVirtual, TotalVirtualMemory, _ui64toa); michael@0: WRITE_STATEX_FIELD(ullAvailVirtual, AvailableVirtualMemory, _ui64toa); michael@0: WRITE_STATEX_FIELD(ullTotalPageFile, TotalPageFile, _ui64toa); michael@0: WRITE_STATEX_FIELD(ullAvailPageFile, AvailablePageFile, _ui64toa); michael@0: WRITE_STATEX_FIELD(ullTotalPhys, TotalPhysicalMemory, _ui64toa); michael@0: WRITE_STATEX_FIELD(ullAvailPhys, AvailablePhysicalMemory, _ui64toa); michael@0: michael@0: #undef WRITE_STATEX_FIELD michael@0: } michael@0: michael@0: if (oomAllocationSizeBufferLen) { michael@0: WriteFile(hFile, kOOMAllocationSizeParameter, michael@0: kOOMAllocationSizeParameterLen, &nBytes, nullptr); michael@0: WriteFile(hFile, oomAllocationSizeBuffer, oomAllocationSizeBufferLen, michael@0: &nBytes, nullptr); michael@0: WriteFile(hFile, "\n", 1, &nBytes, nullptr); michael@0: } michael@0: CloseHandle(hFile); michael@0: } michael@0: } michael@0: michael@0: if (!doReport) { michael@0: return returnValue; michael@0: } michael@0: michael@0: XP_CHAR cmdLine[CMDLINE_SIZE]; michael@0: size = CMDLINE_SIZE; michael@0: p = Concat(cmdLine, L"\"", &size); michael@0: p = Concat(p, crashReporterPath, &size); michael@0: p = Concat(p, L"\" \"", &size); michael@0: p = Concat(p, minidumpPath, &size); michael@0: Concat(p, L"\"", &size); michael@0: michael@0: STARTUPINFO si; michael@0: PROCESS_INFORMATION pi; michael@0: michael@0: ZeroMemory(&si, sizeof(si)); michael@0: si.cb = sizeof(si); michael@0: si.dwFlags = STARTF_USESHOWWINDOW; michael@0: si.wShowWindow = SW_SHOWNORMAL; michael@0: ZeroMemory(&pi, sizeof(pi)); michael@0: michael@0: if (CreateProcess(nullptr, (LPWSTR)cmdLine, nullptr, nullptr, FALSE, 0, michael@0: nullptr, nullptr, &si, &pi)) { michael@0: CloseHandle( pi.hProcess ); michael@0: CloseHandle( pi.hThread ); michael@0: } michael@0: // we're not really in a position to do anything if the CreateProcess fails michael@0: TerminateProcess(GetCurrentProcess(), 1); michael@0: #elif defined(XP_UNIX) michael@0: if (!crashReporterAPIData->IsEmpty()) { michael@0: // write out API data michael@0: int fd = sys_open(extraDataPath, michael@0: O_WRONLY | O_CREAT | O_TRUNC, michael@0: 0666); michael@0: michael@0: if (fd != -1) { michael@0: // not much we can do in case of error michael@0: unused << sys_write(fd, crashReporterAPIData->get(), michael@0: crashReporterAPIData->Length()); michael@0: unused << sys_write(fd, kCrashTimeParameter, kCrashTimeParameterLen); michael@0: unused << sys_write(fd, crashTimeString, crashTimeStringLen); michael@0: unused << sys_write(fd, "\n", 1); michael@0: if (timeSinceLastCrash != 0) { michael@0: unused << sys_write(fd, kTimeSinceLastCrashParameter, michael@0: kTimeSinceLastCrashParameterLen); michael@0: unused << sys_write(fd, timeSinceLastCrashString, michael@0: timeSinceLastCrashStringLen); michael@0: unused << sys_write(fd, "\n", 1); michael@0: } michael@0: if (isGarbageCollecting) { michael@0: unused << sys_write(fd, kIsGarbageCollectingParameter, kIsGarbageCollectingParameterLen); michael@0: unused << sys_write(fd, isGarbageCollecting ? "1" : "0", 1); michael@0: unused << sys_write(fd, "\n", 1); michael@0: } michael@0: if (eventloopNestingLevel > 0) { michael@0: unused << sys_write(fd, kEventLoopNestingLevelParameter, kEventLoopNestingLevelParameterLen); michael@0: char buffer[16]; michael@0: XP_TTOA(eventloopNestingLevel, buffer, 10); michael@0: unused << sys_write(fd, buffer, my_strlen(buffer)); michael@0: unused << sys_write(fd, "\n", 1); michael@0: } michael@0: if (oomAllocationSizeBufferLen) { michael@0: unused << sys_write(fd, kOOMAllocationSizeParameter, michael@0: kOOMAllocationSizeParameterLen); michael@0: unused << sys_write(fd, oomAllocationSizeBuffer, michael@0: oomAllocationSizeBufferLen); michael@0: unused << sys_write(fd, "\n", 1); michael@0: } michael@0: sys_close(fd); michael@0: } michael@0: } michael@0: michael@0: if (!doReport) { michael@0: return returnValue; michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: char* const my_argv[] = { michael@0: crashReporterPath, michael@0: minidumpPath, michael@0: nullptr michael@0: }; michael@0: michael@0: char **env = nullptr; michael@0: char ***nsEnv = _NSGetEnviron(); michael@0: if (nsEnv) michael@0: env = *nsEnv; michael@0: int result = posix_spawnp(nullptr, michael@0: my_argv[0], michael@0: nullptr, michael@0: &spawnattr, michael@0: my_argv, michael@0: env); michael@0: michael@0: if (result != 0) michael@0: return false; michael@0: michael@0: #else // !XP_MACOSX michael@0: pid_t pid = sys_fork(); michael@0: michael@0: if (pid == -1) michael@0: return false; michael@0: else if (pid == 0) { michael@0: #if !defined(MOZ_WIDGET_ANDROID) michael@0: // need to clobber this, as libcurl might load NSS, michael@0: // and we want it to load the system NSS. michael@0: unsetenv("LD_LIBRARY_PATH"); michael@0: unused << execl(crashReporterPath, michael@0: crashReporterPath, minidumpPath, (char*)0); michael@0: #else michael@0: // Invoke the reportCrash activity using am michael@0: if (androidUserSerial) { michael@0: unused << execlp("/system/bin/am", michael@0: "/system/bin/am", michael@0: "start", michael@0: "--user", androidUserSerial, michael@0: "-a", "org.mozilla.gecko.reportCrash", michael@0: "-n", crashReporterPath, michael@0: "--es", "minidumpPath", minidumpPath, michael@0: (char*)0); michael@0: } else { michael@0: unused << execlp("/system/bin/am", michael@0: "/system/bin/am", michael@0: "start", michael@0: "-a", "org.mozilla.gecko.reportCrash", michael@0: "-n", crashReporterPath, michael@0: "--es", "minidumpPath", minidumpPath, michael@0: (char*)0); michael@0: } michael@0: #endif michael@0: _exit(1); michael@0: } michael@0: #endif // XP_MACOSX michael@0: #endif // XP_UNIX michael@0: michael@0: return returnValue; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: static void michael@0: ReserveBreakpadVM() michael@0: { michael@0: if (!gBreakpadReservedVM) { michael@0: gBreakpadReservedVM = VirtualAlloc(nullptr, kReserveSize, MEM_RESERVE, michael@0: PAGE_NOACCESS); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: FreeBreakpadVM() michael@0: { michael@0: if (gBreakpadReservedVM) { michael@0: VirtualFree(gBreakpadReservedVM, 0, MEM_RELEASE); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Filters out floating point exceptions which are handled by nsSigHandlers.cpp michael@0: * and should not be handled as crashes. michael@0: * michael@0: * Also calls FreeBreakpadVM if appropriate. michael@0: */ michael@0: static bool FPEFilter(void* context, EXCEPTION_POINTERS* exinfo, michael@0: MDRawAssertionInfo* assertion) michael@0: { michael@0: if (!exinfo) { michael@0: mozilla::IOInterposer::Disable(); michael@0: FreeBreakpadVM(); michael@0: return true; michael@0: } michael@0: michael@0: PEXCEPTION_RECORD e = (PEXCEPTION_RECORD)exinfo->ExceptionRecord; michael@0: switch (e->ExceptionCode) { michael@0: case STATUS_FLOAT_DENORMAL_OPERAND: michael@0: case STATUS_FLOAT_DIVIDE_BY_ZERO: michael@0: case STATUS_FLOAT_INEXACT_RESULT: michael@0: case STATUS_FLOAT_INVALID_OPERATION: michael@0: case STATUS_FLOAT_OVERFLOW: michael@0: case STATUS_FLOAT_STACK_CHECK: michael@0: case STATUS_FLOAT_UNDERFLOW: michael@0: case STATUS_FLOAT_MULTIPLE_FAULTS: michael@0: case STATUS_FLOAT_MULTIPLE_TRAPS: michael@0: return false; // Don't write minidump, continue exception search michael@0: } michael@0: mozilla::IOInterposer::Disable(); michael@0: FreeBreakpadVM(); michael@0: return true; michael@0: } michael@0: #endif // XP_WIN michael@0: michael@0: static bool ShouldReport() michael@0: { michael@0: // this environment variable prevents us from launching michael@0: // the crash reporter client michael@0: const char *envvar = PR_GetEnv("MOZ_CRASHREPORTER_NO_REPORT"); michael@0: if (envvar && *envvar) { michael@0: return false; michael@0: } michael@0: michael@0: envvar = PR_GetEnv("MOZ_CRASHREPORTER_FULLDUMP"); michael@0: if (envvar && *envvar) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: namespace { michael@0: bool Filter(void* context) { michael@0: mozilla::IOInterposer::Disable(); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: michael@0: nsresult SetExceptionHandler(nsIFile* aXREDirectory, michael@0: bool force/*=false*/) michael@0: { michael@0: if (gExceptionHandler) michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: michael@0: #if !defined(DEBUG) || defined(MOZ_WIDGET_GONK) michael@0: // In non-debug builds, enable the crash reporter by default, and allow michael@0: // disabling it with the MOZ_CRASHREPORTER_DISABLE environment variable. michael@0: // Also enable it by default in debug gonk builds as it is difficult to michael@0: // set environment on startup. michael@0: const char *envvar = PR_GetEnv("MOZ_CRASHREPORTER_DISABLE"); michael@0: if (envvar && *envvar && !force) michael@0: return NS_OK; michael@0: #else michael@0: // In debug builds, disable the crash reporter by default, and allow to michael@0: // enable it with the MOZ_CRASHREPORTER environment variable. michael@0: const char *envvar = PR_GetEnv("MOZ_CRASHREPORTER"); michael@0: if ((!envvar || !*envvar) && !force) michael@0: return NS_OK; michael@0: #endif michael@0: michael@0: #if defined(MOZ_WIDGET_GONK) michael@0: doReport = false; michael@0: headlessClient = true; michael@0: #elif defined(XP_WIN) michael@0: if (XRE_GetWindowsEnvironment() == WindowsEnvironmentType_Desktop) { michael@0: doReport = ShouldReport(); michael@0: } else { michael@0: doReport = false; michael@0: headlessClient = true; michael@0: } michael@0: #else michael@0: // this environment variable prevents us from launching michael@0: // the crash reporter client michael@0: doReport = ShouldReport(); michael@0: #endif michael@0: michael@0: // allocate our strings michael@0: crashReporterAPIData = new nsCString(); michael@0: NS_ENSURE_TRUE(crashReporterAPIData, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: NS_ASSERTION(!crashReporterAPILock, "Shouldn't have a lock yet"); michael@0: crashReporterAPILock = new Mutex("crashReporterAPILock"); michael@0: NS_ASSERTION(!notesFieldLock, "Shouldn't have a lock yet"); michael@0: notesFieldLock = new Mutex("notesFieldLock"); michael@0: michael@0: crashReporterAPIData_Hash = michael@0: new nsDataHashtable(); michael@0: NS_ENSURE_TRUE(crashReporterAPIData_Hash, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: notesField = new nsCString(); michael@0: NS_ENSURE_TRUE(notesField, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: if (!headlessClient) { michael@0: // locate crashreporter executable michael@0: nsCOMPtr exePath; michael@0: nsresult rv = aXREDirectory->Clone(getter_AddRefs(exePath)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: #if defined(XP_MACOSX) michael@0: exePath->Append(NS_LITERAL_STRING("crashreporter.app")); michael@0: exePath->Append(NS_LITERAL_STRING("Contents")); michael@0: exePath->Append(NS_LITERAL_STRING("MacOS")); michael@0: #endif michael@0: michael@0: exePath->AppendNative(NS_LITERAL_CSTRING(CRASH_REPORTER_FILENAME)); michael@0: michael@0: #ifdef XP_WIN32 michael@0: nsString crashReporterPath_temp; michael@0: michael@0: exePath->GetPath(crashReporterPath_temp); michael@0: crashReporterPath = reinterpret_cast(ToNewUnicode(crashReporterPath_temp)); michael@0: #elif !defined(__ANDROID__) michael@0: nsCString crashReporterPath_temp; michael@0: michael@0: exePath->GetNativePath(crashReporterPath_temp); michael@0: crashReporterPath = ToNewCString(crashReporterPath_temp); michael@0: #else michael@0: // On Android, we launch using the application package name michael@0: // instead of a filename, so use ANDROID_PACKAGE_NAME to do that here. michael@0: nsCString package(ANDROID_PACKAGE_NAME "/org.mozilla.gecko.CrashReporter"); michael@0: crashReporterPath = ToNewCString(package); michael@0: #endif michael@0: } michael@0: michael@0: // get temp path to use for minidump path michael@0: #if defined(XP_WIN32) michael@0: nsString tempPath; michael@0: michael@0: // first figure out buffer size michael@0: int pathLen = GetTempPath(0, nullptr); michael@0: if (pathLen == 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: tempPath.SetLength(pathLen); michael@0: GetTempPath(pathLen, (LPWSTR)tempPath.BeginWriting()); michael@0: #elif defined(XP_MACOSX) michael@0: nsCString tempPath; michael@0: FSRef fsRef; michael@0: OSErr err = FSFindFolder(kUserDomain, kTemporaryFolderType, michael@0: kCreateFolder, &fsRef); michael@0: if (err != noErr) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: char path[PATH_MAX]; michael@0: OSStatus status = FSRefMakePath(&fsRef, (UInt8*)path, PATH_MAX); michael@0: if (status != noErr) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: tempPath = path; michael@0: michael@0: #elif defined(__ANDROID__) michael@0: // GeckoAppShell or Gonk's init.rc sets this in the environment michael@0: const char *tempenv = PR_GetEnv("TMPDIR"); michael@0: if (!tempenv) michael@0: return NS_ERROR_FAILURE; michael@0: nsCString tempPath(tempenv); michael@0: #elif defined(XP_UNIX) michael@0: // we assume it's always /tmp on unix systems michael@0: nsCString tempPath = NS_LITERAL_CSTRING("/tmp/"); michael@0: #else michael@0: #error "Implement this for your platform" michael@0: #endif michael@0: michael@0: #ifdef XP_MACOSX michael@0: // Initialize spawn attributes, since this calls malloc. michael@0: if (posix_spawnattr_init(&spawnattr) != 0) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Set spawn attributes. michael@0: size_t attr_count = ArrayLength(pref_cpu_types); michael@0: size_t attr_ocount = 0; michael@0: if (posix_spawnattr_setbinpref_np(&spawnattr, michael@0: attr_count, michael@0: pref_cpu_types, michael@0: &attr_ocount) != 0 || michael@0: attr_ocount != attr_count) { michael@0: posix_spawnattr_destroy(&spawnattr); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: #endif michael@0: michael@0: #ifdef XP_WIN32 michael@0: ReserveBreakpadVM(); michael@0: michael@0: MINIDUMP_TYPE minidump_type = MiniDumpNormal; michael@0: michael@0: // Try to determine what version of dbghelp.dll we're using. michael@0: // MinidumpWithFullMemoryInfo is only available in 6.1.x or newer. michael@0: michael@0: DWORD version_size = GetFileVersionInfoSizeW(L"dbghelp.dll", nullptr); michael@0: if (version_size > 0) { michael@0: std::vector buffer(version_size); michael@0: if (GetFileVersionInfoW(L"dbghelp.dll", michael@0: 0, michael@0: version_size, michael@0: &buffer[0])) { michael@0: UINT len; michael@0: VS_FIXEDFILEINFO* file_info; michael@0: VerQueryValue(&buffer[0], L"\\", (void**)&file_info, &len); michael@0: WORD major = HIWORD(file_info->dwFileVersionMS), michael@0: minor = LOWORD(file_info->dwFileVersionMS), michael@0: revision = HIWORD(file_info->dwFileVersionLS); michael@0: if (major > 6 || (major == 6 && minor > 1) || michael@0: (major == 6 && minor == 1 && revision >= 7600)) { michael@0: minidump_type = MiniDumpWithFullMemoryInfo; michael@0: } michael@0: } michael@0: } michael@0: michael@0: const char* e = PR_GetEnv("MOZ_CRASHREPORTER_FULLDUMP"); michael@0: if (e && *e) { michael@0: minidump_type = MiniDumpWithFullMemory; michael@0: } michael@0: #endif // XP_WIN32 michael@0: michael@0: #ifdef MOZ_WIDGET_ANDROID michael@0: androidUserSerial = getenv("MOZ_ANDROID_USER_SERIAL_NUMBER"); michael@0: #endif michael@0: michael@0: // Initialize the flag and mutex used to avoid dump processing michael@0: // once browser termination has begun. michael@0: NS_ASSERTION(!dumpSafetyLock, "Shouldn't have a lock yet"); michael@0: // Do not deallocate this lock while it is still possible for michael@0: // isSafeToDump to be tested on another thread. michael@0: dumpSafetyLock = new Mutex("dumpSafetyLock"); michael@0: MutexAutoLock lock(*dumpSafetyLock); michael@0: isSafeToDump = true; michael@0: michael@0: // now set the exception handler michael@0: #ifdef XP_LINUX michael@0: MinidumpDescriptor descriptor(tempPath.get()); michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: NotePreviousUnhandledExceptionFilter(); michael@0: #endif michael@0: michael@0: gExceptionHandler = new google_breakpad:: michael@0: ExceptionHandler( michael@0: #ifdef XP_LINUX michael@0: descriptor, michael@0: #else michael@0: tempPath.get(), michael@0: #endif michael@0: michael@0: #ifdef XP_WIN michael@0: FPEFilter, michael@0: #else michael@0: Filter, michael@0: #endif michael@0: MinidumpCallback, michael@0: nullptr, michael@0: #ifdef XP_WIN32 michael@0: google_breakpad::ExceptionHandler::HANDLER_ALL, michael@0: minidump_type, michael@0: (const wchar_t*) nullptr, michael@0: nullptr); michael@0: #else michael@0: true michael@0: #ifdef XP_MACOSX michael@0: , nullptr michael@0: #endif michael@0: #ifdef XP_LINUX michael@0: , -1 michael@0: #endif michael@0: ); michael@0: #endif // XP_WIN32 michael@0: michael@0: if (!gExceptionHandler) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: #ifdef XP_WIN michael@0: gExceptionHandler->set_handle_debug_exceptions(true); michael@0: michael@0: // protect the crash reporter from being unloaded michael@0: gBlockUnhandledExceptionFilter = true; michael@0: gKernel32Intercept.Init("kernel32.dll"); michael@0: bool ok = gKernel32Intercept.AddHook("SetUnhandledExceptionFilter", michael@0: reinterpret_cast(patched_SetUnhandledExceptionFilter), michael@0: (void**) &stub_SetUnhandledExceptionFilter); michael@0: michael@0: #ifdef DEBUG michael@0: if (!ok) michael@0: printf_stderr ("SetUnhandledExceptionFilter hook failed; crash reporter is vulnerable.\n"); michael@0: #endif michael@0: #endif michael@0: michael@0: // store application start time michael@0: char timeString[32]; michael@0: time_t startupTime = time(nullptr); michael@0: XP_TTOA(startupTime, timeString, 10); michael@0: AnnotateCrashReport(NS_LITERAL_CSTRING("StartupTime"), michael@0: nsDependentCString(timeString)); michael@0: michael@0: #if defined(XP_MACOSX) michael@0: // On OS X, many testers like to see the OS crash reporting dialog michael@0: // since it offers immediate stack traces. We allow them to set michael@0: // a default to pass exceptions to the OS handler. michael@0: Boolean keyExistsAndHasValidFormat = false; michael@0: Boolean prefValue = ::CFPreferencesGetAppBooleanValue(CFSTR("OSCrashReporter"), michael@0: kCFPreferencesCurrentApplication, michael@0: &keyExistsAndHasValidFormat); michael@0: if (keyExistsAndHasValidFormat) michael@0: showOSCrashReporter = prefValue; michael@0: #endif michael@0: michael@0: #if defined(MOZ_WIDGET_ANDROID) michael@0: for (unsigned int i = 0; i < library_mappings.size(); i++) { michael@0: u_int8_t guid[sizeof(MDGUID)]; michael@0: google_breakpad::FileID::ElfFileIdentifierFromMappedFile((void const *)library_mappings[i].start_address, guid); michael@0: gExceptionHandler->AddMappingInfo(library_mappings[i].name, michael@0: guid, michael@0: library_mappings[i].start_address, michael@0: library_mappings[i].length, michael@0: library_mappings[i].file_offset); michael@0: } michael@0: #endif michael@0: michael@0: mozalloc_set_oom_abort_handler(AnnotateOOMAllocationSize); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool GetEnabled() michael@0: { michael@0: return gExceptionHandler != nullptr; michael@0: } michael@0: michael@0: bool GetMinidumpPath(nsAString& aPath) michael@0: { michael@0: if (!gExceptionHandler) michael@0: return false; michael@0: michael@0: #ifndef XP_LINUX michael@0: aPath = CONVERT_XP_CHAR_TO_UTF16(gExceptionHandler->dump_path().c_str()); michael@0: #else michael@0: aPath = CONVERT_XP_CHAR_TO_UTF16( michael@0: gExceptionHandler->minidump_descriptor().directory().c_str()); michael@0: #endif michael@0: return true; michael@0: } michael@0: michael@0: nsresult SetMinidumpPath(const nsAString& aPath) michael@0: { michael@0: if (!gExceptionHandler) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: #ifdef XP_WIN32 michael@0: gExceptionHandler->set_dump_path(char16ptr_t(aPath.BeginReading())); michael@0: #elif defined(XP_LINUX) michael@0: gExceptionHandler->set_minidump_descriptor( michael@0: MinidumpDescriptor(NS_ConvertUTF16toUTF8(aPath).BeginReading())); michael@0: #else michael@0: gExceptionHandler->set_dump_path(NS_ConvertUTF16toUTF8(aPath).BeginReading()); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: WriteDataToFile(nsIFile* aFile, const nsACString& data) michael@0: { michael@0: PRFileDesc* fd; michael@0: nsresult rv = aFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 00600, &fd); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = NS_OK; michael@0: if (PR_Write(fd, data.Data(), data.Length()) == -1) { michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: PR_Close(fd); michael@0: return rv; michael@0: } michael@0: michael@0: static nsresult michael@0: GetFileContents(nsIFile* aFile, nsACString& data) michael@0: { michael@0: PRFileDesc* fd; michael@0: nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = NS_OK; michael@0: int32_t filesize = PR_Available(fd); michael@0: if (filesize <= 0) { michael@0: rv = NS_ERROR_FILE_NOT_FOUND; michael@0: } michael@0: else { michael@0: data.SetLength(filesize); michael@0: if (PR_Read(fd, data.BeginWriting(), filesize) == -1) { michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: PR_Close(fd); michael@0: return rv; michael@0: } michael@0: michael@0: // Function typedef for initializing a piece of data that we michael@0: // don't already have. michael@0: typedef nsresult (*InitDataFunc)(nsACString&); michael@0: michael@0: // Attempt to read aFile's contents into aContents, if aFile michael@0: // does not exist, create it and initialize its contents michael@0: // by calling aInitFunc for the data. michael@0: static nsresult michael@0: GetOrInit(nsIFile* aDir, const nsACString& filename, michael@0: nsACString& aContents, InitDataFunc aInitFunc) michael@0: { michael@0: bool exists; michael@0: michael@0: nsCOMPtr dataFile; michael@0: nsresult rv = aDir->Clone(getter_AddRefs(dataFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = dataFile->AppendNative(filename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = dataFile->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: if (aInitFunc) { michael@0: // get the initial value and write it to the file michael@0: rv = aInitFunc(aContents); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = WriteDataToFile(dataFile, aContents); michael@0: } michael@0: else { michael@0: // didn't pass in an init func michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: else { michael@0: // just get the file's contents michael@0: rv = GetFileContents(dataFile, aContents); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // Init the "install time" data. We're taking an easy way out here michael@0: // and just setting this to "the time when this version was first run". michael@0: static nsresult michael@0: InitInstallTime(nsACString& aInstallTime) michael@0: { michael@0: time_t t = time(nullptr); michael@0: char buf[16]; michael@0: sprintf(buf, "%ld", t); michael@0: aInstallTime = buf; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Ensure a directory exists and create it if missing. michael@0: static nsresult michael@0: EnsureDirectoryExists(nsIFile* dir) michael@0: { michael@0: nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700); michael@0: michael@0: if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) { michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Annotate the crash report with a Unique User ID and time michael@0: // since install. Also do some prep work for recording michael@0: // time since last crash, which must be calculated at michael@0: // crash time. michael@0: // If any piece of data doesn't exist, initialize it first. michael@0: nsresult SetupExtraData(nsIFile* aAppDataDirectory, michael@0: const nsACString& aBuildID) michael@0: { michael@0: nsCOMPtr dataDirectory; michael@0: nsresult rv = aAppDataDirectory->Clone(getter_AddRefs(dataDirectory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = dataDirectory->AppendNative(NS_LITERAL_CSTRING("Crash Reports")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: EnsureDirectoryExists(dataDirectory); michael@0: michael@0: #if defined(XP_WIN32) michael@0: nsAutoString dataDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_DATA_DIRECTORY=")); michael@0: michael@0: nsAutoString dataDirectoryPath; michael@0: rv = dataDirectory->GetPath(dataDirectoryPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: dataDirEnv.Append(dataDirectoryPath); michael@0: michael@0: _wputenv(dataDirEnv.get()); michael@0: #else michael@0: // Save this path in the environment for the crash reporter application. michael@0: nsAutoCString dataDirEnv("MOZ_CRASHREPORTER_DATA_DIRECTORY="); michael@0: michael@0: nsAutoCString dataDirectoryPath; michael@0: rv = dataDirectory->GetNativePath(dataDirectoryPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: dataDirEnv.Append(dataDirectoryPath); michael@0: michael@0: char* env = ToNewCString(dataDirEnv); michael@0: NS_ENSURE_TRUE(env, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: PR_SetEnv(env); michael@0: #endif michael@0: michael@0: nsAutoCString data; michael@0: if(NS_SUCCEEDED(GetOrInit(dataDirectory, michael@0: NS_LITERAL_CSTRING("InstallTime") + aBuildID, michael@0: data, InitInstallTime))) michael@0: AnnotateCrashReport(NS_LITERAL_CSTRING("InstallTime"), data); michael@0: michael@0: // this is a little different, since we can't init it with anything, michael@0: // since it's stored at crash time, and we can't annotate the michael@0: // crash report with the stored value, since we really want michael@0: // (now - LastCrash), so we just get a value if it exists, michael@0: // and store it in a time_t value. michael@0: if(NS_SUCCEEDED(GetOrInit(dataDirectory, NS_LITERAL_CSTRING("LastCrash"), michael@0: data, nullptr))) { michael@0: lastCrashTime = (time_t)atol(data.get()); michael@0: } michael@0: michael@0: // not really the best place to init this, but I have the path I need here michael@0: nsCOMPtr lastCrashFile; michael@0: rv = dataDirectory->Clone(getter_AddRefs(lastCrashFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = lastCrashFile->AppendNative(NS_LITERAL_CSTRING("LastCrash")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: memset(lastCrashTimeFilename, 0, sizeof(lastCrashTimeFilename)); michael@0: michael@0: #if defined(XP_WIN32) michael@0: nsAutoString filename; michael@0: rv = lastCrashFile->GetPath(filename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (filename.Length() < XP_PATH_MAX) michael@0: wcsncpy(lastCrashTimeFilename, filename.get(), filename.Length()); michael@0: #else michael@0: nsAutoCString filename; michael@0: rv = lastCrashFile->GetNativePath(filename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (filename.Length() < XP_PATH_MAX) michael@0: strncpy(lastCrashTimeFilename, filename.get(), filename.Length()); michael@0: #endif michael@0: michael@0: if (headlessClient) { michael@0: nsCOMPtr markerFile; michael@0: rv = dataDirectory->Clone(getter_AddRefs(markerFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = markerFile->AppendNative(NS_LITERAL_CSTRING("LastCrashFilename")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: memset(crashMarkerFilename, 0, sizeof(crashMarkerFilename)); michael@0: michael@0: #if defined(XP_WIN32) michael@0: nsAutoString markerFilename; michael@0: rv = markerFile->GetPath(markerFilename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (markerFilename.Length() < XP_PATH_MAX) michael@0: wcsncpy(crashMarkerFilename, markerFilename.get(), michael@0: markerFilename.Length()); michael@0: #else michael@0: nsAutoCString markerFilename; michael@0: rv = markerFile->GetNativePath(markerFilename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (markerFilename.Length() < XP_PATH_MAX) michael@0: strncpy(crashMarkerFilename, markerFilename.get(), michael@0: markerFilename.Length()); michael@0: #endif michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void OOPDeinit(); michael@0: michael@0: nsresult UnsetExceptionHandler() michael@0: { michael@0: if (isSafeToDump) { michael@0: MutexAutoLock lock(*dumpSafetyLock); michael@0: isSafeToDump = false; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: // allow SetUnhandledExceptionFilter michael@0: gBlockUnhandledExceptionFilter = false; michael@0: #endif michael@0: michael@0: delete gExceptionHandler; michael@0: michael@0: // do this here in the unlikely case that we succeeded in allocating michael@0: // our strings but failed to allocate gExceptionHandler. michael@0: delete crashReporterAPIData_Hash; michael@0: crashReporterAPIData_Hash = nullptr; michael@0: michael@0: delete crashReporterAPILock; michael@0: crashReporterAPILock = nullptr; michael@0: michael@0: delete notesFieldLock; michael@0: notesFieldLock = nullptr; michael@0: michael@0: delete crashReporterAPIData; michael@0: crashReporterAPIData = nullptr; michael@0: michael@0: delete notesField; michael@0: notesField = nullptr; michael@0: michael@0: delete lastRunCrashID; michael@0: lastRunCrashID = nullptr; michael@0: michael@0: if (pendingDirectory) { michael@0: NS_Free(pendingDirectory); michael@0: pendingDirectory = nullptr; michael@0: } michael@0: michael@0: if (crashReporterPath) { michael@0: NS_Free(crashReporterPath); michael@0: crashReporterPath = nullptr; michael@0: } michael@0: michael@0: if (eventsDirectory) { michael@0: NS_Free(eventsDirectory); michael@0: eventsDirectory = nullptr; michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: posix_spawnattr_destroy(&spawnattr); michael@0: #endif michael@0: michael@0: if (!gExceptionHandler) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: gExceptionHandler = nullptr; michael@0: michael@0: OOPDeinit(); michael@0: michael@0: delete dumpSafetyLock; michael@0: dumpSafetyLock = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void ReplaceChar(nsCString& str, const nsACString& character, michael@0: const nsACString& replacement) michael@0: { michael@0: nsCString::const_iterator start, end; michael@0: michael@0: str.BeginReading(start); michael@0: str.EndReading(end); michael@0: michael@0: while (FindInReadable(character, start, end)) { michael@0: int32_t pos = end.size_backward(); michael@0: str.Replace(pos - 1, 1, replacement); michael@0: michael@0: str.BeginReading(start); michael@0: start.advance(pos + replacement.Length() - 1); michael@0: str.EndReading(end); michael@0: } michael@0: } michael@0: michael@0: static bool DoFindInReadable(const nsACString& str, const nsACString& value) michael@0: { michael@0: nsACString::const_iterator start, end; michael@0: str.BeginReading(start); michael@0: str.EndReading(end); michael@0: michael@0: return FindInReadable(value, start, end); michael@0: } michael@0: michael@0: static PLDHashOperator EnumerateEntries(const nsACString& key, michael@0: nsCString entry, michael@0: void* userData) michael@0: { michael@0: crashReporterAPIData->Append(key + NS_LITERAL_CSTRING("=") + entry + michael@0: NS_LITERAL_CSTRING("\n")); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // This function is miscompiled with MSVC 2005/2008 when PGO is on. michael@0: #ifdef _MSC_VER michael@0: #pragma optimize("", off) michael@0: #endif michael@0: static nsresult michael@0: EscapeAnnotation(const nsACString& key, const nsACString& data, nsCString& escapedData) michael@0: { michael@0: if (DoFindInReadable(key, NS_LITERAL_CSTRING("=")) || michael@0: DoFindInReadable(key, NS_LITERAL_CSTRING("\n"))) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (DoFindInReadable(data, NS_LITERAL_CSTRING("\0"))) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: escapedData = data; michael@0: michael@0: // escape backslashes michael@0: ReplaceChar(escapedData, NS_LITERAL_CSTRING("\\"), michael@0: NS_LITERAL_CSTRING("\\\\")); michael@0: // escape newlines michael@0: ReplaceChar(escapedData, NS_LITERAL_CSTRING("\n"), michael@0: NS_LITERAL_CSTRING("\\n")); michael@0: return NS_OK; michael@0: } michael@0: #ifdef _MSC_VER michael@0: #pragma optimize("", on) michael@0: #endif michael@0: michael@0: class DelayedNote michael@0: { michael@0: public: michael@0: DelayedNote(const nsACString& aKey, const nsACString& aData) michael@0: : mKey(aKey), mData(aData), mType(Annotation) {} michael@0: michael@0: DelayedNote(const nsACString& aData) michael@0: : mData(aData), mType(AppNote) {} michael@0: michael@0: void Run() michael@0: { michael@0: if (mType == Annotation) { michael@0: AnnotateCrashReport(mKey, mData); michael@0: } else { michael@0: AppendAppNotesToCrashReport(mData); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: nsCString mKey; michael@0: nsCString mData; michael@0: enum AnnotationType { Annotation, AppNote } mType; michael@0: }; michael@0: michael@0: static void michael@0: EnqueueDelayedNote(DelayedNote* aNote) michael@0: { michael@0: if (!gDelayedAnnotations) { michael@0: gDelayedAnnotations = new nsTArray >(); michael@0: } michael@0: gDelayedAnnotations->AppendElement(aNote); michael@0: } michael@0: michael@0: nsresult AnnotateCrashReport(const nsACString& key, const nsACString& data) michael@0: { michael@0: if (!GetEnabled()) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: nsCString escapedData; michael@0: nsresult rv = EscapeAnnotation(key, data, escapedData); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("Cannot call AnnotateCrashReport in child processes from non-main thread."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: PCrashReporterChild* reporter = CrashReporterChild::GetCrashReporter(); michael@0: if (!reporter) { michael@0: EnqueueDelayedNote(new DelayedNote(key, data)); michael@0: return NS_OK; michael@0: } michael@0: if (!reporter->SendAnnotateCrashReport(nsCString(key), escapedData)) michael@0: return NS_ERROR_FAILURE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: MutexAutoLock lock(*crashReporterAPILock); michael@0: michael@0: crashReporterAPIData_Hash->Put(key, escapedData); michael@0: michael@0: // now rebuild the file contents michael@0: crashReporterAPIData->Truncate(0); michael@0: crashReporterAPIData_Hash->EnumerateRead(EnumerateEntries, michael@0: crashReporterAPIData); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult SetGarbageCollecting(bool collecting) michael@0: { michael@0: if (!GetEnabled()) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: isGarbageCollecting = collecting; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void SetEventloopNestingLevel(uint32_t level) michael@0: { michael@0: eventloopNestingLevel = level; michael@0: } michael@0: michael@0: nsresult AppendAppNotesToCrashReport(const nsACString& data) michael@0: { michael@0: if (!GetEnabled()) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: if (DoFindInReadable(data, NS_LITERAL_CSTRING("\0"))) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("Cannot call AnnotateCrashReport in child processes from non-main thread."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: PCrashReporterChild* reporter = CrashReporterChild::GetCrashReporter(); michael@0: if (!reporter) { michael@0: EnqueueDelayedNote(new DelayedNote(data)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Since we don't go through AnnotateCrashReport in the parent process, michael@0: // we must ensure that the data is escaped and valid before the parent michael@0: // sees it. michael@0: nsCString escapedData; michael@0: nsresult rv = EscapeAnnotation(NS_LITERAL_CSTRING("Notes"), data, escapedData); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (!reporter->SendAppendAppNotes(escapedData)) michael@0: return NS_ERROR_FAILURE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: MutexAutoLock lock(*notesFieldLock); michael@0: michael@0: notesField->Append(data); michael@0: return AnnotateCrashReport(NS_LITERAL_CSTRING("Notes"), *notesField); michael@0: } michael@0: michael@0: // Returns true if found, false if not found. michael@0: bool GetAnnotation(const nsACString& key, nsACString& data) michael@0: { michael@0: if (!gExceptionHandler) michael@0: return false; michael@0: michael@0: nsAutoCString entry; michael@0: if (!crashReporterAPIData_Hash->Get(key, &entry)) michael@0: return false; michael@0: michael@0: data = entry; michael@0: return true; michael@0: } michael@0: michael@0: nsresult RegisterAppMemory(void* ptr, size_t length) michael@0: { michael@0: if (!GetEnabled()) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: #if defined(XP_LINUX) || defined(XP_WIN32) michael@0: gExceptionHandler->RegisterAppMemory(ptr, length); michael@0: return NS_OK; michael@0: #else michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif michael@0: } michael@0: michael@0: nsresult UnregisterAppMemory(void* ptr) michael@0: { michael@0: if (!GetEnabled()) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: #if defined(XP_LINUX) || defined(XP_WIN32) michael@0: gExceptionHandler->UnregisterAppMemory(ptr); michael@0: return NS_OK; michael@0: #else michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif michael@0: } michael@0: michael@0: bool GetServerURL(nsACString& aServerURL) michael@0: { michael@0: if (!gExceptionHandler) michael@0: return false; michael@0: michael@0: return GetAnnotation(NS_LITERAL_CSTRING("ServerURL"), aServerURL); michael@0: } michael@0: michael@0: nsresult SetServerURL(const nsACString& aServerURL) michael@0: { michael@0: // store server URL with the API data michael@0: // the client knows to handle this specially michael@0: return AnnotateCrashReport(NS_LITERAL_CSTRING("ServerURL"), michael@0: aServerURL); michael@0: } michael@0: michael@0: nsresult michael@0: SetRestartArgs(int argc, char** argv) michael@0: { michael@0: if (!gExceptionHandler) michael@0: return NS_OK; michael@0: michael@0: int i; michael@0: nsAutoCString envVar; michael@0: char *env; michael@0: char *argv0 = getenv("MOZ_APP_LAUNCHER"); michael@0: for (i = 0; i < argc; i++) { michael@0: envVar = "MOZ_CRASHREPORTER_RESTART_ARG_"; michael@0: envVar.AppendInt(i); michael@0: envVar += "="; michael@0: if (argv0 && i == 0) { michael@0: // Is there a request to suppress default binary launcher? michael@0: envVar += argv0; michael@0: } else { michael@0: envVar += argv[i]; michael@0: } michael@0: michael@0: // PR_SetEnv() wants the string to be available for the lifetime michael@0: // of the app, so dup it here michael@0: env = ToNewCString(envVar); michael@0: if (!env) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: PR_SetEnv(env); michael@0: } michael@0: michael@0: // make sure the arg list is terminated michael@0: envVar = "MOZ_CRASHREPORTER_RESTART_ARG_"; michael@0: envVar.AppendInt(i); michael@0: envVar += "="; michael@0: michael@0: // PR_SetEnv() wants the string to be available for the lifetime michael@0: // of the app, so dup it here michael@0: env = ToNewCString(envVar); michael@0: if (!env) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: PR_SetEnv(env); michael@0: michael@0: // make sure we save the info in XUL_APP_FILE for the reporter michael@0: const char *appfile = PR_GetEnv("XUL_APP_FILE"); michael@0: if (appfile && *appfile) { michael@0: envVar = "MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE="; michael@0: envVar += appfile; michael@0: env = ToNewCString(envVar); michael@0: PR_SetEnv(env); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef XP_WIN32 michael@0: nsresult WriteMinidumpForException(EXCEPTION_POINTERS* aExceptionInfo) michael@0: { michael@0: if (!gExceptionHandler) michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: return gExceptionHandler->WriteMinidumpForException(aExceptionInfo) ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: #endif michael@0: michael@0: #ifdef XP_LINUX michael@0: bool WriteMinidumpForSigInfo(int signo, siginfo_t* info, void* uc) michael@0: { michael@0: return gExceptionHandler->HandleSignal(signo, info, uc); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef XP_MACOSX michael@0: nsresult AppendObjCExceptionInfoToAppNotes(void *inException) michael@0: { michael@0: nsAutoCString excString; michael@0: GetObjCExceptionInfo(inException, excString); michael@0: AppendAppNotesToCrashReport(excString); michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: /* michael@0: * Combined code to get/set the crash reporter submission pref on michael@0: * different platforms. michael@0: */ michael@0: static nsresult PrefSubmitReports(bool* aSubmitReports, bool writePref) michael@0: { michael@0: nsresult rv; michael@0: #if defined(XP_WIN32) michael@0: /* michael@0: * NOTE! This needs to stay in sync with the preference checking code michael@0: * in toolkit/crashreporter/client/crashreporter_win.cpp michael@0: */ michael@0: nsCOMPtr appinfo = michael@0: do_GetService("@mozilla.org/xre/app-info;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString appVendor, appName; michael@0: rv = appinfo->GetVendor(appVendor); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = appinfo->GetName(appName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr regKey michael@0: (do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString regPath; michael@0: michael@0: regPath.AppendLiteral("Software\\"); michael@0: michael@0: // We need to ensure the registry keys are created so we can properly michael@0: // write values to it michael@0: michael@0: // Create appVendor key michael@0: if(!appVendor.IsEmpty()) { michael@0: regPath.Append(appVendor); michael@0: regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, michael@0: NS_ConvertUTF8toUTF16(regPath), michael@0: nsIWindowsRegKey::ACCESS_SET_VALUE); michael@0: regPath.AppendLiteral("\\"); michael@0: } michael@0: michael@0: // Create appName key michael@0: regPath.Append(appName); michael@0: regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, michael@0: NS_ConvertUTF8toUTF16(regPath), michael@0: nsIWindowsRegKey::ACCESS_SET_VALUE); michael@0: regPath.AppendLiteral("\\"); michael@0: michael@0: // Create Crash Reporter key michael@0: regPath.AppendLiteral("Crash Reporter"); michael@0: regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, michael@0: NS_ConvertUTF8toUTF16(regPath), michael@0: nsIWindowsRegKey::ACCESS_SET_VALUE); michael@0: michael@0: // If we're saving the pref value, just write it to ROOT_KEY_CURRENT_USER michael@0: // and we're done. michael@0: if (writePref) { michael@0: rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, michael@0: NS_ConvertUTF8toUTF16(regPath), michael@0: nsIWindowsRegKey::ACCESS_SET_VALUE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t value = *aSubmitReports ? 1 : 0; michael@0: rv = regKey->WriteIntValue(NS_LITERAL_STRING("SubmitCrashReport"), value); michael@0: regKey->Close(); michael@0: return rv; michael@0: } michael@0: michael@0: // We're reading the pref value, so we need to first look under michael@0: // ROOT_KEY_LOCAL_MACHINE to see if it's set there, and then fall back to michael@0: // ROOT_KEY_CURRENT_USER. If it's not set in either place, the pref defaults michael@0: // to "true". michael@0: uint32_t value; michael@0: rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, michael@0: NS_ConvertUTF8toUTF16(regPath), michael@0: nsIWindowsRegKey::ACCESS_QUERY_VALUE); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = regKey->ReadIntValue(NS_LITERAL_STRING("SubmitCrashReport"), &value); michael@0: regKey->Close(); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: *aSubmitReports = !!value; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, michael@0: NS_ConvertUTF8toUTF16(regPath), michael@0: nsIWindowsRegKey::ACCESS_QUERY_VALUE); michael@0: if (NS_FAILED(rv)) { michael@0: *aSubmitReports = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = regKey->ReadIntValue(NS_LITERAL_STRING("SubmitCrashReport"), &value); michael@0: // default to true on failure michael@0: if (NS_FAILED(rv)) { michael@0: value = 1; michael@0: rv = NS_OK; michael@0: } michael@0: regKey->Close(); michael@0: michael@0: *aSubmitReports = !!value; michael@0: return NS_OK; michael@0: #elif defined(XP_MACOSX) michael@0: rv = NS_OK; michael@0: if (writePref) { michael@0: CFPropertyListRef cfValue = (CFPropertyListRef)(*aSubmitReports ? kCFBooleanTrue : kCFBooleanFalse); michael@0: ::CFPreferencesSetAppValue(CFSTR("submitReport"), michael@0: cfValue, michael@0: reporterClientAppID); michael@0: if (!::CFPreferencesAppSynchronize(reporterClientAppID)) michael@0: rv = NS_ERROR_FAILURE; michael@0: } michael@0: else { michael@0: *aSubmitReports = true; michael@0: Boolean keyExistsAndHasValidFormat = false; michael@0: Boolean prefValue = ::CFPreferencesGetAppBooleanValue(CFSTR("submitReport"), michael@0: reporterClientAppID, michael@0: &keyExistsAndHasValidFormat); michael@0: if (keyExistsAndHasValidFormat) michael@0: *aSubmitReports = !!prefValue; michael@0: } michael@0: return rv; michael@0: #elif defined(XP_UNIX) michael@0: /* michael@0: * NOTE! This needs to stay in sync with the preference checking code michael@0: * in toolkit/crashreporter/client/crashreporter_linux.cpp michael@0: */ michael@0: nsCOMPtr reporterINI; michael@0: rv = NS_GetSpecialDirectory("UAppData", getter_AddRefs(reporterINI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: reporterINI->AppendNative(NS_LITERAL_CSTRING("Crash Reports")); michael@0: reporterINI->AppendNative(NS_LITERAL_CSTRING("crashreporter.ini")); michael@0: michael@0: bool exists; michael@0: rv = reporterINI->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!exists) { michael@0: if (!writePref) { michael@0: // If reading the pref, default to true if .ini doesn't exist. michael@0: *aSubmitReports = true; michael@0: return NS_OK; michael@0: } michael@0: // Create the file so the INI processor can write to it. michael@0: rv = reporterINI->Create(nsIFile::NORMAL_FILE_TYPE, 0600); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr iniFactory = michael@0: do_GetService("@mozilla.org/xpcom/ini-processor-factory;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr iniParser; michael@0: rv = iniFactory->CreateINIParser(reporterINI, michael@0: getter_AddRefs(iniParser)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If we're writing the pref, just set and we're done. michael@0: if (writePref) { michael@0: nsCOMPtr iniWriter = do_QueryInterface(iniParser); michael@0: NS_ENSURE_TRUE(iniWriter, NS_ERROR_FAILURE); michael@0: michael@0: rv = iniWriter->SetString(NS_LITERAL_CSTRING("Crash Reporter"), michael@0: NS_LITERAL_CSTRING("SubmitReport"), michael@0: *aSubmitReports ? NS_LITERAL_CSTRING("1") : michael@0: NS_LITERAL_CSTRING("0")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = iniWriter->WriteFile(nullptr, 0); michael@0: return rv; michael@0: } michael@0: michael@0: nsAutoCString submitReportValue; michael@0: rv = iniParser->GetString(NS_LITERAL_CSTRING("Crash Reporter"), michael@0: NS_LITERAL_CSTRING("SubmitReport"), michael@0: submitReportValue); michael@0: michael@0: // Default to "true" if the pref can't be found. michael@0: if (NS_FAILED(rv)) michael@0: *aSubmitReports = true; michael@0: else if (submitReportValue.EqualsASCII("0")) michael@0: *aSubmitReports = false; michael@0: else michael@0: *aSubmitReports = true; michael@0: michael@0: return NS_OK; michael@0: #else michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif michael@0: } michael@0: michael@0: nsresult GetSubmitReports(bool* aSubmitReports) michael@0: { michael@0: return PrefSubmitReports(aSubmitReports, false); michael@0: } michael@0: michael@0: nsresult SetSubmitReports(bool aSubmitReports) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr obsServ = michael@0: mozilla::services::GetObserverService(); michael@0: if (!obsServ) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: rv = PrefSubmitReports(&aSubmitReports, true); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: obsServ->NotifyObservers(nullptr, "submit-reports-pref-changed", nullptr); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: UpdateCrashEventsDir() michael@0: { michael@0: nsCOMPtr eventsDir; michael@0: michael@0: // We prefer the following locations in order: michael@0: // michael@0: // 1. If environment variable is present, use it. We don't expect michael@0: // the environment variable except for tests and other atypical setups. michael@0: // 2. Inside the profile directory. michael@0: // 3. Inside the user application data directory (no profile available). michael@0: // 4. A temporary directory (setup likely is invalid / application is buggy). michael@0: const char *env = PR_GetEnv("CRASHES_EVENTS_DIR"); michael@0: if (env) { michael@0: eventsDir = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); michael@0: if (!eventsDir) { michael@0: return; michael@0: } michael@0: eventsDir->InitWithNativePath(nsDependentCString(env)); michael@0: EnsureDirectoryExists(eventsDir); michael@0: } else { michael@0: nsresult rv = NS_GetSpecialDirectory("ProfD", getter_AddRefs(eventsDir)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: eventsDir->Append(NS_LITERAL_STRING("crashes")); michael@0: EnsureDirectoryExists(eventsDir); michael@0: eventsDir->Append(NS_LITERAL_STRING("events")); michael@0: EnsureDirectoryExists(eventsDir); michael@0: } else { michael@0: rv = NS_GetSpecialDirectory("UAppData", getter_AddRefs(eventsDir)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: eventsDir->Append(NS_LITERAL_STRING("Crash Reports")); michael@0: EnsureDirectoryExists(eventsDir); michael@0: eventsDir->Append(NS_LITERAL_STRING("events")); michael@0: EnsureDirectoryExists(eventsDir); michael@0: } else { michael@0: NS_WARNING("Couldn't get the user appdata directory. Crash events may not be produced."); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: nsString path; michael@0: eventsDir->GetPath(path); michael@0: eventsDirectory = reinterpret_cast(ToNewUnicode(path)); michael@0: #else michael@0: nsCString path; michael@0: eventsDir->GetNativePath(path); michael@0: eventsDirectory = ToNewCString(path); michael@0: #endif michael@0: } michael@0: michael@0: bool GetCrashEventsDir(nsAString& aPath) michael@0: { michael@0: if (!eventsDirectory) { michael@0: return false; michael@0: } michael@0: michael@0: aPath = CONVERT_XP_CHAR_TO_UTF16(eventsDirectory); michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: FindPendingDir() michael@0: { michael@0: if (pendingDirectory) michael@0: return; michael@0: michael@0: nsCOMPtr pendingDir; michael@0: nsresult rv = NS_GetSpecialDirectory("UAppData", getter_AddRefs(pendingDir)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Couldn't get the user appdata directory, crash dumps will go in an unusual location"); michael@0: } michael@0: else { michael@0: pendingDir->Append(NS_LITERAL_STRING("Crash Reports")); michael@0: pendingDir->Append(NS_LITERAL_STRING("pending")); michael@0: michael@0: #ifdef XP_WIN michael@0: nsString path; michael@0: pendingDir->GetPath(path); michael@0: pendingDirectory = reinterpret_cast(ToNewUnicode(path)); michael@0: #else michael@0: nsCString path; michael@0: pendingDir->GetNativePath(path); michael@0: pendingDirectory = ToNewCString(path); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: // The "pending" dir is Crash Reports/pending, from which minidumps michael@0: // can be submitted. Because this method may be called off the main thread, michael@0: // we store the pending directory as a path. michael@0: static bool michael@0: GetPendingDir(nsIFile** dir) michael@0: { michael@0: MOZ_ASSERT(OOPInitialized()); michael@0: if (!pendingDirectory) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr pending = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); michael@0: if (!pending) { michael@0: NS_WARNING("Can't set up pending directory during shutdown."); michael@0: return false; michael@0: } michael@0: #ifdef XP_WIN michael@0: pending->InitWithPath(nsDependentString(pendingDirectory)); michael@0: #else michael@0: pending->InitWithNativePath(nsDependentCString(pendingDirectory)); michael@0: #endif michael@0: pending.swap(*dir); michael@0: return true; michael@0: } michael@0: michael@0: // The "limbo" dir is where minidumps go to wait for something else to michael@0: // use them. If we're |ShouldReport()|, then the "something else" is michael@0: // a minidump submitter, and they're coming from the michael@0: // Crash Reports/pending/ dir. Otherwise, we don't know what the michael@0: // "somthing else" is, but the minidumps stay in [profile]/minidumps/ michael@0: // limbo. michael@0: static bool michael@0: GetMinidumpLimboDir(nsIFile** dir) michael@0: { michael@0: if (ShouldReport()) { michael@0: return GetPendingDir(dir); michael@0: } michael@0: else { michael@0: #ifndef XP_LINUX michael@0: CreateFileFromPath(gExceptionHandler->dump_path(), dir); michael@0: #else michael@0: CreateFileFromPath(gExceptionHandler->minidump_descriptor().directory(), michael@0: dir); michael@0: #endif michael@0: return nullptr != *dir; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: GetMinidumpForID(const nsAString& id, nsIFile** minidump) michael@0: { michael@0: if (!GetMinidumpLimboDir(minidump)) michael@0: return false; michael@0: (*minidump)->Append(id + NS_LITERAL_STRING(".dmp")); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: GetIDFromMinidump(nsIFile* minidump, nsAString& id) michael@0: { michael@0: if (NS_SUCCEEDED(minidump->GetLeafName(id))) { michael@0: id.Replace(id.Length() - 4, 4, NS_LITERAL_STRING("")); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: GetExtraFileForID(const nsAString& id, nsIFile** extraFile) michael@0: { michael@0: if (!GetMinidumpLimboDir(extraFile)) michael@0: return false; michael@0: (*extraFile)->Append(id + NS_LITERAL_STRING(".extra")); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: GetExtraFileForMinidump(nsIFile* minidump, nsIFile** extraFile) michael@0: { michael@0: nsAutoString leafName; michael@0: nsresult rv = minidump->GetLeafName(leafName); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: nsCOMPtr extraF; michael@0: rv = minidump->Clone(getter_AddRefs(extraF)); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: leafName.Replace(leafName.Length() - 3, 3, michael@0: NS_LITERAL_STRING("extra")); michael@0: rv = extraF->SetLeafName(leafName); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: *extraFile = nullptr; michael@0: extraF.swap(*extraFile); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: AppendExtraData(const nsAString& id, const AnnotationTable& data) michael@0: { michael@0: nsCOMPtr extraFile; michael@0: if (!GetExtraFileForID(id, getter_AddRefs(extraFile))) michael@0: return false; michael@0: return AppendExtraData(extraFile, data); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Helpers for AppendExtraData() michael@0: // michael@0: struct Blacklist { michael@0: Blacklist() : mItems(nullptr), mLen(0) { } michael@0: Blacklist(const char** items, int len) : mItems(items), mLen(len) { } michael@0: michael@0: bool Contains(const nsACString& key) const { michael@0: for (int i = 0; i < mLen; ++i) michael@0: if (key.EqualsASCII(mItems[i])) michael@0: return true; michael@0: return false; michael@0: } michael@0: michael@0: const char** mItems; michael@0: const int mLen; michael@0: }; michael@0: michael@0: struct EnumerateAnnotationsContext { michael@0: const Blacklist& blacklist; michael@0: PRFileDesc* fd; michael@0: }; michael@0: michael@0: static void michael@0: WriteAnnotation(PRFileDesc* fd, const nsACString& key, const nsACString& value) michael@0: { michael@0: PR_Write(fd, key.BeginReading(), key.Length()); michael@0: PR_Write(fd, "=", 1); michael@0: PR_Write(fd, value.BeginReading(), value.Length()); michael@0: PR_Write(fd, "\n", 1); michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: EnumerateAnnotations(const nsACString& key, michael@0: nsCString entry, michael@0: void* userData) michael@0: { michael@0: EnumerateAnnotationsContext* ctx = michael@0: static_cast(userData); michael@0: const Blacklist& blacklist = ctx->blacklist; michael@0: michael@0: // skip entries in the blacklist michael@0: if (blacklist.Contains(key)) michael@0: return PL_DHASH_NEXT; michael@0: michael@0: WriteAnnotation(ctx->fd, key, entry); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static bool michael@0: WriteExtraData(nsIFile* extraFile, michael@0: const AnnotationTable& data, michael@0: const Blacklist& blacklist, michael@0: bool writeCrashTime=false, michael@0: bool truncate=false) michael@0: { michael@0: PRFileDesc* fd; michael@0: int truncOrAppend = truncate ? PR_TRUNCATE : PR_APPEND; michael@0: nsresult rv = michael@0: extraFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | truncOrAppend, michael@0: 0600, &fd); michael@0: if (NS_FAILED(rv)) michael@0: return false; michael@0: michael@0: EnumerateAnnotationsContext ctx = { blacklist, fd }; michael@0: data.EnumerateRead(EnumerateAnnotations, &ctx); michael@0: michael@0: if (writeCrashTime) { michael@0: time_t crashTime = time(nullptr); michael@0: char crashTimeString[32]; michael@0: XP_TTOA(crashTime, crashTimeString, 10); michael@0: michael@0: WriteAnnotation(fd, michael@0: nsDependentCString("CrashTime"), michael@0: nsDependentCString(crashTimeString)); michael@0: } michael@0: michael@0: PR_Close(fd); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: AppendExtraData(nsIFile* extraFile, const AnnotationTable& data) michael@0: { michael@0: return WriteExtraData(extraFile, data, Blacklist()); michael@0: } michael@0: michael@0: michael@0: static bool michael@0: WriteExtraForMinidump(nsIFile* minidump, michael@0: const Blacklist& blacklist, michael@0: nsIFile** extraFile) michael@0: { michael@0: nsCOMPtr extra; michael@0: if (!GetExtraFileForMinidump(minidump, getter_AddRefs(extra))) michael@0: return false; michael@0: michael@0: if (!WriteExtraData(extra, *crashReporterAPIData_Hash, michael@0: blacklist, michael@0: true /*write crash time*/, michael@0: true /*truncate*/)) michael@0: return false; michael@0: michael@0: *extraFile = nullptr; michael@0: extra.swap(*extraFile); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // It really only makes sense to call this function when michael@0: // ShouldReport() is true. michael@0: static bool michael@0: MoveToPending(nsIFile* dumpFile, nsIFile* extraFile) michael@0: { michael@0: nsCOMPtr pendingDir; michael@0: if (!GetPendingDir(getter_AddRefs(pendingDir))) michael@0: return false; michael@0: michael@0: if (NS_FAILED(dumpFile->MoveTo(pendingDir, EmptyString()))) { michael@0: return false; michael@0: } michael@0: michael@0: if (extraFile && NS_FAILED(extraFile->MoveTo(pendingDir, EmptyString()))) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: OnChildProcessDumpRequested(void* aContext, michael@0: #ifdef XP_MACOSX michael@0: const ClientInfo& aClientInfo, michael@0: const xpstring& aFilePath michael@0: #else michael@0: const ClientInfo* aClientInfo, michael@0: const xpstring* aFilePath michael@0: #endif michael@0: ) michael@0: { michael@0: nsCOMPtr minidump; michael@0: nsCOMPtr extraFile; michael@0: michael@0: // Hold the mutex until the current dump request is complete, to michael@0: // prevent UnsetExceptionHandler() from pulling the rug out from michael@0: // under us. michael@0: MutexAutoLock lock(*dumpSafetyLock); michael@0: if (!isSafeToDump) michael@0: return; michael@0: michael@0: CreateFileFromPath( michael@0: #ifdef XP_MACOSX michael@0: aFilePath, michael@0: #else michael@0: *aFilePath, michael@0: #endif michael@0: getter_AddRefs(minidump)); michael@0: michael@0: if (!WriteExtraForMinidump(minidump, michael@0: Blacklist(kSubprocessBlacklist, michael@0: ArrayLength(kSubprocessBlacklist)), michael@0: getter_AddRefs(extraFile))) michael@0: return; michael@0: michael@0: if (ShouldReport()) michael@0: MoveToPending(minidump, extraFile); michael@0: michael@0: { michael@0: uint32_t pid = michael@0: #ifdef XP_MACOSX michael@0: aClientInfo.pid(); michael@0: #else michael@0: aClientInfo->pid(); michael@0: #endif michael@0: michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: bool runCallback; michael@0: #endif michael@0: { michael@0: MutexAutoLock lock(*dumpMapLock); michael@0: ChildProcessData* pd = pidToMinidump->PutEntry(pid); michael@0: MOZ_ASSERT(!pd->minidump); michael@0: pd->minidump = minidump; michael@0: pd->sequence = ++crashSequence; michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: runCallback = nullptr != pd->callback; michael@0: #endif michael@0: } michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: if (runCallback) michael@0: NS_DispatchToMainThread(new ReportInjectedCrash(pid)); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: OOPInitialized() michael@0: { michael@0: return pidToMinidump != nullptr; michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: static bool ChildFilter(void *context) { michael@0: mozilla::IOInterposer::Disable(); michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: OOPInit() michael@0: { michael@0: if (OOPInitialized()) michael@0: return; michael@0: michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: NS_ABORT_IF_FALSE(gExceptionHandler != nullptr, michael@0: "attempt to initialize OOP crash reporter before in-process crashreporter!"); michael@0: michael@0: #if defined(XP_WIN) michael@0: childCrashNotifyPipe = michael@0: PR_smprintf("\\\\.\\pipe\\gecko-crash-server-pipe.%i", michael@0: static_cast(::GetCurrentProcessId())); michael@0: michael@0: const std::wstring dumpPath = gExceptionHandler->dump_path(); michael@0: crashServer = new CrashGenerationServer( michael@0: NS_ConvertASCIItoUTF16(childCrashNotifyPipe).get(), michael@0: nullptr, // default security attributes michael@0: nullptr, nullptr, // we don't care about process connect here michael@0: OnChildProcessDumpRequested, nullptr, michael@0: nullptr, nullptr, // we don't care about process exit here michael@0: nullptr, nullptr, // we don't care about upload request here michael@0: true, // automatically generate dumps michael@0: &dumpPath); michael@0: michael@0: #elif defined(XP_LINUX) michael@0: if (!CrashGenerationServer::CreateReportChannel(&serverSocketFd, michael@0: &clientSocketFd)) michael@0: NS_RUNTIMEABORT("can't create crash reporter socketpair()"); michael@0: michael@0: const std::string dumpPath = michael@0: gExceptionHandler->minidump_descriptor().directory(); michael@0: crashServer = new CrashGenerationServer( michael@0: serverSocketFd, michael@0: OnChildProcessDumpRequested, nullptr, michael@0: nullptr, nullptr, // we don't care about process exit here michael@0: true, michael@0: &dumpPath); michael@0: michael@0: #elif defined(XP_MACOSX) michael@0: childCrashNotifyPipe = michael@0: PR_smprintf("gecko-crash-server-pipe.%i", michael@0: static_cast(getpid())); michael@0: const std::string dumpPath = gExceptionHandler->dump_path(); michael@0: michael@0: crashServer = new CrashGenerationServer( michael@0: childCrashNotifyPipe, michael@0: ChildFilter, michael@0: nullptr, michael@0: OnChildProcessDumpRequested, nullptr, michael@0: nullptr, nullptr, michael@0: true, // automatically generate dumps michael@0: dumpPath); michael@0: #endif michael@0: michael@0: if (!crashServer->Start()) michael@0: NS_RUNTIMEABORT("can't start crash reporter server()"); michael@0: michael@0: pidToMinidump = new ChildMinidumpMap(); michael@0: michael@0: dumpMapLock = new Mutex("CrashReporter::dumpMapLock"); michael@0: michael@0: FindPendingDir(); michael@0: UpdateCrashEventsDir(); michael@0: } michael@0: michael@0: static void michael@0: OOPDeinit() michael@0: { michael@0: if (!OOPInitialized()) { michael@0: NS_WARNING("OOPDeinit() without successful OOPInit()"); michael@0: return; michael@0: } michael@0: michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: if (sInjectorThread) { michael@0: sInjectorThread->Shutdown(); michael@0: NS_RELEASE(sInjectorThread); michael@0: } michael@0: #endif michael@0: michael@0: delete crashServer; michael@0: crashServer = nullptr; michael@0: michael@0: delete dumpMapLock; michael@0: dumpMapLock = nullptr; michael@0: michael@0: delete pidToMinidump; michael@0: pidToMinidump = nullptr; michael@0: michael@0: #if defined(XP_WIN) michael@0: PR_Free(childCrashNotifyPipe); michael@0: childCrashNotifyPipe = nullptr; michael@0: #endif michael@0: } michael@0: michael@0: #if defined(XP_WIN) || defined(XP_MACOSX) michael@0: // Parent-side API for children michael@0: const char* michael@0: GetChildNotificationPipe() michael@0: { michael@0: if (!GetEnabled()) michael@0: return kNullNotifyPipe; michael@0: michael@0: MOZ_ASSERT(OOPInitialized()); michael@0: michael@0: return childCrashNotifyPipe; michael@0: } michael@0: #endif michael@0: michael@0: #ifdef MOZ_CRASHREPORTER_INJECTOR michael@0: void michael@0: InjectCrashReporterIntoProcess(DWORD processID, InjectorCrashCallback* cb) michael@0: { michael@0: if (!GetEnabled()) michael@0: return; michael@0: michael@0: if (!OOPInitialized()) michael@0: OOPInit(); michael@0: michael@0: if (!sInjectorThread) { michael@0: if (NS_FAILED(NS_NewThread(&sInjectorThread))) michael@0: return; michael@0: } michael@0: michael@0: { michael@0: MutexAutoLock lock(*dumpMapLock); michael@0: ChildProcessData* pd = pidToMinidump->PutEntry(processID); michael@0: MOZ_ASSERT(!pd->minidump && !pd->callback); michael@0: pd->callback = cb; michael@0: } michael@0: michael@0: nsCOMPtr r = new InjectCrashRunnable(processID); michael@0: sInjectorThread->Dispatch(r, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: ReportInjectedCrash::Run() michael@0: { michael@0: // Crash reporting may have been disabled after this method was dispatched michael@0: if (!OOPInitialized()) michael@0: return NS_OK; michael@0: michael@0: InjectorCrashCallback* cb; michael@0: { michael@0: MutexAutoLock lock(*dumpMapLock); michael@0: ChildProcessData* pd = pidToMinidump->GetEntry(mPID); michael@0: if (!pd || !pd->callback) michael@0: return NS_OK; michael@0: michael@0: MOZ_ASSERT(pd->minidump); michael@0: michael@0: cb = pd->callback; michael@0: } michael@0: michael@0: cb->OnCrash(mPID); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: UnregisterInjectorCallback(DWORD processID) michael@0: { michael@0: if (!OOPInitialized()) michael@0: return; michael@0: michael@0: MutexAutoLock lock(*dumpMapLock); michael@0: pidToMinidump->RemoveEntry(processID); michael@0: } michael@0: michael@0: #endif // MOZ_CRASHREPORTER_INJECTOR michael@0: michael@0: bool michael@0: CheckForLastRunCrash() michael@0: { michael@0: if (lastRunCrashID) michael@0: return true; michael@0: michael@0: // The exception handler callback leaves the filename of the michael@0: // last minidump in a known file. michael@0: nsCOMPtr lastCrashFile; michael@0: CreateFileFromPath(crashMarkerFilename, michael@0: getter_AddRefs(lastCrashFile)); michael@0: michael@0: bool exists; michael@0: if (NS_FAILED(lastCrashFile->Exists(&exists)) || !exists) { michael@0: return false; michael@0: } michael@0: michael@0: nsAutoCString lastMinidump_contents; michael@0: if (NS_FAILED(GetFileContents(lastCrashFile, lastMinidump_contents))) { michael@0: return false; michael@0: } michael@0: lastCrashFile->Remove(false); michael@0: michael@0: #ifdef XP_WIN michael@0: // Ugly but effective. michael@0: nsDependentString lastMinidump( michael@0: reinterpret_cast(lastMinidump_contents.get())); michael@0: #else michael@0: nsAutoCString lastMinidump = lastMinidump_contents; michael@0: #endif michael@0: nsCOMPtr lastMinidumpFile; michael@0: CreateFileFromPath(lastMinidump.get(), michael@0: getter_AddRefs(lastMinidumpFile)); michael@0: michael@0: if (!lastMinidumpFile || NS_FAILED(lastMinidumpFile->Exists(&exists)) || !exists) { michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr lastExtraFile; michael@0: if (!GetExtraFileForMinidump(lastMinidumpFile, michael@0: getter_AddRefs(lastExtraFile))) { michael@0: return false; michael@0: } michael@0: michael@0: FindPendingDir(); michael@0: michael@0: // Move {dump,extra} to pending folder michael@0: if (!MoveToPending(lastMinidumpFile, lastExtraFile)) { michael@0: return false; michael@0: } michael@0: michael@0: lastRunCrashID = new nsString(); michael@0: return GetIDFromMinidump(lastMinidumpFile, *lastRunCrashID); michael@0: } michael@0: michael@0: bool michael@0: GetLastRunCrashID(nsAString& id) michael@0: { michael@0: if (!lastRunCrashID_checked) { michael@0: CheckForLastRunCrash(); michael@0: lastRunCrashID_checked = true; michael@0: } michael@0: michael@0: if (!lastRunCrashID) { michael@0: return false; michael@0: } michael@0: michael@0: id = *lastRunCrashID; michael@0: return true; michael@0: } michael@0: michael@0: #if defined(XP_WIN) michael@0: // Child-side API michael@0: bool michael@0: SetRemoteExceptionHandler(const nsACString& crashPipe) michael@0: { michael@0: // crash reporting is disabled michael@0: if (crashPipe.Equals(kNullNotifyPipe)) michael@0: return true; michael@0: michael@0: NS_ABORT_IF_FALSE(!gExceptionHandler, "crash client already init'd"); michael@0: michael@0: gExceptionHandler = new google_breakpad:: michael@0: ExceptionHandler(L"", michael@0: FPEFilter, michael@0: nullptr, // no minidump callback michael@0: nullptr, // no callback context michael@0: google_breakpad::ExceptionHandler::HANDLER_ALL, michael@0: MiniDumpNormal, michael@0: NS_ConvertASCIItoUTF16(crashPipe).get(), michael@0: nullptr); michael@0: #ifdef XP_WIN michael@0: gExceptionHandler->set_handle_debug_exceptions(true); michael@0: #endif michael@0: michael@0: // we either do remote or nothing, no fallback to regular crash reporting michael@0: return gExceptionHandler->IsOutOfProcess(); michael@0: } michael@0: michael@0: //-------------------------------------------------- michael@0: #elif defined(XP_LINUX) michael@0: michael@0: // Parent-side API for children michael@0: bool michael@0: CreateNotificationPipeForChild(int* childCrashFd, int* childCrashRemapFd) michael@0: { michael@0: if (!GetEnabled()) { michael@0: *childCrashFd = -1; michael@0: *childCrashRemapFd = -1; michael@0: return true; michael@0: } michael@0: michael@0: MOZ_ASSERT(OOPInitialized()); michael@0: michael@0: *childCrashFd = clientSocketFd; michael@0: *childCrashRemapFd = kMagicChildCrashReportFd; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Child-side API michael@0: bool michael@0: SetRemoteExceptionHandler() michael@0: { michael@0: NS_ABORT_IF_FALSE(!gExceptionHandler, "crash client already init'd"); michael@0: michael@0: #ifndef XP_LINUX michael@0: xpstring path = ""; michael@0: #else michael@0: // MinidumpDescriptor requires a non-empty path. michael@0: google_breakpad::MinidumpDescriptor path("."); michael@0: #endif michael@0: gExceptionHandler = new google_breakpad:: michael@0: ExceptionHandler(path, michael@0: nullptr, // no filter callback michael@0: nullptr, // no minidump callback michael@0: nullptr, // no callback context michael@0: true, // install signal handlers michael@0: kMagicChildCrashReportFd); michael@0: michael@0: if (gDelayedAnnotations) { michael@0: for (uint32_t i = 0; i < gDelayedAnnotations->Length(); i++) { michael@0: gDelayedAnnotations->ElementAt(i)->Run(); michael@0: } michael@0: delete gDelayedAnnotations; michael@0: } michael@0: michael@0: // we either do remote or nothing, no fallback to regular crash reporting michael@0: return gExceptionHandler->IsOutOfProcess(); michael@0: } michael@0: michael@0: //-------------------------------------------------- michael@0: #elif defined(XP_MACOSX) michael@0: // Child-side API michael@0: bool michael@0: SetRemoteExceptionHandler(const nsACString& crashPipe) michael@0: { michael@0: // crash reporting is disabled michael@0: if (crashPipe.Equals(kNullNotifyPipe)) michael@0: return true; michael@0: michael@0: NS_ABORT_IF_FALSE(!gExceptionHandler, "crash client already init'd"); michael@0: michael@0: gExceptionHandler = new google_breakpad:: michael@0: ExceptionHandler("", michael@0: Filter, michael@0: nullptr, // no minidump callback michael@0: nullptr, // no callback context michael@0: true, // install signal handlers michael@0: crashPipe.BeginReading()); michael@0: michael@0: // we either do remote or nothing, no fallback to regular crash reporting michael@0: return gExceptionHandler->IsOutOfProcess(); michael@0: } michael@0: #endif // XP_WIN michael@0: michael@0: michael@0: bool michael@0: TakeMinidumpForChild(uint32_t childPid, nsIFile** dump, uint32_t* aSequence) michael@0: { michael@0: if (!GetEnabled()) michael@0: return false; michael@0: michael@0: MutexAutoLock lock(*dumpMapLock); michael@0: michael@0: ChildProcessData* pd = pidToMinidump->GetEntry(childPid); michael@0: if (!pd) michael@0: return false; michael@0: michael@0: NS_IF_ADDREF(*dump = pd->minidump); michael@0: if (aSequence) { michael@0: *aSequence = pd->sequence; michael@0: } michael@0: michael@0: pidToMinidump->RemoveEntry(childPid); michael@0: michael@0: return !!*dump; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // CreatePairedMinidumps() and helpers michael@0: // michael@0: michael@0: void michael@0: RenameAdditionalHangMinidump(nsIFile* minidump, nsIFile* childMinidump, michael@0: const nsACString& name) michael@0: { michael@0: nsCOMPtr directory; michael@0: childMinidump->GetParent(getter_AddRefs(directory)); michael@0: if (!directory) michael@0: return; michael@0: michael@0: nsAutoCString leafName; michael@0: childMinidump->GetNativeLeafName(leafName); michael@0: michael@0: // turn ".dmp" into "-.dmp michael@0: leafName.Insert(NS_LITERAL_CSTRING("-") + name, leafName.Length() - 4); michael@0: michael@0: minidump->MoveToNative(directory, leafName); michael@0: } michael@0: michael@0: static bool michael@0: PairedDumpCallback( michael@0: #ifdef XP_LINUX michael@0: const MinidumpDescriptor& descriptor, michael@0: #else michael@0: const XP_CHAR* dump_path, michael@0: const XP_CHAR* minidump_id, michael@0: #endif michael@0: void* context, michael@0: #ifdef XP_WIN32 michael@0: EXCEPTION_POINTERS* /*unused*/, michael@0: MDRawAssertionInfo* /*unused*/, michael@0: #endif michael@0: bool succeeded) michael@0: { michael@0: nsCOMPtr& minidump = *static_cast< nsCOMPtr* >(context); michael@0: michael@0: xpstring dump; michael@0: #ifdef XP_LINUX michael@0: dump = descriptor.path(); michael@0: #else michael@0: dump = dump_path; michael@0: dump += XP_PATH_SEPARATOR; michael@0: dump += minidump_id; michael@0: dump += dumpFileExtension; michael@0: #endif michael@0: michael@0: CreateFileFromPath(dump, getter_AddRefs(minidump)); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: PairedDumpCallbackExtra( michael@0: #ifdef XP_LINUX michael@0: const MinidumpDescriptor& descriptor, michael@0: #else michael@0: const XP_CHAR* dump_path, michael@0: const XP_CHAR* minidump_id, michael@0: #endif michael@0: void* context, michael@0: #ifdef XP_WIN32 michael@0: EXCEPTION_POINTERS* /*unused*/, michael@0: MDRawAssertionInfo* /*unused*/, michael@0: #endif michael@0: bool succeeded) michael@0: { michael@0: PairedDumpCallback( michael@0: #ifdef XP_LINUX michael@0: descriptor, michael@0: #else michael@0: dump_path, minidump_id, michael@0: #endif michael@0: context, michael@0: #ifdef XP_WIN32 michael@0: nullptr, nullptr, michael@0: #endif michael@0: succeeded); michael@0: michael@0: nsCOMPtr& minidump = *static_cast< nsCOMPtr* >(context); michael@0: michael@0: nsCOMPtr extra; michael@0: return WriteExtraForMinidump(minidump, Blacklist(), getter_AddRefs(extra)); michael@0: } michael@0: michael@0: ThreadId michael@0: CurrentThreadId() michael@0: { michael@0: #if defined(XP_WIN) michael@0: return ::GetCurrentThreadId(); michael@0: #elif defined(XP_LINUX) michael@0: return sys_gettid(); michael@0: #elif defined(XP_MACOSX) michael@0: // Just return an index, since Mach ports can't be directly serialized michael@0: thread_act_port_array_t threads_for_task; michael@0: mach_msg_type_number_t thread_count; michael@0: michael@0: if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) michael@0: return -1; michael@0: michael@0: for (unsigned int i = 0; i < thread_count; ++i) { michael@0: if (threads_for_task[i] == mach_thread_self()) michael@0: return i; michael@0: } michael@0: abort(); michael@0: #else michael@0: # error "Unsupported platform" michael@0: #endif michael@0: } michael@0: michael@0: #ifdef XP_MACOSX michael@0: static mach_port_t michael@0: GetChildThread(ProcessHandle childPid, ThreadId childBlamedThread) michael@0: { michael@0: mach_port_t childThread = MACH_PORT_NULL; michael@0: thread_act_port_array_t threads_for_task; michael@0: mach_msg_type_number_t thread_count; michael@0: michael@0: if (task_threads(childPid, &threads_for_task, &thread_count) michael@0: == KERN_SUCCESS && childBlamedThread < thread_count) { michael@0: childThread = threads_for_task[childBlamedThread]; michael@0: } michael@0: michael@0: return childThread; michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: CreatePairedMinidumps(ProcessHandle childPid, michael@0: ThreadId childBlamedThread, michael@0: nsIFile** childDump) michael@0: { michael@0: if (!GetEnabled()) michael@0: return false; michael@0: michael@0: #ifdef XP_MACOSX michael@0: mach_port_t childThread = GetChildThread(childPid, childBlamedThread); michael@0: #else michael@0: ThreadId childThread = childBlamedThread; michael@0: #endif michael@0: michael@0: xpstring dump_path; michael@0: #ifndef XP_LINUX michael@0: dump_path = gExceptionHandler->dump_path(); michael@0: #else michael@0: dump_path = gExceptionHandler->minidump_descriptor().directory(); michael@0: #endif michael@0: michael@0: // dump the child michael@0: nsCOMPtr childMinidump; michael@0: if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( michael@0: childPid, michael@0: childThread, michael@0: dump_path, michael@0: PairedDumpCallbackExtra, michael@0: static_cast(&childMinidump))) michael@0: return false; michael@0: michael@0: nsCOMPtr childExtra; michael@0: GetExtraFileForMinidump(childMinidump, getter_AddRefs(childExtra)); michael@0: michael@0: // dump the parent michael@0: nsCOMPtr parentMinidump; michael@0: if (!google_breakpad::ExceptionHandler::WriteMinidump( michael@0: dump_path, michael@0: #ifdef XP_MACOSX michael@0: true, // write exception stream michael@0: #endif michael@0: PairedDumpCallback, michael@0: static_cast(&parentMinidump))) { michael@0: michael@0: childMinidump->Remove(false); michael@0: childExtra->Remove(false); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // success michael@0: RenameAdditionalHangMinidump(parentMinidump, childMinidump, michael@0: NS_LITERAL_CSTRING("browser")); michael@0: michael@0: if (ShouldReport()) { michael@0: MoveToPending(childMinidump, childExtra); michael@0: MoveToPending(parentMinidump, nullptr); michael@0: } michael@0: michael@0: childMinidump.forget(childDump); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CreateAdditionalChildMinidump(ProcessHandle childPid, michael@0: ThreadId childBlamedThread, michael@0: nsIFile* parentMinidump, michael@0: const nsACString& name) michael@0: { michael@0: if (!GetEnabled()) michael@0: return false; michael@0: michael@0: #ifdef XP_MACOSX michael@0: mach_port_t childThread = GetChildThread(childPid, childBlamedThread); michael@0: #else michael@0: ThreadId childThread = childBlamedThread; michael@0: #endif michael@0: michael@0: xpstring dump_path; michael@0: #ifndef XP_LINUX michael@0: dump_path = gExceptionHandler->dump_path(); michael@0: #else michael@0: dump_path = gExceptionHandler->minidump_descriptor().directory(); michael@0: #endif michael@0: michael@0: // dump the child michael@0: nsCOMPtr childMinidump; michael@0: if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild( michael@0: childPid, michael@0: childThread, michael@0: dump_path, michael@0: PairedDumpCallback, michael@0: static_cast(&childMinidump))) { michael@0: return false; michael@0: } michael@0: michael@0: RenameAdditionalHangMinidump(childMinidump, parentMinidump, name); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: UnsetRemoteExceptionHandler() michael@0: { michael@0: delete gExceptionHandler; michael@0: gExceptionHandler = nullptr; michael@0: return true; michael@0: } michael@0: michael@0: #if defined(MOZ_WIDGET_ANDROID) michael@0: void AddLibraryMapping(const char* library_name, michael@0: uintptr_t start_address, michael@0: size_t mapping_length, michael@0: size_t file_offset) michael@0: { michael@0: if (!gExceptionHandler) { michael@0: mapping_info info; michael@0: info.name = library_name; michael@0: info.start_address = start_address; michael@0: info.length = mapping_length; michael@0: info.file_offset = file_offset; michael@0: library_mappings.push_back(info); michael@0: } michael@0: else { michael@0: u_int8_t guid[sizeof(MDGUID)]; michael@0: google_breakpad::FileID::ElfFileIdentifierFromMappedFile((void const *)start_address, guid); michael@0: gExceptionHandler->AddMappingInfo(library_name, michael@0: guid, michael@0: start_address, michael@0: mapping_length, michael@0: file_offset); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: } // namespace CrashReporter