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);
}
}