mirror of
https://github.com/rizinorg/cutter.git
synced 2025-01-18 18:38:51 +00:00
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
This commit is contained in:
parent
c3ce953134
commit
a3e140bf4d
@ -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 \
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -152,6 +152,7 @@
|
||||
</property>
|
||||
<addaction name="actionBacktrace"/>
|
||||
<addaction name="actionBreakpoint"/>
|
||||
<addaction name="actionThreads"/>
|
||||
<addaction name="actionMemoryMap"/>
|
||||
<addaction name="actionRegisters"/>
|
||||
<addaction name="actionRegisterRefs"/>
|
||||
@ -932,6 +933,14 @@
|
||||
<string>Backtrace</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionThreads">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Threads</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMemoryMap">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
@ -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();;
|
||||
}
|
||||
|
168
src/widgets/ThreadsWidget.cpp
Normal file
168
src/widgets/ThreadsWidget.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
#include <QShortcut>
|
||||
#include "ThreadsWidget.h"
|
||||
#include "ui_ThreadsWidget.h"
|
||||
#include "common/JsonModel.h"
|
||||
#include "QuickFilterView.h"
|
||||
#include <r_debug.h>
|
||||
|
||||
#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;
|
||||
}
|
49
src/widgets/ThreadsWidget.h
Normal file
49
src/widgets/ThreadsWidget.h
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <memory>
|
||||
#include <QStandardItem>
|
||||
#include <QTableView>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#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::ThreadsWidget> ui;
|
||||
QStandardItemModel *modelThreads;
|
||||
ThreadsFilterModel *modelFilter;
|
||||
RefreshDeferrer *refreshDeferrer;
|
||||
};
|
72
src/widgets/ThreadsWidget.ui
Normal file
72
src/widgets/ThreadsWidget.ui
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ThreadsWidget</class>
|
||||
<widget class="QDockWidget" name="ThreadsWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>463</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Threads</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="dockWidgetContents">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="viewThreads">
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="cornerButtonEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<enum>QAbstractItemView::NoEditTriggers</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QuickFilterView" name="quickFilterView" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QuickFilterView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/QuickFilterView.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
Reference in New Issue
Block a user