From a3e140bf4de93639886d4c1388779d70b60ba3f1 Mon Sep 17 00:00:00 2001 From: yossizap Date: Tue, 22 Oct 2019 15:51:56 +0000 Subject: [PATCH] ThreadsWidget and r2 debugging fixes (#1846) * Added wrappers for dpt and dpt= * Added ThreadsWidget used for thread monitoring during debugging * Updated r2 for dpt, dpt= and drrj fixes * Convert r2's pid status letters to more informative strings * Replaced cmdj with cmd and ensure the thread's data is synced * Moved viewThreas to threadsWidget.ui, switched from onDoubleClick to Activated and added a column cleanup * Add old row cleanup to BacktraceWidget * Clean up * Added sorting and filters * Modified ThreadsWidget for Qt5.3 compatibility --- src/Cutter.pro | 3 + src/core/Cutter.cpp | 19 ++++ src/core/Cutter.h | 7 ++ src/core/MainWindow.cpp | 6 +- src/core/MainWindow.h | 1 + src/core/MainWindow.ui | 9 ++ src/widgets/BacktraceWidget.cpp | 6 ++ src/widgets/ThreadsWidget.cpp | 168 ++++++++++++++++++++++++++++++++ src/widgets/ThreadsWidget.h | 49 ++++++++++ src/widgets/ThreadsWidget.ui | 72 ++++++++++++++ 10 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 src/widgets/ThreadsWidget.cpp create mode 100644 src/widgets/ThreadsWidget.h create mode 100644 src/widgets/ThreadsWidget.ui diff --git a/src/Cutter.pro b/src/Cutter.pro index 3dfb6425..1940f1f5 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -329,6 +329,7 @@ SOURCES += \ dialogs/AsyncTaskDialog.cpp \ widgets/StackWidget.cpp \ widgets/RegistersWidget.cpp \ + widgets/ThreadsWidget.cpp \ widgets/BacktraceWidget.cpp \ dialogs/OpenFileDialog.cpp \ common/CommandTask.cpp \ @@ -459,6 +460,7 @@ HEADERS += \ dialogs/AsyncTaskDialog.h \ widgets/StackWidget.h \ widgets/RegistersWidget.h \ + widgets/ThreadsWidget.h \ widgets/BacktraceWidget.h \ dialogs/OpenFileDialog.h \ common/StringsTask.h \ @@ -552,6 +554,7 @@ FORMS += \ dialogs/AsyncTaskDialog.ui \ widgets/StackWidget.ui \ widgets/RegistersWidget.ui \ + widgets/ThreadsWidget.ui \ widgets/BacktraceWidget.ui \ dialogs/OpenFileDialog.ui \ dialogs/preferences/DebugOptionsWidget.ui \ diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 3a68c01c..3beb001d 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -1028,6 +1028,16 @@ QJsonDocument CutterCore::getStack(int size) return cmdj("pxrj " + QString::number(size) + " @ r:SP"); } +QJsonDocument CutterCore::getProcessThreads(int pid) +{ + if (-1 == pid) { + // Return threads list of the currently debugged PID + return cmdj("dptj"); + } else { + return cmdj("dptj " + QString::number(pid)); + } +} + QJsonDocument CutterCore::getRegisterValues() { return cmdj("drj"); @@ -1118,6 +1128,15 @@ void CutterCore::setRegister(QString regName, QString regValue) emit refreshCodeViews(); } +void CutterCore::setCurrentDebugThread(int tid) +{ + cmd("dpt=" + QString::number(tid)); + emit registersChanged(); + emit refreshCodeViews(); + emit stackChanged(); + syncAndSeekProgramCounter(); +} + void CutterCore::startDebug() { if (!currentlyDebugging) { diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 72c2766a..846631b2 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -221,7 +221,14 @@ public: QString getRegisterName(QString registerRole); RVA getProgramCounterValue(); void setRegister(QString regName, QString regValue); + void setCurrentDebugThread(int tid); QJsonDocument getStack(int size = 0x100); + /** + * @brief Get a list of a given process's threads + * @param pid The pid of the process, -1 for the currently debugged process + * @return JSON object result of dptj + */ + QJsonDocument getProcessThreads(int pid); QJsonDocument getBacktrace(); void startDebug(); void startEmulation(); diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index e7dfb4f1..3b7e6755 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -61,6 +61,7 @@ #include "widgets/RegisterRefsWidget.h" #include "widgets/DisassemblyWidget.h" #include "widgets/StackWidget.h" +#include "widgets/ThreadsWidget.h" #include "widgets/RegistersWidget.h" #include "widgets/BacktraceWidget.h" #include "widgets/HexdumpWidget.h" @@ -315,6 +316,7 @@ void MainWindow::initDocks() stringsDock = new StringsWidget(this, ui->actionStrings); flagsDock = new FlagsWidget(this, ui->actionFlags); stackDock = new StackWidget(this, ui->actionStack); + threadsDock = new ThreadsWidget(this, ui->actionThreads); backtraceDock = new BacktraceWidget(this, ui->actionBacktrace); registersDock = new RegistersWidget(this, ui->actionRegisters); memoryMapDock = new MemoryMapWidget(this, ui->actionMemoryMap); @@ -833,10 +835,11 @@ void MainWindow::restoreDocks() tabifyDockWidget(sectionsDock, commentsDock); - // Add Stack, Registers and Backtrace vertically stacked + // Add Stack, Registers, Threads and Backtrace vertically stacked addDockWidget(Qt::TopDockWidgetArea, stackDock); splitDockWidget(stackDock, registersDock, Qt::Vertical); tabifyDockWidget(stackDock, backtraceDock); + tabifyDockWidget(backtraceDock, threadsDock); updateDockActionsChecked(); } @@ -1166,6 +1169,7 @@ void MainWindow::showDebugDocks() stackDock, registersDock, backtraceDock, + threadsDock, memoryMapDock, breakpointDock }; diff --git a/src/core/MainWindow.h b/src/core/MainWindow.h index ab5a11bb..29111b3a 100644 --- a/src/core/MainWindow.h +++ b/src/core/MainWindow.h @@ -249,6 +249,7 @@ private: QDockWidget *asmDock = nullptr; QDockWidget *calcDock = nullptr; QDockWidget *stackDock = nullptr; + QDockWidget *threadsDock = nullptr; QDockWidget *registersDock = nullptr; QDockWidget *backtraceDock = nullptr; QDockWidget *memoryMapDock = nullptr; diff --git a/src/core/MainWindow.ui b/src/core/MainWindow.ui index f55ad433..bf48b6e1 100644 --- a/src/core/MainWindow.ui +++ b/src/core/MainWindow.ui @@ -152,6 +152,7 @@ + @@ -932,6 +933,14 @@ Backtrace + + + true + + + Threads + + true diff --git a/src/widgets/BacktraceWidget.cpp b/src/widgets/BacktraceWidget.cpp index c81069af..685e7649 100644 --- a/src/widgets/BacktraceWidget.cpp +++ b/src/widgets/BacktraceWidget.cpp @@ -67,6 +67,12 @@ void BacktraceWidget::setBacktraceGrid() modelBacktrace->setItem(i, 4, rowFrameSize); i++; } + + // Remove irrelevant old rows + if (modelBacktrace->rowCount() > i) { + modelBacktrace->removeRows(i, modelBacktrace->rowCount() - i); + } + viewBacktrace->setModel(modelBacktrace); viewBacktrace->resizeColumnsToContents();; } diff --git a/src/widgets/ThreadsWidget.cpp b/src/widgets/ThreadsWidget.cpp new file mode 100644 index 00000000..6823b67d --- /dev/null +++ b/src/widgets/ThreadsWidget.cpp @@ -0,0 +1,168 @@ +#include +#include "ThreadsWidget.h" +#include "ui_ThreadsWidget.h" +#include "common/JsonModel.h" +#include "QuickFilterView.h" +#include + +#include "core/MainWindow.h" + +#define DEBUGGED_PID (-1) + +enum ColumnIndex { + COLUMN_CURRENT = 0, + COLUMN_PID, + COLUMN_STATUS, + COLUMN_PATH, +}; + +ThreadsWidget::ThreadsWidget(MainWindow *main, QAction *action) : + CutterDockWidget(main, action), + ui(new Ui::ThreadsWidget) +{ + ui->setupUi(this); + + // Setup threads model + modelThreads = new QStandardItemModel(1, 4, this); + modelThreads->setHorizontalHeaderItem(COLUMN_CURRENT, new QStandardItem(tr("Current"))); + modelThreads->setHorizontalHeaderItem(COLUMN_PID, new QStandardItem(tr("PID"))); + modelThreads->setHorizontalHeaderItem(COLUMN_STATUS, new QStandardItem(tr("Status"))); + modelThreads->setHorizontalHeaderItem(COLUMN_PATH, new QStandardItem(tr("Path"))); + ui->viewThreads->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); + ui->viewThreads->setFont(Config()->getFont()); + + modelFilter = new ThreadsFilterModel(this); + modelFilter->setSourceModel(modelThreads); + ui->viewThreads->setModel(modelFilter); + + // CTRL+F switches to the filter view and opens it in case it's hidden + QShortcut *searchShortcut = new QShortcut(QKeySequence::Find, this); + connect(searchShortcut, &QShortcut::activated, ui->quickFilterView, &QuickFilterView::showFilter); + searchShortcut->setContext(Qt::WidgetWithChildrenShortcut); + + // ESC switches back to the thread table and clears the buffer + QShortcut *clearShortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this); + connect(clearShortcut, &QShortcut::activated, this, [this]() { + ui->quickFilterView->clearFilter(); + ui->viewThreads->setFocus(); + }); + clearShortcut->setContext(Qt::WidgetWithChildrenShortcut); + + refreshDeferrer = createRefreshDeferrer([this]() { + updateContents(); + }); + + connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, modelFilter, + &ThreadsFilterModel::setFilterWildcard); + connect(Core(), &CutterCore::refreshAll, this, &ThreadsWidget::updateContents); + connect(Core(), &CutterCore::seekChanged, this, &ThreadsWidget::updateContents); + connect(Config(), &Configuration::fontsUpdated, this, &ThreadsWidget::fontsUpdatedSlot); + connect(ui->viewThreads, &QTableView::activated, this, &ThreadsWidget::onActivated); +} + +ThreadsWidget::~ThreadsWidget() {} + +void ThreadsWidget::updateContents() +{ + if (!refreshDeferrer->attemptRefresh(nullptr)) { + return; + } + setThreadsGrid(); +} + +QString ThreadsWidget::translateStatus(QString status) +{ + switch (status.toStdString().c_str()[0]) { + case R_DBG_PROC_STOP: + return "Stopped"; + case R_DBG_PROC_RUN: + return "Running"; + case R_DBG_PROC_SLEEP: + return "Sleeping"; + case R_DBG_PROC_ZOMBIE: + return "Zombie"; + case R_DBG_PROC_DEAD: + return "Dead"; + case R_DBG_PROC_RAISED: + return "Raised event"; + default: + return "Unknown status"; + } +} + +void ThreadsWidget::setThreadsGrid() +{ + QJsonArray threadsValues = Core()->getProcessThreads(DEBUGGED_PID).array(); + int i = 0; + for (const QJsonValue &value : threadsValues) { + QJsonObject threadsItem = value.toObject(); + int pid = threadsItem["pid"].toVariant().toInt(); + QString status = translateStatus(threadsItem["status"].toString()); + QString path = threadsItem["path"].toString(); + bool current = threadsItem["current"].toBool(); + + QStandardItem *rowCurrent = new QStandardItem(QString(current ? "True" : "False")); + QStandardItem *rowPid = new QStandardItem(QString::number(pid)); + QStandardItem *rowStatus = new QStandardItem(status); + QStandardItem *rowPath = new QStandardItem(path); + + modelThreads->setItem(i, COLUMN_CURRENT, rowCurrent); + modelThreads->setItem(i, COLUMN_PID, rowPid); + modelThreads->setItem(i, COLUMN_STATUS, rowStatus); + modelThreads->setItem(i, COLUMN_PATH, rowPath); + i++; + } + + // Remove irrelevant old rows + if (modelThreads->rowCount() > i) { + modelThreads->removeRows(i, modelThreads->rowCount() - i); + } + + modelFilter->setSourceModel(modelThreads); + ui->viewThreads->resizeColumnsToContents();; +} + +void ThreadsWidget::fontsUpdatedSlot() +{ + ui->viewThreads->setFont(Config()->getFont()); +} + +void ThreadsWidget::onActivated(const QModelIndex &index) +{ + if (!index.isValid()) + return; + + int tid = modelFilter->data(index.sibling(index.row(), COLUMN_PID)).toInt(); + + // Verify that the selected tid is still in the threads list since dpt= will + // attach to any given id. If it isn't found simply update the UI. + QJsonArray threadsValues = Core()->getProcessThreads(DEBUGGED_PID).array(); + for (QJsonValue value : threadsValues) { + if (tid == value.toObject()["pid"].toInt()) { + Core()->setCurrentDebugThread(tid); + break; + } + } + + updateContents(); +} + +ThreadsFilterModel::ThreadsFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSortCaseSensitivity(Qt::CaseInsensitive); +} + +bool ThreadsFilterModel::filterAcceptsRow(int row, const QModelIndex &parent) const +{ + // All columns are checked for a match + for (int i = COLUMN_CURRENT; i <= COLUMN_PATH; ++i) { + QModelIndex index = sourceModel()->index(row, i, parent); + if (sourceModel()->data(index).toString().contains(filterRegExp())) { + return true; + } + } + + return false; +} diff --git a/src/widgets/ThreadsWidget.h b/src/widgets/ThreadsWidget.h new file mode 100644 index 00000000..7c264a3d --- /dev/null +++ b/src/widgets/ThreadsWidget.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "core/Cutter.h" +#include "CutterDockWidget.h" + +class MainWindow; + +namespace Ui { +class ThreadsWidget; +} + +class ThreadsFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + ThreadsFilterModel(QObject *parent = nullptr); + +protected: + bool filterAcceptsRow(int row, const QModelIndex &parent) const override; +}; + +class ThreadsWidget : public CutterDockWidget +{ + Q_OBJECT + +public: + explicit ThreadsWidget(MainWindow *main, QAction *action = nullptr); + ~ThreadsWidget(); + +private slots: + void updateContents(); + void setThreadsGrid(); + void fontsUpdatedSlot(); + void onActivated(const QModelIndex &index); + +private: + QString translateStatus(QString status); + std::unique_ptr ui; + QStandardItemModel *modelThreads; + ThreadsFilterModel *modelFilter; + RefreshDeferrer *refreshDeferrer; +}; diff --git a/src/widgets/ThreadsWidget.ui b/src/widgets/ThreadsWidget.ui new file mode 100644 index 00000000..39b989b6 --- /dev/null +++ b/src/widgets/ThreadsWidget.ui @@ -0,0 +1,72 @@ + + + ThreadsWidget + + + + 0 + 0 + 463 + 300 + + + + Threads + + + + + 10 + + + 0 + + + 0 + + + 0 + + + + + true + + + false + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ScrollPerPixel + + + + + + + + 0 + 0 + + + + + + + + + + QuickFilterView + QWidget +
widgets/QuickFilterView.h
+ 1 +
+
+ + +