No qDebug output with Qt 4.8.7 and MinGW

5 minute read

When setting up my Qt based development environment for Ostinato, on my new Win 10 based desktop, I ran into this strange problem where the qDebug output wouldn’t be available/visible on DbgView. After several frustrating days of messing around with various debuggers and alternatives to DbgView, it appears that the problem was in Qt itself.

TL;DR Qt installation had setup win32-g++-4.6 as my QMAKESPEC but C:\Qt\4.8.7\mkspecs\features\win32\windows.prf does not add QT_NEEDS_QMAIN for this makespec, but does so for win32-g++. Adding the below line to windows.prf and regenerating the Makefiles using qmake fixed the problem -

win32-g++-4.6:DEFINES += QT_NEEDS_QMAIN

Now back to the longer story of how I found the problem.

I knew that Qt uses the Win32 API OutputDebugString() for qDebug output for GUI applications. Looking at the code for qt_message_output() (the eventual function called by qDebug), I found it strange that the API was used only for WIN_CE. For WIN32 it uses fprintf()

    if (handler) {
        (*handler)(msgType, buf);
    } else {
#if defined(Q_CC_MWERKS) && defined(Q_OS_MACX)
        mac_default_handler(buf);
#elif defined(QT_USE_SLOG2)
        slog2_default_handler(msgType, buf);
#elif defined(Q_OS_WINCE)
        QString fstr = QString::fromLatin1(buf);
        fstr += QLatin1Char('\n');
        OutputDebugString(reinterpret_cast<const wchar_t> (fstr.utf16()));
#elif defined(Q_OS_SYMBIAN)
        // RDebug::Print has a cap of 256 characters so break it up
        char format[] = "[Qt Message] %S";
        const int maxBlockSize = 256 - sizeof(format);
        const TPtrC8 ptr(reinterpret_cast<const tuint8>(buf));
        for (int i = 0; i  (fstr.utf16()));
    }

As an experiment, I installed a custom message handler -

void msgOut(QtMsgType /*type*/, const char* msg)
{
    QString fstr = QString::fromLatin1(msg);
    fstr += QLatin1Char('\n');
    OutputDebugString(reinterpret_cast<const wchar_t> (fstr.utf16()));
}

int main(int argc, char* argv[])
{
    ...
    qInstallMsgHandler(msgOut);
    ...
}

That worked. But I still believed that it should work out of the box without having to install a custom handler. When looking at the qInstallMessageHandler() code, I noticed this interesting check -

#if defined(Q_OS_WIN) && defined(QT_BUILD_CORE_LIB)
    if (!handler && usingWinMain)
        handler = qWinMsgHandler;
#endif

Hmmm, so when you uninstall a custom handler, instead of falling back to using qt_message_output(), Qt installed a special handler qWinMsgHandler() instead.

So, as an experiment, I installed qWinMsgHandler instead of my custom handler -

qInstallMsgHandler(qWinMsgHandler);

That worked. Which meant, if I cleared the handler instead of setting one, it should install qWinMsgHandler internally -

qInstallMsgHandler(0);

No dice. Huh? What? Why?

Tracing that function using gdb revealed that the usingWinMain variable was false, so qWinMsgHandler doesn’t get installed.

So where is usingWinMain set to true?

In qWinMain (corelib/kernel/qcoreapplication_win.cpp).

Now, that’s qWinMain, not WinMain - the usual entry point for Windows GUI applications. A helpful comment on top of that function pointed WinMain() to be in qtmain_win.cpp

And it’s there. The WinMain function and a call to qWinMain.

So, why isn’t usingWinMain set to true?

I asked gdb where is WinMain in the exe -

(gdb) info func WinMain
All functions matching regular expression "WinMain":

File kernel\qcoreapplication_win.cpp:
void qWinMain(HINSTANCE__*, HINSTANCE__*, char*, int, int&, QVector<char>&);

Non-debugging symbols:
0x004014c0  WinMainCRTStartup
(gdb)

That’s strange - there is no WinMain?!

Let’s look at the backtrace in gdb -

(gdb) set backtrace past-main on
(gdb) bt
#0  main (argc=1, argv=0x1f3e50) at main.cpp:64
#1  0x004013de in __tmainCRTStartup ()
#2  0x76fe8744 in KERNEL32!BaseThreadInitThunk ()
   from C:\WINDOWS\SysWOW64\kernel32.dll
