Detach Cutter interface from debug command execution (#1857)

This commit is contained in:
yossizap 2019-10-31 09:50:08 +00:00 committed by Itay Cohen
parent a3e140bf4d
commit c85e1db3b3
7 changed files with 248 additions and 40 deletions

View File

@ -298,12 +298,6 @@ QString CutterCore::sanitizeStringForCommand(QString s)
return s.replace(regexp, QStringLiteral("_")); return s.replace(regexp, QStringLiteral("_"));
} }
/**
* @brief CutterCore::cmd send a command to radare2
* @param str the command you want to execute
* Note that if you want to seek to an address, you should use CutterCore::seek
* @return command output
*/
QString CutterCore::cmd(const char *str) QString CutterCore::cmd(const char *str)
{ {
CORE_LOCK(); CORE_LOCK();
@ -319,6 +313,54 @@ QString CutterCore::cmd(const char *str)
return o; return o;
} }
bool CutterCore::isDebugTaskInProgress()
{
if (!debugTask.isNull()) {
return true;
}
return false;
}
void CutterCore::asyncCmdEsil(const char *command, QSharedPointer<R2Task> &task)
{
asyncCmd(command, task);
if (task.isNull()) {
return;
}
connect(task.data(), &R2Task::finished, this, [this, task] () {
QString res = task.data()->getResult();
if (res.contains(QStringLiteral("[ESIL] Stopped execution in an invalid instruction"))) {
msgBox.showMessage("Stopped when attempted to run an invalid instruction. You can disable this in Preferences");
}
});
}
void CutterCore::asyncCmd(const char *str, QSharedPointer<R2Task> &task)
{
if (!task.isNull()) {
return;
}
CORE_LOCK();
RVA offset = core->offset;
task = QSharedPointer<R2Task>(new R2Task(str, true));
connect(task.data(), &R2Task::finished, this, [this, offset, task] () {
CORE_LOCK();
if (offset != core->offset) {
updateSeek();
}
});
task->startTask();
}
QString CutterCore::cmdRaw(const QString &str) QString CutterCore::cmdRaw(const QString &str)
{ {
QString cmdStr = str; QString cmdStr = str;
@ -1130,11 +1172,18 @@ void CutterCore::setRegister(QString regName, QString regValue)
void CutterCore::setCurrentDebugThread(int tid) void CutterCore::setCurrentDebugThread(int tid)
{ {
cmd("dpt=" + QString::number(tid)); asyncCmd("dpt=" + QString::number(tid), debugTask);
if (!debugTask.isNull()) {
emit debugTaskStateChanged();
connect(debugTask.data(), &R2Task::finished, this, [this] () {
debugTask.clear();
emit registersChanged(); emit registersChanged();
emit refreshCodeViews(); emit refreshCodeViews();
emit stackChanged(); emit stackChanged();
syncAndSeekProgramCounter(); syncAndSeekProgramCounter();
emit debugTaskStateChanged();
});
}
} }
void CutterCore::startDebug() void CutterCore::startDebug()
@ -1153,6 +1202,7 @@ void CutterCore::startDebug()
emit refreshCodeViews(); emit refreshCodeViews();
} }
emit stackChanged(); emit stackChanged();
emit debugTaskStateChanged();
} }
void CutterCore::startEmulation() void CutterCore::startEmulation()
@ -1175,6 +1225,7 @@ void CutterCore::startEmulation()
emit registersChanged(); emit registersChanged();
emit stackChanged(); emit stackChanged();
emit refreshCodeViews(); emit refreshCodeViews();
emit debugTaskStateChanged();
} }
void CutterCore::attachDebug(int pid) void CutterCore::attachDebug(int pid)
@ -1182,6 +1233,7 @@ void CutterCore::attachDebug(int pid)
if (!currentlyDebugging) { if (!currentlyDebugging) {
offsetPriorDebugging = getOffset(); offsetPriorDebugging = getOffset();
} }
// attach to process with dbg plugin // attach to process with dbg plugin
cmd("o-*; e cfg.debug = true; o+ dbg://" + QString::number(pid)); cmd("o-*; e cfg.debug = true; o+ dbg://" + QString::number(pid));
QString programCounterValue = cmd("dr?`drn PC`").trimmed(); QString programCounterValue = cmd("dr?`drn PC`").trimmed();
@ -1196,11 +1248,20 @@ void CutterCore::attachDebug(int pid)
emit flagsChanged(); emit flagsChanged();
emit changeDebugView(); emit changeDebugView();
} }
emit debugTaskStateChanged();
}
void CutterCore::suspendDebug()
{
debugTask->breakTask();
} }
void CutterCore::stopDebug() void CutterCore::stopDebug()
{ {
if (currentlyDebugging) { if (currentlyDebugging) {
currentlyDebugging = false;
emit debugTaskStateChanged();
if (currentlyEmulating) { if (currentlyEmulating) {
cmd("aeim-; aei-; wcr; .ar-"); cmd("aeim-; aei-; wcr; .ar-");
currentlyEmulating = false; currentlyEmulating = false;
@ -1222,7 +1283,6 @@ void CutterCore::stopDebug()
seekAndShow(offsetPriorDebugging); seekAndShow(offsetPriorDebugging);
setConfig("asm.flags", true); setConfig("asm.flags", true);
setConfig("io.cache", false); setConfig("io.cache", false);
currentlyDebugging = false;
emit flagsChanged(); emit flagsChanged();
emit changeDefinedView(); emit changeDefinedView();
} }
@ -1239,26 +1299,43 @@ void CutterCore::continueDebug()
{ {
if (currentlyDebugging) { if (currentlyDebugging) {
if (currentlyEmulating) { if (currentlyEmulating) {
cmdEsil("aec"); asyncCmdEsil("aec", debugTask);
} else { } else {
cmd("dc"); asyncCmd("dc", debugTask);
} }
if (!debugTask.isNull()) {
emit debugTaskStateChanged();
connect(debugTask.data(), &R2Task::finished, this, [this] () {
debugTask.clear();
syncAndSeekProgramCounter();
emit registersChanged(); emit registersChanged();
emit refreshCodeViews(); emit refreshCodeViews();
emit debugTaskStateChanged();
});
}
} }
} }
void CutterCore::continueUntilDebug(QString offset) void CutterCore::continueUntilDebug(QString offset)
{ {
if (currentlyDebugging) { if (currentlyDebugging) {
if (currentlyEmulating) { if (currentlyEmulating) {
cmdEsil("aecu " + offset); asyncCmdEsil("aecu " + offset, debugTask);
} else { } else {
cmd("dcu " + offset); asyncCmd("dcu " + offset, debugTask);
} }
if (!debugTask.isNull()) {
emit debugTaskStateChanged();
connect(debugTask.data(), &R2Task::finished, this, [this] () {
debugTask.clear();
syncAndSeekProgramCounter();
emit registersChanged(); emit registersChanged();
emit stackChanged(); emit stackChanged();
emit refreshCodeViews(); emit refreshCodeViews();
emit debugTaskStateChanged();
});
}
} }
} }
@ -1266,11 +1343,18 @@ void CutterCore::continueUntilCall()
{ {
if (currentlyDebugging) { if (currentlyDebugging) {
if (currentlyEmulating) { if (currentlyEmulating) {
cmdEsil("aecc"); asyncCmdEsil("aecc", debugTask);
} else { } else {
cmd("dcc"); asyncCmd("dcc", debugTask);
} }
if (!debugTask.isNull()) {
emit debugTaskStateChanged();
connect(debugTask.data(), &R2Task::finished, this, [this] () {
debugTask.clear();
syncAndSeekProgramCounter(); syncAndSeekProgramCounter();
emit debugTaskStateChanged();
});
}
} }
} }
@ -1278,11 +1362,18 @@ void CutterCore::continueUntilSyscall()
{ {
if (currentlyDebugging) { if (currentlyDebugging) {
if (currentlyEmulating) { if (currentlyEmulating) {
cmdEsil("aecs"); asyncCmdEsil("aecs", debugTask);
} else { } else {
cmd("dcs"); asyncCmd("dcs", debugTask);
} }
if (!debugTask.isNull()) {
emit debugTaskStateChanged();
connect(debugTask.data(), &R2Task::finished, this, [this] () {
debugTask.clear();
syncAndSeekProgramCounter(); syncAndSeekProgramCounter();
emit debugTaskStateChanged();
});
}
} }
} }
@ -1290,11 +1381,18 @@ void CutterCore::stepDebug()
{ {
if (currentlyDebugging) { if (currentlyDebugging) {
if (currentlyEmulating) { if (currentlyEmulating) {
cmdEsil("aes"); asyncCmdEsil("aes", debugTask);
} else { } else {
cmd("ds"); asyncCmd("ds", debugTask);
} }
if (!debugTask.isNull()) {
emit debugTaskStateChanged();
connect(debugTask.data(), &R2Task::finished, this, [this] () {
debugTask.clear();
syncAndSeekProgramCounter(); syncAndSeekProgramCounter();
emit debugTaskStateChanged();
});
}
} }
} }
@ -1302,19 +1400,33 @@ void CutterCore::stepOverDebug()
{ {
if (currentlyDebugging) { if (currentlyDebugging) {
if (currentlyEmulating) { if (currentlyEmulating) {
cmdEsil("aeso"); asyncCmdEsil("aeso", debugTask);
} else { } else {
cmd("dso"); asyncCmd("dso", debugTask);
} }
if (!debugTask.isNull()) {
emit debugTaskStateChanged();
connect(debugTask.data(), &R2Task::finished, this, [this] () {
debugTask.clear();
syncAndSeekProgramCounter(); syncAndSeekProgramCounter();
emit debugTaskStateChanged();
});
}
} }
} }
void CutterCore::stepOutDebug() void CutterCore::stepOutDebug()
{ {
if (currentlyDebugging) { if (currentlyDebugging) {
cmd("dsf"); emit debugTaskStateChanged();
asyncCmd("dsf", debugTask);
if (!debugTask.isNull()) {
connect(debugTask.data(), &R2Task::finished, this, [this] () {
debugTask.clear();
syncAndSeekProgramCounter(); syncAndSeekProgramCounter();
emit debugTaskStateChanged();
});
}
} }
} }

View File

@ -18,9 +18,11 @@ class AsyncTaskManager;
class BasicInstructionHighlighter; class BasicInstructionHighlighter;
class CutterCore; class CutterCore;
class Decompiler; class Decompiler;
class R2Task;
#include "plugins/CutterPlugin.h" #include "plugins/CutterPlugin.h"
#include "common/BasicBlockHighlighter.h" #include "common/BasicBlockHighlighter.h"
#include "common/R2Task.h"
#define Core() (CutterCore::instance()) #define Core() (CutterCore::instance())
@ -47,8 +49,25 @@ public:
/* Core functions (commands) */ /* Core functions (commands) */
static QString sanitizeStringForCommand(QString s); static QString sanitizeStringForCommand(QString s);
/**
* @brief send a command to radare2
* @param str the command you want to execute
* @return command output
* @note if you want to seek to an address, you should use CutterCore::seek.
*/
QString cmd(const char *str); QString cmd(const char *str);
QString cmd(const QString &str) { return cmd(str.toUtf8().constData()); } QString cmd(const QString &str) { return cmd(str.toUtf8().constData()); }
/**
* @brief send a command to radare2 asynchronously
* @param str the command you want to execute
* @param task a shared pointer that will be returned with the R2 command task
* @note connect to the &R2Task::finished signal to add your own logic once
* the command is finished. Use task->getResult()/getResultJson() for the
* return value.
* If you want to seek to an address, you should use CutterCore::seek.
*/
void asyncCmd(const char *str, QSharedPointer<R2Task> &task);
void asyncCmd(const QString &str, QSharedPointer<R2Task> &task) { return asyncCmd(str.toUtf8().constData(), task); }
QString cmdRaw(const QString &str); QString cmdRaw(const QString &str);
QJsonDocument cmdj(const char *str); QJsonDocument cmdj(const char *str);
QJsonDocument cmdj(const QString &str) { return cmdj(str.toUtf8().constData()); } QJsonDocument cmdj(const QString &str) { return cmdj(str.toUtf8().constData()); }
@ -56,8 +75,24 @@ public:
QStringList cmdList(const QString &str) { return cmdList(str.toUtf8().constData()); } QStringList cmdList(const QString &str) { return cmdList(str.toUtf8().constData()); }
QString cmdTask(const QString &str); QString cmdTask(const QString &str);
QJsonDocument cmdjTask(const QString &str); QJsonDocument cmdjTask(const QString &str);
/**
* @brief send a command to radare2 and check for ESIL errors
* @param command the command you want to execute
* @note If you want to seek to an address, you should use CutterCore::seek.
*/
void cmdEsil(const char *command); void cmdEsil(const char *command);
void cmdEsil(const QString &command) { cmdEsil(command.toUtf8().constData()); } void cmdEsil(const QString &command) { cmdEsil(command.toUtf8().constData()); }
/**
* @brief send a command to radare2 and check for ESIL errors
* @param command the command you want to execute
* @param task a shared pointer that will be returned with the R2 command task
* @note connect to the &R2Task::finished signal to add your own logic once
* the command is finished. Use task->getResult()/getResultJson() for the
* return value.
* If you want to seek to an address, you should use CutterCore::seek.
*/
void asyncCmdEsil(const char *command, QSharedPointer<R2Task> &task);
void asyncCmdEsil(const QString &command, QSharedPointer<R2Task> &task) { return asyncCmdEsil(command.toUtf8().constData(), task); }
QString getVersionInformation(); QString getVersionInformation();
QJsonDocument parseJson(const char *res, const char *cmd = nullptr); QJsonDocument parseJson(const char *res, const char *cmd = nullptr);
@ -234,6 +269,7 @@ public:
void startEmulation(); void startEmulation();
void attachDebug(int pid); void attachDebug(int pid);
void stopDebug(); void stopDebug();
void suspendDebug();
void syncAndSeekProgramCounter(); void syncAndSeekProgramCounter();
void continueDebug(); void continueDebug();
void continueUntilCall(); void continueUntilCall();
@ -253,6 +289,7 @@ public:
QString getActiveDebugPlugin(); QString getActiveDebugPlugin();
QStringList getDebugPlugins(); QStringList getDebugPlugins();
void setDebugPlugin(QString plugin); void setDebugPlugin(QString plugin);
bool isDebugTaskInProgress();
bool currentlyDebugging = false; bool currentlyDebugging = false;
bool currentlyEmulating = false; bool currentlyEmulating = false;
int currentlyAttachedToPID = -1; int currentlyAttachedToPID = -1;
@ -438,6 +475,11 @@ signals:
void projectSaved(bool successfully, const QString &name); void projectSaved(bool successfully, const QString &name);
/**
* emitted when debugTask started or finished running
*/
void debugTaskStateChanged();
/** /**
* emitted when config regarding disassembly display changes * emitted when config regarding disassembly display changes
*/ */
@ -483,6 +525,8 @@ private:
bool emptyGraph = false; bool emptyGraph = false;
BasicBlockHighlighter *bbHighlighter; BasicBlockHighlighter *bbHighlighter;
BasicInstructionHighlighter biHighlighter; BasicInstructionHighlighter biHighlighter;
QSharedPointer<R2Task> debugTask;
}; };
class RCoreLocked class RCoreLocked

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">
<g>
<rect id="svg_3" height="5.953009" width="1.970973" y="1.138955" x="5.520464" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="null" fill="#b15858"/>
<rect id="svg_6" height="5.966372" width="1.970973" y="1.122789" x="0.516538" stroke-linecap="null" stroke-linejoin="null" stroke-dasharray="null" stroke-width="null" fill="#b15858"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 470 B

View File

@ -25,6 +25,7 @@
<file>img/icons/play_light_emul.svg</file> <file>img/icons/play_light_emul.svg</file>
<file>img/icons/play_light_attach.svg</file> <file>img/icons/play_light_attach.svg</file>
<file>img/icons/media-stop_light.svg</file> <file>img/icons/media-stop_light.svg</file>
<file>img/icons/media-suspend_light.svg</file>
<file>img/icons/media-skip-forward_light.svg</file> <file>img/icons/media-skip-forward_light.svg</file>
<file>img/icons/continue_until_main.svg</file> <file>img/icons/continue_until_main.svg</file>
<file>img/icons/continue_until_call.svg</file> <file>img/icons/continue_until_call.svg</file>

View File

@ -2,7 +2,6 @@
#include "core/MainWindow.h" #include "core/MainWindow.h"
#include "dialogs/AttachProcDialog.h" #include "dialogs/AttachProcDialog.h"
#include <QAction>
#include <QPainter> #include <QPainter>
#include <QMenu> #include <QMenu>
#include <QList> #include <QList>
@ -22,7 +21,6 @@ DebugActions::DebugActions(QToolBar *toolBar, MainWindow *main) :
QIcon startEmulIcon = QIcon(":/img/icons/play_light_emul.svg"); QIcon startEmulIcon = QIcon(":/img/icons/play_light_emul.svg");
QIcon startAttachIcon = QIcon(":/img/icons/play_light_attach.svg"); QIcon startAttachIcon = QIcon(":/img/icons/play_light_attach.svg");
QIcon stopIcon = QIcon(":/img/icons/media-stop_light.svg"); QIcon stopIcon = QIcon(":/img/icons/media-stop_light.svg");
QIcon continueIcon = QIcon(":/img/icons/media-skip-forward_light.svg");
QIcon continueUntilMainIcon = QIcon(":/img/icons/continue_until_main.svg"); QIcon continueUntilMainIcon = QIcon(":/img/icons/continue_until_main.svg");
QIcon continueUntilCallIcon = QIcon(":/img/icons/continue_until_call.svg"); QIcon continueUntilCallIcon = QIcon(":/img/icons/continue_until_call.svg");
QIcon continueUntilSyscallIcon = QIcon(":/img/icons/continue_until_syscall.svg"); QIcon continueUntilSyscallIcon = QIcon(":/img/icons/continue_until_syscall.svg");
@ -30,6 +28,8 @@ DebugActions::DebugActions(QToolBar *toolBar, MainWindow *main) :
QIcon stepOverIcon = QIcon(":/img/icons/step_over_light.svg"); QIcon stepOverIcon = QIcon(":/img/icons/step_over_light.svg");
QIcon stepOutIcon = QIcon(":/img/icons/step_out_light.svg"); QIcon stepOutIcon = QIcon(":/img/icons/step_out_light.svg");
QIcon restartIcon = QIcon(":/img/icons/spin_light.svg"); QIcon restartIcon = QIcon(":/img/icons/spin_light.svg");
continueIcon = QIcon(":/img/icons/media-skip-forward_light.svg");
suspendIcon = QIcon(":/img/icons/media-suspend_light.svg");
// define action labels // define action labels
QString startDebugLabel = tr("Start debug"); QString startDebugLabel = tr("Start debug");
@ -39,13 +39,14 @@ DebugActions::DebugActions(QToolBar *toolBar, MainWindow *main) :
QString stopEmulLabel = tr("Stop emulation"); QString stopEmulLabel = tr("Stop emulation");
QString restartDebugLabel = tr("Restart program"); QString restartDebugLabel = tr("Restart program");
QString restartEmulLabel = tr("Restart emulation"); QString restartEmulLabel = tr("Restart emulation");
QString continueLabel = tr("Continue");
QString continueUMLabel = tr("Continue until main"); QString continueUMLabel = tr("Continue until main");
QString continueUCLabel = tr("Continue until call"); QString continueUCLabel = tr("Continue until call");
QString continueUSLabel = tr("Continue until syscall"); QString continueUSLabel = tr("Continue until syscall");
QString stepLabel = tr("Step"); QString stepLabel = tr("Step");
QString stepOverLabel = tr("Step over"); QString stepOverLabel = tr("Step over");
QString stepOutLabel = tr("Step out"); QString stepOutLabel = tr("Step out");
suspendLabel = tr("Suspend the process");
continueLabel = tr("Continue");
// define actions // define actions
actionStart = new QAction(startDebugIcon, startDebugLabel, this); actionStart = new QAction(startDebugIcon, startDebugLabel, this);
@ -102,6 +103,26 @@ DebugActions::DebugActions(QToolBar *toolBar, MainWindow *main) :
// hide allactions // hide allactions
setAllActionsVisible(false); setAllActionsVisible(false);
// Toggle all buttons except restart, suspend(=continue) and stop since those are
// necessary to avoid staying stuck
toggleActions = { actionStep, actionStepOver, actionStepOut, actionContinueUntilMain,
actionContinueUntilCall, actionContinueUntilSyscall};
connect(Core(), &CutterCore::debugTaskStateChanged, this, [ = ]() {
bool disableToolbar = Core()->isDebugTaskInProgress() || !Core()->currentlyDebugging;
for (QAction *a : toggleActions) {
a->setDisabled(disableToolbar);
}
// Suspend should only be available when other icons are disabled
if (disableToolbar) {
actionContinue->setText(suspendLabel);
actionContinue->setIcon(suspendIcon);
} else {
actionContinue->setText(continueLabel);
actionContinue->setIcon(continueIcon);
}
});
connect(actionStop, &QAction::triggered, Core(), &CutterCore::stopDebug); connect(actionStop, &QAction::triggered, Core(), &CutterCore::stopDebug);
connect(actionStop, &QAction::triggered, [ = ]() { connect(actionStop, &QAction::triggered, [ = ]() {
actionStart->setVisible(true); actionStart->setVisible(true);
@ -150,10 +171,17 @@ DebugActions::DebugActions(QToolBar *toolBar, MainWindow *main) :
}); });
connect(actionStepOver, &QAction::triggered, Core(), &CutterCore::stepOverDebug); connect(actionStepOver, &QAction::triggered, Core(), &CutterCore::stepOverDebug);
connect(actionStepOut, &QAction::triggered, Core(), &CutterCore::stepOutDebug); connect(actionStepOut, &QAction::triggered, Core(), &CutterCore::stepOutDebug);
connect(actionContinue, &QAction::triggered, Core(), &CutterCore::continueDebug);
connect(actionContinueUntilMain, &QAction::triggered, this, &DebugActions::continueUntilMain); connect(actionContinueUntilMain, &QAction::triggered, this, &DebugActions::continueUntilMain);
connect(actionContinueUntilCall, &QAction::triggered, Core(), &CutterCore::continueUntilCall); connect(actionContinueUntilCall, &QAction::triggered, Core(), &CutterCore::continueUntilCall);
connect(actionContinueUntilSyscall, &QAction::triggered, Core(), &CutterCore::continueUntilSyscall); connect(actionContinueUntilSyscall, &QAction::triggered, Core(), &CutterCore::continueUntilSyscall);
connect(actionContinue, &QAction::triggered, Core(), [=]() {
// Switch between continue and suspend depending on the debugger's state
if (Core()->isDebugTaskInProgress()) {
Core()->suspendDebug();
} else {
Core()->continueDebug();
}
});
} }
void DebugActions::setButtonVisibleIfMainExists() void DebugActions::setButtonVisibleIfMainExists()

