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