#3  0x7743582d in ntdll!RtlGetAppContainerNamedObjectPath ()
   from C:\WINDOWS\SYSTEM32\ntdll.dll
#4  0x774357fd in ntdll!RtlGetAppContainerNamedObjectPath ()
   from C:\WINDOWS\SYSTEM32\ntdll.dll
#5  0x00000000 in ?? ()
(gdb)

No WinMain again. Googling around and reading some helpful stuff about main, wmain and WinMain and WinMain provided by Qt, it appears that For MinGW, WinMain() will never be called if main() exists (if you find “official” documentation on this, please send me a pointer)!

Qt works around this problem by doing this piece of preprocessing trickery in gui\kernel\qwindowdefs.h -

#if defined(QT_NEEDS_QMAIN)
#define main qMain
#endif

When QT_NEEDS_QMAIN is defined, the user provided main function is renamed to qMain forcing MinGW to use WinMain as the entry point instead of main.

Does the qmake generated Makefile include QT_NEEDS_QMAIN?

DEFINES       = -DUNICODE -DQT_DLL -DQT_SCRIPT_LIB -DQT_XML_LIB -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_HAVE_MMX -DQT_HAVE_3DNOW -DQT_HAVE_SSE -DQT_HAVE_MMXEXT -DQT_HAVE_SSE2 -DQT_THREAD_SUPPORT

No.

Did the older (Qt 4.3.3) qmake generated Makefile include QT_NEEDS_QMAIN?

DEFINES       = -DUNICODE -DQT_LARGEFILE_SUPPORT -DQT_DLL -DQT_SCRIPT_LIB -DQT_XML_LIB -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_THREAD_SUPPORT -DQT_NEEDS_QMAIN

Yes.

A quick experiment to add QT_NEEDS_QMAIN manually to the Makefile and re-building confirmed that it fixes the problem.

Buy hey wait, we’re not done yet. I’m not manually going to add this everytime I create a new git clone or do a make distclean. Why the hell isn’t qmake adding it to the generated Makefile?

Now reading through C++ code to find out what is happening is one thing. Reading through qmake’s C++ code to parse a .pro file and generate a Makefile ain’t so straightforward that looking at the code will directly tell you anything without spending significant time trying to understand that code -

So, change tactics and use grep inside Qt sources to find all instances of QT_NEEDS_QMAIN

mkspecs/features/win32/windows.prf:    win32-g++:DEFINES += QT_NEEDS_QMAIN
mkspecs/features/win32/windows.prf:    win32-borland:DEFINES += QT_NEEDS_QMAIN

Looking at windows.prf, it appears that if QMAKESPEC is win32-g++ or win32-borland, qmake will add QT_NEEDS_QMAIN to the generated Makefile. What Qt installer setup the makespec for me was win32-g++-4.6.

Adding the below line and regenerating the Makefiles using qmake did indeed add QT_NEEDS_QMAIN to the generated Makefile -

win32-g++-4.6:DEFINES += QT_NEEDS_QMAIN

So that missing line is the cause of our problem.

Or is it?

Now Qt makespec names are essentially a combination of platform and compiler separated by a -. Assuming 4.6 was the compiler version, I wasn’t sure if the scope win32-g++ should evaluate to true for win32-g++-4.6 also - after all win32-g++-4.6 is a specialization of win32-g++ as evidenced by the former’s qmake.conf #including the latter.

Running qmake -d and scanning the output for logs related to windows.prf processing confirmed that it does not - the win32-g++ scope match fails when QMAKESPEC is win32-g++-4.6

I’m not sure if that scope match failure is intended and correct or a qmake bug. I spent some time trying to read through qmake source code to answer that question but gave up after about an hour - it was beyond 2:00 AM and I had already spent all evening and all night on this investigation.

Aside: Although Ostinato is my personal project unrelated to my day job, I write several of these kind of “investigation reports” at the day job when debugging problems. I do it primarily to document the analysis for future use by myself or my colleagues. Reading through such a report offers the impression that the investigation was a straightforward logical line where one step leads to another till we find root cause, but this is never ever the case. There are so many twists, turns, tangents and threads of investigation that you follow during the investigation that lead nowhere and are wasted effort. But this is never reflected in the report.

As a matter of fact, I have long felt that “debugging” or “triaging” a technical problem is very similar to “crime” investigation - well, at least as depicted in books, tv shows and movies! That’s another post though!

Leave a Comment