Show disassembly var tooltips (#3206)

This commit is contained in:
frmdstryr 2023-07-30 04:37:52 -04:00 committed by GitHub
parent d1da807de1
commit 7418f9c76c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 301 additions and 162 deletions

4
.gitignore vendored
View File

@ -94,3 +94,7 @@ docs/source/_build
# Local gdb files # Local gdb files
.gdb_history .gdb_history
.gdbinit .gdbinit
# Kdevelop
.kdev/
*.kdev4

2
rizin

@ -1 +1 @@
Subproject commit 831c472f099dc76012273b7ab5667769aec66f1a Subproject commit a3ad6221fb2c727bd180709c764f1a23326f7f52

View File

@ -783,6 +783,16 @@ bool Configuration::getPreviewValue() const
return s.value("asm.preview").toBool(); return s.value("asm.preview").toBool();
} }
void Configuration::setShowVarTooltips(bool enabled)
{
s.setValue("showVarTooltips", enabled);
}
bool Configuration::getShowVarTooltips() const
{
return s.value("showVarTooltips").toBool();
}
bool Configuration::getGraphBlockEntryOffset() bool Configuration::getGraphBlockEntryOffset()
{ {
return s.value("graphBlockEntryOffset", true).value<bool>(); return s.value("graphBlockEntryOffset", true).value<bool>();

View File

@ -215,6 +215,12 @@ public:
void setPreviewValue(bool checked); void setPreviewValue(bool checked);
bool getPreviewValue() const; bool getPreviewValue() const;
/**
* @brief Show tooltips for known values of registers, variables, and memory when debugging
*/
void setShowVarTooltips(bool enabled);
bool getShowVarTooltips() const;
/** /**
* @brief Recently opened binaries, as shown in NewFileDialog. * @brief Recently opened binaries, as shown in NewFileDialog.
*/ */

View File

@ -82,3 +82,75 @@ RVA DisassemblyPreview::readDisassemblyOffset(QTextCursor tc)
return userData->line.offset; return userData->line.offset;
} }
typedef struct mmio_lookup_context
{
QString selected;
RVA mmio_address;
} mmio_lookup_context_t;
static bool lookup_mmio_addr_cb(void *user, const ut64 key, const void *value)
{
mmio_lookup_context_t *ctx = (mmio_lookup_context_t *)user;
if (ctx->selected == (const char *)value) {
ctx->mmio_address = key;
return false;
}
return true;
}
bool DisassemblyPreview::showDebugValueTooltip(QWidget *parent, const QPoint &pointOfEvent,
const QString &selectedText, const RVA offset)
{
if (selectedText.isEmpty())
return false;
if (selectedText.at(0).isLetter()) {
{
const auto registerRefs = Core()->getRegisterRefValues();
for (auto &reg : registerRefs) {
if (reg.name == selectedText) {
auto msg = QString("reg %1 = %2").arg(reg.name, reg.value);
QToolTip::showText(pointOfEvent, msg, parent);
return true;
}
}
}
if (offset != RVA_INVALID) {
auto vars = Core()->getVariables(offset);
for (auto &var : vars) {
if (var.name == selectedText) {
auto msg = QString("var %1 = %2").arg(var.name, var.value);
QToolTip::showText(pointOfEvent, msg, parent);
return true;
}
}
}
{
// Lookup MMIO address
mmio_lookup_context_t ctx;
ctx.selected = selectedText;
ctx.mmio_address = RVA_INVALID;
auto core = Core()->core();
RzPlatformTarget *arch_target = core->analysis->arch_target;
if (arch_target && arch_target->profile) {
ht_up_foreach(arch_target->profile->registers_mmio, lookup_mmio_addr_cb, &ctx);
}
if (ctx.mmio_address != RVA_INVALID) {
int len = 8; // TODO: Determine proper len of mmio address for the cpu
if (char *r = rz_core_print_hexdump_or_hexdiff_str(core, RZ_OUTPUT_MODE_STANDARD,
ctx.mmio_address, len, false)) {
auto val = QString::fromUtf8(r).trimmed().split("\n").last();
auto msg = QString("mmio %1 %2").arg(selectedText, val);
free(r);
QToolTip::showText(pointOfEvent, msg, parent);
return true;
}
}
}
}
// Else show preview for value?
return false;
}

View File

