Vidalia 0.3.1
CrashReporter.cpp
Go to the documentation of this file.
1/*
2** This file is part of Vidalia, and is subject to the license terms in the
3** LICENSE file, found in the top level directory of this distribution. If you
4** did not receive the LICENSE file with this file, you may obtain it from the
5** Vidalia source package distributed by the Vidalia Project at
6** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7** including this file, may be copied, modified, propagated, or distributed
8** except according to the terms described in the LICENSE file.
9*/
10/*
11** The append_string() function in this file is derived from the implementation
12** of strlcat() by Todd C. Miller. It is licensed as follows:
13**
14** Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
15** All rights reserved.
16**
17** Redistribution and use in source and binary forms, with or without
18** modification, are permitted provided that the following conditions
19** are met:
20** 1. Redistributions of source code must retain the above copyright
21** notice, this list of conditions and the following disclaimer.
22** 2. Redistributions in binary form must reproduce the above copyright
23** notice, this list of conditions and the following disclaimer in the
24** documentation and/or other materials provided with the distribution.
25** 3. The name of the author may not be used to endorse or promote products
26** derived from this software without specific prior written permission.
27**
28** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
29** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
30** AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
31** THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
34** OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
35** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
36** OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37** ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38*/
39
40/*
41** \file CrashReporter.h
42** \brief General routines to install a Breakpad-based exception handler and
43** set options related to launching the crash reporting application.
44*/
45
46#include "CrashReporter.h"
47#include "stringutil.h"
48
49#if defined(Q_OS_WIN32)
50#include <client/windows/handler/exception_handler.h>
51#elif defined(Q_OS_MAC)
52#include <client/mac/handler/exception_handler.h>
53#include <sys/wait.h>
54#include <fcntl.h>
55#elif defined(Q_OS_LINUX)
56#include <client/linux/handler/exception_handler.h>
57#include <sys/wait.h>
58#include <fcntl.h>
59#elif defined(Q_OS_SOLARIS)
60#include <client/solaris/handler/exception_handler.h>
61#endif
62
63#include <QString>
64#include <QStringList>
65#include <QFileInfo>
66#include <QDir>
67
68#include <time.h>
69
70
72{
73#if defined(Q_OS_WIN32)
74typedef wchar_t _char_t;
75typedef HANDLE _file_handle_t;
76#define PATH_SEPARATOR TEXT("\\")
77# ifdef _USE_32BIT_TIME_T
78# define TIME_TO_STRING(buf, buflen, t) \
79 _ltoa_s(t, buf, buflen, 10)
80# else
81# define TIME_TO_STRING(buf, buflen, t) \
82 _i64toa_s(t, buf, buflen, 10)
83# endif
84#else
85typedef char _char_t;
86typedef int _file_handle_t;
87#define PATH_SEPARATOR "/"
88#define TEXT(x) (x)
89#define TIME_TO_STRING(buf, buflen, t) \
90 snprintf(buf, buflen, "%ld", t)
91#endif
92
93/** Pointer to the Breakpad-installed exception handler called if Vidalia
94 * crashes.
95 * \sa install_exception_handler()
96 */
97static google_breakpad::ExceptionHandler *exceptionHandler = 0;
98
99/** If true, the crash reporting application will be displayed when the
100 * Breakpad-installed exception handler is called. Otherwise, the system
101 * will handle the exception itself.
102 * \sa set_crash_reporter()
103 */
104static bool showCrashReporter = false;
105
106/** Absolute path of the crash reporting application that will be launched
107 * from the exception handler.
108 * \sa set_crash_reporter()
109 */
111
112/** Version information for the application being monitored for crashes.
113 * The version will be written to the extra information file alongside
114 * the minidump.
115 */
116static char buildVersion[MAX_VERSION_LEN + 1] = "";
117
118/** Path and filename of the application to restart after displaying
119 * the crash reporting dialog. The contents of this string are encoded
120 * in UTF-8.
121 * \sa set_restart_options()
122 */
123static char restartExecutable[MAX_CMD_LEN + 1] = "";
124
125/** Additional arguments to use when restarting the crashed application.
126 * The contents of this string are encoded in UTF-8.
127 * \sa set_restart_options()
128 */
129static char restartExecutableArgs[MAX_CMD_LEN + 1] = "";
130
131
132/** Records the time at which install_exception_handler() is called, which
133 * is usually as early as possible during application startup. This is used
134 * in minidump_callback() to determine how long the application was running
135 * before it crashed.
136 * \sa install_exception_handler()
137 * \sa minidump_callback()
138 */
139static time_t startupTime = 0;
140
141
142/** Slightly modified version of the strlcat() implementation by Todd C.
143 * Miller (see the top of this file or the LICENSE file for license details),
144 * that supports arguments of either wchar_t* on Windows or the usual char*
145 * everywhere else but retains the semantics of strlcat().
146 */
147static size_t
148append_string(_char_t *dst, const _char_t *src, size_t siz)
149{
150 _char_t *d = dst;
151 const _char_t *s = src;
152 size_t n = siz;
153 size_t dlen;
154
155 /* Find the end of dst and adjust bytes left but don't go past end */
156 while (n-- != 0 && *d != TEXT('\0'))
157 d++;
158 dlen = d - dst;
159 n = siz - dlen;
160
161 if (n == 0)
162#if defined(Q_OS_WIN32)
163 return (dlen + wcslen(s));
164#else
165 return(dlen + strlen(s));
166#endif
167
168 while (*s != TEXT('\0')) {
169 if (n != 1) {
170 *d++ = *s;
171 n--;
172 }
173 s++;
174 }
175 *d = TEXT('\0');
176
177 return(dlen + (s - src)); /* count does not include NUL */
178}
179
180/** Writes the formatted string "<b>key</b>=</b>val\n" to the file handle
181 * specified by <b>hFile</b>. On Windows, <b>hFile</b> is a HANDLE. Everywhere
182 * else, <b>hFile</b> is an int.
183 */
184static void
185write_keyval_to_file(_file_handle_t hFile, const char *key, const char *val)
186{
187#if defined(Q_OS_WIN32)
188 DWORD dwWritten;
189 WriteFile(hFile, key, strlen(key), &dwWritten, NULL);
190 WriteFile(hFile, "=", 1, &dwWritten, NULL);
191 WriteFile(hFile, val, strlen(val), &dwWritten, NULL);
192 WriteFile(hFile, "\n", 1, &dwWritten, NULL);
193#else
194 write(hFile, key, strlen(key));
195 write(hFile, "=", 1);
196 write(hFile, val, strlen(val));
197 write(hFile, "\n", 1);
198#endif
199}
200
201/** Writes to a file extra information used by the crash reporting
202 * application such as how long the application was running before it
203 * crashed, the application to restart, as well as any extra arguments.
204 * The contents of the file are formatted as a series of "Key=Val\n" pairs.
205 * The written file has the same path and base filename as the minidump
206 * file, with ".info" appended to the end. Returns true if the file was
207 * created succesfully. Otherwise, returns false.
208 */
209static bool
210write_extra_dump_info(const _char_t *path, const _char_t *id, time_t crashTime)
211{
212 static const char *KeyBuildVersion = "BuildVersion";
213 static const char *KeyCrashTime = "CrashTime";
214 static const char *KeyStartupTime = "StartupTime";
215 static const char *KeyRestartExecutable = "RestartExecutable";
216 static const char *KeyRestartExecutableArgs = "RestartExecutableArgs";
217
218 _char_t extraInfoPath[MAX_PATH_LEN] = TEXT("");
219 append_string(extraInfoPath, path, MAX_PATH_LEN);
221 append_string(extraInfoPath, id, MAX_PATH_LEN);
222 size_t len = append_string(extraInfoPath, TEXT(".dmp.info"), MAX_PATH_LEN);
223 if (len >= MAX_PATH_LEN)
224 return false;
225
226#if defined(Q_OS_WIN32)
227 HANDLE hFile = CreateFile(extraInfoPath, GENERIC_WRITE, 0, NULL,
228 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
229 if (hFile == INVALID_HANDLE_VALUE)
230 return false;
231#else
232 _file_handle_t hFile = creat(extraInfoPath, S_IRUSR | S_IWUSR);
233 if(hFile == -1) {
234 return false;
235 }
236#endif
237
238 char crashTimeString[24], startupTimeString[24];
239 TIME_TO_STRING(crashTimeString, 24, crashTime);
240 TIME_TO_STRING(startupTimeString, 24, startupTime);
241
242 write_keyval_to_file(hFile, KeyBuildVersion, buildVersion);
243 write_keyval_to_file(hFile, KeyCrashTime, crashTimeString);
244 write_keyval_to_file(hFile, KeyStartupTime, startupTimeString);
245 write_keyval_to_file(hFile, KeyRestartExecutable, restartExecutable);
246 write_keyval_to_file(hFile, KeyRestartExecutableArgs, restartExecutableArgs);
247
248#if defined(Q_OS_WIN32)
249 CloseHandle(hFile);
250#else
251 close(hFile);
252#endif
253 return true;
254}
255
256/** Breakpad-installed exception handler. This function gets called in the
257 * event of a crash. If <b>showCrashReporter</b> is true, this will execute
258 * the crash reporting application, passing it the name and location of the
259 * generated minidump, the absolute path to the (now crashed) Vidalia
260 * executable, and any arguments that may be needed to restart Vidalia.
261 */
262bool
263minidump_callback(const _char_t *path, // Path to the minidump file
264 const _char_t *id, // Minidump UUID
265 void *context, // Callback context
266#if defined(Q_OS_WIN32)
267 EXCEPTION_POINTERS *exInfo,
268 MDRawAssertionInfo *assertionInfo,
269#endif
270 bool succeeded)
271{
272 if (! succeeded || ! showCrashReporter)
273 return false;
274
275 /* Write the extra dump info, such as application uptime, executable to
276 * restart, and any necessary restart arguments. */
277 write_extra_dump_info(path, id, time(NULL));
278
279#if defined(Q_OS_WIN32)
280 /* Format the command line used to launch the crash reporter */
281 _char_t commandLine[MAX_CMD_LEN] = TEXT("");
282 append_string(commandLine, TEXT("\""), MAX_CMD_LEN);
284 append_string(commandLine, TEXT("\" \""), MAX_CMD_LEN);
285 append_string(commandLine, path, MAX_CMD_LEN);
287 append_string(commandLine, id, MAX_CMD_LEN);
288 size_t len = append_string(commandLine, TEXT(".dmp\""), MAX_CMD_LEN);
289 if (len >= MAX_CMD_LEN)
290 return false;
291
292 /* Launch the crash reporter with the name and location of the minidump */
293 PROCESS_INFORMATION pi;
294 STARTUPINFOW si;
295
296 ZeroMemory(&pi, sizeof(pi));
297 ZeroMemory(&si, sizeof(si));
298 si.cb = sizeof(si);
299 si.dwFlags = STARTF_USESHOWWINDOW;
300 si.wShowWindow = SW_SHOWDEFAULT;
301
302 BOOL rc = CreateProcess(NULL, (LPWSTR)commandLine, NULL, NULL, FALSE, 0,
303 NULL, NULL, &si, &pi);
304 if (rc) {
305 CloseHandle(pi.hThread);
306 CloseHandle(pi.hProcess);
307 }
308 TerminateProcess(GetCurrentProcess(), 1);
309 return true;
310#else
311 /* Format the command line used to launch the crash reporter */
312 _char_t args[MAX_CMD_LEN] = TEXT("");
313 size_t len;
314
315 append_string(args, path, MAX_CMD_LEN);
317 append_string(args, id, MAX_CMD_LEN);
318 len = append_string(args, TEXT(".dmp"), MAX_CMD_LEN);
319 if (len >= MAX_CMD_LEN)
320 return false;
321
322 char *nargs[] = {crashReporterExecutable, args, NULL};
323
324 pid_t p = fork(), ret;
325 int status;
326 if(p == 0) {
327 execv(crashReporterExecutable, nargs);
328 } else {
329 ret = wait(&status);
330 if(ret == -1)
331 return false;
332 }
333 return true;
334#endif
335}
336
337bool
338install_exception_handler(const QString &dumpPath)
339{
340 /* Create a directory for the crash dumps if it doesn't already exist */
341 QDir dumpDir(dumpPath);
342 if (! dumpDir.exists() && ! dumpDir.mkdir("."))
343 return false;
344
345 /* Create the exception handler and specify where the dumps should go */
346 exceptionHandler = new google_breakpad::ExceptionHandler(
347#if defined(Q_OS_WIN32)
348 dumpDir.absolutePath().toStdWString(),
349#else
350 dumpDir.absolutePath().toStdString(),
351#endif
352 NULL,
354 NULL,
355#if defined(Q_OS_WIN32)
356 google_breakpad::ExceptionHandler::HANDLER_ALL);
357#else
358 true);
359#endif
360 if (! exceptionHandler)
361 return false;
362
363 startupTime = time(NULL);
364 return true;
365}
366
367void
369{
370 if (exceptionHandler) {
371 delete exceptionHandler;
373 }
374}
375
376bool
377set_crash_reporter(const QString &crashReporter)
378{
379#if defined(Q_OS_WIN32)
380 if (crashReporter.length() <= CrashReporter::MAX_PATH_LEN) {
381 crashReporter.toWCharArray(crashReporterExecutable);
382 crashReporterExecutable[crashReporter.length()] = L'\0';
383#else
384 QByteArray utf8 = crashReporter.toUtf8();
385 if (utf8.length() <= CrashReporter::MAX_PATH_LEN) {
386 memcpy(crashReporterExecutable, utf8.constData(), utf8.length());
387 crashReporterExecutable[utf8.length()] = '\0';
388#endif
389 showCrashReporter = true;
390 } else {
391 /* If the given path is longer than MAX_PATH_LEN, no crash reporting
392 * application will be set since the user's platform wouldn't be able to
393 * execute it anyway.
394 */
395 showCrashReporter = false;
396 }
397 return showCrashReporter;
398}
399
400bool
401set_restart_options(const QString &executable, const QStringList &arguments)
402{
403 QByteArray exe = executable.toUtf8();
404 if (exe.length() > MAX_CMD_LEN)
405 return false;
406
407 QByteArray args = string_format_arguments(arguments).toUtf8();
408 if (args.length() > MAX_CMD_LEN)
409 return false;
410
411 memcpy(restartExecutable, exe.constData(), exe.length());
412 restartExecutable[exe.length()] = '\0';
413
414 memcpy(restartExecutableArgs, args.constData(), args.length());
415 restartExecutableArgs[args.length()] = '\0';
416
417 return true;
418}
419
420bool
421set_build_version(const QString &version)
422{
423 if (version.length() > MAX_VERSION_LEN)
424 return false;
425
426 QByteArray ascii = version.toAscii();
427 memcpy(buildVersion, ascii.constData(), ascii.length());
428 buildVersion[ascii.length()] = '\0';
429
430 return true;
431}
432
433}
434
#define PATH_SEPARATOR
#define TIME_TO_STRING(buf, buflen, t)
#define TEXT(x)
QString p(QString str)
Definition: html.cpp:22
static _char_t crashReporterExecutable[MAX_PATH_LEN+1]
static bool showCrashReporter
static const int MAX_CMD_LEN
Definition: CrashReporter.h:41
static char buildVersion[MAX_VERSION_LEN+1]
static bool write_extra_dump_info(const _char_t *path, const _char_t *id, time_t crashTime)
bool set_build_version(const QString &version)
static size_t append_string(_char_t *dst, const _char_t *src, size_t siz)
bool set_crash_reporter(const QString &crashReporter)
bool install_exception_handler(const QString &dumpPath)
static void write_keyval_to_file(_file_handle_t hFile, const char *key, const char *val)
static google_breakpad::ExceptionHandler * exceptionHandler
static const int MAX_PATH_LEN
Definition: CrashReporter.h:33
bool minidump_callback(const _char_t *path, const _char_t *id, void *context, bool succeeded)
void remove_exception_handler(void)
static const int MAX_VERSION_LEN
Definition: CrashReporter.h:47
static char restartExecutable[MAX_CMD_LEN+1]
bool set_restart_options(const QString &executable, const QStringList &arguments)
static time_t startupTime
static char restartExecutableArgs[MAX_CMD_LEN+1]
QString string_format_arguments(const QStringList &args)
Definition: stringutil.cpp:360