Qt内部机制及逆向

原文:http://bbs.pediy.com/thread-133181.htm


(一)  内部机制
我见过的最严谨的C++框架就是Qt框架,Qt将C++带入了一个新的高度。Qt引入的信号(signal)和槽(slot)技术很有创意,其中一点就是,一个对象可以不要声明就可以调用其它对象的方法。为了运作信号和槽,Qt采用了动态化机制(dynamism)。这种动态化机制可以由Qt框架自动实现,也可以由开发人员通过QMetaObject类手动实现。有关信号和槽的内容可以参考http://doc.trolltech.com/4.7/signalsandslots.html。
我们看一个简单的信号和槽的例子:
// sas.h

#include 

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;
}
SIGNAL和SLOT宏将括号中的内容封装成一个字符串,同时还附加一个ID号,如下所示:
#define SLOT(a)          "1"#a
#define SIGNAL(a)        "2"#a
所以,也可以直接这么写connect函数:
QObject::connect(&a, "2valueChanged(int)", &b, "1setValue(int)");
signals和slots是Qt关键词,可以在头文件中找到,只用于Qt的元编译器(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
emit宏没什么需要解释的,signals宏有点不同,它限定Qt信号为protected方法,而slots宏可以是任意类型。
我们接下来看看Q_OBJECT宏:
/* tmake ignore Q_OBJECT */
#define Q_OBJECT_CHECK \
    template  inline void qt_check_for_QOBJECT_macro(const T &_q_argument) 

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

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

template 
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是 QMetaObject对象,因为需要给属于同一类的全部实例共享,所以它是静态的。metaObject方法仅仅返回staticMetaObject。QT_TR_FUNCTIONS是一个用于所有tr函数的宏,用来实现多语言支持。qt_metacast用于按照类名或它的某个基类名进行动态转换(dynamic cast)【Qt显然不依赖运行时类型检查(RTTI)】。qt_metacall通过索引调用内部信号和槽。以下是QMetaObject的声明:
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;
};
需要注意的是它的d结构。d结构的第一个成员是一个QMetaObject类指针,指向父Qt元数据类。用户设计的类可以从多个类派生,但只能拥有一个QObject(或从它派生)基类,这同时也是超类。看一个Qt对话框的例子,它经常从多个类派生:
class ConvDialog : public QDialog, private Ui::ConvDialog
{
    Q_OBJECT
Moc将产生以下代码:
const QMetaObject ConvDialog::staticMetaObject = {
    { &QDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
      qt_meta_data_ConvDialog, 0 }
};
如果在QDialog前先继承Ui::ConvDialog,moc将会生成:
const QMetaObject ConvDialog::staticMetaObject = {
    { &Ui::ConvDialog::staticMetaObject, qt_meta_stringdata_ConvDialog,
      qt_meta_data_ConvDialog, 0 }
};
这是错误的,因为Ui::ConvDialog不是QObject的一个派生类,由此不拥有staticMetaObject成员,这样做只会导致一个编译错误。
d结构的第二个成员是一个字符数值,表示类的字面元数据。第三个成员是一个无符号整型数组,这个数组是一个表,包含所有元数据的偏移、特征等等。所以,如果你想枚举一个类的信号和槽,那就应该遍历这个表,通过偏移量从stringdata数组中获得方法名。它也涉及到属性(properties)和枚举类型(enums)。第四个成员是以null结尾的QMetaObject类数组,用来保存附加类的元数据信息,它一般由QMetaObject_findMetaObject函数引用。
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;
}
这个函数只被property方法调用,property又被propertyCount, propertyOffset 和 indexOfProperty调用。
下面是我们的Counter 类被moc生成的代码:
/****************************************************************************
** 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 ."
#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(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(reinterpret_cast(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
QT_END_MOC_NAMESPACE
qt_metacall方法通过索引调用其它内部方法。Qt动态机制不采用指针,而由索引实现。实际调用方法的工作由编译器实现。这使得信号和槽的机制执行效率比较高。
参数由一个指向指针数组的指针进行传递,并在调用方法时进行适当的转换。当然,使用指针是将不同类型的参数放在一个数组的唯一办法。参数索引从1开始,因为0号代表函数返回值。我们这个例子中信号和槽被声明为void,所以没有值返回。如果需要返回数据,那么包含在switch中的代码将与下面代码类似:
    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;
        }
另外一个需要关注的是valueChanged,它将调用QMetaObject的activate方法。
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, ¤tSender);
            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, ¤tSender, 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, ¤tSender, 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);
}
这个方法需要做很多工作,包括检查当前的事件是需要立即处理还是先放进事件序列中,并分别调用不同的activate方法,而后继续处理ConnectionList中的下一个事件。如果事件需要立即处理,首先从事件中获得将要调用的方法的ID,从而调用接收方的qt_metacall。过程如下:
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);
从中我们可以明白信号和槽的内部机制:当调用connect函数时,信号和槽的签名转换成ID,然后储存在Connection类中。每次一个信号发出后,与此信号ID相关的事件即被确定,同时对应的槽被调用。
至于动态调用(dynamic invokes),主要通过QMetaObject类提供的invokeMethod方法实现。这个方法不同于信号和槽,它需要从它的返回类型、名字和参数类型构造一个签名,然后查找元数据来得到其ID,最后调用对象的qt_metacall方法。
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 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;
}
方法的ID由indexOfMethod返回。如果方法的签名不能创建,invokeMethod返回false。

(二)  逆向
逆向时我们需要使用Qt提供的元数据。先看看元数据表:
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
};
这个表不仅仅告诉了我们方法的数量,同时也给出了方法的偏移。下面是此结构的C++头:
struct QMetaObjectPrivate
{
    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
};
我们已经知道了方法的数量和偏移,接下来就是分析方法本身的数据。这个数据可以分为五个整数,其意义分别是:签名、参数、类型、标签、特征。签名(signature)段是一个字符串数据中的偏移,对应valueChanged(int)方法的部分声明(不包含返回类型)。参数(parameters)段是对应参数名的偏移。我们例子中的名字就是'newValue'。名字用逗号分开,所以,如果我们的槽有两个参数,那么会显示为'newValue1,newValue2'。类型(type)段对应方法的返回类型。如果像我们例子中一样为空字符串,那么可以确定方法是void类型。标签(tag)段暂时不用(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.)。最后一个段特征(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
};
关于方法的知识就这些,下面我们研究枚举和属性。我们先在代码中加入这两类代码:
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;
};
这时moc会生成:
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"
};
同样,我们也可以得到属性和枚举的数量和偏移。对属性而言,包含3个整数:名字(name)、类型(type)和特征(flags)。类型对应的是属性的类型,我们例子中就是Priority。特征值可分为:
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
};
对于枚举,有名字(name)、特征(flags)、数量(count)和数据(data)。特征段没有使用。数量段表示枚举中条目的数量。数据段是一个元数据表的偏移,指向枚举中的条目。每个条目由两个整数表示:key和value。Key指向当前条目的名字,value是此条目的实际值。
为了获得二进制文件的元数据信息,作者特意用Python写了一个IDA脚本,可以从一个Q_OBJECT类中提取出方法、属性和枚举。代码如下:
# ---------------------------------------------
# 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)
DisplayMetaData函数接收两个参数:元数据表地址和元数据字符串地址。这是因为有时类的结构要在运行时才能确定,就像下面的VC++例子:
.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
但有时候,类结构可以静态确定:
.data:00406000  dd 0                    ; SuperData
.data:00406004  dd offset MetaStrings   ; "Counter"
.data:00406008  dd offset MetaTable
这时,脚本可以直接在SuperData地址上执行,这是QMetaObject中的第一个d结构。Counter类执行脚本后的输出是:
-------------------------------------------------
--- 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,

};
-------------------------------------------------
是不是很爽?方法的签名甚至包括了参数的名字(当可用时)。
(作者还测试了QWidget类,这里略去数百字)……
如何找到类的元数据呢?让我们看Counter类的结构:
.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
最后一个汇编指令设置了虚函数表指针。Q_OBJECT类的虚函数表类似于:
.rdata:00402158 off_402158 dd offset metaObject
.rdata:0040215C            dd offset qt_metacast
.rdata:00402160            dd offset qt_metacall
metaObject方法可以获得元数据表:
.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对应QMetaObject类的结构,我们需要它来执行脚本。
获得类的元数据以后,下一步就是将方法(属性)的名字和实际反汇编代码联系起来。脚本输出了每个方法和属性的索引。要想从索引得到方法的地址,必须考虑moc生成的qt_metacall方法:
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;
}
这个方法可以解析方法名和属性get/set方法名。
qt_metacall的地址可以从虚函数表获得。在分析此函数代码前,我们先看这个枚举:
enum Call {
    InvokeMetaMethod,    // 0
    ReadProperty,    // 1
    WriteProperty,    // 2
    ResetProperty,    // 3
    QueryPropertyDesignable,  // 4
    QueryPropertyScriptable,  // 5
    QueryPropertyStored,  // 6
    QueryPropertyEditable,  // 7
    QueryPropertyUser    // 8
};
然后看反汇编:
.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
假如esi不等于0,那么就是一个InvokeMetaMethod。考虑"_id == 0"的情况:
.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
由此我们很快就可以确定sub_401490就是valueChanged信号:
.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
接下来就无需解释了。同样的方法也可用来解决属性的get/set。对于一个复杂文件,可以结合前面的元数据脚本,编写一个解析器自动解析switch块中的函数。但是要注意的是:switch块是平台和编译器相关的,因此脚本需要频繁调整。此外,一些小的方法可能被内联(inline)进qt_metacall方法中。

转载于:https://www.cnblogs.com/WALKER17/p/6583036.html

你可能感兴趣的:(c/c++,ui,python)