/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Airbag integration * * The Initial Developer of the Original Code is * Ted Mielczarek * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsAirbagExceptionHandler.h" #if defined(XP_WIN32) #ifdef WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN #endif #include "client/windows/handler/exception_handler.h" #include #elif defined(XP_MACOSX) #include "client/mac/handler/exception_handler.h" #include #include #include #else #error "Not yet implemented for this platform" #endif // defined(XP_WIN32) #ifndef HAVE_CPP_2BYTE_WCHAR_T #error "This code expects a 2 byte wchar_t. You should --disable-airbag." #endif #include #include #include "nsDebug.h" #include "nsCRT.h" #include "nsILocalFile.h" #include "nsDataHashtable.h" namespace CrashReporter { #ifdef XP_WIN32 typedef wchar_t XP_CHAR; #define TO_NEW_XP_CHAR(x) ToNewUnicode(x) #define CONVERT_UTF16_TO_XP_CHAR(x) x #define XP_STRLEN(x) wcslen(x) #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) #else typedef char XP_CHAR; #define TO_NEW_XP_CHAR(x) ToNewUTF8String(x) #define CONVERT_UTF16_TO_XP_CHAR(x) NS_ConvertUTF16toUTF8(x) #define XP_STRLEN(x) strlen(x) #define CRASH_REPORTER_FILENAME "crashreporter" #define PATH_SEPARATOR "/" #define XP_PATH_SEPARATOR "/" #define XP_PATH_MAX PATH_MAX #endif // XP_WIN32 static const XP_CHAR dumpFileExtension[] = {'.', 'd', 'm', 'p', '\0'}; // .dmp static const XP_CHAR extraFileExtension[] = {'.', 'e', 'x', 't', 'r', 'a', '\0'}; // .extra static google_breakpad::ExceptionHandler* gExceptionHandler = nsnull; static XP_CHAR* crashReporterPath; // this holds additional data sent via the API static nsDataHashtable* crashReporterAPIData_Hash; static nsCString* crashReporterAPIData = nsnull; 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; } bool MinidumpCallback(const XP_CHAR* dump_path, const XP_CHAR* minidump_id, void* context, #ifdef XP_WIN32 EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, #endif bool succeeded) { XP_CHAR minidumpPath[XP_PATH_MAX]; int size = XP_PATH_MAX; XP_CHAR* p = Concat(minidumpPath, dump_path, &size); p = Concat(p, XP_PATH_SEPARATOR, &size); p = Concat(p, minidump_id, &size); Concat(p, dumpFileExtension, &size); XP_CHAR extraDataPath[XP_PATH_MAX]; size = XP_PATH_MAX; p = Concat(extraDataPath, dump_path, &size); p = Concat(p, XP_PATH_SEPARATOR, &size); p = Concat(p, minidump_id, &size); Concat(p, extraFileExtension, &size); #ifdef XP_WIN32 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); if (!crashReporterAPIData->IsEmpty()) { // write out API data HANDLE hFile = CreateFile(extraDataPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile != INVALID_HANDLE_VALUE) { DWORD nBytes; WriteFile(hFile, crashReporterAPIData->get(), crashReporterAPIData->Length(), &nBytes, NULL); CloseHandle(hFile); } } 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(NULL, (LPWSTR)cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &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 (!crashReporterAPIData->IsEmpty()) { // write out API data int fd = open(extraDataPath, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd != -1) { // not much we can do in case of error write(fd, crashReporterAPIData->get(), crashReporterAPIData->Length()); close (fd); } } pid_t pid = fork(); if (pid == -1) return false; else if (pid == 0) { (void) execl(crashReporterPath, crashReporterPath, minidumpPath, (char*)0); _exit(1); } #endif return succeeded; } static nsresult GetExecutablePath(nsString& exePath) { #if !defined(XP_MACOSX) #ifdef XP_WIN32 exePath.SetLength(XP_PATH_MAX); if (!GetModuleFileName(NULL, (LPWSTR)exePath.BeginWriting(), XP_PATH_MAX)) return NS_ERROR_FAILURE; #else return NS_ERROR_NOT_IMPLEMENTED; #endif NS_NAMED_LITERAL_STRING(pathSep, PATH_SEPARATOR); PRInt32 lastSlash = exePath.RFind(pathSep); if (lastSlash < 0) return NS_ERROR_FAILURE; exePath.Truncate(lastSlash + 1); return NS_OK; #else // !defined(XP_MACOSX) CFBundleRef appBundle = CFBundleGetMainBundle(); if (!appBundle) return NS_ERROR_FAILURE; CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle); if (!executableURL) return NS_ERROR_FAILURE; CFURLRef bundleURL = CFURLCreateCopyDeletingLastPathComponent(NULL, executableURL); CFRelease(executableURL); if (!bundleURL) return NS_ERROR_FAILURE; CFURLRef reporterURL = CFURLCreateCopyAppendingPathComponent( NULL, bundleURL, CFSTR("crashreporter.app/Contents/MacOS/"), false); CFRelease(bundleURL); if (!reporterURL) return NS_ERROR_FAILURE; FSRef fsRef; if (!CFURLGetFSRef(reporterURL, &fsRef)) { CFRelease(reporterURL); return NS_ERROR_FAILURE; } CFRelease(reporterURL); char path[PATH_MAX + 1]; OSStatus status = FSRefMakePath(&fsRef, (UInt8*)path, PATH_MAX); if (status != noErr) return NS_ERROR_FAILURE; int len = strlen(path); path[len] = '/'; path[len + 1] = '\0'; exePath = NS_ConvertUTF8toUTF16(path); return NS_OK; #endif } nsresult SetExceptionHandler(nsILocalFile* aXREDirectory) { nsresult rv; if (gExceptionHandler) return NS_ERROR_ALREADY_INITIALIZED; // check environment var to see if we're enabled. // we're off by default until we sort out the // rest of the infrastructure, // so it must exist and be set to a non-zero value. const char* airbagEnv = PR_GetEnv("MOZ_AIRBAG"); if (airbagEnv == NULL || atoi(airbagEnv) == 0) return NS_ERROR_NOT_AVAILABLE; // allocate our strings crashReporterAPIData = new nsCString(); NS_ENSURE_TRUE(crashReporterAPIData, NS_ERROR_OUT_OF_MEMORY); crashReporterAPIData_Hash = new nsDataHashtable(); NS_ENSURE_TRUE(crashReporterAPIData_Hash, NS_ERROR_OUT_OF_MEMORY); rv = crashReporterAPIData_Hash->Init(); NS_ENSURE_SUCCESS(rv, rv); // locate crashreporter executable nsString exePath; if (aXREDirectory) { aXREDirectory->GetPath(exePath); } else { rv = GetExecutablePath(exePath); NS_ENSURE_SUCCESS(rv, rv); } NS_NAMED_LITERAL_STRING(crashReporterFilename, CRASH_REPORTER_FILENAME); crashReporterPath = TO_NEW_XP_CHAR(exePath + crashReporterFilename); // get temp path to use for minidump path nsString tempPath; #ifdef XP_WIN32 // first figure out buffer size int pathLen = GetTempPath(0, NULL); if (pathLen == 0) return NS_ERROR_FAILURE; tempPath.SetLength(pathLen); GetTempPath(pathLen, (LPWSTR)tempPath.BeginWriting()); #elif defined(XP_MACOSX) FSRef fsRef; OSErr err = FSFindFolder(kUserDomain, kTemporaryFolderType, kCreateFolder, &fsRef); if (err != noErr) return NS_ERROR_FAILURE; tempPath.SetLength(PATH_MAX); OSStatus status = FSRefMakePath(&fsRef, (UInt8*)tempPath.BeginWriting(), PATH_MAX); if (status != noErr) return NS_ERROR_FAILURE; #else //XXX: implement get temp path on other platforms return NS_ERROR_NOT_IMPLEMENTED; #endif // finally, set the exception handler gExceptionHandler = new google_breakpad:: ExceptionHandler(CONVERT_UTF16_TO_XP_CHAR(tempPath).get(), nsnull, MinidumpCallback, nsnull, true); if (!gExceptionHandler) return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } nsresult SetMinidumpPath(const nsAString& aPath) { if (!gExceptionHandler) return NS_ERROR_NOT_INITIALIZED; gExceptionHandler->set_dump_path(CONVERT_UTF16_TO_XP_CHAR(aPath).BeginReading()); return NS_OK; } nsresult UnsetExceptionHandler() { // do this here in the unlikely case that we succeeded in allocating // our strings but failed to allocate gExceptionHandler. if (crashReporterAPIData_Hash) { delete crashReporterAPIData_Hash; crashReporterAPIData_Hash = nsnull; } if (crashReporterPath) { NS_Free(crashReporterPath); crashReporterPath = nsnull; } if (!gExceptionHandler) return NS_ERROR_NOT_INITIALIZED; delete gExceptionHandler; gExceptionHandler = nsnull; 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)) { PRInt32 pos = end.size_backward(); str.Replace(pos - 1, 1, replacement); str.BeginReading(start); start.advance(pos + replacement.Length() - 1); str.EndReading(end); } } static PRBool DoFindInReadable(const nsACString& str, const nsACString& value) { nsACString::const_iterator start, end; str.BeginReading(start); str.EndReading(end); return FindInReadable(value, start, end); } static PLDHashOperator PR_CALLBACK EnumerateEntries(const nsACString& key, nsCString entry, void* userData) { crashReporterAPIData->Append(key + NS_LITERAL_CSTRING("=") + entry + NS_LITERAL_CSTRING("\n")); return PL_DHASH_NEXT; } nsresult AnnotateCrashReport(const nsACString &key, const nsACString &data) { if (!gExceptionHandler) return NS_ERROR_NOT_INITIALIZED; 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; nsCString escapedData(data); // escape backslashes ReplaceChar(escapedData, NS_LITERAL_CSTRING("\\"), NS_LITERAL_CSTRING("\\\\")); // escape newlines ReplaceChar(escapedData, NS_LITERAL_CSTRING("\n"), NS_LITERAL_CSTRING("\\n")); nsresult rv = crashReporterAPIData_Hash->Put(key, escapedData); NS_ENSURE_SUCCESS(rv, rv); // now rebuild the file contents crashReporterAPIData->Truncate(0); crashReporterAPIData_Hash->EnumerateRead(EnumerateEntries, crashReporterAPIData); return NS_OK; } } // namespace CrashReporter