Showing posts with label mingw. Show all posts
Showing posts with label mingw. Show all posts

Thursday, 18 February 2016

Explicit template instantiation - Exhibit 1

As I promised last time, let's put our design to work on some code.

Back in 2013, I wrote a couple of template functions as a quick solution to a very specific problem - outputting non-ASCII characters on a Windows console in a program compiled with Mingw. Here's the header, and here's the source.

I've now applied the idea from the last post, and changed the code from full inclusion to explicit instantiation. There are other changes to be made, but those will wait.

If you go back in the header's history, you'll see that the previous version used full inclusion. As such, this was included in every translation unit (TU) that used this code:

#include "boost/locale.hpp"
#include "windows.h"
#include <cassert>
#include <locale>
#include <sstream>
#include <string>

Not necessarily outrageous. However, all of this was included because of a couple of functions. Not only that, but because the implementation was in the header, some helper functions had to be declared in the header, too, thus leaking implementation details.

Now, applying our new-fangled design, here's what we get.

Header (win_console_out.h)

Contains just the declaration for the functions.

template <typename CharT>
std::string ConvertOutput(CharT const* s);

template <typename CharT>
std::string ConvertOutput(std::basic_string<CharT> const& s);

template <typename CharT = DefaultCharT, typename T = void>
std::string ConvertOutput(T const& t);

Because the only #include we need is <string>, everything else has been removed from the header.

Source (win_console_out.cpp)

Just as before, contains the non-template helper functions. These are not part of the interface, and were removed from the header.

Implementation (win_console_out.ipp)

Most of the previous content of the header ended up here - all the #includes, the declarations for the helper functions, and the template functions definitions.

Explicit instantiation

Finally, each user of this code will supply its own explicit instantiation. In this case, you can see here what we're defining:

template std::string ConvertOutput(char const* s);
template std::string ConvertOutput(

    std::basic_string<char> const& s);
template std::string ConvertOutput(wchar_t const* s);
template std::string ConvertOutput(

    std::basic_string<wchar_t> const& s);

And that's it. I'll use my extensive (hah!) body of published code as a test for this design. If it works, I plan to keep using it, in order to reveal its ugly warts.

I've also created the mechanism for reverting to full inclusion via #defined variables, either globally or on a header-by-header basis. As I usually say, I believe users should have the right to choose, and I strive to keep to that principle.

Thursday, 8 January 2015

OpenSSL - Debug build configuration for mingw32

I've just submitted a patch to OpenSSL, to include a debug build configuration for mingw32, as it currently lacks one.

Not only have I done the obvious - removed optimizations and add the -g compiler option, but I've also used debug-Cygwin as a starting point to include a set of debug #defines for OpenSSL. You can find the patch here.

At first, I sent it to the openssl-dev mailing list. Later, I find out there was already an issue open for this on OpenSSL's RT.

Anyway, the patch makes the changes below.

config

This is the script called from the command line for configuration, which will generate the makefile.

The patch adds MSYS* to the script's platform guessing mechanism, allowing us to build on MSYS2. It wasn't really necessary for the debug configuration, but I like to have the ability to build on MSYS2.

Configure

Configure is a perl script that is called by config.

The patch adds the mingw32 debug configuration (debug-mingw), and changes the part of the script that defines the executable extension, to look for /mingw/ instead of /^mingw/, which then makes it work correctly with both mingw configurations.

As a side note, I think it would have been better to name the debug configurations as <platform-debug>, rather than <debug-platform>, but I don't know the reasons behind this, so I might be wrong.

Makefile.org

This file is a base for the final makefile.

The patch changes platform checks from mingw and mingw* to .*mingw and *mingw*. Curious, now that I'm writing this, it's the first time I've noticed the incoherence. I'll probably go back to the testing board and change that to *ming* on both.

Makefile.shared

This file is used when we're building shared libraries (DLLs on mingw).

This is where I was more bold with the changes. The original file was always passing -Wl,-s to the linker, meaning it would clear the symbols from the object files. However, on a debug build we want to keep the symbols, that's the main reason for wanting a debug build in the first place.

