Qt Internals & Reversing

- Download Qt MetaData Displayer Python script for IDA

Introduction

Half of the text of this article comes from my larger paper "Dynamic C++ Proposal". I decided that it was useful to take the part about Qt internals, put it into another article and extend it by adding a reversing part. Because of its nature, this is not the usual kind of article I write. In fact, I wrote the reversing part in less than a day. So, this is a very easy one. However, I think it is useful for people who need to reverse a Qt application and certainly wouldn't consider reading my other paper about Dynamic C++, which doesn't sound like a paper about Qt and, in fact, isn't a paper about Qt: the paragraph about Qt is only one among many others. Moreover, I haven't seen serious articles about this subject.

The first thing which needs to be considered when reversing Qt applications is what Qt brought to the C++ language. Events (inside the Qt framework) are just virtual functions, so nothing new there. This is not a C++ reversing guide. What is new in Qt are signals and slots, which rely on the dynamism of the Qt framework.

So, first thing I'm going to show how this dynamism works. The second part focus on reversing and, at that point, I will show how to obtain all the metadata one needs when disassembling a Q_OBJECT class.

Internals

The only serious C++ framework I've seen around is the Qt framework. Qt brought C++ to a new level. While the signals and slots technology introduced by Qt is very original, what really was interesting in the whole idea was that an object could call other objects methods regardless of the object declaration. In order for signals and slots to work, dynamism was brought into C++. Well, of course, when using only signals and slots, the developer doesn't directly notice this behavior, it's all handled by the framework. However, this dynamism can be used by the developer through the QMetaObject class as well.

I'm not going to explain the basics of signals and slots. The reader might check out the Qt documentation page. What I will do is to breifly show the internal workings of Qt dynamism. The current version of the Qt framework at the time I'm writing this paper is 4.4.3.

Let's consider a simple signals and slots example:

// sas.h

#include <QObject>

class Counter : public QObject
{
	Q_OBJECT

public:
	Counter() { m_value = 0; };

	int value() const { return m_value; };

	
public slots:

	void setValue(int value)
	{
		if (value != m_value) 
		{
			m_value = value;
			emit valueChanged(value);
		}
	};

signals:
	void valueChanged(int newValue);

private:
	int m_value;
};


// main.cpp

#include "sas.h"

int main(int argc, char *argv[])
{
	Counter a, b;
	QObject::connect(&a, SIGNAL(valueChanged(int)),
		&b, SLOT(setValue(int)));

	a.setValue(12);     // a.value() == 12, b.value() == 12
	b.setValue(48);     // a.value() == 12, b.value() == 48 
	return 0;
}

The SIGNAL and SLOT macro enclose their content in brackets, making it a string. Well, they do one more thing. They put an identification number in the string:

#define SLOT(a)          "1"#a
#define SIGNAL(a)        "2"#a

So, one might as well write:

QObject::connect(&a, "2valueChanged(int)", &b, "1setValue(int)");

The Qt keywords signals and slots, which can be found in the class header, are only useful to the Qt metacompiler (the moc).

# if defined(QT_NO_KEYWORDS)
#  define QT_NO_EMIT
# else
#   define slots
#   define signals protected
# endif
# define Q_SLOTS
# define Q_SIGNALS protected
# define Q_PRIVATE_SLOT(d, signature)
# define Q_EMIT
#ifndef QT_NO_EMIT
# define emit
#endif

In fact, as you can see, even the emit macro just increases readability. Only the signals macro is a bit different, because it qualifies Qt signals as protected methods, whereas slots can be of any kind. The first interesting part is the Q_OBJECT macro:

/* tmake ignore Q_OBJECT */
#define Q_OBJECT_CHECK \
    template <typename T> inline void qt_check_for_QOBJECT_macro(const T &_q_argument) 

const \
    { int i = qYouForgotTheQ_OBJECT_Macro(this, &_q_argument); i = i; }

template <typename T>
inline int qYouForgotTheQ_OBJECT_Macro(T, T) { return 0; }

template <typename T1, typename T2>
inline void qYouForgotTheQ_OBJECT_Macro(T1, T2) {}
#endif // QT_NO_MEMBER_TEMPLATES

/* tmake ignore Q_OBJECT */
#define Q_OBJECT \
public: \
    Q_OBJECT_CHECK \
    static const QMetaObject staticMetaObject; \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    QT_TR_FUNCTIONS \
    virtual int qt_metacall(QMetaObject::Call, int, void **); \
private:

staticMetaObject is the QMetaObject, which is static because, being the metadata class, it's shared by all instances of the same class. The metaObject method just returns staticMetaObject. QT_TR_FUNCTIONS is a macro that defines all tr functions, used for multi-language support. qt_metacast performs a dynamic cast, given the class name or the name of one of its base classes (Qt doesn't rely on RTTI, obviously). qt_metacall calls an internal signal or slot by its index. Before I'm going to discuss the code generated by the moc, here's the QMetaObject declaration:

struct Q_CORE_EXPORT QMetaObject
{
    const char *className() const;
    const QMetaObject *superClass() const;

    QObject *cast(QObject *obj) const;

#ifndef QT_NO_TRANSLATION
    // ### Qt 4: Merge overloads
    QString tr(const char *s, const char *c) const;
    QString trUtf8(const char *s, const char *c) const;
    QString tr(const char *s, const char *c, int n) const;
    QString trUtf8(const char *s, const char *c, int n) const;
#endif // QT_NO_TRANSLATION

    int methodOffset() const;
    int enumeratorOffset() const;
    int propertyOffset() const;
    int classInfoOffset() const;

    int methodCount() const;
    int enumeratorCount() const;
    int propertyCount() const;
    int classInfoCount() const;

    int indexOfMethod(const char *method) const;
    int indexOfSignal(const char *signal) const;
    int indexOfSlot(const char *slot) const;
    int indexOfEnumerator(const char *name) const;
    int indexOfProperty(const char *name) const;
    int indexOfClassInfo(const char *name) const;

    QMetaMethod method(int index) const;
    QMetaEnum enumerator(int index) const;
    QMetaProperty property(int index) const;
    QMetaClassInfo classInfo(int index) const;
    QMetaProperty userProperty() const;

    static bool checkConnectArgs(const char *signal, const char *method);
    static QByteArray normalizedSignature(const char *method);
    static QByteArray normalizedType(const char *type);

    // internal index-based connect
    static bool connect(const QObject *sender, int signal_index,
                        const QObject *receiver, int method_index,
                        int type = 0, int *types = 0);
    // internal index-based disconnect
    static bool disconnect(const QObject *sender, int signal_index,
                           const QObject *receiver, int method_index);
    // internal slot-name based connect
    static void connectSlotsByName(QObject *o);

