bug 366970, crash reporter needs to send Product, Build ID, Platform. r=mento

This commit is contained in:
ted.mielczarek@gmail.com 2007-07-24 18:05:55 -07:00
parent 766a715fb4
commit c9ff2ca6e0
5 changed files with 409 additions and 115 deletions

View File

@ -48,6 +48,7 @@ LIBRARY_NAME = airbagexception_s
REQUIRES = \
xpcom \
string \
xulapp \
$(NULL)
DIRS = \

View File

@ -61,6 +61,7 @@ LOCAL_INCLUDES += -I$(srcdir)
RCINCLUDE = crashreporter.rc
DEFINES += -DUNICODE -D_UNICODE
OS_LIBS += $(call EXPAND_LIBNAME,comctl32 shell32 wininet)
MOZ_WINCONSOLE = 0
endif
include $(topsrcdir)/config/rules.mk

View File

@ -45,27 +45,34 @@
#include <shellapi.h>
#include "resource.h"
#include "client/windows/sender/crash_report_sender.h"
#include <fstream>
#define CRASH_REPORTER_KEY L"Software\\Mozilla\\Crash Reporter"
#define CRASH_REPORTER_VALUE L"Enabled"
#define WM_UPLOADCOMPLETE WM_APP
using std::string;
using std::wstring;
using std::map;
using std::ifstream;
bool ReadConfig();
BOOL CALLBACK EnableDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK EnableDialogProc(HWND hwndDlg, UINT message, WPARAM wParam,
LPARAM lParam);
BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam,
LPARAM lParam);
HANDLE CreateSendThread(HWND hDlg, LPCTSTR dumpFile);
bool CheckCrashReporterEnabled(bool* enabled);
void SetCrashReporterEnabled(bool enabled);
bool SendCrashReport(HINSTANCE hInstance, LPCTSTR dumpFile);
bool SendCrashReport(wstring dumpFile,
const map<wstring,wstring> *query_parameters);
DWORD WINAPI SendThreadProc(LPVOID param);
typedef struct {
HWND hDlg;
LPCTSTR dumpFile;
wstring dumpFile;
const map<wstring,wstring> *query_parameters;
} SENDTHREADDATA;
TCHAR sendURL[2048] = L"\0";
@ -99,6 +106,8 @@ LPCTSTR stringNames[] = {
LPTSTR strings[NUM_STRINGS];
const wchar_t* kExtraDataExtension = L".extra";
void DoInitCommonControls()
{
INITCOMMONCONTROLSEX ic;
@ -143,52 +152,6 @@ bool ReadConfig()
return false;
}
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
bool enabled;
LPTSTR* argv = NULL;
int argc = 0;
DoInitCommonControls();
if (!ReadConfig()) {
MessageBox(NULL, L"Missing crashreporter.ini file", L"Crash Reporter Error", MB_OK | MB_ICONSTOP);
return 0;
}
argv = CommandLineToArgvW(GetCommandLine(), &argc);
if (argc == 1) {
// nothing specified, just ask about enabling
if (!CheckCrashReporterEnabled(&enabled))
enabled = true;
enabled = (1 == DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_ENABLEDIALOG), NULL, (DLGPROC)EnableDialogProc, (LPARAM)enabled));
SetCrashReporterEnabled(enabled);
}
else {
if (!CheckCrashReporterEnabled(&enabled)) {
//ask user if crash reporter should be enabled
enabled = (1 == DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_ENABLEDIALOG), NULL, (DLGPROC)EnableDialogProc, (LPARAM)true));
SetCrashReporterEnabled(enabled);
}
// if enabled, send crash report
if (enabled) {
if (SendCrashReport(hInstance, argv[1]) && deleteDump)
DeleteFile(argv[1]);
//TODO: show details?
}
}
if (argv)
LocalFree(argv);
return 0;
}
BOOL CALLBACK EnableDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
@ -286,8 +249,9 @@ BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM l
SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
SendDlgItemMessage(hwndDlg, IDC_PROGRESS, PBM_SETPOS, 0, 0);
// now create a thread to actually do the sending
LPCTSTR dumpFile = (LPCTSTR)lParam;
hThread = CreateSendThread(hwndDlg, dumpFile);
SENDTHREADDATA* td = (SENDTHREADDATA*)lParam;
td->hDlg = hwndDlg;
CreateThread(NULL, 0, SendThreadProc, td, 0, NULL);
}
return TRUE;
@ -315,9 +279,17 @@ BOOL CALLBACK SendDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM l
}
}
bool SendCrashReport(HINSTANCE hInstance, LPCTSTR dumpFile)
bool SendCrashReport(wstring dumpFile,
const map<wstring,wstring> *query_parameters)
{
int res = (int)DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_SENDDIALOG), NULL, (DLGPROC)SendDialogProc, (LPARAM)dumpFile);
SENDTHREADDATA td;
td.hDlg = NULL;
td.dumpFile = dumpFile;
td.query_parameters = query_parameters;
int res = (int)DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_SENDDIALOG), NULL,
(DLGPROC)SendDialogProc, (LPARAM)&td);
return (res >= 0);
}
@ -325,8 +297,7 @@ DWORD WINAPI SendThreadProc(LPVOID param)
{
bool finishedOk;
SENDTHREADDATA* td = (SENDTHREADDATA*)param;
//XXX: send some extra params?
map<wstring, wstring> params;
wstring url(sendURL);
if (url.empty()) {
finishedOk = false;
@ -334,18 +305,129 @@ DWORD WINAPI SendThreadProc(LPVOID param)
else {
finishedOk = google_airbag::CrashReportSender
::SendCrashReport(url,
params,
wstring(td->dumpFile));
*(td->query_parameters),
td->dumpFile);
}
PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0);
delete td;
return 0;
}
HANDLE CreateSendThread(HWND hDlg, LPCTSTR dumpFile)
//XXX: change this to use Breakpad's string conversion functions
// when we update to latest SVN
bool ConvertString(const char* utf8_string, wstring& ucs2_string)
{
SENDTHREADDATA* td = new SENDTHREADDATA;
td->hDlg = hDlg;
td->dumpFile = dumpFile;
return CreateThread(NULL, 0, SendThreadProc, td, 0, NULL);
wchar_t *buffer = NULL;
int buffer_size = MultiByteToWideChar(CP_ACP, 0, utf8_string,
-1, NULL, 0);
if(buffer_size == 0)
return false;
buffer = new wchar_t[buffer_size];
if(buffer == NULL)
return false;
MultiByteToWideChar(CP_ACP, 0, utf8_string,
-1, buffer, buffer_size);
ucs2_string = buffer;
delete [] buffer;
return true;
}
void ReadExtraData(const wstring& filename,
map<wstring, wstring>& query_parameters)
{
#if _MSC_VER >= 1400 // MSVC 2005/8
ifstream file;
file.open(filename.c_str(), std::ios::in);
#else // _MSC_VER >= 1400
ifstream file(_wfopen(filename.c_str(), L"rb"));
#endif // _MSC_VER >= 1400
if (file.is_open()) {
do {
string in_line;
std::getline(file, in_line);
if (!in_line.empty()) {
int pos = in_line.find('=');
if (pos >= 0) {
wstring key, value;
ConvertString(in_line.substr(0, pos).c_str(), key);
ConvertString(in_line.substr(pos + 1,
in_line.length() - pos - 1).c_str(),
value);
query_parameters[key] = value;
}
}
} while(!file.eof());
file.close();
}
}
wstring GetExtraDataFilename(const wstring& dumpfile)
{
wstring filename(dumpfile);
int dot = filename.rfind('.');
if (dot < 0)
return L"";
filename.replace(dot, filename.length() - dot, kExtraDataExtension);
return filename;
}
int main(int argc, char **argv)
{
map<wstring,wstring> query_parameters;
DoInitCommonControls();
if (!ReadConfig()) {
MessageBox(NULL, L"Missing crashreporter.ini file", L"Crash Reporter Error", MB_OK | MB_ICONSTOP);
return 0;
}
wstring dumpfile;
bool enabled = false;
if (argc > 1) {
if (!ConvertString(argv[1], dumpfile))
return 0;
}
if (dumpfile.empty()) {
// no dump file specified, just ask about enabling
if (!CheckCrashReporterEnabled(&enabled))
enabled = true;
enabled = (1 == DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_ENABLEDIALOG), NULL, (DLGPROC)EnableDialogProc, (LPARAM)enabled));
SetCrashReporterEnabled(enabled);
}
else {
wstring extrafile = GetExtraDataFilename(dumpfile);
if (!extrafile.empty())
ReadExtraData(extrafile, query_parameters);
if (!CheckCrashReporterEnabled(&enabled)) {
//ask user if crash reporter should be enabled
enabled = (1 == DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_ENABLEDIALOG), NULL, (DLGPROC)EnableDialogProc, (LPARAM)true));
SetCrashReporterEnabled(enabled);
}
// if enabled, send crash report
if (enabled) {
if (SendCrashReport(dumpfile, &query_parameters) && deleteDump) {
DeleteFile(dumpfile.c_str());
if (!extrafile.empty())
DeleteFile(extrafile.c_str());
}
//TODO: show details?
}
}
}
#if defined(XP_WIN) && !defined(DEBUG) && !defined(__GNUC__)
// We need WinMain in order to not be a console app. This function is unused
// if we are a console application.
int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR args, int )
{
// Do the real work.
return main(__argc, __argv);
}
#endif