So, the patch not only adds a check for .*mingw instead of mingw, it also changes the linker arguments for the debug configuration, excluding the -s.

And that's it. Not that much work, but a lot of experimenting. And, since shell script and make are not the most user-friendly debugging experiences, it has required a lot of creative experimenting.

Hope someone finds it useful.

Sunday, 1 September 2013

Boost.Locale, ICU and Mingw

I've been doing a few small command-line programs, and needed to output text like this on a Windows terminal: "Opção inválida".

That led me to the Interesting World of Internationalization. Which is a lot more Interesting on Windows than on Linux, I must say. This should work, right?

UINT oldcodepage = GetConsoleOutputCP();
SetConsoleOutputCP(CP_UTF8);
cout << "Olá\n" << endl;
SetConsoleOutputCP(oldcodepage);


In fact, not only did it not work reliably on all the Win7 machines I tried, but the endl wasn't outputted (hence, the "\n"). So, I've decided I needed something more powerful, like, say, ICU. And, fortunately, the size of the ICU DLLs would not be a problem.

Building ICU was easy. I just fired this on an msys terminal:

runConfigureICU MinGW --prefix=<DBG_PATH> --enable-debug --disable-release
make
make install

make clean

runConfigureICU MinGW --prefix=<REL_PATH> --disable-debug --enable-release
make
make install


No, I didn't bother with the recommended C++ changes, I'll look at it next time.

Next, building Boost. First thing was finding out what did I need to do to let Boost.Locale (and, as I found out later, also Boost.Regex) know that I had ICU available.

When invoking b2, I had to pass -sICU_PATH=<PATH_TO_ICU_HOME>. However, since I wanted to use different ICU versions for debug and release, that meant I couldn't just build Boost as I usually do, with --build-type=complete. Instead, I went for something like this:

b2 -sICU_PATH=<ICU_DBG_PATH> --prefix=<BOOST_INSTALL_DIR> 
    --build-dir=<BOOST_BUILD_DIR> toolset=gcc link=static,shared 
    runtime-link=shared threading=single,multi variant=debug 
    install > boost_install.log 2>&1

And I took a look at boost_install.log, as Boost started running its tests, including checking for ICU. And that was a good thing, because I spotted this early on:

- has_icu builds           : no

Boost was complaining about icui18n.dll and icudata.dll. And, sure enough, looking at my ICU lib folder, neither was anywhere to be found.

So, I went to Boost.Locale's (and Boost.Regex's) Jamfile.v2, looking for these dependencies. The first thing I noticed was that these dependencies didn't apply to MSVC. Actually, MSVC's dependencies included DLLs that I had on my system, so I renamed icui18n to icuin, and removed icudata. I also had to remove a couple of this_is_an_invalid_library_names, and finally I got this:

- has_icu builds           : yes

which made me quite happy. So, after Boost debug got built, I ran this little thingie to check that everything was OK:

#include "boost/locale.hpp"
using namespace boost::locale;

#include <algorithm>
using std::for_each;
#include <iostream>
using std::cout;
using std::endl;
#include <locale>
#include <string>
using std::string;

   
int main(int /*argc*/, char */*argv*/[])
{
    localization_backend_manager lbm = 
        localization_backend_manager::global();
    auto s = lbm.get_all_backends();
    for_each(s.begin(), s.end(), [](string& x){ cout << x << endl; });

    generator gen;
    std::locale loc = gen("");
    cout << boost::locale::conv::between("Olá", "cp850", 
        std::use_facet<boost::locale::info>(loc).encoding()) << endl;
} 


This outputted

icu
winapi
std
Olá


This not only shows that Boost.Locale was built with ICU support (line 1), but also that the conversions are working (line 4).

Next: Testing this on a few machines.

Wednesday, 29 May 2013

Logger wrapper concluded... for now

The result of this latest redesign is ready, and you can get it here.

