Add processes widget for kernel and child debugging (#1894)

* Update r2 for dp fixes and general debug fixes

* Added ProcessesWidget for kernel debugging and switching between children

* Update r2 for dpl fixes

* Update r2 for linux and gdbserver dp/dp= fixes

* Added switchedThread and switchedProcess events to refresh their widgets

Seek doesn't always change after switching if the other process is at
the same offset in the same binary so it's better to have another event
for it.

* Disable threads/processes widget during a debugtask/when not debugging and clear it's history after a session

* Improve Processes Widget's UI

- Remove vertical numbers
- Highlight the current process with bold instead of using the "current"
  column

* Updated r2 for fork fixes
This commit is contained in:
yossizap 2019-12-09 18:43:42 +00:00 committed by Itay Cohen
parent 4d306616f9
commit ef97c84351
11 changed files with 400 additions and 3 deletions

@ -1 +1 @@
Subproject commit cb60b5e8fd8dc76d847d9935d2ded4df2e05b63e
Subproject commit 9fca7b1f582a744426e9f0bf650ed3206804510c

View File

@ -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 \

View File

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

View File

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

View File

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

View File

@ -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;

View File

@ -153,6 +153,7 @@
<addaction name="actionBacktrace"/>
<addaction name="actionBreakpoint"/>
<addaction name="actionThreads"/>
<addaction name="actionProcesses"/>
<addaction name="actionMemoryMap"/>
<addaction name="actionRegisters"/>
<addaction name="actionRegisterRefs"/>
@ -941,6 +942,14 @@
<string>Threads</string>
</property>
</action>
<action name="actionProcesses">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Processes</string>
</property>
</action>
<action name="actionMemoryMap">
<property name="checkable">
<bool>true</bool>

View File

@ -0,0 +1,202 @@
#include <QShortcut>
#include "ProcessesWidget.h"
#include "ui_ProcessesWidget.h"
#include "common/JsonModel.h"
#include "QuickFilterView.h"
#include <r_debug.h>
#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;
}

View 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 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::ProcessesWidget> ui;
QStandardItemModel *modelProcesses;
ProcessesFilterModel *modelFilter;
RefreshDeferrer *refreshDeferrer;
};

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProcessesWidget</class>
<widget class="QDockWidget" name="ProcessesWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>463</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Processes</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="viewProcesses">
<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>

View File

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