@ -41,5 +41,13 @@ bool showDisasPreview(QWidget *parent, const QPoint &pointOfEvent, const RVA off
* @return The disassembly offset of the hovered asm text * @return The disassembly offset of the hovered asm text
*/ */
RVA readDisassemblyOffset(QTextCursor tc); RVA readDisassemblyOffset(QTextCursor tc);
/**
* @brief Show a QToolTip that shows the value of the highlighted register, variable, or memory
* @return True if the tooltip is shown
*/
bool showDebugValueTooltip(QWidget *parent, const QPoint &pointOfEvent, const QString &selectedText,
const RVA offset);
} }
#endif #endif

View File

@ -1804,6 +1804,12 @@ QList<VariableDescription> CutterCore::getVariables(RVA at)
} }
desc.type = QString::fromUtf8(tn); desc.type = QString::fromUtf8(tn);
rz_mem_free(tn); rz_mem_free(tn);
if (char *v = rz_core_analysis_var_display(core, var, false)) {
desc.value = QString::fromUtf8(v).trimmed();
rz_mem_free(v);
}
ret.push_back(desc); ret.push_back(desc);
} }
return ret; return ret;

View File

@ -357,6 +357,7 @@ struct VariableDescription
RzAnalysisVarStorageType storageType; RzAnalysisVarStorageType storageType;
QString name; QString name;
QString type; QString type;
QString value;
}; };
struct RegisterRefValueDescription struct RegisterRefValueDescription

View File

@ -65,6 +65,12 @@ AsmOptionsWidget::AsmOptionsWidget(PreferencesDialog *dialog)
&AsmOptionsWidget::relOffCheckBoxToggled); &AsmOptionsWidget::relOffCheckBoxToggled);
connect(Core(), &CutterCore::asmOptionsChanged, this, connect(Core(), &CutterCore::asmOptionsChanged, this,
&AsmOptionsWidget::updateAsmOptionsFromVars); &AsmOptionsWidget::updateAsmOptionsFromVars);
connect(ui->varTooltipsCheckBox, &QCheckBox::toggled, [this](bool checked) {
Config()->setShowVarTooltips(checked);
triggerAsmOptionsChanged();
});
updateAsmOptionsFromVars(); updateAsmOptionsFromVars();
} }
@ -138,6 +144,8 @@ void AsmOptionsWidget::updateAsmOptionsFromVars()
ui->previewCheckBox->setChecked(Config()->getPreviewValue()); ui->previewCheckBox->setChecked(Config()->getPreviewValue());
ui->previewCheckBox->blockSignals(false); ui->previewCheckBox->blockSignals(false);
qhelpers::setCheckedWithoutSignals(ui->varTooltipsCheckBox, Config()->getShowVarTooltips());
QList<ConfigCheckbox>::iterator confCheckbox; QList<ConfigCheckbox>::iterator confCheckbox;
// Set the value for each checkbox in "checkboxes" as it exists in the configuration // Set the value for each checkbox in "checkboxes" as it exists in the configuration

View File

