From c792e02da86c50bffb5282ae6508b197ff3c5b4f Mon Sep 17 00:00:00 2001 From: mrbesen Date: Sun, 8 Jan 2023 15:08:07 +0100 Subject: [PATCH] initial --- .gitignore | 6 ++ include/qtdebugger.h | 34 ++++++++++ qtdebugger.pro | 38 +++++++++++ src/injector.cpp | 32 +++++++++ src/qtdebugger.cpp | 153 +++++++++++++++++++++++++++++++++++++++++++ ui/debugwindow.ui | 100 ++++++++++++++++++++++++++++ 6 files changed, 363 insertions(+) create mode 100644 .gitignore create mode 100644 include/qtdebugger.h create mode 100644 qtdebugger.pro create mode 100644 src/injector.cpp create mode 100644 src/qtdebugger.cpp create mode 100644 ui/debugwindow.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6608b91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.so* +ui/*.h +build/ +Makefile +.qmake.stash +.vscode/settings.json diff --git a/include/qtdebugger.h b/include/qtdebugger.h new file mode 100644 index 0000000..d72f3fe --- /dev/null +++ b/include/qtdebugger.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +#include + +class QMainWindow; +class QTreeWidgetItem; + +namespace Ui { + class DebugWindow; +} + + +class QtDebugger : public QObject { + Q_OBJECT + +public: + explicit QtDebugger(QObject* parent = nullptr); + ~QtDebugger(); + +private slots: + void refresh(); + void currentWindowChanged( int row ); + void currentObjectChanged( QTreeWidgetItem* new_ ); + +protected: + QMainWindow* debugWindow = nullptr; + +private: + Ui::DebugWindow* ui; +}; diff --git a/qtdebugger.pro b/qtdebugger.pro new file mode 100644 index 0000000..d41c0ff --- /dev/null +++ b/qtdebugger.pro @@ -0,0 +1,38 @@ +TEMPLATE = lib + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++17 + +# debugging + +LIBS += -ldl +CONFIG += debug + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +MOC_DIR = build/generated/ +UI_DIR = ui/ +OBJECTS_DIR = build/ +INCLUDEPATH += $$PWD/include/ + +SOURCES += \ + src/injector.cpp \ + src/qtdebugger.cpp + +HEADERS += \ + include/qtdebugger.h + +FORMS += \ + ui/debugwindow.ui diff --git a/src/injector.cpp b/src/injector.cpp new file mode 100644 index 0000000..fa87c86 --- /dev/null +++ b/src/injector.cpp @@ -0,0 +1,32 @@ + +// this file injects the debugger into the QApplication + +#include "qtdebugger.h" + +#include + +#include + +#include + +class QApplication; + +int QApplication::exec() { + // init debugger + QtDebugger debugger; + + // get real exec call + static int (*real_exec)() = (int (*)())dlsym(RTLD_NEXT, "_ZN12QApplication4execEv"); + if ( !real_exec ) { + // failed to init real exec + return -1; + } + + // call real exec call + int returnVal = real_exec(); + + // end debugger + + return returnVal; +} + diff --git a/src/qtdebugger.cpp b/src/qtdebugger.cpp new file mode 100644 index 0000000..3b0eaca --- /dev/null +++ b/src/qtdebugger.cpp @@ -0,0 +1,153 @@ +#include "qtdebugger.h" +#include "ui_debugwindow.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +QtDebugger::QtDebugger(QObject* parent) : QObject(parent), debugWindow( new QMainWindow() ), ui( new Ui::DebugWindow() ) { + this->ui->setupUi( debugWindow ); + debugWindow->show(); + + QList sizes; + sizes << 40 << 70 << 150 << 490; + this->ui->splitter->setSizes( sizes ); + + QObject::connect( this->ui->refreshButton, &QPushButton::clicked, this, &QtDebugger::refresh ); + QObject::connect( this->ui->windowList, &QListWidget::currentRowChanged, this, &QtDebugger::currentWindowChanged ); + QObject::connect( this->ui->objectTree, &QTreeWidget::currentItemChanged, this, &QtDebugger::currentObjectChanged ); + + this->refresh(); +} + +QtDebugger::~QtDebugger() { + delete this->debugWindow; + delete this->ui; +} + +void QtDebugger::refresh() { + this->ui->windowList->clear(); + this->ui->objectTree->clear(); + this->ui->propertiesTree->clear(); + + // get windows + QWindowList windows = qApp->allWindows(); + for( const QWindow* win : windows ) { + QListWidgetItem* item = new QListWidgetItem( win->title() ); + if ( win->winId() == debugWindow->winId() ) { + // not selectable or anything + item->setFlags( item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled) ); + } + item->setData(Qt::UserRole, win->winId()); + this->ui->windowList->addItem( item ); + } + + this->currentWindowChanged( 0 ); +} + +void QtDebugger::currentWindowChanged( int row ) { + this->ui->objectTree->clear(); + this->ui->propertiesTree->clear(); + + if ( row == -1) { + return; + } + + // find the window id + QListWidgetItem* item = this->ui->windowList->item( row ); + if( !item ) { + return; + } + QVariant data = item->data(Qt::UserRole); + bool ok = true; + WId wid = data.toULongLong( &ok ); + + // find the window + QWindowList windows = qApp->allWindows(); + auto it = std::find_if( windows.constBegin(), windows.constEnd(), [wid](const QWindow* w){ return w->winId() == wid; } ); + if( !ok || it == windows.constEnd() ) { + qWarning() << "invalid window"; + return; + } + const QWindow* win = *it; + + // update the tree + QTreeWidgetItem* root = new QTreeWidgetItem( ); + root->setText( 0, "root" ); + this->ui->objectTree->insertTopLevelItem( 0, root ); + + std::function walkTree = [&](const QObject* obj, uint32_t maxdepth, uint32_t currentDepth, QTreeWidgetItem* root) { + if ( !obj ) { + return; + } + + QTreeWidgetItem* item = new QTreeWidgetItem( ); + item->setText(0, obj->metaObject()->className() ); + item->setText(1, obj->objectName() ); + item->setData(0, Qt::UserRole, QVariant((unsigned long long) obj)); + + root->addChild( item ); + if( currentDepth < maxdepth ) { + const QObjectList& childs = obj->children(); + for( const QObject* child : childs ) { + walkTree( child, maxdepth, currentDepth+1, item ); + } + } + }; + + walkTree( qApp, 128, 0, root); + walkTree( win, 128, 0, root); +} + +void QtDebugger::currentObjectChanged( QTreeWidgetItem* new_ ) { + this->ui->propertiesTree->clear(); + + if ( !new_ ) { + qWarning() << "invalid row"; + return; + } + + // find the object + QVariant variant = new_->data(0, Qt::UserRole); + QObject* obj = (QObject*) variant.toULongLong(); + if( !obj ) { + qWarning() << "invalid Object"; + return; + } + + // update the tree + std::function readProperties + = [&] (const QMetaObject* meta) { + if (!meta) { + return; + } + + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setText(0, meta->className()); + this->ui->propertiesTree->insertTopLevelItem( 0, item ); + item->setExpanded( true ); + + // read superclass properties + readProperties(meta->superClass()); + + for( int32_t i = meta->propertyOffset(); i < meta->propertyCount(); ++i ) { + QMetaProperty prop = meta->property(i); + QString propName = prop.name(); + QVariant propVal = prop.read(obj); + // QString value = propVal.canConvert() ? propVal.value() : ""; + + QTreeWidgetItem* propItem = new QTreeWidgetItem(); + propItem->setText(0, prop.typeName()); + propItem->setText(1, propName); + propItem->setData(2, Qt::DisplayRole, propVal); + item->addChild( propItem ); + } + }; + readProperties( obj->metaObject() ); +} diff --git a/ui/debugwindow.ui b/ui/debugwindow.ui new file mode 100644 index 0000000..e59400e --- /dev/null +++ b/ui/debugwindow.ui @@ -0,0 +1,100 @@ + + + DebugWindow + + + + 0 + 0 + 511 + 812 + + + + DebugWindow + + + + Qt::Vertical + + + false + + + + + 0 + 20 + + + + + 16777215 + 40 + + + + Refresh + + + + + + 20 + + + 2 + + + 200 + + + + Type + + + + + Name + + + + + + 3 + + + 200 + + + + Type + + + + + Name + + + + + Value + + + + + + + + 0 + 0 + 511 + 24 + + + + + + + +