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)
: Decompiler("r2dec", "r2dec", parent)
{
task = nullptr;
}
bool R2DecDecompiler::isAvailable()
@ -58,47 +59,59 @@ bool R2DecDecompiler::isAvailable()
return Core()->cmdList("e cmd.pdc=?").contains(QStringLiteral("pdd"));
}
AnnotatedCode R2DecDecompiler::decompileAt(RVA addr)
void R2DecDecompiler::decompileAt(RVA addr)
{
AnnotatedCode code = {};
QString s;
QJsonObject json = Core()->cmdj("pddj @ " + QString::number(addr)).object();
if (json.isEmpty()) {
return code;
if (task) {
return;
}
for (const auto &line : json["log"].toArray()) {
if (!line.isString()) {
continue;
}
code.code.append(line.toString() + "\n");
}
task = new R2Task("pddj @ " + QString::number(addr));
connect(task, &R2Task::finished, this, [this]() {
AnnotatedCode code = {};
QString s;
auto linesArray = json["lines"].toArray();
for (const auto &line : linesArray) {
QJsonObject lineObject = line.toObject();
if (lineObject.isEmpty()) {
continue;
QJsonObject json = task->getResultJson().object();
delete task;
task = nullptr;
if (json.isEmpty()) {
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()) {
if (!line.isString()) {
continue;
for (const auto &line : json["log"].toArray()) {
if (!line.isString()) {
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
#include "CutterCommon.h"
#include "R2Task.h"
#include <QString>
#include <QObject>
@ -54,19 +55,30 @@ public:
Decompiler(const QString &id, const QString &name, QObject *parent = nullptr);
virtual ~Decompiler() = default;
QString getId() const { return id; }
QString getName() const { return name; }
QString getId() const { return id; }
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
{
Q_OBJECT
private:
R2Task *task;
public:
explicit R2DecDecompiler(QObject *parent = nullptr);
AnnotatedCode decompileAt(RVA addr) override;
void decompileAt(RVA addr) override;
bool isRunning() override { return task != nullptr; }
static bool isAvailable();
};

View File

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

View File

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

View File

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

View File

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

View File

@ -49,6 +49,20 @@
</property>
</widget>
</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>
<spacer name="horizontalSpacer">
<property name="orientation">
@ -70,7 +84,7 @@
</widget>
</item>
<item>
<widget class="QComboBox" name="decompilerComboBox" />
<widget class="QComboBox" name="decompilerComboBox"/>
</item>
</layout>
</item>