# HG changeset patch # User michi_cc # Date 2009-10-04 21:08:30 # Node ID ab2ccc5258502d99a6616ba9e000676723bd4f2d # Parent 946b6b8895b919d380971b96cbea5b37c6101815 (svn r17706) -Codechange: [OSX] Rework the crash handling to use the common CrashLog infrastructure. diff --git a/source.list b/source.list --- a/source.list +++ b/source.list @@ -79,7 +79,11 @@ townname.cpp #if OS2 os/os2/os2.cpp #else - os/unix/crashlog_unix.cpp + #if OSX + os/macosx/crashlog_osx.cpp + #else + os/unix/crashlog_unix.cpp + #end os/unix/unix.cpp #end #end diff --git a/src/os/macosx/crashlog_osx.cpp b/src/os/macosx/crashlog_osx.cpp new file mode 100644 --- /dev/null +++ b/src/os/macosx/crashlog_osx.cpp @@ -0,0 +1,238 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file crashlog_osx.cpp OS X crash log handler */ + +#include "../../stdafx.h" +#include "../../crashlog.h" +#include "../../string_func.h" +#include "../../gamelog.h" +#include "macos.h" + +#include +#include +#include +#include +#include + + +/* Macro testing a stack address for valid alignment. */ +#if defined(__i386__) +#define IS_ALIGNED(addr) (((uintptr_t)(addr) & 0xf) == 8) +#else +#define IS_ALIGNED(addr) (((uintptr_t)(addr) & 0xf) == 0) +#endif + +/* printf format specification for 32/64-bit addresses. */ +#if __LP64__ +#define PRINTF_PTR "0x%016lx" +#else +#define PRINTF_PTR "0x%08lx" +#endif + +#define MAX_STACK_FRAMES 64 + +/** + * OSX implementation for the crash logger. + */ +class CrashLogOSX : public CrashLog { + /** Signal that has been thrown. */ + int signum; + + char filename_save[MAX_PATH]; ///< Path of crash.sav + char filename_log[MAX_PATH]; ///< Path of crash.log + + /* virtual */ char *LogOSVersion(char *buffer, const char *last) const + { + int ver_maj, ver_min, ver_bug; + GetMacOSVersion(&ver_maj, &ver_min, &ver_bug); + + const NXArchInfo *arch = NXGetLocalArchInfo(); + + return buffer + seprintf(buffer, last, + "Operating system:\n" + " Name: Mac OS X\n" + " Release: %d.%d.%d\n" + " Machine: %s\n" + " Min Ver: %d\n\n", + ver_maj, ver_min, ver_bug, + arch != NULL ? arch->description : "unknown", + MAC_OS_X_VERSION_MIN_REQUIRED + ); + } + + /* virtual */ char *LogError(char *buffer, const char *last, const char *message) const + { + return buffer + seprintf(buffer, last, + "Crash reason:\n" + " Signal: %s (%d)\n" + " Message: %s\n\n", + strsignal(this->signum), + this->signum, + message == NULL ? "" : message + ); + } + + /* virtual */ char *LogStacktrace(char *buffer, const char *last) const + { + /* As backtrace() is only implemented in 10.5 or later, + * we're rolling our own here. Mostly based on + * http://stackoverflow.com/questions/289820/getting-the-current-stack-trace-on-mac-os-x + * and some details looked up in the Darwin sources. */ + buffer += seprintf(buffer, last, "\nStacktrace:\n"); + + void **frame; +#if defined(__ppc__) || defined(__ppc64__) + /* Apple says __builtin_frame_address can be broken on PPC. */ + __asm__ volatile("mr %0, r1" : "=r" (frame)); +#else + frame = (void **)__builtin_frame_address(0); +#endif + + for (int i = 0; frame != NULL && i < MAX_STACK_FRAMES; i++) { + /* Get IP for current stack frame. */ +#if defined(__ppc__) || defined(__ppc64__) + void *ip = frame[2]; +#else + void *ip = frame[1]; +#endif + if (ip == NULL) break; + + /* Print running index. */ + buffer += seprintf(buffer, last, " [%02d]", i); + + Dl_info dli; + bool dl_valid = dladdr(ip, &dli) != 0; + + const char *fname = "???"; + if (dl_valid && dli.dli_fname) { + /* Valid image name? Extract filename from the complete path. */ + const char *s = strrchr(dli.dli_fname, '/'); + if (s != NULL) { + fname = s + 1; + } else { + fname = dli.dli_fname; + } + } + /* Print image name and IP. */ + buffer += seprintf(buffer, last, " %-20s " PRINTF_PTR, fname, (uintptr_t)ip); + + /* Print function offset if information is available. */ + if (dl_valid && dli.dli_sname != NULL && dli.dli_saddr != NULL) { + /* Try to demangle a possible C++ symbol. */ + int status = -1; + char *func_name = abi::__cxa_demangle(dli.dli_sname, NULL, 0, &status); + + ptrdiff_t offset = (intptr_t)ip - (intptr_t)dli.dli_saddr; + buffer += seprintf(buffer, last, " (%s + %d)", func_name != NULL ? func_name : dli.dli_sname, offset); + + free(func_name); + } + buffer += seprintf(buffer, last, "\n"); + + /* Get address of next stack frame. */ + void **next = (void **)frame[0]; + /* Frame address not increasing or not aligned? Broken stack, exit! */ + if (next <= frame || !IS_ALIGNED(next)) break; + frame = next; + } + + return buffer + seprintf(buffer, last, "\n"); + } + +public: + /** + * A crash log is always generated by signal. + * @param signum the signal that was caused by the crash. + */ + CrashLogOSX(int signum) : signum(signum) + { + filename_log[0] = '\0'; + filename_save[0] = '\0'; + } + + /** Generate the crash log. */ + bool MakeCrashLog() + { + char buffer[65536]; + bool ret = true; + + printf("Crash encountered, generating crash log...\n"); + this->FillCrashLog(buffer, lastof(buffer)); + printf("%s\n", buffer); + printf("Crash log generated.\n\n"); + + printf("Writing crash log to disk...\n"); + if (!this->WriteCrashLog(buffer, filename_log, lastof(filename_log))) { + filename_log[0] = '\0'; + ret = false; + } + + printf("Writing crash savegame...\n"); + if (!this->WriteSavegame(filename_save, lastof(filename_save))) { + filename_save[0] = '\0'; + ret = false; + } + + return ret; + } + + /** Show a dialog with the crash information. */ + void DisplayCrashDialog() const + { + static const char crash_title[] = + "A serious fault condition occured in the game. The game will shut down."; + static const char crash_info[] = + "Please send the generated crash information and the last (auto)save to the developers. " + "This will greatly help debugging. The correct place to do this is http://bugs.openttd.org.\n\n" + "Generated file(s):\n%s\n%s"; + + char message[1024]; + seprintf(message, lastof(message), crash_info, this->filename_log, this->filename_save); + + ShowMacDialog(crash_title, message, "Quit"); + } +}; + +/** The signals we want our crash handler to handle. */ +static const int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL, SIGSYS }; + +/** + * Entry point for the crash handler. + * @note Not static so it shows up in the backtrace. + * @param signum the signal that caused us to crash. + */ +void CDECL HandleCrash(int signum) +{ + /* Disable all handling of signals by us, so we don't go into infinite loops. */ + for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) { + signal(*i, SIG_DFL); + } + + if (GamelogTestEmergency()) { + ShowMacDialog("A serious fault condition occured in the game. The game will shut down.", + "As you loaded an emergency savegame no crash information will be generated.\n", + "Quit"); + abort(); + } + + CrashLogOSX log(signum); + log.MakeCrashLog(); + log.DisplayCrashDialog(); + + CrashLog::AfterCrashLogCleanup(); + abort(); +} + +/* static */ void CrashLog::InitialiseCrashLog() +{ + for (const int *i = _signals_to_handle; i != endof(_signals_to_handle); i++) { + signal(*i, HandleCrash); + } +} diff --git a/src/os/macosx/macos.h b/src/os/macosx/macos.h --- a/src/os/macosx/macos.h +++ b/src/os/macosx/macos.h @@ -30,29 +30,8 @@ #endif -/* - * Functions to show the popup window - * use ShowMacDialog when you want to control title, message and text on the button - * ShowMacAssertDialog is used by assert - * ShowMacErrorDialog should be used when an unrecoverable error shows up. It only contains the title, which will should tell what went wrong - * the function then adds text that tells the user to update and then report the bug if it's present in the newest version - * It also quits in a nice way since we call it when we know something happened that will crash OpenTTD (like a needed pointer turns out to be NULL or similar) - */ -void ShowMacDialog ( const char *title, const char *message, const char *buttonLabel ); -void ShowMacAssertDialog ( const char *function, const char *file, const int line, const char *expression ); -void ShowMacErrorDialog(const char *error); - -/* Since MacOS X users will never see an assert unless they started the game from a terminal - * we're using a custom assert(e) macro. */ -#undef assert - -#ifdef NDEBUG -#define assert(e) ((void)0) -#else - -#define assert(e) \ - (__builtin_expect(!(e), 0) ? ShowMacAssertDialog ( __func__, __FILE__, __LINE__, #e ): (void)0 ) -#endif +/** Helper function displaying a message the best possible way. */ +void ShowMacDialog(const char *title, const char *message, const char *button_label); void GetMacOSVersion(int *return_major, int *return_minor, int *return_bugfix); diff --git a/src/os/macosx/macos.mm b/src/os/macosx/macos.mm --- a/src/os/macosx/macos.mm +++ b/src/os/macosx/macos.mm @@ -10,6 +10,7 @@ #include "../../stdafx.h" #include "../../core/bitmath_func.hpp" #include "../../rev.h" +#include "macos.h" #define Rect OTTDRect #define Point OTTDPoint @@ -17,15 +18,6 @@ #undef Rect #undef Point -#include -#include -#include -#include - -#ifndef CPU_SUBTYPE_POWERPC_970 -#define CPU_SUBTYPE_POWERPC_970 ((cpu_subtype_t) 100) -#endif - /* * This file contains objective C * Apple uses objective C instead of plain C to interact with OS specific/native functions @@ -62,74 +54,6 @@ void GetMacOSVersion(int *return_major, } } -void ToggleFullScreen(bool fs); - -static char *GetOSString() -{ - static char buffer[175]; - const char *CPU; - char newgrf[125]; - // get the hardware info - host_basic_info_data_t hostInfo; - mach_msg_type_number_t infoCount; - - infoCount = HOST_BASIC_INFO_COUNT; - host_info( - mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount - ); - - // replace the hardware info with strings, that tells a bit more than just an int - switch (hostInfo.cpu_subtype) { -#ifdef __POWERPC__ - case CPU_SUBTYPE_POWERPC_750: CPU = "G3"; break; - case CPU_SUBTYPE_POWERPC_7400: - case CPU_SUBTYPE_POWERPC_7450: CPU = "G4"; break; - case CPU_SUBTYPE_POWERPC_970: CPU = "G5"; break; - default: CPU = "Unknown PPC"; break; -#else - /* it looks odd to have a switch for two cases, but it leaves room for easy - * expansion. Odds are that Apple will some day use newer CPUs than i686 - */ - case CPU_SUBTYPE_PENTPRO: CPU = "i686"; break; - default: CPU = "Unknown Intel"; break; -#endif - } - - /* Get the version of OSX */ - char OS[20]; - int version_major, version_minor, version_bugfix; - GetMacOSVersion(&version_major, &version_minor, &version_bugfix); - - if (version_major != -1 && version_minor != -1 && version_bugfix != -1) { - snprintf(OS, lengthof(OS), "%d.%d.%d", version_major, version_minor, version_bugfix); - } else { - snprintf(OS, lengthof(OS), "uncertain %d.%d.%d", version_major, version_minor, version_bugfix); - } - - // make a list of used newgrf files -/* if (_first_grffile != NULL) { - char *n = newgrf; - const GRFFile *file; - - for (file = _first_grffile; file != NULL; file = file->next) { - n = strecpy(n, " ", lastof(newgrf)); - n = strecpy(n, file->filename, lastof(newgrf)); - } - } else {*/ - sprintf(newgrf, "none"); -// } - - snprintf( - buffer, lengthof(buffer), - "Please add this info: (tip: copy-paste works)\n" - "CPU: %s, OSX: %s, OpenTTD version: %s\n" - "NewGRF files:%s", - CPU, OS, _openttd_revision, newgrf - ); - return buffer; -} - - #ifdef WITH_SDL void ShowMacDialog(const char *title, const char *message, const char *buttonLabel) @@ -139,7 +63,7 @@ void ShowMacDialog(const char *title, co #elif defined WITH_COCOA -void CocoaDialog(const char *title, const char *message, const char *buttonLabel); +extern void CocoaDialog(const char *title, const char *message, const char *buttonLabel); void ShowMacDialog(const char *title, const char *message, const char *buttonLabel) { @@ -156,42 +80,15 @@ void ShowMacDialog(const char *title, co #endif -void ShowMacAssertDialog(const char *function, const char *file, const int line, const char *expression) + +void ShowOSErrorBox(const char *buf, bool system) { - const char *buffer = - [[NSString stringWithFormat:@ - "An assertion has failed and OpenTTD must quit.\n" - "%s in %s (line %d)\n" - "\"%s\"\n" - "\n" - "You should report this error the OpenTTD developers if you think you found a bug.\n" - "\n" - "%s", - function, file, line, expression, GetOSString()] cString - ]; - NSLog(@"%s", buffer); - ToggleFullScreen(0); - ShowMacDialog("Assertion Failed", buffer, "Quit"); - - // abort so that a debugger has a chance to notice - abort(); -} - - -void ShowMacErrorDialog(const char *error) -{ - const char *buffer = - [[NSString stringWithFormat:@ - "Please update to the newest version of OpenTTD\n" - "If the problem presists, please report this to\n" - "http://bugs.openttd.org\n" - "\n" - "%s", - GetOSString()] cString - ]; - ToggleFullScreen(0); - ShowMacDialog(error, buffer, "Quit"); - abort(); + /* Display the error in the best way possible. */ + if (system) { + ShowMacDialog("OpenTTD has encountered an error", buf, "Quit"); + } else { + ShowMacDialog(buf, "See the readme for more info.\nMost likely you are missing files from the original TTD.", "Quit"); + } } diff --git a/src/os/macosx/osx_stdafx.h b/src/os/macosx/osx_stdafx.h --- a/src/os/macosx/osx_stdafx.h +++ b/src/os/macosx/osx_stdafx.h @@ -20,6 +20,12 @@ #include #include +/* Some gcc versions include assert.h via this header. As this would interfere + * with our own assert redefinition, include this header first. */ +#if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)) +# include +#endif + /* __LP64__ only exists in 10.5 and higher */ #if defined(__APPLE__) && !defined(__LP64__) # define __LP64__ 0 diff --git a/src/os/unix/unix.cpp b/src/os/unix/unix.cpp --- a/src/os/unix/unix.cpp +++ b/src/os/unix/unix.cpp @@ -218,21 +218,17 @@ void ShowInfo(const char *str) fprintf(stderr, "%s\n", str); } +#if !defined(__APPLE__) void ShowOSErrorBox(const char *buf, bool system) { -#if defined(__APPLE__) - /* this creates an NSAlertPanel with the contents of 'buf' - * this is the native and nicest way to do this on OSX */ - ShowMacDialog( buf, "See readme for more info\nMost likely you are missing files from the original TTD", "Quit" ); -#else /* All unix systems, except OSX. Only use escape codes on a TTY. */ if (isatty(fileno(stderr))) { fprintf(stderr, "\033[1;31mError: %s\033[0;39m\n", buf); } else { fprintf(stderr, "Error: %s\n", buf); } +} #endif -} #ifdef WITH_COCOA void cocoaSetupAutoreleasePool(); diff --git a/src/stdafx.h b/src/stdafx.h --- a/src/stdafx.h +++ b/src/stdafx.h @@ -58,14 +58,7 @@ #include #include #include - -/* MacOS X will use an NSAlert to display failed assertaions since they're lost unless running from a terminal - * strgen always runs from terminal and don't need a window for asserts */ -#if !defined(__APPLE__) || defined(STRGEN) - #include -#else - #include "os/macosx/macos.h" -#endif +#include #if defined(UNIX) || defined(__MINGW32__) #include diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -16,6 +16,7 @@ #ifdef WITH_COCOA #include "../../stdafx.h" +#include "../../os/macosx/macos.h" #define Rect OTTDRect #define Point OTTDPoint diff --git a/src/video/cocoa/wnd_quartz.mm b/src/video/cocoa/wnd_quartz.mm --- a/src/video/cocoa/wnd_quartz.mm +++ b/src/video/cocoa/wnd_quartz.mm @@ -17,6 +17,7 @@ #ifdef ENABLE_COCOA_QUARTZ #include "../../stdafx.h" +#include "../../os/macosx/macos.h" #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 diff --git a/src/video/cocoa/wnd_quickdraw.mm b/src/video/cocoa/wnd_quickdraw.mm --- a/src/video/cocoa/wnd_quickdraw.mm +++ b/src/video/cocoa/wnd_quickdraw.mm @@ -18,6 +18,7 @@ #define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_3 #include "../../stdafx.h" +#include "../../os/macosx/macos.h" #define Rect OTTDRect #define Point OTTDPoint