    // internal index-based signal activation
    static void activate(QObject *sender, int signal_index, void **argv);
    static void activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv);
    static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
    static void activate(QObject *sender, const QMetaObject *, int from_local_signal_index, 
    	int to_local_signal_index, void 

**argv);
    // internal guarded pointers
    static void addGuard(QObject **ptr);
    static void removeGuard(QObject **ptr);
    static void changeGuard(QObject **ptr, QObject *o);

    static bool invokeMethod(QObject *obj, const char *member,
                             Qt::ConnectionType,
                             QGenericReturnArgument ret,
                             QGenericArgument val0 = QGenericArgument(0),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument());

    // [ ... several invokeMethod overloads ...]

    enum Call {
        InvokeMetaMethod,
        ReadProperty,
        WriteProperty,
        ResetProperty,
        QueryPropertyDesignable,
        QueryPropertyScriptable,
        QueryPropertyStored,
        QueryPropertyEditable,
        QueryPropertyUser
    };

#ifdef QT3_SUPPORT
    QT3_SUPPORT const char *superClassName() const;
#endif

    struct { // private data
        const QMetaObject *superdata;
        const char *stringdata;
        const uint *data;
        const QMetaObject **extradata;
    } d;
};

The important part of QMetaObject is the internal d struct. The first member of this struct is a QMetaObject class pointer. This member points to the parent Qt object metadata class. A class like ours can inherit from more than just one class, but it can have only one QObject (or from it derived) base class, and that's the super class. Moreover, the moc relies on the fact that in a QObject derived class declaration the first inherited class is a QObject (or derived) base class. Let's take a Qt dialog, which often uses multiple inheritance in its implementation:

