diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index eb161d641..49478bb94 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -10,6 +10,7 @@ #include #include +#include "CutterDescriptions.h" #include "common/TempConfig.h" #include "common/BasicInstructionHighlighter.h" #include "common/Configuration.h" @@ -2801,23 +2802,29 @@ bool CutterCore::isBreakpoint(const QList &breakpoints, RVA addr) return breakpoints.contains(addr); } -QList CutterCore::getProcessThreads(int pid = -1) +QList CutterCore::getProcessThreads(int pid) { CORE_LOCK(); - RzList *list = rz_debug_pids(core->dbg, pid != -1 ? pid : core->dbg->pid); + auto dbg = core_->dbg; + if (!dbg || !dbg->cur || !dbg->cur->threads) { + return {}; + } + RzList *list = core_->dbg->cur->threads(dbg, pid != -1 ? pid : dbg->pid); RzListIter *iter; RzDebugPid *p; - QList ret; + QList ret; CutterRzListForeach (list, iter, RzDebugPid, p) { - ProcessDescription proc; + ThreadDescription proc; - proc.current = core->dbg->pid == p->pid; + proc.current = dbg->tid == p->pid; proc.ppid = p->ppid; proc.pid = p->pid; proc.uid = p->uid; proc.status = static_cast(p->status); proc.path = p->path; + proc.pc = p->pc; + proc.tls = p->tls; ret << proc; } @@ -2825,9 +2832,32 @@ QList CutterCore::getProcessThreads(int pid = -1) return ret; } -QList CutterCore::getAllProcesses() +QList CutterCore::getProcesses(int pid) { - return getProcessThreads(0); + CORE_LOCK(); + auto dbg = core_->dbg; + if (!dbg || !dbg->cur || !dbg->cur->threads) { + return {}; + } + RzList *list = core_->dbg->cur->pids(dbg, pid >= 0 ? pid : dbg->pid); + RzListIter *iter; + RzDebugPid *p; + QList ret; + + CutterRzListForeach (list, iter, RzDebugPid, p) { + ProcessDescription proc; + + proc.current = core->dbg->pid == p->pid; + proc.ppid = p->ppid; + proc.pid = p->pid; + proc.uid = p->uid; + proc.status = static_cast(p->status); + proc.path = p->path; + + ret << proc; + } + rz_list_free(list); + return ret; } QList CutterCore::getMemoryMap() diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 1715c4dae..ae2a67964 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -443,7 +443,7 @@ class CUTTER_EXPORT CutterCore : public QObject * @param pid The pid of the process, -1 for the currently debugged process * @return List of ProcessDescription */ - QList getProcessThreads(int pid); + QList getProcessThreads(int pid = -1); /** * @brief Get a list of heap chunks * Uses RZ_API rz_heap_chunks_list to get vector of chunks @@ -652,7 +652,13 @@ class CUTTER_EXPORT CutterCore : public QObject QList getMemoryMap(); QList getAllSearch(QString searchFor, QString space, QString in); QList getBreakpoints(); - QList getAllProcesses(); + /** + * @brief Get list of processes attachable by debugger + * + * @param pid 0 - all processes, -1 - currently debugged process + * @return QList + */ + QList getProcesses(int pid = 0); /** * @brief Get the right RzReg object based on the cutter state (debugging vs emulating) */ diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index bb04e12fa..cfbf96537 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -346,6 +346,18 @@ struct ProcessDescription QString path; }; +struct ThreadDescription +{ + bool current; + int pid; + int uid; + int ppid; + RzDebugPidState status; + QString path; + ut64 pc; + ut64 tls; +}; + struct RefDescription { QString ref; diff --git a/src/dialogs/AttachProcDialog.cpp b/src/dialogs/AttachProcDialog.cpp index 5c0abed34..fa2180ec1 100644 --- a/src/dialogs/AttachProcDialog.cpp +++ b/src/dialogs/AttachProcDialog.cpp @@ -19,7 +19,7 @@ void ProcessModel::updateData() { beginResetModel(); - processes = Core()->getAllProcesses(); + processes = Core()->getProcesses(); endResetModel(); } diff --git a/src/widgets/ProcessesWidget.cpp b/src/widgets/ProcessesWidget.cpp index 3b2d4dac2..3c06ca58a 100644 --- a/src/widgets/ProcessesWidget.cpp +++ b/src/widgets/ProcessesWidget.cpp @@ -106,7 +106,7 @@ void ProcessesWidget::setProcessesGrid() int i = 0; QFont font; - for (const auto &processesItem : Core()->getProcessThreads(DEBUGGED_PID)) { + for (const auto &processesItem : Core()->getProcesses(DEBUGGED_PID)) { st64 pid = processesItem.pid; st64 uid = processesItem.uid; QString status = translateStatus(processesItem.status); @@ -155,7 +155,7 @@ void ProcessesWidget::onActivated(const QModelIndex &index) int pid = modelFilter->data(index.sibling(index.row(), ProcessesWidget::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. - for (const auto &value : Core()->getAllProcesses()) { + for (const auto &value : Core()->getProcesses(DEBUGGED_PID)) { if (pid == value.pid) { QMessageBox msgBox(this); switch (value.status) { diff --git a/src/widgets/ThreadsWidget.cpp b/src/widgets/ThreadsWidget.cpp index 8d4741288..3fdb5a562 100644 --- a/src/widgets/ThreadsWidget.cpp +++ b/src/widgets/ThreadsWidget.cpp @@ -1,31 +1,127 @@ #include #include "ThreadsWidget.h" +#include "CutterCommon.h" +#include "Helpers.h" #include "ui_ThreadsWidget.h" -#include "common/JsonModel.h" #include "QuickFilterView.h" #include #include "core/MainWindow.h" -#define DEBUGGED_PID (-1) +ThreadModel::ThreadModel(QObject *parent) : QAbstractListModel(parent) {} -ThreadsWidget::ThreadsWidget(MainWindow *main) : CutterDockWidget(main), ui(new Ui::ThreadsWidget) +void ThreadModel::setList(QList data) +{ + beginResetModel(); + this->threads = data; + endResetModel(); +} + +int ThreadModel::rowCount(const QModelIndex &) const +{ + return threads.count(); +} + +int ThreadModel::columnCount(const QModelIndex &) const +{ + return ThreadModel::ColumnIndex::COLUMN_COUNT; +} + +QVariant ThreadModel::data(const QModelIndex &index, int role) const +{ + if (index.row() >= threads.count()) + return QVariant(); + + const ThreadDescription &thread = threads.at(index.row()); + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case ColumnIndex::COLUMN_PID: + return thread.current ? QString("*%0").arg(thread.pid) : QString("%0").arg(thread.pid); + case ColumnIndex::COLUMN_STATUS: + return translateStatus(thread.status); + case ColumnIndex::COLUMN_PATH: + return thread.path; + case ColumnIndex::COLUMN_PC: + return RzAddressString(thread.pc); + case ColumnIndex::COLUMN_TLS: + return RzAddressString(thread.tls); + default: + return QVariant(); + } + case Qt::FontRole: { + if (thread.current) { + QFont font; + font.setBold(true); + return font; + } + return QVariant(); + } + case Qt::EditRole: + switch (index.column()) { + case ColumnIndex::COLUMN_PID: + return thread.pid; + case ColumnIndex::COLUMN_PC: + return thread.pc; + case ColumnIndex::COLUMN_TLS: + return thread.tls; + default: + return data(index, Qt::DisplayRole); + } + break; + default: + return QVariant(); + } +} + +QVariant ThreadModel::headerData(int section, Qt::Orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case ColumnIndex::COLUMN_PID: + return tr("TID"); + case ColumnIndex::COLUMN_STATUS: + return tr("Status"); + case ColumnIndex::COLUMN_PATH: + return tr("Path"); + case ColumnIndex::COLUMN_PC: + return tr("PC"); + case ColumnIndex::COLUMN_TLS: + return tr("TLS"); + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +ThreadsWidget::ThreadsWidget(MainWindow *main) + : CutterDockWidget(main), + ui(new Ui::ThreadsWidget), + menuText(this), + addressableItemContextMenu(this, main) { ui->setupUi(this); // Setup threads model - modelThreads = new QStandardItemModel(1, 3, this); - modelThreads->setHorizontalHeaderItem(ThreadsWidget::COLUMN_PID, new QStandardItem(tr("PID"))); - modelThreads->setHorizontalHeaderItem(ThreadsWidget::COLUMN_STATUS, - new QStandardItem(tr("Status"))); - modelThreads->setHorizontalHeaderItem(ThreadsWidget::COLUMN_PATH, - new QStandardItem(tr("Path"))); + modelThreads = new ThreadModel(this); + ui->viewThreads->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter); ui->viewThreads->verticalHeader()->setVisible(false); - ui->viewThreads->setFont(Config()->getFont()); + fontsUpdatedSlot(); - modelFilter = new ThreadsFilterModel(this); + modelFilter = new QSortFilterProxyModel(this); modelFilter->setSourceModel(modelThreads); + + modelFilter->setFilterCaseSensitivity(Qt::CaseInsensitive); + modelFilter->setSortCaseSensitivity(Qt::CaseInsensitive); + modelFilter->setFilterKeyColumn(-1); + modelFilter->setFilterRole(Qt::DisplayRole); + modelFilter->setSortRole(Qt::EditRole); + ui->viewThreads->setModel(modelFilter); // CTRL+F switches to the filter view and opens it in case it's hidden @@ -44,8 +140,11 @@ ThreadsWidget::ThreadsWidget(MainWindow *main) : CutterDockWidget(main), ui(new refreshDeferrer = createRefreshDeferrer([this]() { updateContents(); }); + menuText.setSeparator(true); + qhelpers::prependQAction(&menuText, &addressableItemContextMenu); + connect(ui->quickFilterView, &QuickFilterView::filterTextChanged, modelFilter, - &ThreadsFilterModel::setFilterWildcard); + &QSortFilterProxyModel::setFilterWildcard); connect(Core(), &CutterCore::refreshAll, this, &ThreadsWidget::updateContents); connect(Core(), &CutterCore::registersChanged, this, &ThreadsWidget::updateContents); connect(Core(), &CutterCore::debugTaskStateChanged, this, &ThreadsWidget::updateContents); @@ -54,6 +153,11 @@ ThreadsWidget::ThreadsWidget(MainWindow *main) : CutterDockWidget(main), ui(new connect(Core(), &CutterCore::switchedProcess, this, &ThreadsWidget::updateContents); connect(Config(), &Configuration::fontsUpdated, this, &ThreadsWidget::fontsUpdatedSlot); connect(ui->viewThreads, &QTableView::activated, this, &ThreadsWidget::onActivated); + connect(ui->viewThreads, &QWidget::customContextMenuRequested, this, + &ThreadsWidget::tableCustomContextMenu); + connect(ui->viewThreads->selectionModel(), &QItemSelectionModel::currentChanged, this, + &ThreadsWidget::onCurrentChanged); + ui->viewThreads->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); } ThreadsWidget::~ThreadsWidget() {} @@ -66,7 +170,7 @@ void ThreadsWidget::updateContents() if (!Core()->currentlyDebugging) { // Remove rows from the previous debugging session - modelThreads->removeRows(0, modelThreads->rowCount()); + modelThreads->setList(QList()); return; } @@ -78,7 +182,7 @@ void ThreadsWidget::updateContents() } } -QString ThreadsWidget::translateStatus(const char status) +QString ThreadModel::translateStatus(const char status) const { switch (status) { case RZ_DBG_PROC_STOP: @@ -100,36 +204,8 @@ QString ThreadsWidget::translateStatus(const char status) void ThreadsWidget::setThreadsGrid() { - int i = 0; - QFont font; - - for (const auto &threadsItem : Core()->getProcessThreads(DEBUGGED_PID)) { - st64 pid = threadsItem.pid; - QString status = translateStatus(threadsItem.status); - QString path = threadsItem.path; - bool current = threadsItem.current; - // Use bold font to highlight active thread - font.setBold(current); - QStandardItem *rowPid = new QStandardItem(QString::number(pid)); - rowPid->setFont(font); - QStandardItem *rowStatus = new QStandardItem(status); - rowStatus->setFont(font); - QStandardItem *rowPath = new QStandardItem(path); - rowPath->setFont(font); - modelThreads->setItem(i, ThreadsWidget::COLUMN_PID, rowPid); - modelThreads->setItem(i, ThreadsWidget::COLUMN_STATUS, rowStatus); - modelThreads->setItem(i, ThreadsWidget::COLUMN_PATH, rowPath); - i++; - } - - // Remove irrelevant old rows - if (modelThreads->rowCount() > i) { - modelThreads->removeRows(i, modelThreads->rowCount() - i); - } - - modelFilter->setSourceModel(modelThreads); + modelThreads->setList(Core()->getProcessThreads()); ui->viewThreads->resizeColumnsToContents(); - ; } void ThreadsWidget::fontsUpdatedSlot() @@ -142,11 +218,12 @@ void ThreadsWidget::onActivated(const QModelIndex &index) if (!index.isValid()) return; - int tid = modelFilter->data(index.sibling(index.row(), ThreadsWidget::COLUMN_PID)).toInt(); + int tid = modelFilter->data(index.sibling(index.row(), ThreadModel::COLUMN_PID), Qt::EditRole) + .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. - for (const auto &value : Core()->getProcessThreads(DEBUGGED_PID)) { + for (const auto &value : Core()->getProcessThreads()) { if (tid == value.pid) { Core()->setCurrentDebugThread(tid); break; @@ -156,21 +233,25 @@ void ThreadsWidget::onActivated(const QModelIndex &index) updateContents(); } -ThreadsFilterModel::ThreadsFilterModel(QObject *parent) : QSortFilterProxyModel(parent) +void ThreadsWidget::onCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) { - setFilterCaseSensitivity(Qt::CaseInsensitive); - setSortCaseSensitivity(Qt::CaseInsensitive); -} + Q_UNUSED(previous) -bool ThreadsFilterModel::filterAcceptsRow(int row, const QModelIndex &parent) const -{ - // All columns are checked for a match - for (int i = ThreadsWidget::COLUMN_PID; i <= ThreadsWidget::COLUMN_PATH; ++i) { - QModelIndex index = sourceModel()->index(row, i, parent); - if (qhelpers::filterStringContains(sourceModel()->data(index).toString(), this)) { - return true; - } + RVA offset = 0; + if (current.column() == ThreadModel::ColumnIndex::COLUMN_TLS) { + offset = current.data(Qt::EditRole).toULongLong(); + menuText.setText(tr("TLS (%0)").arg(RzAddressString(offset))); + } else { + offset = current.sibling(current.row(), ThreadModel::ColumnIndex::COLUMN_PC) + .data(Qt::EditRole) + .toULongLong(); + menuText.setText(tr("PC (%0)").arg(RzAddressString(offset))); } - return false; + addressableItemContextMenu.setTarget(offset); } + +void ThreadsWidget::tableCustomContextMenu(const QPoint &pos) +{ + addressableItemContextMenu.exec(this->ui->viewThreads->viewport()->mapToGlobal(pos)); +} \ No newline at end of file diff --git a/src/widgets/ThreadsWidget.h b/src/widgets/ThreadsWidget.h index fd452d1e1..8a3ff5ace 100644 --- a/src/widgets/ThreadsWidget.h +++ b/src/widgets/ThreadsWidget.h @@ -1,12 +1,10 @@ #pragma once -#include -#include -#include -#include #include #include "core/Cutter.h" +#include "AddressableItemContextMenu.h" +#include "CutterDescriptions.h" #include "CutterDockWidget.h" class MainWindow; @@ -15,28 +13,45 @@ namespace Ui { class ThreadsWidget; } -class ThreadsFilterModel : public QSortFilterProxyModel +class ThreadModel : public QAbstractListModel { Q_OBJECT -public: - ThreadsFilterModel(QObject *parent = nullptr); - -protected: - bool filterAcceptsRow(int row, const QModelIndex &parent) const override; -}; + friend class ThreadsWidget; -class ThreadsWidget : public CutterDockWidget -{ - Q_OBJECT +private: + QList threads; public: enum ColumnIndex { COLUMN_PID = 0, COLUMN_STATUS, COLUMN_PATH, + COLUMN_PC, + COLUMN_TLS, + + COLUMN_COUNT, }; + ThreadModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + void setList(QList data); + + const ThreadDescription *description(const QModelIndex &index) const; + QString translateStatus(const char status) const; +}; + +class ThreadsWidget : public CutterDockWidget +{ + Q_OBJECT +public: explicit ThreadsWidget(MainWindow *main); ~ThreadsWidget(); @@ -45,11 +60,14 @@ private slots: void setThreadsGrid(); void fontsUpdatedSlot(); void onActivated(const QModelIndex &index); + void tableCustomContextMenu(const QPoint &pos); + void onCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); private: - QString translateStatus(const char status); std::unique_ptr ui; - QStandardItemModel *modelThreads; - ThreadsFilterModel *modelFilter; + ThreadModel *modelThreads; + QSortFilterProxyModel *modelFilter; RefreshDeferrer *refreshDeferrer; + QAction menuText; + AddressableItemContextMenu addressableItemContextMenu; };