lib_logger.h has grown quite a bit. In order to properly take advantage of __FILE__ and __LINE__ I needed to go with macros. Lots of macros, but the work in creating all these macros was softened by a little bit of perl scripting.

Note: I considered using __func__, as well, but since POCO's Logger doesn't support it, I'd have to manage that in my own Logger. However, I believe that my Logger's design goals don't include this kind of management; it's a bridge to an actual implementation, it should own nothing, and it should impose little to no unwanted weight on the client code. So, I've decided that if the client code needs __func__, it'll have to take care of it. __FILE__ and __LINE__ should be enough to follow a trail, if needed.

Another reason for this macro growth was that I wanted this to work with both GCC and MSVC. There is a difference between LoggerBridge<bool condition, etc> and the LoggerBridge<false, etc> specialization - the latter has no template functions. This poses no problem with GCC; since template functions have default template parameters, the call is similar for both template and non-template functions. On MSVC, however, template functions don't have default template parameters. Therefore, the call is object.function<type>() for one and object.function() for the other.

My first instinct was te change the specialization, changing its functions to template functions. But, after considering it further, I decided not to. Default template parameters for template functions are in the Standard, and (hopefully) sooner or later will also be in MSVC. Until then, the MSVC code will have more preprocessor "magic".

I've tested this in Qt Creator and MSVC, with my Logger (wrapper around POCO's Logger) and LoggerCout (minimum interface implementation), with values of 1 and 0 for WANT_LOGGING. All went as expected.

So, when all is said and done, what are the requirements for a client app that wants to replace the default Logger? Look at class LoggerCout for the minimum interface:

class LoggerCout
{
public:
    explicit LoggerCout(std::string const&, char const* = nullptr);
 

    static void CreateLogger(std::string const&, std::string const&);
 

    bool IsCritical() const;
    bool IsDebug() const;
    bool IsError() const;
    bool IsFatal() const;
    bool IsInformation() const;
    bool IsNotice() const;
    bool IsTrace() const;
    bool IsWarning() const;
 

    void Critical(std::string const& msg);
    void Debug(std::string const& msg);
    void Error(std::string const& msg);
    void Fatal(std::string const& msg);
    void Information(std::string const& msg);
    void Notice(std::string const& msg);
    void Trace(std::string const& msg);
    void Warning(std::string const& msg);
};

I don't think that's unreasonable. Of course, any other functions you need will have to be added to LoggerBridge manually. Since I've started this redesign, I've seen Herb Sutter's 2012 concurrency presentation, and was intrigued by the wrapper design he presented, especially the part about the wrapper being completely oblivious to the wrapee's interface. I'll certainly investigate if I can adapt it here, although the absence of inlining (via the use of functors/function pointers) may make it unsuitable. Anyway, for now, this is what we have.

There's one final question I'll need to answer, and that's what I'll be working on next: How does this affect performance? I'll prepare some testing with the following scenarios:
  • No logging
  • Logging with this solution
  • Bypassing this solution and logging directly with POCO's Logger
As variations, some runs will have trivial processing, where logging should take up most of the work, and in others I'll simulate some heavy work, probably by use of sleep(). And I'll also test the use of POCO's AsyncChannel. This will give me some idea of what to expect.

Yep, I know, I finish a lot of these posts with a "Next, I'll do such and such". The truth is there's always work to be done. I like it that way.

Sunday, 26 May 2013

Logger - SFINAE solution for implementation selection

Before we continue, a quick recap:
  • We have some reusable code (a lib).
  • We want to add logging to it.
  • We want to allow the client app to replace the logger implementation.
In order to achieve the goal stated in the last point, we defined a minimum interface that the client app's logger will have to implement, and we'll now introduce a mechanism to make our lib decay into that interface, if the client app's logger doesn't implement our optimal interface.

As stated last time, we have this (slightly modified):

template<typename T, typename RESULT, 
    typename ARG1, typename ARG2, typename ARG3>
