diff --git a/src/Cutter.pro b/src/Cutter.pro index f4f06110..1ed8f3d7 100644 --- a/src/Cutter.pro +++ b/src/Cutter.pro @@ -349,7 +349,8 @@ SOURCES += \ common/HighDpiPixmap.cpp \ widgets/GraphGridLayout.cpp \ widgets/HexWidget.cpp \ - common/SelectionHighlight.cpp + common/SelectionHighlight.cpp \ + common/Decompiler.cpp HEADERS += \ core/Cutter.h \ @@ -476,7 +477,8 @@ HEADERS += \ widgets/GraphLayout.h \ widgets/GraphGridLayout.h \ widgets/HexWidget.h \ - common/SelectionHighlight.h + common/SelectionHighlight.h \ + common/Decompiler.h FORMS += \ dialogs/AboutDialog.ui \ diff --git a/src/CutterApplication.cpp b/src/CutterApplication.cpp index 63ac70b7..374a6f32 100644 --- a/src/CutterApplication.cpp +++ b/src/CutterApplication.cpp @@ -3,6 +3,7 @@ #include "CutterApplication.h" #include "plugins/PluginManager.h" #include "CutterConfig.h" +#include "common/Decompiler.h" #include #include @@ -118,6 +119,10 @@ CutterApplication::CutterApplication(int &argc, char **argv) : QApplication(argc Config()->loadInitial(); Core()->loadCutterRC(); + if (R2DecDecompiler::isAvailable()) { + Core()->registerDecompiler(new R2DecDecompiler(Core())); + } + bool analLevelSpecified = false; int analLevel = 0; diff --git a/src/common/Decompiler.cpp b/src/common/Decompiler.cpp new file mode 100644 index 00000000..b06aca91 --- /dev/null +++ b/src/common/Decompiler.cpp @@ -0,0 +1,67 @@ + +#include "Decompiler.h" +#include "Cutter.h" + +#include +#include + +Decompiler::Decompiler(const QString &id, const QString &name, QObject *parent) + : QObject(parent), + id(id), + name(name) +{ +} + +R2DecDecompiler::R2DecDecompiler(QObject *parent) + : Decompiler("r2dec", "r2dec", parent) +{ +} + +bool R2DecDecompiler::isAvailable() +{ + return Core()->cmdList("e cmd.pdc=?").contains(QStringLiteral("pdd")); +} + +DecompiledCode R2DecDecompiler::decompileAt(RVA addr) +{ + DecompiledCode code; + QString s; + + QJsonObject json = Core()->cmdj("pddj @ " + QString::number(addr)).object(); + if (json.isEmpty()) { + return code; + } + + for (const auto &line : json["log"].toArray()) { + if (!line.isString()) { + continue; + } + code.lines.append(DecompiledCode::Line(line.toString())); + } + + auto linesArray = json["lines"].toArray(); + code.lines.reserve(code.lines.size() + linesArray.size()); + for (const auto &line : linesArray) { + QJsonObject lineObject = line.toObject(); + if (lineObject.isEmpty()) { + continue; + } + DecompiledCode::Line codeLine; + codeLine.str = lineObject["str"].toString(); + bool ok; + codeLine.addr = lineObject["offset"].toVariant().toULongLong(&ok); + if (!ok) { + codeLine.addr = RVA_INVALID; + } + code.lines.append(codeLine); + } + + for (const auto &line : json["errors"].toArray()) { + if (!line.isString()) { + continue; + } + code.lines.append(DecompiledCode::Line(line.toString())); + } + + return code; +} \ No newline at end of file diff --git a/src/common/Decompiler.h b/src/common/Decompiler.h new file mode 100644 index 00000000..0bb37471 --- /dev/null +++ b/src/common/Decompiler.h @@ -0,0 +1,71 @@ +#ifndef DECOMPILER_H +#define DECOMPILER_H + +#include "CutterCommon.h" + +#include +#include + +/** + * Describes the result of a Decompilation Process with optional metadata + */ +struct DecompiledCode { + /** + * A single line of decompiled code + */ + struct Line + { + QString str; + + /** + * Offset of the original instruction + */ + RVA addr; + + Line() + { + this->addr = RVA_INVALID; + } + + explicit Line(const QString &str, RVA addr = RVA_INVALID) + { + this->str = str; + this->addr = addr; + } + }; + QList lines = {}; +}; + +/** + * Implements a decompiler that can be registered using CutterCore::registerDecompiler() + */ +class Decompiler: public QObject +{ + Q_OBJECT + +private: + const QString id; + const QString name; + +public: + Decompiler(const QString &id, const QString &name, QObject *parent = nullptr); + virtual ~Decompiler() = default; + + QString getId() const { return id; } + QString getName() const { return name; } + + virtual DecompiledCode decompileAt(RVA addr) =0; +}; + +class R2DecDecompiler: public Decompiler +{ + Q_OBJECT + +public: + explicit R2DecDecompiler(QObject *parent = nullptr); + DecompiledCode decompileAt(RVA addr) override; + + static bool isAvailable(); +}; + +#endif //DECOMPILER_H diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 88da92f0..7d7019d8 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -10,6 +10,7 @@ #include "common/R2Task.h" #include "common/Json.h" #include "core/Cutter.h" +#include "Decompiler.h" #include "r_asm.h" #include "sdb.h" @@ -932,67 +933,32 @@ RVA CutterCore::getOffsetJump(RVA addr) return value; } -DecompiledCode CutterCore::getDecompiledCodePDC(RVA addr) + +QList CutterCore::getDecompilers() { - DecompiledCode code; - auto lines = cmd("pdc @ " + QString::number(addr)).split('\n'); - code.lines.reserve(lines.size()); - for (const auto &line : lines) { - code.lines.append(DecompiledCode::Line(line)); - } - return code; + return decompilers; } -bool CutterCore::getR2DecAvailable() +Decompiler *CutterCore::getDecompilerById(const QString &id) { - return cmdList("e cmd.pdc=?").contains(QStringLiteral("pdd")); + for (Decompiler *dec : decompilers) { + if (dec->getId() == id) { + return dec; + } + } + return nullptr; } -DecompiledCode CutterCore::getDecompiledCodeR2Dec(RVA addr) +bool CutterCore::registerDecompiler(Decompiler *decompiler) { - DecompiledCode code; - QString s; - - QJsonObject json = cmdj("pddj @ " + QString::number(addr)).object(); - if (json.isEmpty()) { - return code; + if (getDecompilerById(decompiler->getId())) { + return false; } - - for (const auto &line : json["log"].toArray()) { - if (!line.isString()) { - continue; - } - code.lines.append(DecompiledCode::Line(line.toString())); - } - - auto linesArray = json["lines"].toArray(); - code.lines.reserve(code.lines.size() + linesArray.size()); - for (const auto &line : linesArray) { - QJsonObject lineObject = line.toObject(); - if (lineObject.isEmpty()) { - continue; - } - DecompiledCode::Line codeLine; - codeLine.str = lineObject["str"].toString(); - bool ok; - codeLine.addr = lineObject["offset"].toVariant().toULongLong(&ok); - if (!ok) { - codeLine.addr = RVA_INVALID; - } - code.lines.append(codeLine); - } - - for (const auto &line : json["errors"].toArray()) { - if (!line.isString()) { - continue; - } - code.lines.append(DecompiledCode::Line(line.toString())); - } - - return code; + decompiler->setParent(this); + decompilers.push_back(decompiler); + return true; } - QJsonDocument CutterCore::getFileInfo() { return cmdj("ij"); diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 6e823bd0..eae18004 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -14,6 +14,8 @@ class AsyncTaskManager; class CutterCore; +class Decompiler; + #include "plugins/CutterPlugin.h" #include "common/BasicBlockHighlighter.h" @@ -247,10 +249,19 @@ public: int currentlyAttachedToPID = -1; QString currentlyOpenFile; - /* Pseudocode */ - DecompiledCode getDecompiledCodePDC(RVA addr); - bool getR2DecAvailable(); - DecompiledCode getDecompiledCodeR2Dec(RVA addr); + /* Decompilers */ + QList getDecompilers(); + Decompiler *getDecompilerById(const QString &id); + + /** + * Register a new decompiler + * + * The decompiler must have a unique id, otherwise this method will fail. + * The decompiler's parent will be set to this CutterCore instance, so it will automatically be freed later. + * + * @return whether the decompiler was registered successfully + */ + bool registerDecompiler(Decompiler *decompiler); RVA getOffsetJump(RVA addr); QJsonDocument getFileInfo(); @@ -449,6 +460,8 @@ private: RVA offsetPriorDebugging = RVA_INVALID; QErrorMessage msgBox; + QList decompilers; + bool emptyGraph = false; BasicBlockHighlighter *bbHighlighter; }; diff --git a/src/core/CutterDescriptions.h b/src/core/CutterDescriptions.h index 904d722a..994738b3 100644 --- a/src/core/CutterDescriptions.h +++ b/src/core/CutterDescriptions.h @@ -304,35 +304,6 @@ struct VariableDescription { QString type; }; -/** - * Describes the result of a Decompilation Process with optional metadata - */ -struct DecompiledCode { - /** - * A single line of decompiled code - */ - struct Line { - QString str; - - /** - * Offset of the original instruction - */ - RVA addr; - - Line() - { - this->addr = RVA_INVALID; - } - - explicit Line(const QString &str, RVA addr = RVA_INVALID) - { - this->str = str; - this->addr = addr; - } - }; - QList lines = {}; -}; - Q_DECLARE_METATYPE(FunctionDescription) Q_DECLARE_METATYPE(ImportDescription) Q_DECLARE_METATYPE(ExportDescription) @@ -370,6 +341,5 @@ Q_DECLARE_METATYPE(BreakpointDescription) Q_DECLARE_METATYPE(ProcessDescription) Q_DECLARE_METATYPE(RegisterRefDescription) Q_DECLARE_METATYPE(VariableDescription) -Q_DECLARE_METATYPE(DecompiledCode::Line) #endif // DESCRIPTIONS_H diff --git a/src/widgets/PseudocodeWidget.cpp b/src/widgets/PseudocodeWidget.cpp index 119e0e46..9e0962a4 100644 --- a/src/widgets/PseudocodeWidget.cpp +++ b/src/widgets/PseudocodeWidget.cpp @@ -5,6 +5,7 @@ #include "common/Helpers.h" #include "common/TempConfig.h" #include "common/SelectionHighlight.h" +#include "common/Decompiler.h" #include #include @@ -58,12 +59,16 @@ PseudocodeWidget::PseudocodeWidget(MainWindow *main, QAction *action) : doRefresh(Core()->getOffset()); }); - if (Core()->getR2DecAvailable()) { - ui->decompilerComboBox->setEnabled(true); - ui->decompilerComboBox->setCurrentIndex(DecompilerCBR2Dec); - } else { + auto decompilers = Core()->getDecompilers(); + for (auto dec : decompilers) { + ui->decompilerComboBox->addItem(dec->getName(), dec->getId()); + } + + if(decompilers.size() <= 1) { ui->decompilerComboBox->setEnabled(false); - ui->decompilerComboBox->setCurrentIndex(DecompilerCBPdc); + if (decompilers.isEmpty()) { + ui->textEdit->setPlainText(tr("No Decompiler available.")); + } } connectCursorPositionChanged(false); @@ -77,23 +82,21 @@ PseudocodeWidget::~PseudocodeWidget() = default; void PseudocodeWidget::doRefresh(RVA addr) { + if (ui->decompilerComboBox->currentIndex() < 0) { + return; + } + + Decompiler *dec = Core()->getDecompilerById(ui->decompilerComboBox->currentData().toString()); + if (!dec) { + return; + } + if (addr == RVA_INVALID) { ui->textEdit->setPlainText(tr("Click Refresh to generate Pseudocode from current offset.")); return; } - DecompiledCode decompiledCode; - switch (ui->decompilerComboBox->currentIndex()) { - case DecompilerCBR2Dec: - if (Core()->getR2DecAvailable()) { - decompiledCode = Core()->getDecompiledCodeR2Dec(addr); - break; - } // else fallthrough - case DecompilerCBPdc: - default: - decompiledCode = Core()->getDecompiledCodePDC(addr); - break; - } + DecompiledCode decompiledCode = dec->decompileAt(addr); textLines = {}; textLines.reserve(decompiledCode.lines.size()); diff --git a/src/widgets/PseudocodeWidget.h b/src/widgets/PseudocodeWidget.h index 6f53ab73..5d29302e 100644 --- a/src/widgets/PseudocodeWidget.h +++ b/src/widgets/PseudocodeWidget.h @@ -31,7 +31,6 @@ private slots: void seekChanged(); private: - enum DecompilerComboBoxValues { DecompilerCBR2Dec, DecompilerCBPdc }; std::unique_ptr ui; QSyntaxHighlighter *syntaxHighlighter; diff --git a/src/widgets/PseudocodeWidget.ui b/src/widgets/PseudocodeWidget.ui index 3ec61cf0..733d2f7f 100644 --- a/src/widgets/PseudocodeWidget.ui +++ b/src/widgets/PseudocodeWidget.ui @@ -70,18 +70,7 @@ - - - - r2dec - - - - - pdc - - - +