From d0458597d192841a4a6d62f21e8c1bbad0242435 Mon Sep 17 00:00:00 2001 From: Itay Cohen Date: Mon, 22 Apr 2019 11:43:34 +0300 Subject: [PATCH] Show a tooltip with Asm\Hex preview on search hits (#1480) * implement search preview tooltip --- src/core/Cutter.cpp | 95 +++++++++++++++++++++++++++++++++ src/core/Cutter.h | 6 ++- src/widgets/FunctionsWidget.cpp | 30 +---------- src/widgets/SearchWidget.cpp | 34 ++++++++++++ 4 files changed, 135 insertions(+), 30 deletions(-) diff --git a/src/core/Cutter.cpp b/src/core/Cutter.cpp index 4ea90e3b..46dde094 100644 --- a/src/core/Cutter.cpp +++ b/src/core/Cutter.cpp @@ -2571,6 +2571,42 @@ QList CutterCore::disassembleLines(RVA offset, int lines) return r; } + +/** + * @brief return hexdump of from an by a given formats + * @param address - the address from which to print the hexdump + * @param size - number of bytes to print + * @param format - the type of hexdump (qwords, words. decimal, etc) + */ +QString CutterCore::hexdump(RVA address, int size, HexdumpFormats format) +{ + QString command = "px"; + switch (format) { + case HexdumpFormats::Normal: + break; + case HexdumpFormats::Half: + command += "h"; + break; + case HexdumpFormats::Word: + command += "w"; + break; + case HexdumpFormats::Quad: + command += "q"; + break; + case HexdumpFormats::Signed: + command += "d"; + break; + case HexdumpFormats::Octal: + command += "o"; + break; + } + + return cmd(QString("%1 %2 @ %3") + .arg(command) + .arg(size) + .arg(address)); +} + QByteArray CutterCore::hexStringToBytes(const QString &hex) { QByteArray hexChars = hex.toUtf8(); @@ -2672,3 +2708,62 @@ BasicBlockHighlighter* CutterCore::getBBHighlighter() { return bbHighlighter; } + + +/** + * @brief get a compact disassembly preview for tooltips + * @param address - the address from which to print the disassembly + * @param num_of_lines - number of instructions to print + */ +QStringList CutterCore::getDisassemblyPreview(RVA address, int num_of_lines) +{ + QList disassemblyLines; + { + // temporarily simplify the disasm output to get it colorful and simple to read + TempConfig tempConfig; + tempConfig + .set("scr.color", COLOR_MODE_16M) + .set("asm.lines", false) + .set("asm.var", false) + .set("asm.comments", false) + .set("asm.bytes", false) + .set("asm.lines.fcn", false) + .set("asm.lines.out", false) + .set("asm.lines.bb", false) + .set("asm.bb.line", false); + + disassemblyLines = disassembleLines(address, num_of_lines + 1); + } + QStringList disasmPreview; + for (const DisassemblyLine &line : disassemblyLines) { + disasmPreview << line.text; + if (disasmPreview.length() >= num_of_lines) { + disasmPreview << "..."; + break; + } + } + if (!disasmPreview.isEmpty()) { + return disasmPreview; + } else { + return QStringList(); + } +} + +/** + * @brief get a compact hexdump preview for tooltips + * @param address - the address from which to print the hexdump + * @param size - number of bytes to print + */ +QString CutterCore::getHexdumpPreview(RVA address, int size) +{ + // temporarily simplify the disasm output to get it colorful and simple to read + TempConfig tempConfig; + tempConfig + .set("scr.color", COLOR_MODE_16M) + .set("asm.offset", true) + .set("hex.header", false) + .set("hex.cols", 16); + return ansiEscapeToHtml(hexdump(address, size, HexdumpFormats::Normal)).replace(QLatin1Char('\n'), "
"); +} + + diff --git a/src/core/Cutter.h b/src/core/Cutter.h index 0b2369f1..4e1fcbd1 100644 --- a/src/core/Cutter.h +++ b/src/core/Cutter.h @@ -78,6 +78,7 @@ public: QString cmdFunctionAt(QString addr); QString cmdFunctionAt(RVA addr); QString createFunctionAt(RVA addr, QString name); + QStringList getDisassemblyPreview(RVA address, int num_of_lines); /* Flags */ void delFlag(RVA addr); @@ -184,7 +185,7 @@ public: QString getConfig(const QString &k) { return getConfig(k.toUtf8().constData()); } QList getColorThemes(); - /* Assembly related methods */ + /* Assembly\Hexdump related methods */ QByteArray assemble(const QString &code); QString disassemble(const QByteArray &data); QString disassembleSingleInstruction(RVA addr); @@ -192,6 +193,9 @@ public: static QByteArray hexStringToBytes(const QString &hex); static QString bytesToHexString(const QByteArray &bytes); + enum class HexdumpFormats { Normal, Half, Word, Quad, Signed, Octal }; + QString hexdump(RVA offset, int size, HexdumpFormats format); + QString getHexdumpPreview(RVA offset, int size); void setCPU(QString arch, QString cpu, int bits); void setEndianness(bool big); diff --git a/src/widgets/FunctionsWidget.cpp b/src/widgets/FunctionsWidget.cpp index b6d1794c..f975ae09 100644 --- a/src/widgets/FunctionsWidget.cpp +++ b/src/widgets/FunctionsWidget.cpp @@ -198,37 +198,9 @@ QVariant FunctionModel::data(const QModelIndex &index, int role) const return static_cast(Qt::AlignLeft | Qt::AlignVCenter); case Qt::ToolTipRole: { - QList disassemblyLines; - { - // temporarily simplify the disasm output to get it colorful and simple to read - TempConfig tempConfig; - tempConfig - .set("scr.color", COLOR_MODE_16M) - .set("asm.lines", false) - .set("asm.var", false) - .set("asm.comments", false) - .set("asm.bytes", false) - .set("asm.lines.fcn", false) - .set("asm.lines.out", false) - .set("asm.lines.bb", false) - .set("asm.bb.line", false); - - disassemblyLines = Core()->disassembleLines(function.offset, kMaxTooltipDisasmPreviewLines + 1); - } - QStringList disasmPreview; - for (const DisassemblyLine &line : disassemblyLines) { - if (!function.contains(line.offset)) { - break; - } - disasmPreview << line.text; - if (disasmPreview.length() >= kMaxTooltipDisasmPreviewLines) { - disasmPreview << "..."; - break; - } - } + QStringList disasmPreview = Core()->getDisassemblyPreview(function.offset, kMaxTooltipDisasmPreviewLines); const QStringList &summary = Core()->cmdList(QString("pdsf @ %1").arg(function.offset)); - const QFont &fnt = Config()->getFont(); QFontMetrics fm{ fnt }; diff --git a/src/widgets/SearchWidget.cpp b/src/widgets/SearchWidget.cpp index e88aae8f..a593d8b2 100644 --- a/src/widgets/SearchWidget.cpp +++ b/src/widgets/SearchWidget.cpp @@ -8,6 +8,14 @@ #include #include +namespace { + +static const int kMaxTooltipWidth = 500; +static const int kMaxTooltipDisasmPreviewLines = 10; +static const int kMaxTooltipHexdumpBytes = 64; + +} + static const QMap kSearchBoundariesValues { {"io.maps", "All maps"}, {"io.map", "Current map"}, @@ -58,6 +66,31 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const default: return QVariant(); } + case Qt::ToolTipRole: { + + QString previewContent = QString(); + // if result is CODE, show disassembly + if (!exp.code.isEmpty()) { + previewContent = Core()->getDisassemblyPreview(exp.offset, kMaxTooltipDisasmPreviewLines) + .join("
"); + // if result is DATA or Disassembly is N/A + } else if (!exp.data.isEmpty() || previewContent.isEmpty()) { + previewContent = Core()->getHexdumpPreview(exp.offset, kMaxTooltipHexdumpBytes); + } + + const QFont &fnt = Config()->getFont(); + QFontMetrics fm{ fnt }; + + QString toolTipContent = QString("
") + .arg(fnt.family()) + .arg(qMax(6, fnt.pointSize() - 1)); // slightly decrease font size, to keep more text in the same box + + toolTipContent += tr("
Preview:
%1
") + .arg(previewContent); + + toolTipContent += "
"; + return toolTipContent; + } case SearchDescriptionRole: return QVariant::fromValue(exp); default: @@ -130,6 +163,7 @@ SearchWidget::SearchWidget(MainWindow *main, QAction *action) : ui(new Ui::SearchWidget) { ui->setupUi(this); + setStyleSheet(QString("QToolTip { max-width: %1px; opacity: 230; }").arg(kMaxTooltipWidth)); ui->searchInCombo->blockSignals(true); QMap::const_iterator mapIter;