Make Decompilation asynchronous (#1721)

This commit is contained in:
Florian Märkl 2019-08-28 19:01:12 +02:00 committed by GitHub
parent d49caae37b
commit 76b9354673
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 55 deletions

View File

@ -51,6 +51,7 @@ Decompiler::Decompiler(const QString &id, const QString &name, QObject *parent)
R2DecDecompiler::R2DecDecompiler(QObject *parent) R2DecDecompiler::R2DecDecompiler(QObject *parent)
: Decompiler("r2dec", "r2dec", parent) : Decompiler("r2dec", "r2dec", parent)
{ {
task = nullptr;
} }
bool R2DecDecompiler::isAvailable() bool R2DecDecompiler::isAvailable()
@ -58,47 +59,59 @@ bool R2DecDecompiler::isAvailable()
return Core()->cmdList("e cmd.pdc=?").contains(QStringLiteral("pdd")); return Core()->cmdList("e cmd.pdc=?").contains(QStringLiteral("pdd"));
} }
AnnotatedCode R2DecDecompiler::decompileAt(RVA addr) void R2DecDecompiler::decompileAt(RVA addr)
{ {
AnnotatedCode code = {}; if (task) {
QString s; return;
QJsonObject json = Core()->cmdj("pddj @ " + QString::number(addr)).object();
if (json.isEmpty()) {
return code;
} }
for (const auto &line : json["log"].toArray()) { task = new R2Task("pddj @ " + QString::number(addr));
if (!line.isString()) { connect(task, &R2Task::finished, this, [this]() {
continue; AnnotatedCode code = {};
} QString s;
code.code.append(line.toString() + "\n");
}
auto linesArray = json["lines"].toArray(); QJsonObject json = task->getResultJson().object();
for (const auto &line : linesArray) { delete task;
QJsonObject lineObject = line.toObject(); task = nullptr;
if (lineObject.isEmpty()) { if (json.isEmpty()) {
continue; code.code = tr("Failed to parse JSON from r2dec");
emit finished(code);
return;
} }
CodeAnnotation annotation = {};
annotation.type = CodeAnnotation::Type::Offset;
annotation.start = code.code.length();
code.code.append(lineObject["str"].toString() + "\n");
annotation.end = code.code.length();
bool ok;
annotation.offset.offset = lineObject["offset"].toVariant().toULongLong(&ok);
if (ok) {
code.annotations.push_back(annotation);
}
}
for (const auto &line : json["errors"].toArray()) { for (const auto &line : json["log"].toArray()) {
if (!line.isString()) { if (!line.isString()) {
continue; continue;
}
code.code.append(line.toString() + "\n");
} }
code.code.append(line.toString() + "\n");
}
return code; auto linesArray = json["lines"].toArray();
} for (const auto &line : linesArray) {
QJsonObject lineObject = line.toObject();
if (lineObject.isEmpty()) {
continue;
}
CodeAnnotation annotation = {};
annotation.type = CodeAnnotation::Type::Offset;
annotation.start = code.code.length();
code.code.append(lineObject["str"].toString() + "\n");
annotation.end = code.code.length();
bool ok;
annotation.offset.offset = lineObject["offset"].toVariant().toULongLong(&ok);
if (ok) {
code.annotations.push_back(annotation);
}
}
for (const auto &line : json["errors"].toArray()) {
if (!line.isString()) {
continue;
}
code.code.append(line.toString() + "\n");
}
emit finished(code);
});
task->startTask();
}

View File

@ -2,6 +2,7 @@
#define DECOMPILER_H #define DECOMPILER_H
#include "CutterCommon.h" #include "CutterCommon.h"
#include "R2Task.h"
#include <QString> #include <QString>
#include <QObject> #include <QObject>
@ -54,19 +55,30 @@ public:
Decompiler(const QString &id, const QString &name, QObject *parent = nullptr); Decompiler(const QString &id, const QString &name, QObject *parent = nullptr);
virtual ~Decompiler() = default; virtual ~Decompiler() = default;
QString getId() const { return id; } QString getId() const { return id; }
QString getName() const { return name; } QString getName() const { return name; }
virtual bool isRunning() { return false; }
virtual bool isCancelable() { return false; }
virtual AnnotatedCode decompileAt(RVA addr) =0; virtual void decompileAt(RVA addr) =0;
virtual void cancel() {}
signals:
void finished(AnnotatedCode code);
}; };
class R2DecDecompiler: public Decompiler class R2DecDecompiler: public Decompiler
{ {
Q_OBJECT Q_OBJECT
private:
R2Task *task;
public: public:
explicit R2DecDecompiler(QObject *parent = nullptr); explicit R2DecDecompiler(QObject *parent = nullptr);
AnnotatedCode decompileAt(RVA addr) override; void decompileAt(RVA addr) override;
bool isRunning() override { return task != nullptr; }
static bool isAvailable(); static bool isAvailable();
}; };

View File

