Show a tooltip with Asm\Hex preview on search hits (#1480)

* implement search preview tooltip
This commit is contained in:
Itay Cohen 2019-04-22 11:43:34 +03:00 committed by GitHub
parent f1fa05e647
commit d0458597d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 135 additions and 30 deletions

View File

@ -2571,6 +2571,42 @@ QList<DisassemblyLine> CutterCore::disassembleLines(RVA offset, int lines)
return r;
}
/**
* @brief return hexdump of <size> from an <offset> 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<DisassemblyLine> 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'), "<br>");
}

View File

@ -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<QString> 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);

View File

@ -198,37 +198,9 @@ QVariant FunctionModel::data(const QModelIndex &index, int role) const
return static_cast<int>(Qt::AlignLeft | Qt::AlignVCenter);
case Qt::ToolTipRole: {
QList<DisassemblyLine> 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 };

View File

@ -8,6 +8,14 @@
#include <QComboBox>
#include <QShortcut>
namespace {
static const int kMaxTooltipWidth = 500;
static const int kMaxTooltipDisasmPreviewLines = 10;
static const int kMaxTooltipHexdumpBytes = 64;
}
static const QMap<QString, QString> 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("<br>");
// 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("<html><div style=\"font-family: %1; font-size: %2pt; white-space: nowrap;\">")
.arg(fnt.family())
.arg(qMax(6, fnt.pointSize() - 1)); // slightly decrease font size, to keep more text in the same box
toolTipContent += tr("<div style=\"margin-bottom: 10px;\"><strong>Preview</strong>:<br>%1</div>")
.arg(previewContent);
toolTipContent += "</div></html>";
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<QString, QString>::const_iterator mapIter;