View File

@ -2,6 +2,8 @@
#include "core/Cutter.h" #include "core/Cutter.h"
#include <QAction>
class MainWindow; class MainWindow;
class QToolBar; class QToolBar;
class QToolButton; class QToolButton;
@ -28,7 +30,17 @@ public:
QAction *actionStop; QAction *actionStop;
QAction *actionAllContinues; QAction *actionAllContinues;
// Continue and suspend interchange during runtime
QIcon continueIcon;
QIcon suspendIcon;
QString suspendLabel;
QString continueLabel;
private: private:
/**
* @brief buttons that will be disabled/enabled on (disable/enable)DebugToolbar
*/
QList<QAction *> toggleActions;
MainWindow *main; MainWindow *main;
QList<QAction *> allActions; QList<QAction *> allActions;
QToolButton *continueUntilButton; QToolButton *continueUntilButton;

View File

@ -67,7 +67,12 @@ void ThreadsWidget::updateContents()
if (!refreshDeferrer->attemptRefresh(nullptr)) { if (!refreshDeferrer->attemptRefresh(nullptr)) {
return; return;
} }
setThreadsGrid(); setThreadsGrid();
if (Core()->isDebugTaskInProgress() || !Core()->currentlyDebugging) {
ui->viewThreads->setSelectionMode(QAbstractItemView::NoSelection);
}
} }
QString ThreadsWidget::translateStatus(QString status) QString ThreadsWidget::translateStatus(QString status)