class DoesDebug
{
    template <typename U, RESULT (U::*)(ARG1, ARG2, ARG3)> struct Check;
    template <typename U> static char checkFn(Check<U, &U::Debug> *);
    template <typename U> static int checkFn(...);
public:
    enum { Exist = sizeof(checkFn<T>(0)) == sizeof(char) };
};


This will be our basic workhorse, and we'll need one for each member function.

We'll put it to work with this:

DoesDebug<Logger, void, std::string const&, char const*, int>::Exist

Upon finding this expression, the compiler will generate DoesDebug like this:
  • typename T = Logger
  • typename RESULT = void
  • typename ARG1 = std::string const&
  • typename ARG2 = char const*
  • typename ARG3 = int
Now, the compiler must calculate DoesDebug<etc...>::Exist. That means, resolving checkFn<Logger>(0).

There are two candidates for this:
char checkFn(Check<U, &U::Debug> *);
int checkFn(...);

The second candidate will always be the last option for the compiler, because of its "..." argument. As for the first, it must instantiate the Check struct, like this:

template <Logger, void (Logger::*)(std::string const&, 
   char const*, int)> struct Check;

The compiler will only be able to instantiate this if &Logger::Debug can be matched with this member function pointer: void (Logger::*)(string const&, char const*, int). So, there must be an overload of Logger::Debug() that has this signature. If that is the case, the compiler successfully instantiates Check, which means sizeof(checkFn<T>(0)) is char, because that's the return type of the first overload of CheckFn; that means Exist is true, because:

Exist = sizeof(char) == sizeof(char)

On the other hand, if there is no Logger::Debug() with the required signature, the compiler can't instantiate struct Check, which means it can't instantiate the first checkFn overload, and it'll go with the second. This means sizeof(checkFn<T>(0)) is int, and Exist is false.

Hence, we have a way to check whether our lib's optimal interface exists. What we need now is a way to use it.

template <bool condition, typename LoggerImpl>
class LoggerBridge
{
public:
...
    void Debug(std::string const& msg);
 

    template <typename LI = LoggerImpl>
    typename std::enable_if<DoesDebug<LI, void, std::string const&, 
        char const*, int>::Exist, void>::type
    Debug(std::string const&, char const*, int);
 

    template <typename LI = LoggerImpl>
    typename std::enable_if<!DoesDebug<LI, void, std::string const&, 
        char const*, int>::Exist, void>::type
    Debug(std::string const&, char const*, int);

...
private:
    LoggerImpl l;
};

We'll use two identical versions of Debug(string const&, char const*, int), but using enable_if with opposed conditions (DoesDebug<> and !DoesDebug<>) ensures only one will be generated by the compiler.

These functions then become:

template <bool condition, typename T> template <typename LI>
typename std::enable_if<DoesDebug<LI, void, std::string const&,  
    char const*, int>::Exist, void>::type
LoggerBridge<condition, T>::Debug(const std::string& msg, char const* file, int line)
{
    l.Debug(msg, file, line);
}
 

template <bool condition, typename T> template <typename LI>
typename std::enable_if<!DoesDebug<LI, void, std::string const&, 
    char const*, int>::Exist, void>::type
LoggerBridge<condition, T>::Debug(const std::string& msg, char const*, int)
{
    l.Debug(msg);
}


So, when DoesDebug is true, i.e., when the method LoggerImpl::Debug(string const&, char const*, int) exists, the compiler will use the first function; otherwise, it uses the second, which logs the message and ignores the remaining arguments. How? The template enable_if only has a type member when the condition is true. So, when the condition is false, type does not exist, which means the compiler excludes it from overload selection.

You'll notice the functions became template functions. My first version didn't work, and after some searching, I arrived here, where Johannes Schaub - litb answer explained why:
That's because when the class template is instantiated (which happens when you create an object of type Y<int> among other cases), it instantiates all its member declarations (not necessarily their definitions/bodies!).
(...)
You need to make the member templates' enable_if depend on a parameter of the member template itself.
So, the problem is that when the Debug() functions weren't templated, the compiler instantiated all of them when LoggerBridge was instantiated. That means both versions of Debug(string const& msg, char const* file, int line) were instantiated, and GCC complained that one could not be overloaded with the other. So, I changed the member functions to templates, in order to do this: "make the member templates' enable_if depend on a parameter of the member template itself".

