Fri, 18 Oct 2013 21:52:09 +0300
Added a crash catcher which trigger under Linux. It calls GDB and tries to get a backtrace. Also integrated assertion failure handling to this new system. Removed the print() function in the process (because the new bomb box uses a text edit and QTextDocuments's print method clashes with the macro and I figured it was a good idea to rid it anyway) and replaced all calls with log().
--- a/ldforge.pro Fri Oct 18 18:16:54 2013 +0300 +++ b/ldforge.pro Fri Oct 18 21:52:09 2013 +0300 @@ -24,9 +24,11 @@ CONFIG += debug_and_release CONFIG (debug, debug|release) { - TARGET = ldforge_debug + TARGET = ldforge_debug + DEFINES += DEBUG } else { TARGET = ldforge + DEFINES += RELEASE } unix {
--- a/src/colors.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/colors.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -35,19 +35,16 @@ // ----------------------------------------------------------------------------- void initColors() { LDColor* col; - print ("%1: initializing color information.\n", __func__); + log ("%1: initializing color information.\n", __func__); // Always make sure there's 16 and 24 available. They're special like that. col = new LDColor; - col->faceColor = - col->hexcode = "#AAAAAA"; + col->faceColor = col->hexcode = "#AAAAAA"; col->edgeColor = Qt::black; g_LDColors[maincolor] = col; col = new LDColor; - col->faceColor = - col->edgeColor = - col->hexcode = "#000000"; + col->faceColor = col->edgeColor = col->hexcode = "#000000"; g_LDColors[edgecolor] = col; parseLDConfig();
--- a/src/common.h Fri Oct 18 18:16:54 2013 +0300 +++ b/src/common.h Fri Oct 18 21:52:09 2013 +0300 @@ -25,7 +25,6 @@ #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <stdint.h> #include <stdarg.h> #include <QString> @@ -75,10 +74,6 @@ # define DIRSLASH_CHAR '/' #endif // WIN32 -#ifdef RELEASE -# define NDEBUG // remove asserts -#endif // RELEASE - #define PROP_NAME(GET) m_##GET #define READ_ACCESSOR(T, GET) \ @@ -144,12 +139,17 @@ // Replace assert with a version that shows a GUI dialog if possible. // On Windows I just can't get the actual error messages otherwise. +void assertionFailure (const char* file, int line, const char* funcname, const char* expr); + #ifdef assert -#undef assert +# undef assert #endif // assert -void assertionFailure (const char* file, int line, const char* funcname, const char* expr); -#define assert(N) ((N) ? (void) 0 : assertionFailure (__FILE__, __LINE__, FUNCNAME, #N)) +#ifdef DEBUG +# define assert(N) ((N) ? (void) 0 : assertionFailure (__FILE__, __LINE__, FUNCNAME, #N)) +#else +# define assert(N) +#endif // DEBUG // Version string identifier QString versionString();
--- a/src/config.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/config.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -61,7 +61,7 @@ // ----------------------------------------------------------------------------- bool Config::load() { QSettings* settings = getSettingsObject(); - print ("config::load: Loading configuration file from %1\n", settings->fileName()); + log ("config::load: Loading configuration file from %1\n", settings->fileName()); for (Config* cfg : g_configPointers) { if (!cfg) @@ -80,7 +80,7 @@ // ----------------------------------------------------------------------------- bool Config::save() { QSettings* settings = getSettingsObject(); - print ("Saving configuration to %1...\n", settings->fileName()); + log ("Saving configuration to %1...\n", settings->fileName()); for (Config* cfg : g_configPointers) { if (!cfg)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/crashcatcher.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -0,0 +1,113 @@ +#ifdef __unix__ + +#include <QString> +#include <QProcess> +#include <QTemporaryFile> +#include <QMessageBox> +#include <unistd.h> +#include <signal.h> +#include <sys/prctl.h> +#include "crashcatcher.h" +#include "types.h" +#include "dialogs.h" + +// Is the crash catcher active now? +static bool g_crashCatcherActive = false; + +// If an assertion failed, what was it? +static str g_assertionFailure; + +// List of signals to catch and crash on +static QList<int> g_signalsToCatch ({ + SIGSEGV, // segmentation fault + SIGABRT, // abort() calls + SIGFPE, // floating point exceptions (e.g. division by zero) + SIGILL, // illegal instructions +}); + +// ============================================================================= +// ----------------------------------------------------------------------------- +static void handleSignal (int sig) +{ printf ("caught signal %d\n", sig); + + if (g_crashCatcherActive) + { printf ("caught signal while crash catcher is active!\n"); + return; + } + + if (g_signalsToCatch.indexOf (sig) != -1) + { const pid_t pid = getpid(); + QProcess proc; + QTemporaryFile commandsFile; + + g_crashCatcherActive = true; + printf ("%s: crashed with signal %d, launching gdb\n", __func__, sig); + + if (commandsFile.open()) + { commandsFile.write (fmt ("attach %1\n", pid).toLocal8Bit()); + commandsFile.write (str ("backtrace full\n").toLocal8Bit()); + commandsFile.write (str ("detach\n").toLocal8Bit()); + commandsFile.write (str ("quit").toLocal8Bit()); + commandsFile.flush(); + commandsFile.close(); + } + + QStringList args ({"-x", commandsFile.fileName()}); + + proc.start ("gdb", args); + + // Linux doesn't allow ptrace to be used on anything but direct child processes + // so we need to use prctl to register an exception to this to allow GDB attach to us. + // We need to do this now and no earlier because only now we actually know GDB's PID. + prctl (PR_SET_PTRACER, proc.pid(), 0, 0, 0); + + proc.waitForFinished (5000); + str output = QString (proc.readAllStandardOutput()); + str err = QString (proc.readAllStandardError()); + + bombBox (fmt ("<h3>Program crashed with signal %1</h3>\n\n" + "%2" + "<p><b>GDB <tt>stdout</tt>:</b></p><pre>%3</pre>\n" + "<p><b>GDB <tt>stderr</tt>:</b></p><pre>%4</pre>", + sig, (!g_assertionFailure.isEmpty()) ? g_assertionFailure : "", output, err)); + exit (137); + } +} + +// ============================================================================= +// ----------------------------------------------------------------------------- +void initCrashCatcher() +{ struct sigaction sighandler; + sighandler.sa_handler = &handleSignal; + sighandler.sa_flags = 0; + sigemptyset (&sighandler.sa_mask); + + for (int sig : g_signalsToCatch) + sigaction (sig, &sighandler, null); + + log ("%1: crash catcher hooked to signals: %2\n", __func__, g_signalsToCatch); +} +#endif // #ifdef __unix__ + +// ============================================================================= +// This function must be readily available in both Windows and Linux. We display +// the bomb box straight in Windows while in Linux we let abort() trigger the +// signal handler, which will cause the usual bomb box with GDB diagnostics. +// Said prompt will embed the assertion failure information. +// ----------------------------------------------------------------------------- +void assertionFailure (const char* file, int line, const char* funcname, const char* expr) +{ str errmsg = fmt ( + "<p><b>File</b>: <tt>%1</tt><br />" + "<b>Line</b>: <tt>%2</tt><br />" + "<b>Function:</b> <tt>%3</tt></p>" + "<p>Assertion <b><tt>`%4'</tt></b> failed.</p>", + file, line, funcname, expr); + + g_assertionFailure = errmsg; + +#ifndef __unix__ + bombBox (errmsg); +#endif + + abort(); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/crashcatcher.h Fri Oct 18 21:52:09 2013 +0300 @@ -0,0 +1,11 @@ +#ifndef LDFORGE_CRASHCATCHER_H +#define LDFORGE_CRASHCATCHER_H + +#ifdef __unix__ + +void initCrashCatcher(); + +#else // ifdef __unix__ +#define initCrashCatcher() +#endif // ifdef __unix__ +#endif // ifndef LDFORGE_CRASHCATCHER_H \ No newline at end of file
--- a/src/dialogs.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/dialogs.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -28,8 +28,8 @@ #include <QProgressBar> #include <QCheckBox> #include <QDesktopServices> +#include <QMessageBox> #include <QUrl> - #include "dialogs.h" #include "widgets.h" #include "gui.h" @@ -42,6 +42,8 @@ #include "ui_openprogress.h" #include "ui_extprogpath.h" #include "ui_about.h" +#include "ui_bombbox.h" +#include "moc_dialogs.cpp" extern const char* g_extProgPathFilter; extern_cfg (String, io_ldpath); @@ -332,4 +334,15 @@ void AboutDialog::slot_mail() { QDesktopServices::openUrl (QUrl ("mailto:Santeri Piippo <slatenails64@gmail.com>?subject=LDForge")); } -#include "moc_dialogs.cpp" + +// ============================================================================= +// ----------------------------------------------------------------------------- +void bombBox (const str& message) +{ QDialog dlg (g_win); + Ui_BombBox ui; + + ui.setupUi (&dlg); + ui.m_text->setText (message); + ui.buttonBox->button (QDialogButtonBox::Close)->setText (QObject::tr ("Damn it")); + dlg.exec(); +}
--- a/src/dialogs.h Fri Oct 18 18:16:54 2013 +0300 +++ b/src/dialogs.h Fri Oct 18 21:52:09 2013 +0300 @@ -135,4 +135,6 @@ void slot_mail(); }; +void bombBox (const str& message); + #endif // LDFORGE_DIALOGS_H
--- a/src/download.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/download.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -41,6 +41,7 @@ // ----------------------------------------------------------------------------- void PartDownloader::k_download() { str path = getDownloadPath(); + assert (false); if (path == "" || QDir (path).exists() == false) { critical (PartDownloader::tr ("You need to specify a valid path for " @@ -232,7 +233,7 @@ return; modifyDest (dest); - print ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest); + log ("DOWNLOAD: %1 -> %2\n", url, PartDownloader::getDownloadPath() + DIRSLASH + dest); PartDownloadRequest* req = new PartDownloadRequest (url, dest, primary, this); m_filesToDownload << dest; @@ -320,7 +321,7 @@ QDir dir (dirpath); if (!dir.exists()) - { print ("Creating %1...\n", dirpath); + { log ("Creating %1...\n", dirpath); if (!dir.mkpath (dirpath)) critical (fmt (tr ("Couldn't create the directory %1!"), dirpath));
--- a/src/extprogs.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/extprogs.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -216,7 +216,7 @@ #endif // _WIN32 - print ("cmdline: %1 %2\n", path, argv.join (" ")); + log ("cmdline: %1 %2\n", path, argv.join (" ")); // Temporary files for stdin and stdout if (!mkTempFile (input, inputname) || !mkTempFile (output, outputname)) @@ -238,7 +238,7 @@ proc.waitForFinished(); #ifndef RELEASE - print ("%1", str (proc.readAllStandardOutput())); + log ("%1", str (proc.readAllStandardOutput())); #endif // RELEASE str err = "";
--- a/src/file.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/file.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -199,7 +199,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- File* openLDrawFile (str relpath, bool subdirs) -{ print ("%1: Try to open %2\n", __func__, relpath); +{ log ("%1: Try to open %2\n", __func__, relpath); File* f = new File; str fullPath; @@ -241,7 +241,7 @@ } // Did not find the file. - print ("could not find %1\n", relpath); + log ("could not find %1\n", relpath); delete f; return null; } @@ -1147,7 +1147,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void loadLogoedStuds() -{ print ("Loading logoed studs...\n"); +{ log ("Loading logoed studs...\n"); delete g_logoedStud; delete g_logoedStud2;
--- a/src/gldraw.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/gldraw.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -302,7 +302,7 @@ if (obj->color() == i) return; - print ("%1: Unknown color %2!\n", __func__, obj->color()); + log ("%1: Unknown color %2!\n", __func__, obj->color()); g_warnedColors << obj->color(); return; }
--- a/src/main.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/main.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -30,6 +30,8 @@ #include "primitives.h" #include "gldraw.h" #include "configDialog.h" +#include "dialogs.h" +#include "crashcatcher.h" QList<LDFile*> g_loadedFiles; ForgeWindow* g_win = null; @@ -49,18 +51,19 @@ { QApplication app (argc, argv); app.setOrganizationName (APPNAME); app.setApplicationName (APPNAME); + g_app = &app; - g_app = &app; + initCrashCatcher(); LDFile::setCurrent (null); // Load or create the configuration if (!Config::load()) - { print ("Creating configuration file...\n"); + { log ("Creating configuration file...\n"); if (Config::save()) - print ("Configuration file successfully created.\n"); + log ("Configuration file successfully created.\n"); else - print ("failed to create configuration file!\n"); + log ("failed to create configuration file!\n"); } LDPaths::initPaths(); @@ -135,38 +138,4 @@ // ----------------------------------------------------------------------------- QString fullVersionString() { return fmt ("v%1 %2", versionString(), versionMoniker()); -} - -// ============================================================================= -// ----------------------------------------------------------------------------- -void assertionFailure (const char* file, int line, const char* funcname, const char* expr) -{ str errmsg = fmt ("File: %1\nLine: %2:\nFunction %3:\n\nAssertion `%4' failed", - file, line, funcname, expr); - -#ifndef RELEASE - errmsg += ", aborting."; -#else - errmsg += "."; -#endif - - printf ("%s\n", errmsg.toStdString().c_str()); - -#ifndef RELEASE - if (g_win) - g_win->deleteLater(); - - errmsg.replace ("\n", "<br />"); - - QMessageBox box (null); - const QMessageBox::StandardButton btn = QMessageBox::Close; - box.setWindowTitle ("Fatal Error"); - box.setIconPixmap (getIcon ("bomb")); - box.setWindowIcon (getIcon ("ldforge")); - box.setText (errmsg); - box.addButton (btn); - box.button (btn)->setText ("Damn it"); - box.setDefaultButton (btn); - box.exec(); - abort(); -#endif } \ No newline at end of file
--- a/src/messagelog.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/messagelog.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -118,8 +118,10 @@ // ----------------------------------------------------------------------------- void DoLog (std::initializer_list<StringFormatArg> args) { const str msg = DoFormat (args); - g_win->addMessage (msg); + + if (g_win) + g_win->addMessage (msg); // Also print it to stdout - print ("%1\n", msg); + fprint (stdout, "%1", msg); }
--- a/src/misc.h Fri Oct 18 18:16:54 2013 +0300 +++ b/src/misc.h Fri Oct 18 21:52:09 2013 +0300 @@ -19,10 +19,10 @@ #ifndef MISC_H #define MISC_H +#include <QVector> #include "config.h" #include "common.h" #include "types.h" -#include <qvector.h> #define NUM_PRIMES 500
--- a/src/primitives.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/primitives.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -49,7 +49,7 @@ // ============================================================================= // ----------------------------------------------------------------------------- void loadPrimitives() -{ print ("Loading primitives...\n"); +{ log ("Loading primitives...\n"); loadPrimitiveCatgories(); // Try to load prims.cfg
--- a/src/types.cpp Fri Oct 18 18:16:54 2013 +0300 +++ b/src/types.cpp Fri Oct 18 21:52:09 2013 +0300 @@ -204,9 +204,9 @@ void matrix::puts() const { for (short i = 0; i < 3; ++i) { for (short j = 0; j < 3; ++j) - print ("%1\t", m_vals[ (i * 3) + j]); + log ("%1\t", m_vals[ (i * 3) + j]); - print ("\n"); + log ("\n"); } }
--- a/src/types.h Fri Oct 18 18:16:54 2013 +0300 +++ b/src/types.h Fri Oct 18 21:52:09 2013 +0300 @@ -323,14 +323,12 @@ void DoLog (std::initializer_list<StringFormatArg> args); // Macros to access these functions -# ifndef IN_IDE_PARSER +# ifndef IN_IDE_PARSER #define fmt(...) DoFormat ({__VA_ARGS__}) -# define print(...) doPrint (g_file_stdout, {__VA_ARGS__}) # define fprint(F, ...) doPrint (F, {__VA_ARGS__}) -# define log(...) DoLog({ __VA_ARGS__ }); +# define log(...) DoLog({ __VA_ARGS__ }) #else str fmt (const char* fmtstr, ...); -void print (const char* fmtstr, ...); void fprint (File& f, const char* fmtstr, ...); void log (const char* fmtstr, ...); #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/bombbox.ui Fri Oct 18 21:52:09 2013 +0300 @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BombBox</class> + <widget class="QDialog" name="BombBox"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>676</width> + <height>569</height> + </rect> + </property> + <property name="windowTitle"> + <string>Fatal Error</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../ldforge.qrc">:/icons/bomb.png</pixmap> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>138</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QTextEdit" name="m_text"> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../ldforge.qrc"/> + </resources> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>BombBox</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>BombBox</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>