class ConvDialog : public QDialog, private Ui::ConvDialog
{
    Q_OBJECT

Which makes the moc produce this code:

const QMetaObject ConvDialog::staticMetaObject = {
    { &QDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
      qt_meta_data_ConvDialog, 0 }
};

But, if ConvDialog inherits Ui::ConvDialog before QDialog, the moc generates:

const QMetaObject ConvDialog::staticMetaObject = {
    { &Ui::ConvDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
      qt_meta_data_ConvDialog, 0 }
};

Which is wrong, because Ui::ConvDialog is not a derived class of QObject and thus hasn't got a staticMetaObject member. Doing so will result in a compiling error.

The second member of the d struct is a char array, which contains the literal metadata of the class. The third member is an unsigned int array. This array is a table and it contains all the metadata offsets, flags etc. So, if one wants to enumerate the slots and signals of a class, one would have to go through this table and get the right offsets to obtain the methods names from the stringdata array. It also references properties and enums. The fourth member is a null terminated array of QMetaObject classes. This member provides storage for metadata information for additional classes. I've never seen it used, but it is referenced by the QMetaObject_findMetaObject function.

static const QMetaObject *QMetaObject_findMetaObject(const QMetaObject *self, const char *name)
{
    while (self) {
        if (strcmp(self->d.stringdata, name) == 0)
            return self;
        if (self->d.extradata) {
            const QMetaObject **e = self->d.extradata;
            while (*e) {
                if (const QMetaObject *m =QMetaObject_findMetaObject((*e), name))
                    return m;
                ++e;
            }
        }
        self = self->d.superdata;
    }
    return self;
}

This function gets called only by the property method, which, in turn, gets called by propertyCount, propertyOffset and indexOfProperty.

And here's the moc generated code for our Counter class:

/****************************************************************************
** Meta object code from reading C++ file 'sas.h'
**
** Created: Mon 3. Nov 15:20:11 2008
**      by: The Qt Meta Object Compiler version 59 (Qt 4.4.3)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include "../sas.h"
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'sas.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 59
#error "This file was generated using the moc from 4.4.3. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif

QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_Counter[] = {

 // content:
       1,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   10, // methods
       0,    0, // properties
       0,    0, // enums/sets

 // signals: signature, parameters, type, tag, flags
      18,    9,    8,    8, 0x05,

 // slots: signature, parameters, type, tag, flags
      42,   36,    8,    8, 0x0a,

       0        // eod
};

static const char qt_meta_stringdata_Counter[] = {
    "Counter\0\0newValue\0valueChanged(int)\0"
    "value\0setValue(int)\0"
};

const QMetaObject Counter::staticMetaObject = {
    { &QObject::staticMetaObject, qt_meta_stringdata_Counter,
      qt_meta_data_Counter, 0 }
};

const QMetaObject *Counter::metaObject() const
{
    return &staticMetaObject;
}

void *Counter::qt_metacast(const char *_clname)
{
    if (!_clname) return 0;
    if (!strcmp(_clname, qt_meta_stringdata_Counter))
        return static_cast<void*>(const_cast< Counter*>(this));
    return QObject::qt_metacast(_clname);
}

int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        switch (_id) {
        case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
        }
        _id -= 2;
    }
    return _id;
}

// SIGNAL 0
void Counter::valueChanged(int _t1)
{
    void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE

The qt_metacall method calls other internal methods of the class by their index. Qt dynamism relies on indexes, avoiding pointers. The job of actually calling methods is left to the compiler. This implementation makes the signals and slots mechanism quite fast.

Arguments are passed through a pointer to pointer array and casted appropriately when calling the method. Using pointers, of course, is the only way to put all kinds of types in an array. Arguments start from position 1, because position 0 is reserved for the data to return. The signals and slots in the example are declared void and, thus, have no data to return. If a slot had data to return, the code contained in the switch would look like this:

    if (_c == QMetaObject::InvokeMetaMethod) {
        switch (_id) {
        case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 2: { int _r = exampleMethod((*reinterpret_cast< int(*)>(_a[1])));
            if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; }  break;
        }

The other interesting method generated by the moc is valueChanged, which represents the code executed to emit the valueChanged signal. This code calls the activate method of QMetaObject, which is just an overload of this activate method:

void QMetaObject::activate(QObject *sender, int from_signal_index, int to_signal_index, void **argv)
{
    // [... other code ...]

    // emit signals in the following order: from_signal_index <= signals <= to_signal_index, signal < 0
    for (int signal = from_signal_index;
         (signal >= from_signal_index && signal <= to_signal_index) || (signal == -2);
         (signal == to_signal_index ? signal = -2 : ++signal))
    {
        if (signal >= connectionLists->count()) {
            signal = to_signal_index;
            continue;
        }
        const QObjectPrivate::ConnectionList &connectionList = connectionLists->at(signal);
        int count = connectionList.count();
        for (int i = 0; i < count; ++i) {
            const QObjectPrivate::Connection *c = &connectionList[i];
            if (!c->receiver)
                continue;

            QObject * const receiver = c->receiver;

            // determine if this connection should be sent immediately or
            // put into the event queue
            if ((c->connectionType == Qt::AutoConnection
                 && (currentThreadData != sender->d_func()->threadData
                     || receiver->d_func()->threadData != sender->d_func()->threadData))
                || (c->connectionType == Qt::QueuedConnection)) {
                queued_activate(sender, signal, *c, argv);
                continue;
            } else if (c->connectionType == Qt::BlockingQueuedConnection) {
                blocking_activate(sender, signal, *c, argv);
                continue;
            }

            const int method = c->method;
            QObjectPrivate::Sender currentSender;
            currentSender.sender = sender;
            currentSender.signal = signal < 0 ? from_signal_index : signal;
            QObjectPrivate::Sender * const previousSender =
                QObjectPrivate::setCurrentSender(receiver, &currentSender);
            locker.unlock();

            if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
                qt_signal_spy_callback_set.slot_begin_callback(receiver,
                                                               method,
                                                               argv ? argv : empty_argv);
            }

#if defined(QT_NO_EXCEPTIONS)
            receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
#else
            try {
                receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
            } catch (...) {
                locker.relock();

                QObjectPrivate::resetCurrentSender(receiver, &currentSender, previousSender);

                --connectionLists->inUse;
                Q_ASSERT(connectionLists->inUse >= 0);
                if (connectionLists->orphaned && !connectionLists->inUse)
                    delete connectionLists;
                throw;
            }
#endif

            locker.relock();

            if (qt_signal_spy_callback_set.slot_end_callback != 0)
                qt_signal_spy_callback_set.slot_end_callback(receiver, method);

            QObjectPrivate::resetCurrentSender(receiver, &currentSender, previousSender);

            if (connectionLists->orphaned)
                break;
        }

        if (connectionLists->orphaned)
            break;
    }

    --connectionLists->inUse;
    Q_ASSERT(connectionLists->inUse >= 0);
    if (connectionLists->orphaned && !connectionLists->inUse)
        delete connectionLists;

    locker.unlock();

    if (qt_signal_spy_callback_set.signal_end_callback != 0)
        qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
}

This method does lots of stuff, including checking whether the current connection should be processed immediately or put into the event queue. If so, it calls the appropriate activate method variant and then continues with the next connection in the ConnectionList. Otherwise, if the connection should be processed immediately, the id of the method to call is retrived from the current connection and the qt_metacall method of the receiver gets called. To simplify the execution flow:

const QObjectPrivate::ConnectionList &connectionList = connectionLists->at(signal);
int count = connectionList.count();
for (int i = 0; i < count; ++i) {
	const QObjectPrivate::Connection *c = &connectionList[i];

	QObject * const receiver = c->receiver;
	const int method = c->method;
	receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);

And this tells us all we need to know about the internals of signals and slots. When calling the connect function, the signal and slot signatures are converted to their ids, which are then stored in the Connection class. Everytime a signal is emitted, the connections for the signal's id are retrieved and their slots are called by their ids.

The last part which needs to be discussed are dynamic invokes. The QMetaObject class offers the invokeMethod method to dinamically call a method. This method is a bit different than signals and slots, because it needs to build a signature for the method to call from its return type, name and arguments types, and then look up the metadata to retrieve its id, before calling the qt_metacall method of the object.

bool QMetaObject::invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type,
                 QGenericReturnArgument ret,
                 QGenericArgument val0,
                 QGenericArgument val1,
                 QGenericArgument val2,
                 QGenericArgument val3,
                 QGenericArgument val4,
                 QGenericArgument val5,
                 QGenericArgument val6,
                 QGenericArgument val7,
                 QGenericArgument val8,
                 QGenericArgument val9)
{
    if (!obj)
        return false;

    QVarLengthArray<char, 512> sig;
    int len = qstrlen(member);
    if (len <= 0)
        return false;
    sig.append(member, len);
    sig.append('(');

    enum { MaximumParamCount = 11 };
    const char *typeNames[] = {ret.name(), val0.name(), val1.name(), val2.name(), val3.name(),
                               val4.name(), val5.name(), val6.name(), val7.name(), val8.name(),
                               val9.name()};

    int paramCount;
    for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) {
        len = qstrlen(typeNames[paramCount]);
        if (len <= 0)
            break;
        sig.append(typeNames[paramCount], len);
        sig.append(',');
    }
    if (paramCount == 1)
        sig.append(')'); // no parameters
    else
        sig[sig.size() - 1] = ')';
    sig.append('\0');

    int idx = obj->metaObject()->indexOfMethod(sig.constData());
    if (idx < 0) {
        QByteArray norm = QMetaObject::normalizedSignature(sig.constData());
        idx = obj->metaObject()->indexOfMethod(norm.constData());
    }
    if (idx < 0)
        return false;

    // check return type
    if (ret.data()) {
        const char *retType = obj->metaObject()->method(idx).typeName();
        if (qstrcmp(ret.name(), retType) != 0) {
            // normalize the return value as well
            // the trick here is to make a function signature out of the return type
            // so that we can call normalizedSignature() and avoid duplicating code
            QByteArray unnormalized;
            int len = qstrlen(ret.name());

            unnormalized.reserve(len + 3);
            unnormalized = "_(";        // the function is called "_"
            unnormalized.append(ret.name());
            unnormalized.append(')');

            QByteArray normalized = QMetaObject::normalizedSignature(unnormalized.constData());
            normalized.truncate(normalized.length() - 1); // drop the ending ')'

            if (qstrcmp(normalized.constData() + 2, retType) != 0)
                return false;
        }
    }
    void *param[] = {ret.data(), val0.data(), val1.data(), val2.data(), val3.data(), val4.data(),
                     val5.data(), val6.data(), val7.data(), val8.data(), val9.data()};
    if (type == Qt::AutoConnection) {
        type = QThread::currentThread() == obj->thread()
               ? Qt::DirectConnection
               : Qt::QueuedConnection;
    }

    if (type == Qt::DirectConnection) {
        return obj->qt_metacall(QMetaObject::InvokeMetaMethod, idx, param) < 0;
    } else {
        if (ret.data()) {
            qWarning("QMetaObject::invokeMethod: Unable to invoke methods with return values in queued "
                     "connections");
            return false;
        }
        int nargs = 1; // include return type
        void **args = (void **) qMalloc(paramCount * sizeof(void *));
        int *types = (int *) qMalloc(paramCount * sizeof(int));
        types[0] = 0; // return type
        args[0] = 0;
        for (int i = 1; i < paramCount; ++i) {
            types[i] = QMetaType::type(typeNames[i]);
            if (types[i]) {
                args[i] = QMetaType::construct(types[i], param[i]);
                ++nargs;
            } else if (param[i]) {
                qWarning("QMetaObject::invokeMethod: Unable to handle unregistered datatype '%s'",
                         typeNames[i]);
                for (int x = 1; x < i; ++x) {
                    if (types[x] && args[x])
                        QMetaType::destroy(types[x], args[x]);
                }
                qFree(types);
                qFree(args);
                return false;
            }
        }

        if (type == Qt::QueuedConnection) {
            QCoreApplication::postEvent(obj, new QMetaCallEvent(idx, 0, -1, nargs, types, args));
        } else {
            if (QThread::currentThread() == obj->thread()) {
                qWarning("QMetaObject::invokeMethod: Dead lock detected in BlockingQueuedConnection: "
                         "Receiver is %s(%p)",
                         obj->metaObject()->className(), obj);
            }

            // blocking queued connection
#ifdef QT_NO_THREAD
            QCoreApplication::postEvent(obj, new QMetaCallEvent(idx, 0, -1, nargs, types, args));
#else
            QSemaphore semaphore;
            QCoreApplication::postEvent(obj, new QMetaCallEvent(idx, 0, -1, nargs, types, args, &semaphore));
            semaphore.acquire();
#endif // QT_NO_THREAD
        }
    }
    return true;
}

The method id is retrieved through indexOfMethod. If the method signature can't be found, invokeMethod returns false.

And this should give you a general idea about Qt internals.

Reversing

When reversing we want to use all the metadata Qt offers. In order to do that, let's reconsider the metadata table:

QT_BEGIN_MOC_NAMESPACE
static const uint qt_meta_data_Counter[] = {

 // content:
       1,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   10, // methods
       0,    0, // properties
       0,    0, // enums/sets

 // signals: signature, parameters, type, tag, flags
      18,    9,    8,    8, 0x05,

 // slots: signature, parameters, type, tag, flags
      42,   36,    8,    8, 0x0a,

       0        // eod
};

As one can notice, the table tells us not only the method count, but also the offset of the methods (10). Here's the C++ declared header of this structure:

struct QMetaObjectPrivate
{
    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
};

This structure is contained in "qmetaobject.cpp" like the rest of the information we need to parse the metadata table. Before taking into account properties and enums, let's consider methods. We already have the method count and offset, what now has to be analyzed is the data of methods itself. This data is subdivided into five integers, which represent according to the moc: signature, parameters, type, tag, flags. The signature field is an offset into the string data and refers a partial declaration (without return type) of the method: valueChanged(int). The parameters field is another offset to the names of the arguments. Yes, the names: 'newValue'. The names are separated by commas, so if our slot had two arguments, it would display: 'newValue1,newValue2'. The type field refers the return type of the method. If it's an empty string, like in our case, then the method is declared as void. The tag field is unused for the moment. I paste you the description found in the sources: "Tags are special macros recognized by moc that make it possible to add extra information about a method. For the moment, moc doesn't support any special tags.". The last field flags is not an offset, but, as the name suggests, flags. Here's the list of possible flags:

enum MethodFlags  {
    AccessPrivate = 0x00,
    AccessProtected = 0x01,
    AccessPublic = 0x02,
    AccessMask = 0x03, //mask

    MethodMethod = 0x00,
    MethodSignal = 0x04,
    MethodSlot = 0x08,
    MethodTypeMask = 0x0c,

    MethodCompatibility = 0x10,
    MethodCloned = 0x20,
    MethodScriptable = 0x40
};

This is all we need to know about methods. Let's now consider enums and properties. For this purpose I added one of each to the code example above:

class Counter : public QObject
{
	Q_OBJECT
	Q_PROPERTY(Priority priority READ priority WRITE setPriority)
    Q_ENUMS(Priority)


public:
	Counter() { m_value = 0; };
	
	enum Priority { High, Low, VeryHigh, VeryLow };

     void setPriority(Priority priority) { m_priority = priority; };
     Priority priority() const { return m_priority; };

	int value() const { return m_value; };
	
public slots:

		void setValue(int value)
		{
			if (value != m_value) 
			{
				m_value = value;
				emit valueChanged(value);
			}
		};
		
signals:
		void valueChanged(int newValue);

private:
	int m_value;
	Priority m_priority;
};

And the moc generates:

static const uint qt_meta_data_Counter[] = {

 // content:
       1,       // revision
       0,       // classname
       0,    0, // classinfo
       2,   10, // methods
       1,   20, // properties
       1,   23, // enums/sets

 // signals: signature, parameters, type, tag, flags
      18,    9,    8,    8, 0x05,

 // slots: signature, parameters, type, tag, flags
      42,   36,    8,    8, 0x0a,

 // properties: name, type, flags
      65,   56, 0x0009510b,

 // enums: name, flags, count, data
      56, 0x0,    4,   27,

 // enum data: key, value
      74, uint(Counter::High),
      79, uint(Counter::Low),
      83, uint(Counter::VeryHigh),
      92, uint(Counter::VeryLow),

       0        // eod
};

static const char qt_meta_stringdata_Counter[] = {
    "Counter\0\0newValue\0valueChanged(int)\0"
    "value\0setValue(int)\0Priority\0priority\0"
    "High\0Low\0VeryHigh\0VeryLow\0"
};

Again, we have the properties and enums count and their offsets. Let's start with properties. The data of properties is made of 3 integers: name, type, flags. The name field refers, of course, the name. The type field refers the type of the property, in our case: Priority. Finally, the flags field contains flags and here's the list:

enum PropertyFlags  {
    Invalid = 0x00000000,
    Readable = 0x00000001,
    Writable = 0x00000002,
    Resettable = 0x00000004,
    EnumOrFlag = 0x00000008,
    StdCppSet = 0x00000100,
//    Override = 0x00000200,
    Designable = 0x00001000,
    ResolveDesignable = 0x00002000,
    Scriptable = 0x00004000,
    ResolveScriptable = 0x00008000,
    Stored = 0x00010000,
    ResolveStored = 0x00020000,
    Editable = 0x00040000,
    ResolveEditable = 0x00080000,
    User = 0x00100000,
    ResolveUser = 0x00200000
};

Let's consider now the enum: name, flags, count, data. We already know what the name field is by now. The flags field isn't used. The count field represents the numbers of items contained in the enum. The data field is an offset into the metadata table and points to the items of the enum. Every item is represented by two integers: key, value. The key field points to the name of the current item, whereas value is the actual value of the item.

In order to retrieve the metadata information from a binary file, I wrote a script for IDA. I wrote the script in Python thanks to the IDAPython plugin. Many reasons for using Python: writing the same code with IDC script would be way too much effort and Python can be re-used even outside IDA. The script extracts methods, properties and enums from the layout of a Q_OBJECT class. You can download it from here. Here's the code:

# ---------------------------------------------
# MetaData Parser Class
# ---------------------------------------------

# change for 64 bit exes
b64bit = False
# i'm assuming that the exe is Little Endian
# the external methods used by this class are Byte(addr) and Dword(addr)

def AddressSize():
    if b64bit == True:
        return 8
    return 4

def ReadAddress(addr):
    if b64bit == True:
        return (Dword(addr+4) << 32) | Dword(addr)
    return Dword(addr)

class MetaParser:
    
    def __init__(self, stringsaddr, tableaddr):
        self.MetaStrings = stringsaddr
        self.MetaTable = tableaddr
    
    def ReadString(self, addr):
        c = addr
        b = Byte(c)
        str = ""
        while b != 0:
            # set a limit, just in case
            if (c - addr) > 1000:
                return "error"
            str += chr(b)
            c += 1
            b = Byte(c)
        return str
    
    # read metadata
    
    """
    struct QMetaObjectPrivate
    {
        int revision;
        int className;
        int classInfoCount, classInfoData;
        int methodCount, methodData;
        int propertyCount, propertyData;
        int enumeratorCount, enumeratorData;
    };
    """
    
    # ---------------------------------------------
    # enums (quick way to convert them to python)
    # ---------------------------------------------
    
    class Enum:
        def __init__(self, **entries): self.__dict__.update(entries)
        
    MethodFlags = Enum(             \
        AccessPrivate = 0x00,       \
        AccessProtected = 0x01,     \
        AccessPublic = 0x02,        \
        AccessMask = 0x03,          \
        MethodMethod = 0x00,        \
        MethodSignal = 0x04,        \
        MethodSlot = 0x08,          \
        MethodTypeMask = 0x0c,      \
        MethodCompatibility = 0x10, \
        MethodCloned = 0x20,        \
        MethodScriptable = 0x40)
        
    PropertyFlags = Enum(               \
        Invalid = 0x00000000,           \
        Readable = 0x00000001,          \
        Writable = 0x00000002,          \
        Resettable = 0x00000004,        \
        EnumOrFlag = 0x00000008,        \
        StdCppSet = 0x00000100,         \
        Designable = 0x00001000,        \
        ResolveDesignable = 0x00002000, \
        Scriptable = 0x00004000,        \
        ResolveScriptable = 0x00008000, \
        Stored = 0x00010000,            \
        ResolveStored = 0x00020000,     \
        Editable = 0x00040000,          \
        ResolveEditable = 0x00080000,   \
        User = 0x00100000,              \
        ResolveUser = 0x00200000)


    # ---------------------------------------------
    # methods
    # ---------------------------------------------
    
    def GetClassName(self):
        stringaddr = Dword(self.MetaTable + 4) + self.MetaStrings
        return self.ReadString(stringaddr)
    
    def GetMethodNumber(self):
        return Dword(self.MetaTable + 16)
    
    def GetMethodSignature(self, method_index):
        if method_index >= self.GetMethodNumber():
            return "error: method index out of range"
        
        method_offset = (Dword(self.MetaTable + 20) * 4) + (method_index * (5 * 4))
        
        # get accessibility
        
        access_flags = self.GetMethodAccess(method_index)
        access_type = "private:   "
        if access_flags == self.MethodFlags.AccessProtected:
            access_type = "protected: "
        elif access_flags == self.MethodFlags.AccessPublic:
            access_type = "public:    "
        
        # read return type
        rettype = self.ReadString(Dword(self.MetaTable + method_offset + 8) + self.MetaStrings)
        
        if rettype == "":
            rettype = "void"
        
        # read partial signature
        psign = self.ReadString(Dword(self.MetaTable + method_offset) + self.MetaStrings)
        
        # retrieve argument types
        
        par_index = psign.find("(")
        arg_types = psign[(par_index + 1):(len(psign) - 1)].split(",")
        
        # read argument names
        arg_names = self.ReadString(Dword(self.MetaTable + method_offset + 4) \
            + self.MetaStrings).split(",")
        
        # if argument types and names are not the same number,
        # then show signature without argument names
        if len(arg_types) != len(arg_names):
            return access_type + rettype + " " + psign
        
        # build signatrue with argument names
        
        ntypes = len(arg_types)
        x = 0
        args = ""
        while x < ntypes:
            if x != 0:
                args += ", "
            if arg_types[x] == "":
                args += arg_names[x]
            elif arg_names[x] == "":
                args += arg_types[x]
            else:
                args += (arg_types[x] + " " + arg_names[x])
            # increment loop
            x += 1
        
        return access_type + rettype + " " + psign[0:(par_index + 1)] + args + ")"
    
    
    def GetMethodFlags(self, method_index):
        if method_index >= self.GetMethodNumber():
            return -1
        method_offset = (Dword(self.MetaTable + 20) * 4) + (method_index * (5 * 4))
        return Dword(self.MetaTable + method_offset + 16)
    
    def GetMethodType(self, method_index):
        return self.GetMethodFlags(method_index) & self.MethodFlags.MethodTypeMask
    
    def GetMethodAccess(self, method_index):
        return self.GetMethodFlags(method_index) & self.MethodFlags.AccessMask
    
    def GetPropertyNumber(self):
        return Dword(self.MetaTable + 24)
    
    def GetPropertyDecl(self, property_index):
        if property_index >= self.GetPropertyNumber():
            return "error: property index out of range"
        
        property_offset = (Dword(self.MetaTable + 28) * 4) + (property_index * (3 * 4))
        
        # read name
        pr_name = self.ReadString(Dword(self.MetaTable + property_offset) + self.MetaStrings)
        
        # read type
        pr_type = self.ReadString(Dword(self.MetaTable + property_offset + 4) + self.MetaStrings)
        
        return pr_type + " " + pr_name
    
    def GetPropertyFlags(self, property_index):
        if property_index >= self.GetPropertyNumber():
            return -1
        property_offset = (Dword(self.MetaTable + 28) * 4) + (property_index * (3 * 4))
        return Dword(self.MetaTable + property_offset + 8)
    
    def PropertyFlagsToString(self, flags):
        if flags == 0:
            return "Invalid"
        
        fstr = ""
        if flags & self.PropertyFlags.Readable:
            fstr += " | Readable"
        if flags & self.PropertyFlags.Writable:
            fstr += " | Writable"
        if flags & self.PropertyFlags.Resettable:
            fstr += " | Resettable"
        if flags & self.PropertyFlags.EnumOrFlag:
            fstr += " | EnumOrFlag"
        if flags & self.PropertyFlags.StdCppSet:
            fstr += " | StdCppSet"
        if flags & self.PropertyFlags.Designable:
            fstr += " | Designable"
        if flags & self.PropertyFlags.ResolveDesignable:
            fstr += " | ResolveDesignable"
        if flags & self.PropertyFlags.Scriptable:
            fstr += " | Scriptable"
        if flags & self.PropertyFlags.ResolveScriptable:
            fstr += " | ResolveScriptable"
        if flags & self.PropertyFlags.Stored:
            fstr += " | Stored"
        if flags & self.PropertyFlags.ResolveStored:
            fstr += " | ResolveStored"
        if flags & self.PropertyFlags.Editable:
            fstr += " | Editable"
        if flags & self.PropertyFlags.ResolveEditable:
            fstr += " | ResolveEditable"
        if flags & self.PropertyFlags.User:
            fstr += " | User"
        if flags & self.PropertyFlags.ResolveUser:
            fstr += " | ResolveUser"
        
        return fstr[3:]
        
        
    def GetEnumNumber(self):
        return Dword(self.MetaTable + 32)
        
    def GetEnumDecl(self, enum_index):
        if enum_index >= self.GetPropertyNumber():
            return "error: property index out of range"
        
        enum_offset = (Dword(self.MetaTable + 36) * 4) + (enum_index * (4 * 4))
        
        # read name
        enum_name = self.ReadString(Dword(self.MetaTable + enum_offset) + self.MetaStrings)
        
        # read number of items
        items_num = Dword(self.MetaTable + enum_offset + 8)
        
        # items addr
        items_addr = (Dword(self.MetaTable + enum_offset + 12) * 4) + self.MetaTable
        
        decl = "enum " + enum_name + "\n{\n"
        
        # add items
        x = 0
        while x < items_num:
            # read item name
            item_name = self.ReadString(Dword(items_addr) + self.MetaStrings)
            # read data
            item_data = "0x%X" % Dword(items_addr + 4)
            # add
            decl += "    " + item_name + " = " + item_data + ",\n"
            # inc loop
            x += 1
            items_addr += 8
            
        decl += "\n};"
        
        return decl

# ---------------------------------------------
# Display MetaData
# ---------------------------------------------

def DisplayMethod(parser, method_index):
    print(str(method_index) + " - " + parser.GetMethodSignature(method_index))
    
def DisplayProperty(parser, property_index):
    print(str(property_index) + " - " + parser.GetPropertyDecl(property_index))
    flags = parser.GetPropertyFlags(property_index)
    print("    flags: " + parser.PropertyFlagsToString(flags))
    
def DisplayEnum(parser, enum_index):
    print("[" + str(enum_index) + "]\n" + parser.GetEnumDecl(enum_index) + "\n")

def DisplayMetaData(stringsaddr, tableaddr):
    parser = MetaParser(stringsaddr, tableaddr)
    
    print("\n-------------------------------------------------")
    print("--- " + "Qt MetaData Displayer by Daniel Pistelli")
    print("--- " + "metadata of the class: " + parser.GetClassName() + "\n")
    
    num_methods = parser.GetMethodNumber()
    num_properties = parser.GetPropertyNumber()
    num_enums = parser.GetEnumNumber()
    
    # ---------------------------------------------
    # methods
    # ---------------------------------------------
    
    # signals
    
    print("--- Signals:\n")
    
    x = 0
    while x < num_methods:
        # print if it's a signal
        if parser.GetMethodType(x) == parser.MethodFlags.MethodSignal:
            DisplayMethod(parser, x)
        # increment loop
        x += 1 
        
    # slots
    
    print("\n--- Slots:\n")
    
    x = 0
    while x < num_methods:
        # print if it's a slot
        if parser.GetMethodType(x) == parser.MethodFlags.MethodSlot:
            DisplayMethod(parser, x)
        # increment loop
        x += 1 
    
    # other methods
    
    print("\n--- Other Methods:\n")
    
    x = 0
    while x < num_methods:
        # print if it's a slot
        if parser.GetMethodType(x) == parser.MethodFlags.MethodMethod:
            DisplayMethod(parser, x)
        # increment loop
        x += 1
        
    # ---------------------------------------------
    # properties
    # ---------------------------------------------
    
    print("\n--- Properties:\n")
    
    x = 0
    while x < num_properties:
        DisplayProperty(parser, x)
        # increment loop
        x += 1
    
    # ---------------------------------------------
    # enums
    # ---------------------------------------------
    
    print("\n--- Enums:\n")
    
    x = 0
    while x < num_enums:
        DisplayEnum(parser, x)
        # increment loop
        x += 1
    
    print("-------------------------------------------------\n")

# ---------------------------------------------
# Main
# ---------------------------------------------

addrtoparse = ScreenEA()
if addrtoparse != 0:
    stringsaddr = ReadAddress(addrtoparse + AddressSize())
    tableaddr = ReadAddress(addrtoparse + AddressSize() * 2)
    if stringsaddr != 0 or tableaddr != 0:
        DisplayMetaData(stringsaddr, tableaddr)

I'll explain how to reach the metadata of a class in a moment. The DisplayMetaData function accepts two parameters: the metadata table address and the metadata strings address. That's because sometimes it happens that the layout of a class is set at runtime like this VC++ applications does:

.text:00401D60 sub_401D60      proc near   ; DATA XREF: .rdata:00402108
.text:00401D60  push    ebp
.text:00401D61  mov     ebp, esp
.text:00401D63  mov     eax, ds:?staticMetaObject@QObject@@2UQMetaObject@@B 
			; QMetaObject const QObject::staticMetaObject
.text:00401D68  mov     dword_403070, eax
.text:00401D6D  mov     dword_403074, offset Str2 ; "Counter"
.text:00401D77  mov     dword_403078, offset unk_4021A8
.text:00401D81  mov     dword_40307C, 0
.text:00401D8B  pop     ebp
.text:00401D8C  retn
.text:00401D8C sub_401D60      endp

Sometimes, the layout is already set in the physical file:

.data:00406000  dd 0                    ; SuperData
.data:00406004  dd offset MetaStrings   ; "Counter"
.data:00406008  dd offset MetaTable

In that case, the script can be directly launched at the address of the SuperData item, which is the first one of the d struct inside QMetaObject. The output of the script for the above Counter class is:

-------------------------------------------------
--- Qt MetaData Displayer by Daniel Pistelli
--- metadata of the class: Counter

--- Signals:

0 - protected: void valueChanged(int newValue)

--- Slots:

1 - public:    void setValue(int value)

--- Other Methods:


--- Properties:

0 - Priority priority
    flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable | Scriptable | Stored | ResolveEditable

--- Enums:

[0]
enum Priority
{
    High = 0x0,
    Low = 0x1,
    VeryHigh = 0x2,
    VeryLow = 0x3,

};

-------------------------------------------------

Nice, isn't it? The signature of the methods contain even argument names (when available). To check the quality of the script, I tested it on the larger class QWidget inside the module 'QtGui4'. Here's the result:

-------------------------------------------------
--- Qt MetaData Displayer by Daniel Pistelli
--- metadata of the class: QWidget

--- Signals:

0 - protected: void customContextMenuRequested(QPoint pos)

--- Slots:

1 - public:    void setEnabled(bool)
2 - public:    void setDisabled(bool)
3 - public:    void setWindowModified(bool)
4 - public:    void setWindowTitle(QString)
5 - public:    void setStyleSheet(QString styleSheet)
6 - public:    void setFocus()
7 - public:    void update()
8 - public:    void repaint()
9 - public:    void setVisible(bool visible)
10 - public:    void setHidden(bool hidden)
11 - public:    void show()
12 - public:    void hide()
13 - public:    void setShown(bool shown)
14 - public:    void showMinimized()
15 - public:    void showMaximized()
16 - public:    void showFullScreen()
17 - public:    void showNormal()
18 - public:    bool close()
19 - public:    void raise()
20 - public:    void lower()
21 - protected: void updateMicroFocus()
22 - private:   void _q_showIfNotHidden()

--- Other Methods:


--- Properties:

0 - bool modal
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
1 - Qt::WindowModality windowModality
    flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
2 - bool enabled
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
3 - QRect geometry
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
4 - QRect frameGeometry
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
5 - QRect normalGeometry
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
6 - int x
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
7 - int y
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
8 - QPoint pos
    flags: Readable | Writable | Scriptable | ResolveEditable
9 - QSize frameSize
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
10 - QSize size
    flags: Readable | Writable | Scriptable | ResolveEditable
11 - int width
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
12 - int height
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
13 - QRect rect
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
14 - QRect childrenRect
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
15 - QRegion childrenRegion
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
16 - QSizePolicy sizePolicy
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
17 - QSize minimumSize
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
18 - QSize maximumSize
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
19 - int minimumWidth
    flags: Readable | Writable | StdCppSet | Scriptable | ResolveEditable
20 - int minimumHeight
    flags: Readable | Writable | StdCppSet | Scriptable | ResolveEditable
21 - int maximumWidth
    flags: Readable | Writable | StdCppSet | Scriptable | ResolveEditable
22 - int maximumHeight
    flags: Readable | Writable | StdCppSet | Scriptable | ResolveEditable
23 - QSize sizeIncrement
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
24 - QSize baseSize
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
25 - QPalette palette
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
26 - QFont font
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
27 - QCursor cursor
    flags: Readable | Writable | Resettable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
28 - bool mouseTracking
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
29 - bool isActiveWindow
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
30 - Qt::FocusPolicy focusPolicy
    flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
31 - bool focus
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
32 - Qt::ContextMenuPolicy contextMenuPolicy
    flags: Readable | Writable | EnumOrFlag | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
33 - bool updatesEnabled
    flags: Readable | Writable | StdCppSet | Scriptable | Stored | ResolveEditable
34 - bool visible
    flags: Readable | Writable | StdCppSet | Scriptable | Stored | ResolveEditable
35 - bool minimized
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
36 - bool maximized
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
37 - bool fullScreen
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
38 - QSize sizeHint
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
39 - QSize minimumSizeHint
    flags: Readable | Designable | Scriptable | Stored | ResolveEditable
40 - bool acceptDrops
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
41 - QString windowTitle
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
42 - QIcon windowIcon
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
43 - QString windowIconText
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
44 - double windowOpacity
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
45 - bool windowModified
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
46 - QString toolTip
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
47 - QString statusTip
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
48 - QString whatsThis
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
49 - QString accessibleName
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
50 - QString accessibleDescription
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
51 - Qt::LayoutDirection layoutDirection
    flags: Readable | Writable | Resettable | EnumOrFlag | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
52 - bool autoFillBackground
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
53 - QString styleSheet
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
54 - QLocale locale
    flags: Readable | Writable | Resettable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable
55 - QString windowFilePath
    flags: Readable | Writable | StdCppSet | Designable | Scriptable | Stored | ResolveEditable

--- Enums:

-------------------------------------------------

The output is 100% right when compared against the QWidget source code.

Now, I'll show you how to find the metadata of a class. Let's consider the allocation of the Counter class:

.text:004012F2  mov     eax, ds:_ZN7QObjectC2EPS_
.text:004012F7  mov     [esp+88h+var_84], ecx
.text:004012FB  mov     [ebp+var_3C], 2
.text:00401302  call    eax ; _ZN7QObjectC2EPS_
.text:00401304  mov     eax, [ebp+var_4C]
.text:00401307  mov     dword ptr [eax], offset virtual_ptrs

The last instruction of the assembly above sets the vtable pointer. The vtable of a Q_OBJECT class looks this:

.rdata:00402158 off_402158 dd offset metaObject
.rdata:0040215C            dd offset qt_metacast
.rdata:00402160            dd offset qt_metacall

The method metaObject can be used to obtain the metadata tables:

.text:00401430 metaObject      proc near             
.text:00401430  push    ebp
.text:00401431  mov     eax, offset dword_406000 ; QMetaObject class layout
.text:00401436  mov     ebp, esp
.text:00401438  pop     ebp
.text:00401439  retn
.text:00401439 metaObject      endp

dword_406000 refers the class layout of QMetaObject, which we need to execute the script.

The next step, after retrieving the metadata of a class, is linking method (and property) names to their actual disassembled code. The script prints the index of each method and property. To obtain a method address from an index, it is necessary to consider the qt_metacall method generated by the moc:

int Counter::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QObject::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        switch (_id) {
        case 0: valueChanged((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: setValue((*reinterpret_cast< int(*)>(_a[1]))); break;
        }
        _id -= 2;
    }
#ifndef QT_NO_PROPERTIES
      else if (_c == QMetaObject::ReadProperty) {
        void *_v = _a[0];
        switch (_id) {
        case 0: *reinterpret_cast< Priority*>(_v) = priority(); break;
        }
        _id -= 1;
    } else if (_c == QMetaObject::WriteProperty) {
        void *_v = _a[0];
        switch (_id) {
        case 0: setPriority(*reinterpret_cast< Priority*>(_v)); break;
        }
        _id -= 1;
    } else if (_c == QMetaObject::ResetProperty) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyDesignable) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyScriptable) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyStored) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyEditable) {
        _id -= 1;
    } else if (_c == QMetaObject::QueryPropertyUser) {
        _id -= 1;
    }
#endif // QT_NO_PROPERTIES
    return _id;
}

This method can solve both method names and property get/set method names.

The address of qt_metacall is obtained from the vtable. Before analyzing the code of this method, it is necessary to take into account this enum:

enum Call {
    InvokeMetaMethod,		// 0
    ReadProperty,		// 1
    WriteProperty,		// 2
    ResetProperty,		// 3
    QueryPropertyDesignable,	// 4
    QueryPropertyScriptable,	// 5
    QueryPropertyStored,	// 6
    QueryPropertyEditable,	// 7
    QueryPropertyUser		// 8
};

Now the disassembly:

.text:004014D0 qt_metacall     proc near
.text:004014D0
.text:004014D0 var_28 = dword ptr -28h
.text:004014D0 var_24 = dword ptr -24h
.text:004014D0 var_20 = dword ptr -20h
.text:004014D0 var_1C = dword ptr -1Ch
.text:004014D0 var_10 = dword ptr -10h
.text:004014D0 var_C  = dword ptr -0Ch
.text:004014D0 var_8  = dword ptr -8
.text:004014D0 var_4  = dword ptr -4
.text:004014D0 arg_0  = dword ptr  8
.text:004014D0 arg_4  = dword ptr  0Ch
.text:004014D0 arg_8  = dword ptr  10h
.text:004014D0 arg_C  = dword ptr  14h
.text:004014D0
.text:004014D0  push    ebp
.text:004014D1  mov     ebp, esp
.text:004014D3  sub     esp, 28h
.text:004014D6  mov     [ebp+var_C], ebx
.text:004014D9  mov     eax, [ebp+arg_0]
.text:004014DC  mov     ebx, [ebp+arg_8]
.text:004014DF  mov     [ebp+var_8], esi
.text:004014E2  mov     esi, [ebp+arg_4] ; esi = _c
.text:004014E5  mov     [ebp+var_4], edi
.text:004014E8  mov     edi, [ebp+arg_C]
.text:004014EB  mov     [ebp+var_10], eax
.text:004014EE  mov     [esp+28h+var_20], ebx
.text:004014F2  mov     [esp+28h+var_1C], edi
.text:004014F6  mov     [esp+28h+var_24], esi
.text:004014FA  mov     [esp+28h+var_28], eax
.text:004014FD  call    _ZN7QObject11qt_metacallEN11QMetaObject4CallEiPPv
.text:00401502  test    eax, eax        ; eax = _id
.text:00401504  mov     ebx, eax
.text:00401506  js      short loc_40151C ; _id < 0 ?
.text:00401508  test    esi, esi
.text:0040150A  jnz     short loc_401530 ; _c != InvokeMetaMethod
.text:0040150C  test    eax, eax        ; _id == 0 ?
.text:0040150E  jz      loc_4015B8
.text:00401514  cmp     eax, 1          ; _id == 1 ?
.text:00401517  jz      short loc_401590

If the esi register isn't equal to 0, then it's an InvokeMetaMethod call. The switich is represented by the last instructions. Let's follow the "_id == 0" case:

.text:004015B8 loc_4015B8:
.text:004015B8  mov     eax, [edi+4]
.text:004015BB  mov     edx, [ebp+var_10]
.text:004015BE  mov     eax, [eax]
.text:004015C0  mov     [esp+28h+var_28], edx
.text:004015C3  mov     [esp+28h+var_24], eax
.text:004015C7  call    sub_401490

So, we can now establish that sub_401490 really is the valueChanged signal:

.text:00401490 ; void __cdecl signal_valueChanged(int this, int newValue)
.text:00401490 signal_valueChanged proc near
.text:00401490
.text:00401490 var_18 = dword ptr -18h
.text:00401490 var_14 = dword ptr -14h
.text:00401490 var_10 = dword ptr -10h
.text:00401490 var_C  = dword ptr -0Ch
.text:00401490 var_8  = dword ptr -8
.text:00401490 var_4  = dword ptr -4
.text:00401490 this   = dword ptr  8
.text:00401490 newValue = dword ptr  0Ch
.text:00401490
.text:00401490  push    ebp
.text:00401491  xor     ecx, ecx
.text:00401493  mov     ebp, esp
.text:00401495  lea     eax, [ebp+newValue]
.text:00401498  sub     esp, 18h
.text:0040149B  mov     [ebp+var_8], 0
.text:004014A2  mov     edx, offset dword_406000
.text:004014A7  mov     [ebp+var_4], eax
.text:004014AA  lea     eax, [ebp+var_8]
.text:004014AD  mov     [esp+18h+var_C], eax
.text:004014B1  mov     eax, [ebp+this]
.text:004014B4  mov     [esp+18h+var_10], ecx
.text:004014B8  mov     [esp+18h+var_14], edx
.text:004014BC  mov     [esp+18h+var_18], eax
.text:004014BF  call    ds:_ZN11QMetaObject8activateEP7QObjectPKS_iPPv
.text:004014C5  leave
.text:004014C6  retn

Now the method is understandable. The same approach can be used to solve the get/set method names of properties. Of course, for a large analysis on a file, a little parser for switch blocks could be written and combined with the Qt MetaData Parser script in order to solve all methods automatically. However, switch blocks are deeply platform and compiler dependent: thus, that script needs to be modified frequently. Moreover, small methods are sometimes inlined inside the qt_metacall method, this should be considered when writing such a script.

Conclusions

This article took me just one day and I enjoyed writing it. Although it's an easy one, someone might find it useful, also because, in my opinion, the Qt framework will be used more and more by software developers.

Daniel Pistelli