@ -48,8 +48,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>582</width> <width>686</width>
<height>766</height> <height>886</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
@ -65,51 +65,114 @@
<string>Disassembly</string> <string>Disassembly</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="12" column="2"> <item row="5" column="2">
<widget class="QSpinBox" name="nbytesSpinBox"> <widget class="QComboBox" name="caseComboBox">
<property name="enabled"> <item>
<bool>true</bool> <property name="text">
</property> <string>Lowercase</string>
</property>
</item>
<item>
<property name="text">
<string>Uppercase (asm.ucase)</string>
</property>
</item>
<item>
<property name="text">
<string>Capitalize (asm.capitalize)</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="17" column="2"> <item row="17" column="1">
<widget class="QSpinBox" name="asmTabsOffSpinBox"> <widget class="QLabel" name="asmTabsLabel">
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
</widget>
</item>
<item row="19" column="1" colspan="2">
<widget class="QCheckBox" name="lbytesCheckBox">
<property name="text"> <property name="text">
<string>Align bytes to the left (asm.lbytes)</string> <string>Tabs in assembly (asm.tabs):</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QCheckBox" name="previewCheckBox">
<property name="text">
<string>Show preview when hovering:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="syntaxLabel">
<property name="text">
<string>Syntax (asm.syntax):</string>
</property> </property>
<property name="textInteractionFlags"> <property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="2"> <item row="11" column="1">
<widget class="QCheckBox" name="relOffFlagsCheckBox"> <widget class="QCheckBox" name="bytesCheckBox">
<property name="text"> <property name="text">
<string>Flags (asm.reloff.flags)</string> <string>Display the bytes of each instruction (asm.bytes)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Show Disassembly as:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="realnameCheckBox">
<property name="text">
<string>Display flags' real name (asm.flags.real)</string>
</property>
</widget>
</item>
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="relOffsetLabel">
<property name="text">
<string>Show offsets relative to:</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="relOffsetCheckBox">
<property name="text">
<string>Functions (asm.reloff)</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="18" column="1">
<widget class="QLabel" name="asmTabsOffLabel">
<property name="text">
<string>The number of tabulate spaces after the offset (asm.tabs.off):</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="19" column="1" colspan="2">
<widget class="QCheckBox" name="indentCheckBox">
<property name="text">
<string>Indent disassembly based on reflines depth (asm.indent)</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="offsetCheckBox">
<property name="text">
<string>Show offsets (asm.offset)</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -132,23 +195,47 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="14" column="1" colspan="2">
<widget class="QCheckBox" name="offsetCheckBox"> <widget class="QCheckBox" name="bblineCheckBox">
<property name="text"> <property name="text">
<string>Show offsets (asm.offset)</string> <string>Show empty line after every basic block (asm.bb.line)</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="1"> <item row="15" column="1">
<widget class="QLabel" name="asmTabsOffLabel"> <widget class="QCheckBox" name="previewCheckBox">
<property name="text"> <property name="text">
<string>The number of tabulate spaces after the offset (asm.tabs.off):</string> <string>Show preview when hovering</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="syntaxLabel">
<property name="text">
<string>Syntax (asm.syntax):</string>
</property> </property>
<property name="textInteractionFlags"> <property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="21" column="1" colspan="2">
<widget class="QCheckBox" name="bytespaceCheckBox">
<property name="text">
<string>Separate bytes with whitespace (asm.bytes.space)</string>
</property>
</widget>
</item>
<item row="18" column="2">
<widget class="QSpinBox" name="asmTabsOffSpinBox">
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
</widget>
</item>
<item row="12" column="1"> <item row="12" column="1">
<widget class="QLabel" name="nbytesLabel"> <widget class="QLabel" name="nbytesLabel">
<property name="enabled"> <property name="enabled">
@ -163,13 +250,13 @@
</widget> </widget>
</item> </item>
<item row="20" column="1" colspan="2"> <item row="20" column="1" colspan="2">
<widget class="QCheckBox" name="bytespaceCheckBox"> <widget class="QCheckBox" name="lbytesCheckBox">
<property name="text"> <property name="text">
<string>Separate bytes with whitespace (asm.bytes.space)</string> <string>Align bytes to the left (asm.lbytes)</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="2"> <item row="17" column="2">
<widget class="QSpinBox" name="asmTabsSpinBox"> <widget class="QSpinBox" name="asmTabsSpinBox">
<property name="maximum"> <property name="maximum">
<number>100</number> <number>100</number>
@ -179,107 +266,27 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="11" column="1"> <item row="8" column="2">
<widget class="QCheckBox" name="bytesCheckBox"> <widget class="QCheckBox" name="relOffFlagsCheckBox">
<property name="text"> <property name="text">
<string>Display the bytes of each instruction (asm.bytes)</string> <string>Flags (asm.reloff.flags)</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="2">
<widget class="QComboBox" name="caseComboBox">
<item>
<property name="text">
<string>Lowercase</string>
</property>
</item>
<item>
<property name="text">
<string>Uppercase (asm.ucase)</string>
</property>
</item>
<item>
<property name="text">
<string>Capitalize (asm.capitalize)</string>
</property>
</item>
</widget>
</item>
<item row="14" column="1" colspan="2">
<widget class="QCheckBox" name="bblineCheckBox">
<property name="text">
<string>Show empty line after every basic block (asm.bb.line)</string>
</property>
</widget>
</item>
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="relOffsetLabel">
<property name="text">
<string>Show offsets relative to:</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="relOffsetCheckBox">
<property name="text">
<string>Functions (asm.reloff)</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="2"> <item row="4" column="2">
<widget class="QComboBox" name="syntaxComboBox"/> <widget class="QComboBox" name="syntaxComboBox"/>
</item> </item>
<item row="0" column="1" colspan="2"> <item row="12" column="2">
<spacer name="verticalSpacer_3"> <widget class="QSpinBox" name="nbytesSpinBox">
<property name="orientation"> <property name="enabled">
<enum>Qt::Vertical</enum> <bool>true</bool>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Show Disassembly as:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="1"> <item row="16" column="1">
<widget class="QLabel" name="asmTabsLabel"> <widget class="QCheckBox" name="varTooltipsCheckBox">
<property name="text"> <property name="text">
<string>Tabs in assembly (asm.tabs):</string> <string>Show known variable values when hovering</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="18" column="1" colspan="2">
<widget class="QCheckBox" name="indentCheckBox">
<property name="text">
<string>Indent disassembly based on reflines depth (asm.indent)</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="realnameCheckBox">
<property name="text">
<string>Display flags' real name (asm.flags.real)</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -415,8 +422,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>454</width> <width>518</width>
<height>286</height> <height>326</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_6"> <layout class="QVBoxLayout" name="verticalLayout_6">

