mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 12:37:37 +00:00
410 lines
12 KiB
C++
Executable File
410 lines
12 KiB
C++
Executable File
/* -*- 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 <ted.mielczarek@gmail.com>
|
|
* 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 <string.h>
|
|
#elif defined(XP_MACOSX)
|
|
#include "client/mac/handler/exception_handler.h"
|
|
#include <string>
|
|
#include <Carbon/Carbon.h>
|
|
#include <fcntl.h>
|
|
#elif defined(XP_LINUX)
|
|
#include "client/linux/handler/exception_handler.h"
|
|
#include <fcntl.h>
|
|
#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 <stdlib.h>
|
|
#include <prenv.h>
|
|
#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
|
|
// "<reporter path>" "<minidump path>"
|
|
#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;
|
|
|
|
// if this is false, we don't launch the crash reporter
|
|
static bool doReport = true;
|
|
|
|
// this holds additional data sent via the API
|
|
static nsDataHashtable<nsCStringHashKey,nsCString>* 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);
|
|
}
|
|
}
|
|
|
|
if (!doReport) {
|
|
return succeeded;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (!doReport) {
|
|
return succeeded;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
nsresult SetExceptionHandler(nsILocalFile* aXREDirectory,
|
|
const char* aServerURL)
|
|
{
|
|
nsresult rv;
|
|
|
|
if (gExceptionHandler)
|
|
return NS_ERROR_ALREADY_INITIALIZED;
|
|
|
|
// this environment variable prevents us from launching
|
|
// the crash reporter client
|
|
const char* noReportEnv = PR_GetEnv("MOZ_CRASHREPORTER_NO_REPORT");
|
|
if (noReportEnv && *noReportEnv)
|
|
doReport = false;
|
|
|
|
// allocate our strings
|
|
crashReporterAPIData = new nsCString();
|
|
NS_ENSURE_TRUE(crashReporterAPIData, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
crashReporterAPIData_Hash =
|
|
new nsDataHashtable<nsCStringHashKey,nsCString>();
|
|
NS_ENSURE_TRUE(crashReporterAPIData_Hash, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
rv = crashReporterAPIData_Hash->Init();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// locate crashreporter executable
|
|
nsCOMPtr<nsIFile> exePath;
|
|
rv = aXREDirectory->Clone(getter_AddRefs(exePath));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
exePath->Append(NS_LITERAL_STRING(CRASH_REPORTER_FILENAME));
|
|
|
|
nsString crashReporterPath_temp;
|
|
exePath->GetPath(crashReporterPath_temp);
|
|
|
|
crashReporterPath = TO_NEW_XP_CHAR(crashReporterPath_temp);
|
|
|
|
// get temp path to use for minidump path
|
|
nsString tempPath;
|
|
#if defined(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;
|
|
|
|
char path[PATH_MAX];
|
|
OSStatus status = FSRefMakePath(&fsRef, (UInt8*)path, PATH_MAX);
|
|
if (status != noErr)
|
|
return NS_ERROR_FAILURE;
|
|
tempPath = NS_ConvertUTF8toUTF16(path);
|
|
|
|
#elif defined(XP_UNIX)
|
|
// we assume it's always /tmp on unix systems
|
|
tempPath = NS_LITERAL_STRING("/tmp/");
|
|
#else
|
|
//XXX: implement get temp path on other platforms
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
#endif
|
|
|
|
// now 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;
|
|
|
|
// store server URL with the API data
|
|
if (aServerURL)
|
|
AnnotateCrashReport(NS_LITERAL_CSTRING("ServerURL"),
|
|
nsDependentCString(aServerURL));
|
|
|
|
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
|