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().

Fri, 18 Oct 2013 21:52:09 +0300

author
Santeri Piippo <crimsondusk64@gmail.com>
date
Fri, 18 Oct 2013 21:52:09 +0300
changeset 513
29eb671b34f6
parent 512
adab82ab13a5
child 514
d78fea0f664c

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().

ldforge.pro file | annotate | diff | comparison | revisions
src/colors.cpp file | annotate | diff | comparison | revisions
src/common.h file | annotate | diff | comparison | revisions
src/config.cpp file | annotate | diff | comparison | revisions
src/crashcatcher.cpp file | annotate | diff | comparison | revisions
src/crashcatcher.h file | annotate | diff | comparison | revisions
src/dialogs.cpp file | annotate | diff | comparison | revisions
src/dialogs.h file | annotate | diff | comparison | revisions
src/download.cpp file | annotate | diff | comparison | revisions
src/extprogs.cpp file | annotate | diff | comparison | revisions
src/file.cpp file | annotate | diff | comparison | revisions
src/gldraw.cpp file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/messagelog.cpp file | annotate | diff | comparison | revisions
src/misc.h file | annotate | diff | comparison | revisions
src/primitives.cpp file | annotate | diff | comparison | revisions
src/types.cpp file | annotate | diff | comparison | revisions
src/types.h file | annotate | diff | comparison | revisions
ui/bombbox.ui file | annotate | diff | comparison | revisions
--- 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>

mercurial