After getting it to work with GCC, it was time to move over to MSVC. And solve some more problems, obviously. Actually, it was just one problem - warning/error C4519 default template arguments are only allowed on a class template, because of this:
Default template arguments for function templates     No     No
The two Nos refer to VC10 and VC11. So, I gathered, if I can't use default template arguments, I'll have to use them explicitly:

#ifdef _MSC_VER 
    template <typename LI>
#else
    template <typename LI = LoggerImpl>
#endif
    typename std::enable_if<DoesDebug<LI, void, std::string const&,
        char const*, int>::Exist, void>::type
    Debug(std::string const&, char const*, int); 

And, on the call site:

#ifdef _MSC_VER 
    t.Debug<Logger>("String", __FILE__, __LINE__);
#else
    t.Debug("String", __FILE__, __LINE__);
#endif


And there you go. I'm now checking how to hide this from the user, but here I'll probably cop out and use a #define. I'll use macros for creating the logger and calling the methods, anyway; there's no way I'm going to manually write all those __FILE__s and __LINE__s.

A few closing notes.
  • As I said, I'm adapting my Logger bare-bones example for this, and one thing I've already realized is the amount of boilerplate code is huge. It doesn't really bother me that much, because it's work I'll only do once. But it's still a lot more "code" than I expected.
  • All of the above may be obvious to anyone experienced in C++. However, what you read here reflects my first serious contact with templates, the previous contacts being the usual Oh-templates-are-very-useful-look-how-easily-you-can-create-a-container. And this was definitely my first contact with SFINAE. Two weeks ago, all I knew was I needed a way to select function implementations at compile time and that my cursory observation of enable_if, many months ago, showed it could be done. I'm infinitely grateful to all the folks throughout the web (on StackOverflow, on other programming forums, and on their personal sites/blogs) for taking the time to explain these concepts.
  • This solution seems better than what I have here, as far as boilerplate is concerned, but I didn't understand it completely. It's on my study list.

Friday, 22 March 2013

Building changes

So, long time, no writing... again.

After some more testing, and a fair bit of reading, I've come to the conclusion that I should change my work environment.