@ -47,6 +47,11 @@ QString R2Task::getResult()
return QString::fromUtf8(task->res); return QString::fromUtf8(task->res);
} }
QJsonDocument R2Task::getResultJson()
{
return Core()->parseJson(task->res, task->cmd);
}
const char *R2Task::getResultRaw() const char *R2Task::getResultRaw()
{ {
return task->res; return task->res;

View File

@ -23,6 +23,7 @@ public:
void joinTask(); void joinTask();
QString getResult(); QString getResult();
QJsonDocument getResultJson();
const char *getResultRaw(); const char *getResultRaw();
signals: signals:

View File

@ -41,13 +41,14 @@ PseudocodeWidget::PseudocodeWidget(MainWindow *main, QAction *action) :
if (dec->getId() == selectedDecompilerId) { if (dec->getId() == selectedDecompilerId) {
ui->decompilerComboBox->setCurrentIndex(ui->decompilerComboBox->count() - 1); ui->decompilerComboBox->setCurrentIndex(ui->decompilerComboBox->count() - 1);
} }
connect(dec, &Decompiler::finished, this, &PseudocodeWidget::decompilationFinished);
} }
if(decompilers.size() <= 1) { decompilerSelectionEnabled = decompilers.size() > 1;
ui->decompilerComboBox->setEnabled(false); ui->decompilerComboBox->setEnabled(decompilerSelectionEnabled);
if (decompilers.isEmpty()) {
ui->textEdit->setPlainText(tr("No Decompiler available.")); if (decompilers.isEmpty()) {
} ui->textEdit->setPlainText(tr("No Decompiler available."));
} }
connect(ui->decompilerComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &PseudocodeWidget::decompilerSelected); connect(ui->decompilerComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &PseudocodeWidget::decompilerSelected);
@ -61,6 +62,7 @@ PseudocodeWidget::PseudocodeWidget(MainWindow *main, QAction *action) :
connect(mCtxMenu, &QMenu::triggered, this, &PseudocodeWidget::refreshPseudocode); connect(mCtxMenu, &QMenu::triggered, this, &PseudocodeWidget::refreshPseudocode);
addActions(mCtxMenu->actions()); addActions(mCtxMenu->actions());
ui->progressLabel->setVisible(false);
doRefresh(RVA_INVALID); doRefresh(RVA_INVALID);
} }
@ -74,7 +76,7 @@ void PseudocodeWidget::doRefresh(RVA addr)
} }
Decompiler *dec = Core()->getDecompilerById(ui->decompilerComboBox->currentData().toString()); Decompiler *dec = Core()->getDecompilerById(ui->decompilerComboBox->currentData().toString());
if (!dec) { if (!dec || dec->isRunning()) {
return; return;
} }
@ -83,10 +85,34 @@ void PseudocodeWidget::doRefresh(RVA addr)
return; return;
} }
code = dec->decompileAt(addr); dec->decompileAt(addr);
if (dec->isRunning()) {
ui->progressLabel->setVisible(true);
ui->decompilerComboBox->setEnabled(false);
if (dec->isCancelable()) {
ui->refreshButton->setText(tr("Cancel"));
} else {
ui->refreshButton->setEnabled(false);
}
return;
}
}
void PseudocodeWidget::refreshPseudocode()
{
doRefresh(Core()->getOffset());
}
void PseudocodeWidget::decompilationFinished(AnnotatedCode code)
{
ui->progressLabel->setVisible(false);
ui->decompilerComboBox->setEnabled(decompilerSelectionEnabled);
ui->refreshButton->setText(tr("Refresh"));
ui->refreshButton->setEnabled(true);
this->code = code;
if (code.code.isEmpty()) { if (code.code.isEmpty()) {
ui->textEdit->setPlainText(tr("Cannot decompile at") + " " + RAddressString( ui->textEdit->setPlainText(tr("Cannot decompile at this address (Not a function?)"));
addr) + " " + tr("(Not a function?)"));
return; return;
} else { } else {
connectCursorPositionChanged(true); connectCursorPositionChanged(true);
@ -96,11 +122,6 @@ void PseudocodeWidget::doRefresh(RVA addr)
} }
} }
void PseudocodeWidget::refreshPseudocode()
{
doRefresh(Core()->getOffset());
}
void PseudocodeWidget::decompilerSelected() void PseudocodeWidget::decompilerSelected()
{ {
Config()->setSelectedDecompiler(ui->decompilerComboBox->currentData().toString()); Config()->setSelectedDecompiler(ui->decompilerComboBox->currentData().toString());

View File

@ -36,11 +36,13 @@ private slots:
void decompilerSelected(); void decompilerSelected();
void cursorPositionChanged(); void cursorPositionChanged();
void seekChanged(); void seekChanged();
void decompilationFinished(AnnotatedCode code);
private: private:
std::unique_ptr<Ui::PseudocodeWidget> ui; std::unique_ptr<Ui::PseudocodeWidget> ui;
QSyntaxHighlighter *syntaxHighlighter; QSyntaxHighlighter *syntaxHighlighter;
bool decompilerSelectionEnabled;
AnnotatedCode code; AnnotatedCode code;

View File

@ -49,6 +49,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="progressLayout">
<property name="leftMargin">
<number>8</number>
</property>
<item>
<widget class="QLabel" name="progressLabel">
<property name="text">
<string>Decompiling...</string>
</property>
</widget>
</item>
</layout>
</item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
@ -70,7 +84,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="decompilerComboBox" /> <widget class="QComboBox" name="decompilerComboBox"/>
</item> </item>
</layout> </layout>
</item> </item>