dump signals

This commit is contained in:
mrbesen 2023-12-16 13:13:28 +01:00
parent 329f6561f2
commit 2922508ca5
Signed by: MrBesen
GPG Key ID: 596B2350DCD67504
7 changed files with 300 additions and 22 deletions

View File

@ -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;

55
include/signaldumper.h Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#include <QObject>
#include <QString>
#include <QFile>
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

View File

@ -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 \

View File

@ -12,8 +12,9 @@
#include <set>
#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<QWidget*>( 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<QTreeWidgetItem*> items = this->ui->objectTree->selectedItems();

View File

@ -173,7 +173,7 @@ std::vector<ResourceExplorer::MemoryMap> ResourceExplorer::readMemoryMaps() {
QString line = QString::fromUtf8( maps.readLine() );
if( line.isEmpty() ) continue;
QVector<QStringRef> parts = line.splitRef(' ', QString::SplitBehavior::SkipEmptyParts );
QVector<QStringRef> parts = line.splitRef(' ', Qt::SkipEmptyParts );
if ( parts.size() != 6) {
continue;
}

181
src/signaldumper.cpp Normal file
View File

@ -0,0 +1,181 @@
#include "signaldumper.h"
#include <QList>
#include <QMetaObject>
#include <QMetaType>
#include <QMetaMethod>
#include <QObject>
#include <QVariant>
#include <QCoreApplication>
#include <QProcess>
#include <QDebug>
#include <sys/types.h>
#include <sys/stat.h>
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<QByteArray> 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<void **>(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();
}

View File

@ -21,8 +21,34 @@
<bool>false</bool>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="updateStyleBox">
<property name="text">
<string>Change Style of Selected Widget</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="resourceExplorerButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<property name="text">
<string>Resource Explorer</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="refreshButton">
<property name="minimumSize">
<size>
@ -41,22 +67,10 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resourceExplorerButton">
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>40</height>
</size>
</property>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="snifferBox">
<property name="text">
<string>Resource Explorer</string>
<string>Enable Signal/Slot sniffer</string>
</property>
</widget>
</item>