I became more aware of the MinGW fracture, and its implications. I've been using mingw.org's MinGW (I'll just call it MinGW), and I had planned to have 3 environments - MinGW, MSVC, and Clang. I've decided to add mingw-32 (already added by Qt Creator 2.6.2) and mingw-64. Yes, there has been plenty of building going on (I might change my name to Bob, eventually), but, as usual, there's also been plenty of learning (this should be a hint that things went less than smoothly).

The plan was as follows: Build for mingw, mingw-32 and MSVC. Then, add mingw-64. And, finally, Clang. I've since decided I'll leave these two for a later time, for now I'll just focus on what I already have on my system.

Building... never a dull moment, heh? And I'm fortunate enough to count Mr. Murphy among my friends, so I like to think I'm given a bonus, as far as non-dull-moments are concerned.

On my first attempt(s) at building Qt 5 (with MinGW), I included WebKit. And Mr. Murphy was kind enough to provide me with a token of our aforementioned friendship (and a new learning experience). Consider this:

g++ <snip an obscene amount of -Ds and -Is> -o path\to\InspectorBackendCommands.o path\to\InspectorBackendCommands.cpp

This was where the build first failed. And the error message complained about not being able to find the file InspectorBackendCommands.cpp. More exactly, the file path\toInspectorBackendCommands.cpp. Ah, you noticed the missing separator as well? Good. Because it sent me on a lovely quest to understand what exactly could be causing this particular concatenation to fail. The fact that it failed on both a path separator and an escape character gave this theory a bit more credibility.

Naturally, after taking a look at the makefiles (and the .pro files, and the .pri files) I realized the concatenation couldn't be the cause of the problem. I decided to run the command individually, and when I pasted it on the cmd prompt, it got truncated. Which led me to suspect some sort of limit was hit by this particular g++ invocation. Which was then confirmed by this.

However, something else was going on here, because the error message didn't truncate anything. It just removed a character. Since I ran make with debug on, I knew it was creating a temporary .bat file and running it, so I recreated the .bat file, I inserted "-DWEBKIT_DO_YOU_REALLY_NEED_ALL_THIS_CRAP_TO_BUILD" into the g++ invocation and ran it. And, naturally, it was now missing some other character.

I still don't understand what exactly is going on here. It's a sort of limit, but one that apparently removes one character, instead of truncating a string. Still, as much as I like a good challenge, I've decided to let this one pass. I made an half-assed attempt at recovering from this failure - I shortened this invocation by removing some stuff I suspected wasn't necessary and reran make. It soon stopped again, on another obscenely long line, much longer than this one. I could've tried to shorten this one, too, but I didn't even know where to start.

In the end, I decided to follow this piece of advice from here:
Consider skipping qtwebkit (...). This module is quite big, takes a long time to compile and if often a source of compile errors, so it is recommend to only download it if you intend to use it.

I don't know if I intend to use it, but I know I don't intend to spend the necessary time to solve what is not, at this moment, an important problem.

So, I deleted everything, recloned the git repo, and ran init-repository with --no-webkit. And it built.

I've since found out that there's a solution for this, but it involves changing mingw's make and rebuilding it. I'm not going to do that now.

All the other builds went smoothly with both versions of MinGW. Qt posed no problems because it was already built with mingw-32.

Then, I started building with MSVC 2012 Desktop. Everything went OK (some minor problems, easily solved), until Qt. While it built, it didn't build properly, some modules were missing (e.g., webkit wasn't even showing up in the logs). I've posted a request for help here, but got no luck. Then, I got suspicious that maybe Qt's cleaning process wasn't quite working as it should, so I deleted my local repo and cloned it again.

And this time, nmake gave me an error, about webkit requiring zlib. Which was odd, because I had zlib, and I was including it in the build paths. Then, I decided to just use -qt-zlib, and everything went smoothly.

So, of all the libs I've built (openssl, zlib, libssh2, icu, boost, poco, qt), qt is definitely the more finicky, at least on Windows. But, since it's also the lib with the largest scope, I guess that's to be expected.

Next time: Testing the builds.

Saturday, 22 September 2012

Mingw And The Mystery of the Missing Console

So, you create a new GUI project in Qt Creator and then decide, during development, to get some console output; say, you're testing some code you're not exactly sure how it will work, and you haven't even built a GUI yet, so it's easier to get some couts going.

Well, you may or may not comment out gui references in your QT variable, in you .pro file. But you better don't forget to add CONFIG += console. What happens if you don't? Well, let's assume you have something like this:

#include <iostream>
using std::cout;
using std::endl;

int main(int argc, char *argv[])
{
    cout << "start" << endl;
}

If you run it in the IDE, all is well. If you open a DOS window and run it from there, you get nothing. No error, no output, nothing. You may wonder if anything is going wrong before execution hits main(), so you fire gdb, set a breakpoint in main() and run it. It stops on your breakpoint, steps perfectly through you cout, but still nothing happens.

And, throughout all of this, not a single warning. It sets my eyes on fire with warnings if I have a variable that I'm not using (yet), but it doesn't seem to have the insight to tell me "You're using cout, but you didn't specify -subsystem,console; if you're expecting to see any output, boy, are you in for some Interesting Times".

Anway...

Testing on libssh2 + asio is still going on.

Also, I've just installed VS 2012 Express. I've already built libssh2 with it (and OpenSSL + zlib). It was easier building it with mingw, so there goes my theory that everything is easier to build on Windows if you have VC++.

Tuesday, 19 June 2012

Upgrading

In the process of building the libs I'm using, I've made several updates/upgrades/additions to my toolset. It didn't get out of control, but I felt the need to organize it properly, and to create a stable foundation.