View File

@ -44,26 +44,91 @@
#include "client/windows/handler/exception_handler.h"
#include <string.h>
#else
#error "Not yet implemented for this platform"
#endif // 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"
static google_airbag::ExceptionHandler* gAirbagExceptionHandler = nsnull;
namespace CrashReporter {
using std::wstring;
static const PRUnichar dumpFileExtension[] = {'.', 'd', 'm', 'p',
'\"', '\0'}; // .dmp"
static const PRUnichar extraFileExtension[] = {'.', 'e', 'x', 't',
'r', 'a', '\0'}; // .extra
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
static const PRInt32 kGUIDLength = 36;
// length of a GUID + .dmp" (yes, trailing double quote)
static const PRInt32 kMinidumpFilenameLength =
kGUIDLength + sizeof(dumpFileExtension) / sizeof(dumpFileExtension[0]);
#ifdef XP_WIN32
static TCHAR crashReporterExe[MAX_PATH] = L"\0";
static TCHAR minidumpPath[MAX_PATH] = L"\0";
NS_NAMED_LITERAL_STRING(crashReporterFilename, "crashreporter.exe");
#else
NS_NAMED_LITERAL_STRING(crashReporterFilename, "crashreporter");
#endif
void AirbagMinidumpCallback(const std::wstring &minidump_id,
void *context, bool succeeded)
static google_airbag::ExceptionHandler* gExceptionHandler = nsnull;
// for ease of replacing the dump path when someone
// calls SetMinidumpPath
static nsString crashReporterCmdLine_withoutDumpPath;
// this is set up so we don't have to do heap allocation in the handler
static PRUnichar* crashReporterCmdLine = nsnull;
// points at the end of the previous string
// so we can append the minidump filename
static PRUnichar* crashReporterCmdLineEnd = nsnull;
// space to hold a filename for the API data
static PRUnichar* crashReporterAPIDataFilename = nsnull;
static PRUnichar* crashReporterAPIDataFilenameEnd = nsnull;
// this holds additional data sent via the API
static nsCString crashReporterAPIData;
static void MinidumpCallback(const wstring &minidump_id,
void *context, bool succeeded)
{
// append minidump filename to command line
memcpy(crashReporterCmdLineEnd, minidump_id.c_str(),
kGUIDLength * sizeof(PRUnichar));
// this will copy the null terminator as well
memcpy(crashReporterCmdLineEnd + kGUIDLength,
dumpFileExtension, sizeof(dumpFileExtension));
// append minidump filename to API data filename
memcpy(crashReporterAPIDataFilenameEnd, minidump_id.c_str(),
kGUIDLength * sizeof(PRUnichar));
// this will copy the null terminator as well
memcpy(crashReporterAPIDataFilenameEnd + kGUIDLength,
extraFileExtension, sizeof(extraFileExtension));
#ifdef XP_WIN32
if (!crashReporterAPIData.IsEmpty()) {
// write out API data
HANDLE hFile = CreateFile(crashReporterAPIDataFilename, 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;
TCHAR cmdLine[2*MAX_PATH];
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
@ -71,11 +136,8 @@ void AirbagMinidumpCallback(const std::wstring &minidump_id,
si.wShowWindow = SW_SHOWNORMAL;
ZeroMemory(&pi, sizeof(pi));
wcscat(minidumpPath, minidump_id.c_str());
wcscat(minidumpPath, L".dmp");
wsprintf(cmdLine, L"\"%s\" \"%s\"", crashReporterExe, minidumpPath);
if (CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
if (CreateProcess(NULL, (LPWSTR)crashReporterCmdLine, NULL, NULL, FALSE, 0,
NULL, NULL, &si, &pi)) {
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
@ -84,9 +146,65 @@ void AirbagMinidumpCallback(const std::wstring &minidump_id,
#endif
}
nsresult SetAirbagExceptionHandler()
static nsresult BuildCommandLine(const nsAString &tempPath)
{
if (gAirbagExceptionHandler)
nsString crashReporterCmdLine_temp =
crashReporterCmdLine_withoutDumpPath + NS_LITERAL_STRING(" \"") + tempPath;
PRInt32 cmdLineLength = crashReporterCmdLine_temp.Length();
// allocate extra space for minidump file name
crashReporterCmdLine_temp.SetLength(cmdLineLength + kMinidumpFilenameLength
+ 1);
crashReporterCmdLine = ToNewUnicode(crashReporterCmdLine_temp);
crashReporterCmdLineEnd = crashReporterCmdLine + cmdLineLength;
// build API data filename
if(crashReporterAPIDataFilename != nsnull) {
NS_Free(crashReporterAPIDataFilename);
crashReporterAPIDataFilename = nsnull;
}
nsString apiDataFilename_temp(tempPath);
PRInt32 filenameLength = apiDataFilename_temp.Length();
apiDataFilename_temp.SetLength(filenameLength + kMinidumpFilenameLength + 1);
crashReporterAPIDataFilename = ToNewUnicode(apiDataFilename_temp);
crashReporterAPIDataFilenameEnd =
crashReporterAPIDataFilename + filenameLength;
return NS_OK;
}
static nsresult GetExecutablePath(nsString& exePath)
{
#ifdef XP_WIN32
// sort of arbitrary, but MAX_PATH is kinda small
exePath.SetLength(4096);
if (!GetModuleFileName(NULL, (LPWSTR)exePath.BeginWriting(), 4096))
return NS_ERROR_FAILURE;
#else
return NS_ERROR_NOT_IMPLEMENTED;
#endif
#ifdef XP_WIN32
NS_NAMED_LITERAL_STRING(pathSep, "\\");
#else
NS_NAMED_LITERAL_STRING(pathSep, "/");
#endif
PRInt32 lastSlash = exePath.RFind(pathSep);
if (lastSlash < 0)
return NS_ERROR_FAILURE;
exePath.Truncate(lastSlash + 1);
return NS_OK;
}
nsresult SetExceptionHandler(nsILocalFile* aXREDirectory)
{
nsresult rv;
if (gExceptionHandler)
return NS_ERROR_ALREADY_INITIALIZED;
// check environment var to see if we're enabled.
@ -95,64 +213,153 @@ nsresult SetAirbagExceptionHandler()
// 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_OK;
return NS_ERROR_NOT_AVAILABLE;
// locate crashreporter executable
nsString exePath;
if (aXREDirectory) {
aXREDirectory->GetPath(exePath);
}
else {
rv = GetExecutablePath(exePath);
NS_ENSURE_SUCCESS(rv, rv);
}
// note that we enclose the exe filename in double quotes
crashReporterCmdLine_withoutDumpPath = NS_LITERAL_STRING("\"") +
exePath + crashReporterFilename + NS_LITERAL_STRING("\"");
// get temp path to use for minidump path
nsString tempPath;
#ifdef XP_WIN32
//TODO: check registry to see if crash reporting is enabled
if (!GetTempPath(MAX_PATH, minidumpPath))
// first figure out buffer size
int pathLen = GetTempPath(0, NULL);
if (pathLen == 0)
return NS_ERROR_FAILURE;
std::wstring tempStr(minidumpPath);
gAirbagExceptionHandler = new google_airbag::ExceptionHandler(tempStr,
AirbagMinidumpCallback,
nsnull,
true);
if (GetModuleFileName(NULL, crashReporterExe, MAX_PATH)) {
// get crashreporter exe
LPTSTR s = wcsrchr(crashReporterExe, '\\');
if (s) {
s++;
wcscpy(s, L"crashreporter.exe");
}
}
#else
return NS_ERROR_NOT_IMPLEMENTED;
tempPath.SetLength(pathLen);
GetTempPath(pathLen, (LPWSTR)tempPath.BeginWriting());
#endif
if (!gAirbagExceptionHandler)
rv = BuildCommandLine(tempPath);
NS_ENSURE_SUCCESS(rv, rv);
// finally, set the exception handler
gExceptionHandler = new google_airbag::ExceptionHandler(
PromiseFlatString(tempPath).get(),
MinidumpCallback,
nsnull,
true);
if (!gExceptionHandler)
return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
nsresult SetAirbagMinidumpPath(const nsAString* aPath)
nsresult SetMinidumpPath(const nsAString& aPath)
{
NS_ENSURE_ARG_POINTER(aPath);
if (!gAirbagExceptionHandler)
if (!gExceptionHandler)
return NS_ERROR_NOT_INITIALIZED;
std::wstring path;
#ifdef XP_WIN32
wcsncpy(minidumpPath, PromiseFlatString(*aPath).get(), MAX_PATH);
path = std::wstring(minidumpPath);
int l = wcslen(minidumpPath);
if (minidumpPath[l-1] != '\\' && l < MAX_PATH - 1) {
minidumpPath[l] = '\\';
minidumpPath[l+1] = '\0';
if(crashReporterCmdLine != nsnull) {
NS_Free(crashReporterCmdLine);
crashReporterCmdLine = nsnull;
}
#ifdef XP_WIN32
NS_NAMED_LITERAL_STRING(pathSep, "\\");
#else
NS_NAMED_LITERAL_STRING(pathSep, "/");
#endif
gAirbagExceptionHandler->set_dump_path(path);
nsresult rv;
if(!StringEndsWith(aPath, pathSep)) {
rv = BuildCommandLine(aPath + pathSep);
}
else {
rv = BuildCommandLine(aPath);
}
NS_ENSURE_SUCCESS(rv, rv);
gExceptionHandler->set_dump_path(PromiseFlatString(aPath).get());
return NS_OK;
}
nsresult UnsetAirbagExceptionHandler()
nsresult UnsetExceptionHandler()
{
if (!gAirbagExceptionHandler)
if (!gExceptionHandler)
return NS_ERROR_NOT_INITIALIZED;
delete gAirbagExceptionHandler;
gAirbagExceptionHandler = nsnull;
delete gExceptionHandler;
gExceptionHandler = nsnull;
if(crashReporterCmdLine != nsnull) {
NS_Free(crashReporterCmdLine);
crashReporterCmdLine = nsnull;
}
if(crashReporterAPIDataFilename != nsnull) {
NS_Free(crashReporterAPIDataFilename);
crashReporterAPIDataFilename = nsnull;
}
crashReporterCmdLineEnd = 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);
}
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;
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"));
crashReporterAPIData.Append(key + NS_LITERAL_CSTRING("=") + escapedData +
NS_LITERAL_CSTRING("\n"));
return NS_OK;
}
} // namespace CrashReporter

View File

@ -42,8 +42,11 @@
#include "nsXPCOM.h"
#include "nsStringGlue.h"
nsresult SetAirbagExceptionHandler();
nsresult SetAirbagMinidumpPath(const nsAString* aPath);
nsresult UnsetAirbagExceptionHandler();
namespace CrashReporter {
nsresult SetExceptionHandler(nsILocalFile* aXREDirectory);
nsresult SetMinidumpPath(const nsAString& aPath);
nsresult UnsetExceptionHandler();
nsresult AnnotateCrashReport(const nsACString &key, const nsACString &data);
}
#endif /* nsAirbagExceptionHandler_h__ */