CrashReporter.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to the
00008 **  terms described in the LICENSE file.
00009 */
00010 /*
00011 **  The append_string() function in this file is derived from the implementation
00012 **  of strlcat() by Todd C. Miller. It is licensed as follows:
00013 **
00014 **  Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
00015 **  All rights reserved.
00016 **
00017 **  Redistribution and use in source and binary forms, with or without
00018 **  modification, are permitted provided that the following conditions
00019 **  are met:
00020 **  1. Redistributions of source code must retain the above copyright
00021 **     notice, this list of conditions and the following disclaimer.
00022 **  2. Redistributions in binary form must reproduce the above copyright
00023 **     notice, this list of conditions and the following disclaimer in the
00024 **     documentation and/or other materials provided with the distribution.
00025 **  3. The name of the author may not be used to endorse or promote products
00026 **     derived from this software without specific prior written permission.
00027 **
00028 **  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
00029 **  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
00030 **  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
00031 **  THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
00032 **  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
00033 **  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
00034 **  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
00035 **  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
00036 **  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
00037 **  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00038 */
00039 
00040 /*
00041 ** \file CrashReporter.h
00042 ** \version $Id$
00043 ** \brief General routines to install a Breakpad-based exception handler and
00044 ** set options related to launching the crash reporting application.
00045 */
00046 
00047 #include "CrashReporter.h"
00048 #include "stringutil.h"
00049 
00050 #if defined(Q_OS_WIN32)
00051 #include <client/windows/handler/exception_handler.h>
00052 #elif defined(Q_OS_MAC)
00053 #include <client/mac/handler/exception_handler.h>
00054 #elif defined(Q_OS_LINUX)
00055 #include <client/linux/handler/exception_handler.h>
00056 #elif defined(Q_OS_SOLARIS)
00057 #include <client/solaris/handler/exception_handler.h>
00058 #endif
00059 
00060 #include <QString>
00061 #include <QStringList>
00062 #include <QFileInfo>
00063 #include <QDir>
00064 
00065 #include <time.h>
00066 
00067 
00068 namespace CrashReporter
00069 {
00070 #if defined(Q_OS_WIN32)
00071 typedef wchar_t         _char_t;
00072 typedef HANDLE          _file_handle_t;
00073 #define PATH_SEPARATOR  TEXT("\\")
00074 # ifdef _USE_32BIT_TIME_T
00075 #   define TIME_TO_STRING(buf, buflen, t) \
00076       _ltoa_s(t, buf, buflen, 10)
00077 # else
00078 #   define TIME_TO_STRING(buf, buflen, t) \
00079       _i64toa_s(t, buf, buflen, 10)
00080 # endif
00081 #else
00082 typedef char            _char_t;
00083 typedef int             _file_handle_t;
00084 #define PATH_SEPARATOR  "/"
00085 #define TEXT(x)         (x)
00086 #define TIME_TO_STRING(buf, buflen, t) \
00087   snprintf(buf, buflen, "%ld", t)
00088 #endif
00089 
00090 /** Pointer to the Breakpad-installed exception handler called if Vidalia
00091  * crashes.
00092  * \sa install_exception_handler()
00093  */
00094 static google_breakpad::ExceptionHandler *exceptionHandler = 0;
00095 
00096 /** If true, the crash reporting application will be displayed when the
00097  * Breakpad-installed exception handler is called. Otherwise, the system
00098  * will handle the exception itself.
00099  * \sa set_crash_reporter()
00100  */
00101 static bool showCrashReporter = false;
00102 
00103 /** Absolute path of the crash reporting application that will be launched
00104  * from the exception handler.
00105  * \sa set_crash_reporter()
00106  */
00107 static _char_t crashReporterExecutable[MAX_PATH_LEN + 1] = TEXT("");
00108 
00109 /** Version information for the application being monitored for crashes.
00110  * The version will be written to the extra information file alongside
00111  * the minidump.
00112  */
00113 static char buildVersion[MAX_VERSION_LEN + 1] = "";
00114 
00115 /** Path and filename of the application to restart after displaying
00116  * the crash reporting dialog. The contents of this string are encoded
00117  * in UTF-8.
00118  * \sa set_restart_options()
00119  */
00120 static char restartExecutable[MAX_CMD_LEN + 1] = "";
00121 
00122 /** Additional arguments to use when restarting the crashed application.
00123  * The contents of this string are encoded in UTF-8.
00124  * \sa set_restart_options()
00125  */
00126 static char restartExecutableArgs[MAX_CMD_LEN + 1] = "";
00127 
00128 
00129 /** Records the time at which install_exception_handler() is called, which
00130  * is usually as early as possible during application startup. This is used
00131  * in minidump_callback() to determine how long the application was running
00132  * before it crashed.
00133  * \sa install_exception_handler()
00134  * \sa minidump_callback()
00135  */
00136 static time_t startupTime = 0;
00137 
00138 
00139 /** Slightly modified version of the strlcat() implementation by Todd C. 
00140  * Miller (see the top of this file or the LICENSE file for license details),
00141  * that supports arguments of either wchar_t* on Windows or the usual char*
00142  * everywhere else but retains the semantics of strlcat().
00143  */
00144 static size_t
00145 append_string(_char_t *dst, const _char_t *src, size_t siz)
00146 {
00147   _char_t *d = dst;
00148   const _char_t *s = src;
00149   size_t n = siz;
00150   size_t dlen;
00151 
00152   /* Find the end of dst and adjust bytes left but don't go past end */
00153   while (n-- != 0 && *d != TEXT('\0'))
00154     d++;
00155   dlen = d - dst;
00156   n = siz - dlen;
00157 
00158   if (n == 0)
00159 #if defined(Q_OS_WIN32)
00160     return (dlen + wcslen(s));
00161 #else
00162     return(dlen + strlen(s));
00163 #endif
00164 
00165   while (*s != TEXT('\0')) {
00166     if (n != 1) {
00167       *d++ = *s;
00168       n--;
00169     }
00170     s++;
00171   }
00172   *d = TEXT('\0');
00173 
00174   return(dlen + (s - src));     /* count does not include NUL */
00175 }
00176 
00177 /** Writes the formatted string "<b>key</b>=</b>val\n" to the file handle
00178  * specified by <b>hFile</b>. On Windows, <b>hFile</b> is a HANDLE. Everywhere
00179  * else, <b>hFile</b> is an int.
00180  */
00181 static void
00182 write_keyval_to_file(_file_handle_t hFile, const char *key, const char *val)
00183 {
00184 #if defined(Q_OS_WIN32)
00185   DWORD dwWritten;
00186   WriteFile(hFile, key, strlen(key), &dwWritten, NULL);
00187   WriteFile(hFile, "=", 1, &dwWritten, NULL);
00188   WriteFile(hFile, val, strlen(val), &dwWritten, NULL);
00189   WriteFile(hFile, "\n", 1, &dwWritten, NULL);
00190 #else
00191   write(hFile, key, strlen(key));
00192   write(hFile, "=", 1);
00193   write(hFile, val, strlen(val));
00194   write(hFile, "\n", 1);
00195 #endif
00196 }
00197 
00198 /** Writes to a file extra information used by the crash reporting
00199  * application such as how long the application was running before it
00200  * crashed, the application to restart, as well as any extra arguments.
00201  * The contents of the file are formatted as a series of "Key=Val\n" pairs.
00202  * The written file has the same path and base filename as the minidump
00203  * file, with ".info" appended to the end. Returns true if the file was
00204  * created succesfully. Otherwise, returns false.
00205  */
00206 static bool
00207 write_extra_dump_info(const _char_t *path, const _char_t *id, time_t crashTime)
00208 {
00209   static const char *KeyBuildVersion = "BuildVersion";
00210   static const char *KeyCrashTime = "CrashTime";
00211   static const char *KeyStartupTime = "StartupTime";
00212   static const char *KeyRestartExecutable = "RestartExecutable";
00213   static const char *KeyRestartExecutableArgs = "RestartExecutableArgs";
00214 
00215   _char_t extraInfoPath[MAX_PATH_LEN] = TEXT("");
00216   append_string(extraInfoPath, path, MAX_PATH_LEN);
00217   append_string(extraInfoPath, PATH_SEPARATOR, MAX_PATH_LEN);
00218   append_string(extraInfoPath, id, MAX_PATH_LEN);
00219   size_t len = append_string(extraInfoPath, TEXT(".dmp.info"), MAX_PATH_LEN);
00220   if (len >= MAX_PATH_LEN)
00221     return false;
00222 
00223 #if defined(Q_OS_WIN32)
00224   HANDLE hFile = CreateFile(extraInfoPath, GENERIC_WRITE, 0, NULL,
00225                             CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
00226   if (hFile == INVALID_HANDLE_VALUE)
00227     return false;
00228 #else
00229   /* TODO: Implement for non-Windowses */
00230 #endif
00231   
00232   char crashTimeString[24], startupTimeString[24];
00233   TIME_TO_STRING(crashTimeString, 24, crashTime);
00234   TIME_TO_STRING(startupTimeString, 24, startupTime);
00235 
00236   write_keyval_to_file(hFile, KeyBuildVersion, buildVersion);
00237   write_keyval_to_file(hFile, KeyCrashTime, crashTimeString);
00238   write_keyval_to_file(hFile, KeyStartupTime, startupTimeString);
00239   write_keyval_to_file(hFile, KeyRestartExecutable, restartExecutable);
00240   write_keyval_to_file(hFile, KeyRestartExecutableArgs, restartExecutableArgs);
00241 
00242 #if defined(Q_OS_WIN32)
00243   CloseHandle(hFile);
00244 #else
00245   /* TODO: Implement for non-Windowses */
00246   /* close(hFile); */
00247 #endif
00248   return true;
00249 }
00250 
00251 /** Breakpad-installed exception handler. This function gets called in the
00252  * event of a crash. If <b>showCrashReporter</b> is true, this will execute
00253  * the crash reporting application, passing it the name and location of the
00254  * generated minidump, the absolute path to the (now crashed) Vidalia
00255  * executable, and any arguments that may be needed to restart Vidalia.
00256  */
00257 bool
00258 minidump_callback(const _char_t *path,    // Path to the minidump file
00259                   const _char_t *id,      // Minidump UUID
00260                   void *context,          // Callback context
00261 #if defined(Q_OS_WIN32)
00262                   EXCEPTION_POINTERS *exInfo,
00263                   MDRawAssertionInfo *assertionInfo,
00264 #endif
00265                   bool succeeded)
00266 {
00267   if (! succeeded || ! showCrashReporter)
00268     return false;
00269 
00270   /* Write the extra dump info, such as application uptime, executable to
00271    * restart, and any necessary restart arguments. */
00272   write_extra_dump_info(path, id, time(NULL));
00273 
00274   /* Format the command line used to launch the crash reporter */
00275   _char_t commandLine[MAX_CMD_LEN] = TEXT("");
00276   append_string(commandLine, TEXT("\""), MAX_CMD_LEN);
00277   append_string(commandLine, crashReporterExecutable, MAX_CMD_LEN);
00278   append_string(commandLine, TEXT("\" \""), MAX_CMD_LEN);
00279   append_string(commandLine, path, MAX_CMD_LEN);
00280   append_string(commandLine, PATH_SEPARATOR, MAX_CMD_LEN);
00281   append_string(commandLine, id, MAX_CMD_LEN);
00282   size_t len = append_string(commandLine, TEXT(".dmp\""), MAX_CMD_LEN);
00283   if (len >= MAX_CMD_LEN)
00284     return false;
00285 
00286   /* Launch the crash reporter with the name and location of the minidump */
00287 #if defined(Q_OS_WIN32)
00288   PROCESS_INFORMATION pi;
00289   STARTUPINFOW si;
00290 
00291   ZeroMemory(&pi, sizeof(pi));
00292   ZeroMemory(&si, sizeof(si));
00293   si.cb = sizeof(si);
00294   si.dwFlags = STARTF_USESHOWWINDOW;
00295   si.wShowWindow = SW_SHOWDEFAULT;
00296 
00297   BOOL rc = CreateProcess(NULL, (LPWSTR)commandLine, NULL, NULL, FALSE, 0,
00298                           NULL, NULL, &si, &pi);
00299   if (rc) {
00300     CloseHandle(pi.hThread);
00301     CloseHandle(pi.hProcess);
00302   }
00303   TerminateProcess(GetCurrentProcess(), 1);
00304   return true;
00305 #else
00306   /* TODO: Implement for non-Windowses */
00307   return false;
00308 #endif
00309 }
00310 
00311 bool
00312 install_exception_handler(const QString &dumpPath)
00313 {
00314   /* Create a directory for the crash dumps if it doesn't already exist */
00315   QDir dumpDir(dumpPath);
00316   if (! dumpDir.exists() && ! dumpDir.mkdir("."))
00317     return false;
00318 
00319   /* Create the exception handler and specify where the dumps should go */
00320   exceptionHandler = new google_breakpad::ExceptionHandler(
00321 #if defined(Q_OS_WIN32)
00322                             dumpDir.absolutePath().toStdWString(),
00323 #else
00324                             dumpDir.absolutePath().toStdString(),
00325 #endif
00326                             NULL,
00327                             minidump_callback,
00328                             NULL,
00329 #if defined(Q_OS_WIN32)
00330                             google_breakpad::ExceptionHandler::HANDLER_ALL);
00331 #else
00332                             true);
00333 #endif
00334   if (! exceptionHandler)
00335     return false;
00336 
00337   startupTime = time(NULL);
00338   return true;
00339 }
00340 
00341 void
00342 remove_exception_handler(void)
00343 {
00344   if (exceptionHandler) {
00345     delete exceptionHandler;
00346     exceptionHandler = 0;
00347   }
00348 }
00349 
00350 bool
00351 set_crash_reporter(const QString &crashReporter)
00352 {
00353 #if defined(Q_OS_WIN32)
00354   if (crashReporter.length() <= CrashReporter::MAX_PATH_LEN) {
00355     crashReporter.toWCharArray(crashReporterExecutable);
00356     crashReporterExecutable[crashReporter.length()] = L'\0';
00357 #else
00358   QByteArray utf8 = crashReporter.toUtf8();
00359   if (utf8.length() <= CrashReporter::MAX_PATH_LEN) {
00360     memcpy(crashReporterExecutable, utf8.constData(), utf8.length());
00361     crashReporterExecutable[utf8.length()] = '\0';
00362 #endif
00363     showCrashReporter = true;
00364   } else {
00365     /* If the given path is longer than MAX_PATH_LEN, no crash reporting
00366      * application will be set since the user's platform wouldn't be able to
00367      * execute it anyway.
00368      */
00369     showCrashReporter = false;
00370   }
00371   return showCrashReporter;
00372 }
00373 
00374 bool
00375 set_restart_options(const QString &executable, const QStringList &arguments)
00376 {
00377   QByteArray exe = executable.toUtf8();
00378   if (exe.length() > MAX_CMD_LEN)
00379     return false;
00380 
00381   QByteArray args = string_format_arguments(arguments).toUtf8();
00382   if (args.length() > MAX_CMD_LEN)
00383     return false;
00384 
00385   memcpy(restartExecutable, exe.constData(), exe.length());
00386   restartExecutable[exe.length()] = '\0';
00387 
00388   memcpy(restartExecutableArgs, args.constData(), args.length());
00389   restartExecutableArgs[args.length()] = '\0';
00390 
00391   return true;
00392 }
00393 
00394 bool
00395 set_build_version(const QString &version)
00396 {
00397   if (version.length() > MAX_VERSION_LEN)
00398     return false;
00399 
00400   QByteArray ascii = version.toAscii();
00401   memcpy(buildVersion, ascii.constData(), ascii.length());
00402   buildVersion[ascii.length()] = '\0';
00403 
00404   return true;
00405 }
00406 
00407 }
00408 
Generated on Mon Aug 30 23:09:48 2010 for Vidalia by  doxygen 1.6.3