So, I've been in upgrade mode. It all began with mingw, which I upgraded to gcc 4.7. Then, I've rebuilt the libs I've been using: Boost, libssh2 (and its OpenSSL and zlib dependencies), and Poco C++.

Ever since I installed a newer version of mingw (newer than the custom mingw that shipped with Qt Creator), I've found some hints that I might have trouble later on, especially where debugging is concerned. So, in order to see how well my proposed "stable foundation" worked, I created a simple C++ console program (called "Goooooooooood Morning, World"; yeah, I strive for originality), to test it.

As I hit "Debug", I got this wonderful message about how my debug process was having second thoughts about life and wasn't exactly in the mood to do its job at the moment, and could I please call back at a later time, etc, etc.

As a previous Borland tools user, the idea that command-line tools are being run when I click on a button on the IDE or select a menu item is still somewhat foreign. While this is not the case for some actions (e.g., building), because I was always aware of the command-line being there, other actions (say, debugging) have a less-than-obvious - to me - relation to the command-line. So, I've braced myself for a journey filled with the symbols of mysticism... er, I mean, debugging.

However, before I started, and with my head still full of potential incompatibility problems, I've figured if things went wrong I might have to rebuild Qt/Qt Creator with gcc 4.7; and, if that were the case, I might as well have an early start. So, while I was going through the debugger problem, I had a tail -f hapilly running in a DOS prompt on the build log, and I'm quite satisfied to say that both Qt and Qt Creator built successfully from source (which was another of the reasons why I did it - to see how it worked out).

Anyway... when I finally hit on the idea that the debugging process was actually a command-line execution, I went to my gdb-python27.exe and ran it (Qt Creator works with the python-enabled gdb). And it told me, in no uncertain terms, that it would absolutely refuse to do anything whatsoever until I got it on a date with python27.dll. Which led me to copy it to Qt Creator's debugging tools folder (which contains a copy of this DLL), and update its path in my toolchains.

Then, I had another go at debugging my "Goooooooooood Morning, World". And this time everything ran perfectly... except for the SIGILL, that is. Consulting an online dictionary, one could think the definition of SIGILL is "a seall or a signett". One would be wrong, though.

So, what was causing an illegal instruction? Google gave me no useful answers. After some time looking through Qt Creator's debugging tools docs, I found out about the debugging log, and fired another debugging session. When I got the error, I went to the log. And there was the first clue - my app was loading the wrong sdtlibc++ DLL. Why? Because the Run Settings for my project had Qt's paths before any other path (Qt's default behaviour). I wasn't entirely sure this was the cause, but I've changed it, anyway, because that was not the behaviour I wanted.

After this change, the debugging session ran perfectly. Then, I decided to up the ante, and add a QString (Qt's string class) to my example. After all, the Qt libs I'm using were built with Qt Creator's custom mingw and gcc 4.4. But, this time, everything went smoothly. No sign of the dreaded incompatibility.

So, for the time being, I'm declaring my current environment as "stable", and I'm hoping to keep it that way for a while.

Now, then... lessons learned:
  • If an IDE action corresponds to a command-line program being run, open a shell and run it yourself. You may have to do some digging to find out if your IDE is passing any arguments to this program.
  • Thoroughly investigate your IDE's debugging options and tools.
  • Run your app from the IDE and from the OS (and, on Windows, use Process Explorer to see what it's loading). This is especially useful as the ultimate test for missing DLLs.
  • If you have several toolchains (and, particularly, several versions of the same compiler), add to your build flags the option that prints version info. In my case, I added CFLAGS += -v. This way, I can look at the build output and check if the IDE is using the correct version.

Next steps:
  • I'm taking a closer look at make, to see if I need to have it include more info on the output, and how might I do it. The --debug and -w options are a good start, but I was looking for something more akin to ksh's -x or -v options.
  • I'll be taking some of Qt's examples for a building/running/debugging spin, to see if my environment is actually stable.

But, for now, back to my project.