From 2922508ca5e8549f0b85ce9470ef390a3e9b3d77 Mon Sep 17 00:00:00 2001 From: mrbesen Date: Sat, 16 Dec 2023 13:13:28 +0100 Subject: [PATCH] dump signals --- include/qtdebugger.h | 5 ++ include/signaldumper.h | 55 ++++++++++++ qtdebugger.pro | 6 +- src/qtdebugger.cpp | 25 +++++- src/resourceexplorer.cpp | 2 +- src/signaldumper.cpp | 181 +++++++++++++++++++++++++++++++++++++++ ui/debugwindow.ui | 48 +++++++---- 7 files changed, 300 insertions(+), 22 deletions(-) create mode 100644 include/signaldumper.h create mode 100644 src/signaldumper.cpp diff --git a/include/qtdebugger.h b/include/qtdebugger.h index a222934..671d094 100644 --- a/include/qtdebugger.h +++ b/include/qtdebugger.h @@ -9,6 +9,8 @@ class QMainWindow; class QTreeWidgetItem; +class SignalDumper; + namespace Ui { class DebugWindow; } @@ -25,11 +27,14 @@ private slots: void refresh(); void currentObjectChanged( QTreeWidgetItem* new_, QTreeWidgetItem* old ); void openResourceExplorer(); + void toggleDumper(); + void toggleStyle(); protected: void resetStyleSheet(); QMainWindow* debugWindow = nullptr; + SignalDumper* dumper = nullptr; QString oldStyleSheet; bool oldStyleSheetValid = false; diff --git a/include/signaldumper.h b/include/signaldumper.h new file mode 100644 index 0000000..6703c85 --- /dev/null +++ b/include/signaldumper.h @@ -0,0 +1,55 @@ +#pragma once + + +#include +#include +#include + + +class QProcess; + +class SignalDumper : public QObject { + Q_OBJECT + +public: + static void dumpStr(QString str); + + explicit SignalDumper(QObject* parent); + virtual ~SignalDumper(); + + void startDump(); + void endDump(); + +private: + static SignalDumper* Dumper; + + void dump(QString str); + + QProcess* displayProc = nullptr; + QFile* fifo = nullptr; +}; + +// declaration of "hidden" qt features + +#define registerCall _Z32qt_register_signal_spy_callbacksP21QSignalSpyCallbackSet + +QT_BEGIN_NAMESPACE + +struct QSignalSpyCallbackSet +{ + typedef void (*BeginCallback)(QObject* caller, int signal_or_method_index, void** argv); + typedef void (*EndCallback)(QObject* caller, int signal_or_method_index); + BeginCallback signal_begin_callback, slot_begin_callback; + EndCallback signal_end_callback, slot_end_callback; +}; + +// from qcoreapplication.cpp:263 : qt_register_signal_spy_callbacks +// or qobject_p.h:82 +extern "C" void Q_CORE_EXPORT _Z32qt_register_signal_spy_callbacksP21QSignalSpyCallbackSet(const QSignalSpyCallbackSet &callback_set); + +struct QMetaObjectPrivate +{ + Q_CORE_EXPORT static QMetaMethod signal(const QMetaObject* m, int signal_index); +}; + +QT_END_NAMESPACE diff --git a/qtdebugger.pro b/qtdebugger.pro index b8122ad..1a1553f 100644 --- a/qtdebugger.pro +++ b/qtdebugger.pro @@ -30,12 +30,14 @@ INCLUDEPATH += $$PWD/include/ SOURCES += \ src/injector.cpp \ src/qtdebugger.cpp \ - src/resourceexplorer.cpp + src/resourceexplorer.cpp \ + src/signaldumper.cpp HEADERS += \ include/injector.h \ include/qtdebugger.h \ - include/resourceexplorer.h + include/resourceexplorer.h \ + include/signaldumper.h FORMS += \ ui/debugwindow.ui \ diff --git a/src/qtdebugger.cpp b/src/qtdebugger.cpp index de54b31..66096bb 100644 --- a/src/qtdebugger.cpp +++ b/src/qtdebugger.cpp @@ -12,8 +12,9 @@ #include #include "resourceexplorer.h" +#include "signaldumper.h" -QtDebugger::QtDebugger(QObject* parent) : QObject(parent), debugWindow( new QMainWindow() ), ui( new Ui::DebugWindow() ) { +QtDebugger::QtDebugger(QObject* parent) : QObject(parent), debugWindow( new QMainWindow() ), dumper( new SignalDumper(this) ), ui( new Ui::DebugWindow() ) { this->ui->setupUi( debugWindow ); debugWindow->show(); @@ -24,6 +25,8 @@ QtDebugger::QtDebugger(QObject* parent) : QObject(parent), debugWindow( new QMai QObject::connect( this->ui->refreshButton, &QPushButton::clicked, this, &QtDebugger::refresh ); QObject::connect( this->ui->objectTree, &QTreeWidget::currentItemChanged, this, &QtDebugger::currentObjectChanged ); QObject::connect( this->ui->resourceExplorerButton, &QPushButton::clicked, this, &QtDebugger::openResourceExplorer ); + QObject::connect( this->ui->snifferBox, &QCheckBox::toggled, this, &QtDebugger::toggleDumper); + QObject::connect( this->ui->updateStyleBox, &QCheckBox::toggled, this, &QtDebugger::toggleStyle); this->refresh(); } @@ -93,7 +96,7 @@ void QtDebugger::currentObjectChanged( QTreeWidgetItem* new_, QTreeWidgetItem* o } QWidget* widget = qobject_cast( obj ); - if ( widget ) { + if ( widget && ui->updateStyleBox->isChecked() ) { this->oldStyleSheet = widget->styleSheet(); this->oldStyleSheetValid = true; widget->setStyleSheet( "background-color: black;" ); @@ -135,6 +138,24 @@ void QtDebugger::openResourceExplorer() { re->show(); } +void QtDebugger::toggleDumper() { + const bool newState = ui->snifferBox->isChecked(); + if(newState) { + dumper->startDump(); + } else { + dumper->endDump(); + } +} + +void QtDebugger::toggleStyle() { + const bool newState = ui->updateStyleBox->isChecked(); + if(newState) { + + } else { + resetStyleSheet(); + } +} + void QtDebugger::resetStyleSheet() { // get currently selected item QList items = this->ui->objectTree->selectedItems(); diff --git a/src/resourceexplorer.cpp b/src/resourceexplorer.cpp index a90ce1b..45388c6 100644 --- a/src/resourceexplorer.cpp +++ b/src/resourceexplorer.cpp @@ -173,7 +173,7 @@ std::vector ResourceExplorer::readMemoryMaps() { QString line = QString::fromUtf8( maps.readLine() ); if( line.isEmpty() ) continue; - QVector parts = line.splitRef(' ', QString::SplitBehavior::SkipEmptyParts ); + QVector parts = line.splitRef(' ', Qt::SkipEmptyParts ); if ( parts.size() != 6) { continue; } diff --git a/src/signaldumper.cpp b/src/signaldumper.cpp new file mode 100644 index 0000000..76a80da --- /dev/null +++ b/src/signaldumper.cpp @@ -0,0 +1,181 @@ +#include "signaldumper.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +static const uint32_t IndentSpacesCount = 4; +static int iLevel = 0; + +SignalDumper* SignalDumper::Dumper = nullptr; + +static void signalDumperCallback(QObject* caller, int signal_index, void** argv) +{ + Q_ASSERT(caller); Q_ASSERT(argv); Q_UNUSED(argv); + const QMetaObject *mo = caller->metaObject(); + Q_ASSERT(mo); + QMetaMethod member = QMetaObjectPrivate::signal(mo, signal_index); + Q_ASSERT(member.isValid()); + + QByteArray str; + str.fill(' ', iLevel++ * IndentSpacesCount); + str += "Signal: "; + str += mo->className(); + str += '('; + + QString objname = caller->objectName(); + str += objname.toLocal8Bit(); + if (!objname.isEmpty()) + str += ' '; + str += QByteArray::number(quintptr(caller), 16); + + str += ") "; + str += member.name(); + str += " ("; + + QList args = member.parameterTypes(); + for (int i = 0; i < args.count(); ++i) { + const QByteArray &arg = args.at(i); + int typeId = QMetaType::type(args.at(i).constData()); + if (arg.endsWith('*') || arg.endsWith('&')) { + str += '('; + str += arg; + str += ')'; + if (arg.endsWith('&')) + str += '@'; + + quintptr addr = quintptr(*reinterpret_cast(argv[i + 1])); + str.append(QByteArray::number(addr, 16)); + } else if (typeId != QMetaType::UnknownType) { + Q_ASSERT(typeId != QMetaType::Void); // void parameter => metaobject is corrupt + str.append(arg) + .append('(') + .append(QVariant(typeId, argv[i + 1]).toString().toLocal8Bit()) + .append(')'); + } + str.append(", "); + } + if (str.endsWith(", ")) + str.chop(2); + str.append(')'); + + SignalDumper::dumpStr(str); +} + +static void signalDumperCallbackSlot(QObject* caller, int method_index, void** argv) +{ + Q_ASSERT(caller); Q_ASSERT(argv); Q_UNUSED(argv); + const QMetaObject *mo = caller->metaObject(); + Q_ASSERT(mo); + QMetaMethod member = mo->method(method_index); + if (!member.isValid()) + return; + + QByteArray str; + str.fill(' ', iLevel * IndentSpacesCount); + str += "Slot: "; + str += mo->className(); + str += '('; + + QString objname = caller->objectName(); + str += objname.toLocal8Bit(); + if (!objname.isEmpty()) + str += ' '; + str += QByteArray::number(quintptr(caller), 16); + + str += ") "; + str += member.methodSignature(); + + SignalDumper::dumpStr(str); +} + +static void signalDumperCallbackEndSignal(QObject* caller, int /*signal_index*/) +{ + Q_ASSERT(caller); Q_ASSERT(caller->metaObject()); + --iLevel; + Q_ASSERT(iLevel >= 0); +} + +void SignalDumper::dumpStr(QString str) { + if(Dumper) { + Dumper->dump(str); + } +} + +SignalDumper::SignalDumper(QObject* parent) : QObject(parent) {} + +SignalDumper::~SignalDumper() { + if(Dumper == this) { + endDump(); + } +} + +void SignalDumper::startDump() +{ + if(Dumper) { + return; + } + Dumper = this; + + // mkfifo + QString fifoName = "/tmp/signalLogger." + QString::number(QCoreApplication::applicationPid()); + QByteArray fifoNameArr = fifoName.toLocal8Bit(); + if(::mkfifo(fifoNameArr.data(), 0700)) { + qCritical() << "could not create fifo" << errno; + } + + // make process + displayProc = new QProcess(this); + displayProc->start("gnome-terminal", { "--", "cat", fifoName }); + + if(fifo) { + fifo->deleteLater(); + } + + fifo = new QFile(fifoName); + fifo->open(QIODevice::WriteOnly); + + iLevel = 0; + + // start listening + static QSignalSpyCallbackSet set = { signalDumperCallback, + signalDumperCallbackSlot, signalDumperCallbackEndSignal, 0 }; + registerCall(set); +} + +void SignalDumper::endDump() +{ + if(Dumper != this) { + return; + } + + // stop listening + static QSignalSpyCallbackSet nset = { 0, 0, 0 ,0 }; + registerCall(nset); + + // kill process + displayProc->terminate(); + displayProc->deleteLater(); + + // del fifo + fifo->remove(); + + Dumper = nullptr; +} + +void SignalDumper::dump(QString str) { + str += '\n'; + QByteArray arr = str.toLocal8Bit(); + fifo->write(arr); + fifo->flush(); +} diff --git a/ui/debugwindow.ui b/ui/debugwindow.ui index 71db8d6..fd338f9 100644 --- a/ui/debugwindow.ui +++ b/ui/debugwindow.ui @@ -21,8 +21,34 @@ false - - + + + + + Change Style of Selected Widget + + + + + + + + 0 + 20 + + + + + 16777215 + 40 + + + + Resource Explorer + + + + @@ -41,22 +67,10 @@ - - - - - 0 - 20 - - - - - 16777215 - 40 - - + + - Resource Explorer + Enable Signal/Slot sniffer