Thiago Macieira
Qt
KDE
Posted by Thiago Macieira
 in Qt, KDE
 on Thursday, June 05, 2008 @ 09:52

I have just committed an interesting change to Qt 4.4 now, fixing an open task reported by David Faure. By itself, the change is hardly worth mentioning:

-#if (defined(Q_OS_UNIX) || defined(Q_CC_MINGW)) && defined(QT_DEBUG)
+#if (defined(Q_OS_UNIX) || defined(Q_CC_MINGW))
abort(); // trap; generates core dump
#else
exit(1); // goodbye cruel world

The interesting part is the story behind the change.

Last night, some KDE friends (Dirk and David to be precise) pointed out that Q_ASSERT and qFatal only call abort(2) if Qt was compiled in debug mode, thereby making it impossible to use a crash handler to display a message to the user, maybe to restart the application as well. You could argue that an application shipped to a user should not have assertions enabled and should not be tripping them anyways, even if you had them enabled. And you’d be correct. The issue was that the application in question was still in development phase, in debug mode, therefore with assertions enabled. But Qt was in release mode, so it simply called exit(3) instead of aborting. The result is that the application being debugged and developed simply disappears, leaving no trace behind of why.

So they asked me: why was that the code like that in the first place?

Inspired by a recent movie I watched in the cinema, that set me off in an archeology expedition, digging through the history of Qt code — more precisely, the qglobal.cpp file. After a few minutes, I had managed to trace down the actual change, after going through two renames of the file (from src/tools/qglobal.cpp [Qt 1 to 3 forms] to src/core/global/qglobal.cpp [unreleased Qt 4 form] to src/corelib/global/qglobal.cpp [current form]), one wide-reaching whitespace change, removing all Tabs in Qt source code, and several renames of the macros, to this change:

Author: Haavard Nord
Date: Tue Apr 18 15:43:46 1995 +0100

fatal() calls abort() if debug flag defined

-#if defined(UNIX)
+#if defined(UNIX) && defined(DEBUG)
abort(); // trap; generates core dump
#else
exit( 1 ); // goodbye cruel world

That means I have just reverted a 13-year-old commit by one of the Trolltech founders!

If we dig further, to the history of the abort() line itself, we end up in change number 43, whose log message is:

Author: Haavard Nord
Date: Mon Sep 5 05:54:23 1994 +0100

Initial revision

And I can’t go beyond that… As with many projects, Qt’s first authors decision to use a version control system was an afterthought. That change above added 43 files and 8438 lines of code.

So, here’s what the qFatal function looked like in Sep 5, 1994:

void fatal( const char *msg, ... )              // print message and exit
{
    char buf[240];
    va_list ap;
    va_start( ap, msg );                        // use variable arg list
    if ( handler ) {
        vsprintf( buf, msg, ap );
        (*handler)( buf );
    }
    else {
        vfprintf( stderr, msg, ap );
        fprintf( stderr, "n" );                // add newline
    }
    va_end( ap );
#if defined(UNIX)
    abort();                                    // trap; generates core dump
#else
    exit( 1 );                                  // goodbye cruel world
#endif
}

And here’s what the equivalent code looks like today, June 5th, 2008, in what will be released as Qt 4.4.1 (edited for brevity):

void qFatal(const char *msg, ...)
{
    char buf[QT_BUFFER_LENGTH];
    buf[QT_BUFFER_LENGTH - 1] = '';
    va_list ap;
    va_start(ap, msg); // use variable arg list
    if (msg)
        qvsnprintf(buf, QT_BUFFER_LENGTH - 1, msg, ap);
    va_end(ap);

    qt_message_output(QtFatalMsg, buf);
}

void qt_message_output(QtMsgType msgType, const char *buf)
{
    if (handler) {
        (*handler)(msgType, buf);
    } else {
[...]
        fprintf(stderr, "%sn", buf);
        fflush(stderr);
    }

    if (msgType == QtFatalMsg
        || (msgType == QtWarningMsg
            && (!qgetenv("QT_FATAL_WARNINGS").isNull())) ) {
[...]
#if (defined(Q_OS_UNIX) || defined(Q_CC_MINGW))
        abort(); // trap; generates core dump
#else
        exit(1); // goodbye cruel world
#endif
    }
}

7 Responses to “Restoring original Qt behaviour”

» Posted by illogic-al
 on Thursday, June 05, 2008 @ 14:49

Thanks for blogging this. I’ve stumbled upon some bug(s) in Amarok and/or phonon_qt7 in OS X that are causing crashes w/ no drkonqui or os x crash handler being called. I’m hoping I can apply this patch myself and get a bactrace going without gdb. Hmm, maybe i’ll just use gdb directly…

» Posted by WhatAboutWindows
 on Thursday, June 05, 2008 @ 16:49

What about Windows? Why isn’t abort also called for MS Windows platforms? On Windows you can trap the sigabrt and use the built-in or external crash reporter.

» Posted by Nicolas
 on Thursday, June 05, 2008 @ 20:24

@WhatAboutWindows: er, there is no such thing as sigabrt on Windows.

» Posted by Kelvie
 on Friday, June 06, 2008 @ 07:06

I was in on that discussion :)

Anyways, I’m glad that this finally got changed. I find it most convenient to develop with the libraries built in release with debug symbols (that’s the default configuration too!) and have always worked around this by just setting a breakpoint on exit(3) to see the final stack trace.

» Posted by Diederik van der Boor
 on Friday, June 06, 2008 @ 12:06

Thanks for this change! It annoyed me too, and I’ve worked around it by using qInstallMessageHandler().

» Posted by WhatAboutWindows
 on Saturday, June 07, 2008 @ 03:11

abort and sigabrt exist on windows - Go read the MSDN documentation: http://msdn.microsoft.com/en-us/library/k089yyh0(VS.80).aspx

» Posted by WhatAboutWindows
 on Saturday, June 07, 2008 @ 03:13

abort and sigbart exist on windows - read the MSDN documentation: http://msdn.microsoft.com/en-us/library/k089yyh0(VS.80).aspx



© 2008 Nokia Corporation and/or its subsidiaries. Nokia, Qt and their respective logos are trademarks of Nokia Corporation in Finland and/or other countries worldwide.
All other trademarks are property of their respective owners.