diff --git a/radare2 b/radare2 index cb60b5e8..9fca7b1f 160000 --- a/radare2 +++ b/radare2 @@ -1 +1 @@ -Subproject commit cb60b5e8fd8dc76d847d9935d2ded4df2e05b63e +Subproject commit 9fca7b1f582a744426e9f0bf650ed3206804510c diff --git a/src/Cutter.pro b/src/Cutter.pro index 6ac0329f..7772f4e5 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -332,6 +332,7 @@ SOURCES += \ widgets/StackWidget.cpp \ widgets/RegistersWidget.cpp \ widgets/ThreadsWidget.cpp \ + widgets/ProcessesWidget.cpp \ widgets/BacktraceWidget.cpp \ dialogs/OpenFileDialog.cpp \ common/CommandTask.cpp \ @@ -466,6 +467,7 @@ HEADERS += \ widgets/StackWidget.h \ widgets/RegistersWidget.h \ widgets/ThreadsWidget.h \ + widgets/ProcessesWidget.h \ widgets/BacktraceWidget.h \ dialogs/OpenFileDialog.h \ common/StringsTask.h \ @@ -563,6 +565,7 @@ FORMS += \ widgets/StackWidget.ui \ widgets/RegistersWidget.ui \ widgets/ThreadsWidget.ui \ + widgets/ProcessesWidget.ui \ widgets/BacktraceWidget.ui \ dialogs/OpenFileDialog.ui \ dialogs/preferences/DebugOptionsWidget.ui \ diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 60a3c241..f0db880c 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -1098,6 +1098,16 @@ QJsonDocument CutterCore::getProcessThreads(int pid) } } +QJsonDocument CutterCore::getChildProcesses(int pid) +{ + // Return the currently debugged process and it's children + if (-1 == pid) { + return cmdj("dpj"); + } + // Return the given pid and it's child processes + return cmdj("dpj " + QString::number(pid)); +} + QJsonDocument CutterCore::getRegisterValues() { return cmdj("drj"); @@ -1199,6 +1209,30 @@ void CutterCore::setCurrentDebugThread(int tid) emit refreshCodeViews(); emit stackChanged(); syncAndSeekProgramCounter(); + emit switchedThread(); + emit debugTaskStateChanged(); + }); + } +} + +void CutterCore::setCurrentDebugProcess(int pid) +{ + if (!currentlyDebugging) { + return; + } + + emit debugTaskStateChanged(); + asyncCmd("dp=" + QString::number(pid), debugTask); + if (!debugTask.isNull()) { + emit debugTaskStateChanged(); + connect(debugTask.data(), &R2Task::finished, this, [this] () { + debugTask.clear(); + emit registersChanged(); + emit refreshCodeViews(); + emit stackChanged(); + emit flagsChanged(); + syncAndSeekProgramCounter(); + emit switchedProcess(); emit debugTaskStateChanged(); }); } diff --git a/src/core/Cutter.h b/src/core/Cutter.h index c00fc256..b0168919 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -259,6 +259,10 @@ public: RVA getProgramCounterValue(); void setRegister(QString regName, QString regValue); void setCurrentDebugThread(int tid); + /** + * @brief Attach to a given pid from a debug session + */ + void setCurrentDebugProcess(int pid); QJsonDocument getStack(int size = 0x100); /** * @brief Get a list of a given process's threads @@ -266,6 +270,12 @@ public: * @return JSON object result of dptj */ QJsonDocument getProcessThreads(int pid); + /** + * @brief Get a list of a given process's child processes + * @param pid The pid of the process, -1 for the currently debugged process + * @return JSON object result of dptj + */ + QJsonDocument getChildProcesses(int pid); QJsonDocument getBacktrace(); void startDebug(); void startEmulation(); @@ -480,6 +490,9 @@ signals: void refreshCodeViews(); void stackChanged(); + void switchedThread(); + void switchedProcess(); + void classNew(const QString &cls); void classDeleted(const QString &cls); void classRenamed(const QString &oldName, const QString &newName); diff --git a/src/core/MainWindow.cpp b/src/core/MainWindow.cpp index f92b8c5d..736670fe 100644 --- a/src/core/MainWindow.cpp +++ b/src/core/MainWindow.cpp @@ -62,6 +62,7 @@ #include "widgets/DisassemblyWidget.h" #include "widgets/StackWidget.h" #include "widgets/ThreadsWidget.h" +#include "widgets/ProcessesWidget.h" #include "widgets/RegistersWidget.h" #include "widgets/BacktraceWidget.h" #include "widgets/HexdumpWidget.h" @@ -317,6 +318,7 @@ void MainWindow::initDocks() flagsDock = new FlagsWidget(this, ui->actionFlags); stackDock = new StackWidget(this, ui->actionStack); threadsDock = new ThreadsWidget(this, ui->actionThreads); + processesDock = new ProcessesWidget(this, ui->actionProcesses); backtraceDock = new BacktraceWidget(this, ui->actionBacktrace); registersDock = new RegistersWidget(this, ui->actionRegisters); memoryMapDock = new MemoryMapWidget(this, ui->actionMemoryMap); @@ -840,6 +842,7 @@ void MainWindow::restoreDocks() splitDockWidget(stackDock, registersDock, Qt::Vertical); tabifyDockWidget(stackDock, backtraceDock); tabifyDockWidget(backtraceDock, threadsDock); + tabifyDockWidget(threadsDock, processesDock); updateDockActionsChecked(); } diff --git a/src/core/MainWindow.h b/src/core/MainWindow.h index 29111b3a..f6350ffb 100644 --- a/src/core/MainWindow.h +++ b/src/core/MainWindow.h @@ -250,6 +250,7 @@ private: QDockWidget *calcDock = nullptr; QDockWidget *stackDock = nullptr; QDockWidget *threadsDock = nullptr; + QDockWidget *processesDock = nullptr; QDockWidget *registersDock = nullptr; QDockWidget *backtraceDock = nullptr; QDockWidget *memoryMapDock = nullptr; diff --git a/src/core/MainWindow.ui b/src/core/MainWindow.ui index bf48b6e1..9b181c0c 100644 --- a/src/core/MainWindow.ui +++ b/src/core/MainWindow.ui @@ -153,6 +153,7 @@ + @@ -941,6 +942,14 @@ Threads + + + true + + + Processes + + true diff --git a/src/widgets/ProcessesWidget.cpp b/src/widgets/ProcessesWidget.cpp new file mode 100644 index 00000000..b4e4b2d4 --- /dev/null +++ b/src/widgets/ProcessesWidget.cpp @@ -0,0 +1,202 @@ +#include +#include "ProcessesWidget.h" +#include "ui_ProcessesWidget.h" +#include "common/JsonModel.h" +#include "QuickFilterView.h" +#include + +#include "core/MainWindow.h" + +#define DEBUGGED_PID (-1) + +enum ColumnIndex { + COLUMN_PID = 0, + COLUMN_UID, + COLUMN_STATUS, + COLUMN_PATH, +}; + +ProcessesWidget::ProcessesWidget(MainWindow *main, QAction *action) : + CutterDockWidget(main, action), + ui(new Ui::ProcessesWidget) +{ + ui->setupUi(this); + + // Setup processes model + modelProcesses = new QStandardItemModel(1, 4, this); + modelProcesses->setHorizontalHeaderItem(COLUMN_PID, new QStandardItem(tr("PID"))); + modelProcesses->setHorizontalHeaderItem(COLUMN_UID, new QStandardItem(tr("UID"))); + modelProcesses->setHorizontalHeaderItem(COLUMN_STATUS, new QStandardItem(tr("Status"))); + modelProcesses->setHorizontalHeaderItem(COLUMN_PATH, new QStandardItem(tr("Path"))); + ui->viewProcesses->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); + ui->viewProcesses->verticalHeader()->setVisible(false); + ui->viewProcesses->setFont(Config()->getFont()); + + modelFilter = new ProcessesFilterModel(this); + modelFilter->setSourceModel(modelProcesses); + ui->viewProcesses->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 processes table and clears the buffer + QShortcut *clearShortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this); + connect(clearShortcut, &QShortcut::activated, this, [this]() { + ui->quickFilterView->clearFilter(); + ui->viewProcesses->setFocus(); + }); + clearShortcut->setContext(Qt::WidgetWithChildrenShortcut); + + refreshDeferrer = createRefreshDeferrer([this]() { + updateContents(); + }); + + connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, modelFilter, + &ProcessesFilterModel::setFilterWildcard); + connect(Core(), &CutterCore::refreshAll, this, &ProcessesWidget::updateContents); + connect(Core(), &CutterCore::seekChanged, this, &ProcessesWidget::updateContents); + connect(Core(), &CutterCore::debugTaskStateChanged, this, &ProcessesWidget::updateContents); + // Seek doesn't necessarily change when switching processes + connect(Core(), &CutterCore::switchedProcess, this, &ProcessesWidget::updateContents); + connect(Config(), &Configuration::fontsUpdated, this, &ProcessesWidget::fontsUpdatedSlot); + connect(ui->viewProcesses, &QTableView::activated, this, &ProcessesWidget::onActivated); +} + +ProcessesWidget::~ProcessesWidget() {} + +void ProcessesWidget::updateContents() +{ + if (!refreshDeferrer->attemptRefresh(nullptr)) { + return; + } + + if (Core()->currentlyDebugging) { + setProcessesGrid(); + } else { + // Remove rows from the previous debugging session + modelProcesses->removeRows(0, modelProcesses->rowCount()); + } + + if (Core()->isDebugTaskInProgress() || !Core()->currentlyDebugging) { + ui->viewProcesses->setDisabled(true); + } else { + ui->viewProcesses->setDisabled(false); + } +} + +QString ProcessesWidget::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 ProcessesWidget::setProcessesGrid() +{ + QJsonArray processesValues = Core()->getChildProcesses(DEBUGGED_PID).array(); + int i = 0; + QFont font; + + for (const QJsonValue &value : processesValues) { + QJsonObject processesItem = value.toObject(); + int pid = processesItem["pid"].toVariant().toInt(); + int uid = processesItem["uid"].toVariant().toInt(); + QString status = translateStatus(processesItem["status"].toString()); + QString path = processesItem["path"].toString(); + bool current = processesItem["current"].toBool(); + + // Use bold font to highlight active thread + font.setBold(current); + + QStandardItem *rowPid = new QStandardItem(QString::number(pid)); + QStandardItem *rowUid = new QStandardItem(QString::number(uid)); + QStandardItem *rowStatus = new QStandardItem(status); + QStandardItem *rowPath = new QStandardItem(path); + + rowPid->setFont(font); + rowUid->setFont(font); + rowStatus->setFont(font); + rowPath->setFont(font); + + modelProcesses->setItem(i, COLUMN_PID, rowPid); + modelProcesses->setItem(i, COLUMN_UID, rowUid); + modelProcesses->setItem(i, COLUMN_STATUS, rowStatus); + modelProcesses->setItem(i, COLUMN_PATH, rowPath); + i++; + } + + // Remove irrelevant old rows + if (modelProcesses->rowCount() > i) { + modelProcesses->removeRows(i, modelProcesses->rowCount() - i); + } + + modelFilter->setSourceModel(modelProcesses); + ui->viewProcesses->resizeColumnsToContents();; +} + +void ProcessesWidget::fontsUpdatedSlot() +{ + ui->viewProcesses->setFont(Config()->getFont()); +} + +void ProcessesWidget::onActivated(const QModelIndex &index) +{ + if (!index.isValid()) + return; + + int pid = modelFilter->data(index.sibling(index.row(), COLUMN_PID)).toInt(); + + // Verify that the selected pid is still in the processes list since dp= will + // attach to any given id. If it isn't found simply update the UI. + QJsonArray processesValues = Core()->getChildProcesses(DEBUGGED_PID).array(); + for (QJsonValue value : processesValues) { + QString status = value.toObject()["status"].toString(); + if (pid == value.toObject()["pid"].toInt()) { + if (QString(R_DBG_PROC_ZOMBIE) == status || QString(R_DBG_PROC_DEAD) == status) { + QMessageBox msgBox; + msgBox.setText(tr("Unable to switch to the requested process.")); + msgBox.exec(); + } else { + Core()->setCurrentDebugProcess(pid); + } + break; + } + } + + updateContents(); +} + +ProcessesFilterModel::ProcessesFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSortCaseSensitivity(Qt::CaseInsensitive); +} + +bool ProcessesFilterModel::filterAcceptsRow(int row, const QModelIndex &parent) const +{ + // All columns are checked for a match + for (int i = COLUMN_PID; 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/ProcessesWidget.h b/src/widgets/ProcessesWidget.h new file mode 100644 index 00000000..2552fca6 --- /dev/null +++ b/src/widgets/ProcessesWidget.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "core/Cutter.h" +#include "CutterDockWidget.h" + +class MainWindow; + +namespace Ui { +class ProcessesWidget; +} + +class ProcessesFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + ProcessesFilterModel(QObject *parent = nullptr); + +protected: + bool filterAcceptsRow(int row, const QModelIndex &parent) const override; +}; + +class ProcessesWidget : public CutterDockWidget +{ + Q_OBJECT + +public: + explicit ProcessesWidget(MainWindow *main, QAction *action = nullptr); + ~ProcessesWidget(); + +private slots: + void updateContents(); + void setProcessesGrid(); + void fontsUpdatedSlot(); + void onActivated(const QModelIndex &index); + +private: + QString translateStatus(QString status); + std::unique_ptr ui; + QStandardItemModel *modelProcesses; + ProcessesFilterModel *modelFilter; + RefreshDeferrer *refreshDeferrer; +}; diff --git a/src/widgets/ProcessesWidget.ui b/src/widgets/ProcessesWidget.ui new file mode 100644 index 00000000..5027fbc6 --- /dev/null +++ b/src/widgets/ProcessesWidget.ui @@ -0,0 +1,72 @@ + + + ProcessesWidget + + + + 0 + 0 + 463 + 300 + + + + Processes + + + + + 10 + + + 0 + + + 0 + + + 0 + + + + + true + + + false + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::ScrollPerPixel + + + + + + + + 0 + 0 + + + + + + + + + + QuickFilterView + QWidget +
widgets/QuickFilterView.h
+ 1 +
+
+ + +
diff --git a/src/widgets/ThreadsWidget.cpp b/src/widgets/ThreadsWidget.cpp index 941bb4d1..a3ad4c20 100644 --- a/src/widgets/ThreadsWidget.cpp +++ b/src/widgets/ThreadsWidget.cpp @@ -55,6 +55,10 @@ ThreadsWidget::ThreadsWidget(MainWindow *main, QAction *action) : &ThreadsFilterModel::setFilterWildcard); connect(Core(), &CutterCore::refreshAll, this, &ThreadsWidget::updateContents); connect(Core(), &CutterCore::seekChanged, this, &ThreadsWidget::updateContents); + connect(Core(), &CutterCore::debugTaskStateChanged, this, &ThreadsWidget::updateContents); + // Seek doesn't necessarily change when switching threads/processes + connect(Core(), &CutterCore::switchedThread, this, &ThreadsWidget::updateContents); + connect(Core(), &CutterCore::switchedProcess, this, &ThreadsWidget::updateContents); connect(Config(), &Configuration::fontsUpdated, this, &ThreadsWidget::fontsUpdatedSlot); connect(ui->viewThreads, &QTableView::activated, this, &ThreadsWidget::onActivated); } @@ -67,10 +71,17 @@ void ThreadsWidget::updateContents() return; } - setThreadsGrid(); + if (Core()->currentlyDebugging) { + setThreadsGrid(); + } else { + // Remove rows from the previous debugging session + modelThreads->removeRows(0, modelThreads->rowCount()); + } if (Core()->isDebugTaskInProgress() || !Core()->currentlyDebugging) { - ui->viewThreads->setSelectionMode(QAbstractItemView::NoSelection); + ui->viewThreads->setDisabled(true); + } else { + ui->viewThreads->setDisabled(false); } }