View File

@ -534,33 +534,44 @@ GraphView::EdgeConfiguration DisassemblerGraphView::edgeConfiguration(GraphView:
bool DisassemblerGraphView::eventFilter(QObject *obj, QEvent *event) bool DisassemblerGraphView::eventFilter(QObject *obj, QEvent *event)
{ {
if (event->type() == QEvent::Type::ToolTip && Config()->getGraphPreview()) { if ((Config()->getGraphPreview() || Config()->getShowVarTooltips())
&& event->type() == QEvent::Type::ToolTip) {
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
QPoint pointOfEvent = helpEvent->globalPos(); QPoint pointOfEvent = helpEvent->globalPos();
QPoint point = viewToLogicalCoordinates(helpEvent->pos()); QPoint point = viewToLogicalCoordinates(helpEvent->pos());
GraphBlock *block = getBlockContaining(point); if (auto block = getBlockContaining(point)) {
// Get pos relative to start of block
QPoint pos = point - QPoint(block->x, block->y);
if (block == nullptr) { // offsetFrom is the address which on top the cursor triggered this
return false; RVA offsetFrom = RVA_INVALID;
}
// offsetFrom is the address which on top the cursor triggered this /*
RVA offsetFrom = RVA_INVALID; * getAddrForMouseEvent() doesn't work for jmps, like
* getInstrForMouseEvent() with false as a 3rd argument.
*/
Instr *inst = getInstrForMouseEvent(*block, &pos, true);
if (inst != nullptr) {
offsetFrom = inst->addr;
}
/* // Don't preview anything for a small scale
* getAddrForMouseEvent() doesn't work for jmps, like if (getViewScale() >= 0.8) {
* getInstrForMouseEvent() with false as a 3rd argument. if (Config()->getGraphPreview()
*/ && DisassemblyPreview::showDisasPreview(this, pointOfEvent, offsetFrom)) {
Instr *inst = getInstrForMouseEvent(*block, &point, true); return true;
if (inst != nullptr) { }
offsetFrom = inst->addr; if (Config()->getShowVarTooltips() && inst) {
} auto token = getToken(inst, pos.x());
if (token
// Don't preview anything for a small scale && DisassemblyPreview::showDebugValueTooltip(this, pointOfEvent,
if (getViewScale() >= 0.8) { token->content, offsetFrom)) {
return DisassemblyPreview::showDisasPreview(this, pointOfEvent, offsetFrom); return true;
}
}
}
} }
} }
return CutterGraphView::eventFilter(obj, event); return CutterGraphView::eventFilter(obj, event);

View File

@ -636,17 +636,23 @@ bool DisassemblyWidget::eventFilter(QObject *obj, QEvent *event)
return true; return true;
} }
} else if (Config()->getPreviewValue() } else if ((Config()->getPreviewValue() || Config()->getShowVarTooltips())
&& event->type() == QEvent::ToolTip && event->type() == QEvent::ToolTip && obj == mDisasTextEdit->viewport()) {
&& obj == mDisasTextEdit->viewport()) {
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event); QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
auto cursorForWord = mDisasTextEdit->cursorForPosition(helpEvent->pos()); auto cursorForWord = mDisasTextEdit->cursorForPosition(helpEvent->pos());
cursorForWord.select(QTextCursor::WordUnderCursor); cursorForWord.select(QTextCursor::WordUnderCursor);
RVA offsetFrom = DisassemblyPreview::readDisassemblyOffset(cursorForWord); RVA offsetFrom = DisassemblyPreview::readDisassemblyOffset(cursorForWord);
return DisassemblyPreview::showDisasPreview(this, helpEvent->globalPos(), offsetFrom); if (Config()->getPreviewValue()
&& DisassemblyPreview::showDisasPreview(this, helpEvent->globalPos(), offsetFrom)) {
return true;
}
if (Config()->getShowVarTooltips()
&& DisassemblyPreview::showDebugValueTooltip(
this, helpEvent->globalPos(), cursorForWord.selectedText(), offsetFrom)) {
return true;
}
} }
return MemoryDockWidget::eventFilter(obj, event); return MemoryDockWidget::eventFilter(obj, event);