diff --git a/src/disassemblysettingspage.ui b/src/disassemblysettingspage.ui index 60dfcee8..b1f2471b 100644 --- a/src/disassemblysettingspage.ui +++ b/src/disassemblysettingspage.ui @@ -32,14 +32,14 @@ 0 - + Source Code Search Paths: - + KEditListWidget::All @@ -60,6 +60,20 @@ + + + + Show hexdump: + + + + + + + + + + diff --git a/src/models/disassemblymodel.cpp b/src/models/disassemblymodel.cpp index 6d2a58e2..d2ee7cd6 100644 --- a/src/models/disassemblymodel.cpp +++ b/src/models/disassemblymodel.cpp @@ -82,6 +82,8 @@ QVariant DisassemblyModel::headerData(int section, Qt::Orientation orientation, return tr("Address"); else if (section == BranchColumn) return tr("Branches"); + else if (section == HexdumpColumn) + return tr("Hexdump"); else if (section == DisassemblyColumn) return tr("Assembly / Disassembly"); @@ -122,6 +124,8 @@ QVariant DisassemblyModel::data(const QModelIndex& index, int role) const return QString::number(data.addr, 16); } else if (index.column() == BranchColumn) { return data.branchVisualisation; + } else if (index.column() == HexdumpColumn) { + return data.hexdump; } else if (index.column() == DisassemblyColumn) { const auto block = m_document->findBlockByLineNumber(index.row()); if (role == SyntaxHighlightRole) diff --git a/src/models/disassemblymodel.h b/src/models/disassemblymodel.h index 8456d1d4..8835225e 100644 --- a/src/models/disassemblymodel.h +++ b/src/models/disassemblymodel.h @@ -56,6 +56,7 @@ class DisassemblyModel : public QAbstractTableModel { AddrColumn, BranchColumn, + HexdumpColumn, DisassemblyColumn, COLUMN_COUNT }; diff --git a/src/models/disassemblyoutput.cpp b/src/models/disassemblyoutput.cpp index 0cf72df6..53d16e4d 100644 --- a/src/models/disassemblyoutput.cpp +++ b/src/models/disassemblyoutput.cpp @@ -207,29 +207,37 @@ DisassemblyOutput::ObjectdumpOutput DisassemblyOutput::objdumpParse(const QByteA continue; } - // detect lines like: - // 4f616:\t84 c0\ttest %al,%al - auto lineRest = QStringView(asmLine); - - // :\t - const auto addr = [&]() -> quint64 { - const auto prefix = QLatin1String(" "); - const auto suffix = QLatin1String(":\t"); - if (!lineRest.startsWith(prefix)) - return 0; + // a line looks like this: + // [spaces]addr:\t [branch visualization] [hexdump]\tdiassembly + // we can simplify parsing by splitting it into three parts + + const auto parts = asmLine.split(QLatin1Char('\t')); - const auto addrEnd = lineRest.indexOf(suffix, prefix.size()); - if (addrEnd == -1) + if (parts.size() == 1 && asmLine.endsWith(QLatin1Char(':'))) { + // we got a line like: + // std::__cxx11::basic_string, std::allocator >::_M_local_data(): + // pass them to the disassembler since this can be used for inlining + disassemblyLines.push_back({0, asmLine, {}, {}, {}, {currentSourceFileName, sourceCodeLine}}); + continue; + } + + const auto addr = [addrString = parts.value(0).trimmed(), &asmLine]() -> uint64_t { + const auto suffix = QLatin1Char(':'); + if (!addrString.endsWith(suffix)) return 0; bool ok = false; - auto ret = lineRest.mid(0, addrEnd).toULongLong(&ok, 16); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + auto ret = addrString.leftRef(addrString.length() - 1).toULongLong(&ok, 16); +#else + auto ret = addrString.left(addrString.length() - 1).toULongLong(&ok, 16); +#endif + if (!ok) { qCWarning(disassemblyoutput) << "unhandled asm line format:" << asmLine; return 0; } - lineRest = lineRest.mid(addrEnd + suffix.size()); return ret; }(); @@ -238,19 +246,23 @@ DisassemblyOutput::ObjectdumpOutput DisassemblyOutput::objdumpParse(const QByteA // | 64 a3 .... // \-> 65 23 .... // so we can simply skip all characters until we meet a letter or a number - const auto branchVisualisation = [&]() -> QStringView { - if (!addr) - return {}; - auto firstHexIt = std::find_if(lineRest.cbegin(), lineRest.cend(), isHexCharacter); - auto size = std::distance(lineRest.cbegin(), firstHexIt); - auto ret = lineRest.mid(0, size); - lineRest = lineRest.mid(size); - return ret; + struct BranchesAndHexdump + { + QString branchVisualisation; + QString hexdump; + }; + const auto [branchVisualisation, hexdump] = [branchesAndHex = parts.value(1)]() -> BranchesAndHexdump { + auto firstHexIt = std::find_if(branchesAndHex.cbegin(), branchesAndHex.cend(), isHexCharacter); + auto size = std::distance(branchesAndHex.cbegin(), firstHexIt); + auto branchVisualisation = branchesAndHex.mid(0, size); + auto hexdump = branchesAndHex.mid(size); + return {branchVisualisation, hexdump.trimmed()}; }(); disassemblyLines.push_back({addr, - lineRest.toString(), - branchVisualisation.toString(), + parts.value(2).trimmed(), + branchVisualisation, + hexdump, extractLinkedFunction(asmLine), {currentSourceFileName, sourceCodeLine}}); } diff --git a/src/models/disassemblyoutput.h b/src/models/disassemblyoutput.h index 8f9ab896..d4d636ee 100644 --- a/src/models/disassemblyoutput.h +++ b/src/models/disassemblyoutput.h @@ -25,6 +25,7 @@ struct DisassemblyOutput quint64 addr = 0; QString disassembly; QString branchVisualisation; + QString hexdump; LinkedFunction linkedFunction; Data::FileLine fileLine; }; diff --git a/src/resultsdisassemblypage.cpp b/src/resultsdisassemblypage.cpp index daac9722..9bbdb3dd 100644 --- a/src/resultsdisassemblypage.cpp +++ b/src/resultsdisassemblypage.cpp @@ -425,11 +425,16 @@ ResultsDisassemblyPage::ResultsDisassemblyPage(CostContextMenu* costContextMenu, m_disassemblyModel, &m_currentDisasmSearchIndex, 0); ui->assemblyView->setColumnHidden(DisassemblyModel::BranchColumn, !settings->showBranches()); + ui->assemblyView->setColumnHidden(DisassemblyModel::HexdumpColumn, !settings->showHexdump()); connect(settings, &Settings::showBranchesChanged, this, [this](bool showBranches) { ui->assemblyView->setColumnHidden(DisassemblyModel::BranchColumn, !showBranches); }); + connect(settings, &Settings::showHexdumpChanged, this, [this](bool showHexdump) { + ui->assemblyView->setColumnHidden(DisassemblyModel::HexdumpColumn, !showHexdump); + }); + #if KFSyntaxHighlighting_FOUND QStringList schemes; @@ -489,6 +494,7 @@ void ResultsDisassemblyPage::setupAsmViewModel() ui->assemblyView->header()->setStretchLastSection(false); ui->assemblyView->header()->setSectionResizeMode(DisassemblyModel::AddrColumn, QHeaderView::ResizeToContents); ui->assemblyView->header()->setSectionResizeMode(DisassemblyModel::BranchColumn, QHeaderView::Interactive); + ui->assemblyView->header()->setSectionResizeMode(DisassemblyModel::HexdumpColumn, QHeaderView::Interactive); ui->assemblyView->header()->setSectionResizeMode(DisassemblyModel::DisassemblyColumn, QHeaderView::Stretch); for (int col = DisassemblyModel::COLUMN_COUNT; col < m_disassemblyModel->columnCount(); col++) { diff --git a/src/settings.cpp b/src/settings.cpp index 1439aa16..d3578325 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -245,6 +245,11 @@ void Settings::loadFromFile() connect(this, &Settings::showBranchesChanged, [sharedConfig](bool showBranches) { sharedConfig->group("Disassembly").writeEntry("showBranches", showBranches); }); + + setShowBranches(sharedConfig->group("Disassembly").readEntry("showHexdump", false)); + connect(this, &Settings::showHexdumpChanged, [sharedConfig](bool showHexdump) { + sharedConfig->group("Disassembly").writeEntry("showHexdump", showHexdump); + }); } void Settings::setSourceCodePaths(const QString& paths) @@ -270,3 +275,11 @@ void Settings::setShowBranches(bool showBranches) emit showBranchesChanged(m_showBranches); } } + +void Settings::setShowHexdump(bool showHexdump) +{ + if (m_showHexdump != showHexdump) { + m_showHexdump = showHexdump; + emit showHexdumpChanged(m_showHexdump); + } +} diff --git a/src/settings.h b/src/settings.h index d892ce97..abca4c9e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -158,6 +158,10 @@ class Settings : public QObject { return m_showBranches; } + bool showHexdump() const + { + return m_showHexdump; + } void loadFromFile(); @@ -182,6 +186,7 @@ class Settings : public QObject void sourceCodePathsChanged(const QString& paths); void perfPathChanged(const QString& perfPath); void showBranchesChanged(bool showBranches); + void showHexdumpChanged(bool showHexdump); public slots: void setPrettifySymbols(bool prettifySymbols); @@ -206,6 +211,7 @@ public slots: void setSourceCodePaths(const QString& paths); void setPerfPath(const QString& path); void setShowBranches(bool showBranches); + void setShowHexdump(bool showHexdump); private: using QObject::QObject; @@ -230,6 +236,7 @@ public slots: QString m_sourceCodePaths; QString m_perfMapPath; bool m_showBranches = true; + bool m_showHexdump = false; QString m_lastUsedEnvironment; diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 92b1a73a..db4c7818 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -336,9 +336,11 @@ void SettingsDialog::addSourcePathPage() setupMultiPath(disassemblyPage->sourcePaths, disassemblyPage->label, buttonBox()->button(QDialogButtonBox::Ok)); disassemblyPage->showBranches->setChecked(settings->showBranches()); + disassemblyPage->showHexdump->setChecked(settings->showHexdump()); connect(buttonBox(), &QDialogButtonBox::accepted, this, [this, colon, settings] { settings->setSourceCodePaths(disassemblyPage->sourcePaths->items().join(colon)); settings->setShowBranches(disassemblyPage->showBranches->isChecked()); + settings->setShowHexdump(disassemblyPage->showHexdump->isChecked()); }); } diff --git a/tests/modeltests/tst_disassemblyoutput.cpp b/tests/modeltests/tst_disassemblyoutput.cpp index c84a53e9..b67e9ff8 100644 --- a/tests/modeltests/tst_disassemblyoutput.cpp +++ b/tests/modeltests/tst_disassemblyoutput.cpp @@ -129,6 +129,7 @@ private slots: } file->write(text.toUtf8()); + file->flush(); return file->fileName(); } @@ -242,14 +243,24 @@ private slots: QVERIFY(result.errorMessage.isEmpty()); auto isValidVisualisationCharacter = [](QChar character) { - const static auto validCharacters = - std::initializer_list {QLatin1Char(' '), QLatin1Char('\t'), QLatin1Char('|'), QLatin1Char('/'), - QLatin1Char('\\'), QLatin1Char('-'), QLatin1Char('>'), QLatin1Char('+')}; + const static auto validCharacters = std::initializer_list { + QLatin1Char(' '), QLatin1Char('\t'), QLatin1Char('|'), QLatin1Char('/'), QLatin1Char('\\'), + QLatin1Char('-'), QLatin1Char('>'), QLatin1Char('+'), QLatin1Char('X')}; return std::any_of(validCharacters.begin(), validCharacters.end(), [character](auto validCharacter) { return character == validCharacter; }); }; + auto isValidHexdumpCharacter = [](QChar character) { + const static auto validCharacters = std::initializer_list { + QLatin1Char(' '), QLatin1Char('0'), QLatin1Char('1'), QLatin1Char('2'), QLatin1Char('3'), + QLatin1Char('4'), QLatin1Char('5'), QLatin1Char('6'), QLatin1Char('7'), QLatin1Char('8'), + QLatin1Char('9'), QLatin1Char('a'), QLatin1Char('b'), QLatin1Char('c'), QLatin1Char('d'), + QLatin1Char('e'), QLatin1Char('f')}; + return std::any_of(validCharacters.begin(), validCharacters.end(), + [character](auto validCharacter) { return character == validCharacter; }); + }; + for (const auto& line : result.disassemblyLines) { QVERIFY(!line.branchVisualisation.isEmpty()); @@ -257,15 +268,7 @@ private slots: QVERIFY(std::all_of(line.branchVisualisation.cbegin(), line.branchVisualisation.cend(), isValidVisualisationCharacter)); - QVERIFY(!line.disassembly.isEmpty()); - // check that we removed everyting before the hexdump - bool ok = false; -#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - line.disassembly.leftRef(2).toInt(&ok, 16); -#else - line.disassembly.left(2).toInt(&ok, 16); -#endif - QVERIFY(ok); + QVERIFY(std::all_of(line.hexdump.cbegin(), line.hexdump.cend(), isValidHexdumpCharacter)); // Check that address is valid QVERIFY(line.addr >= address && line.addr < address + size); @@ -310,6 +313,23 @@ private slots: const auto parsed = DisassemblyOutput::objdumpParse(dataFile.readAll()); QCOMPARE(parsed.mainSourceFileName, mainSourceFileName); QCOMPARE(parsed.disassemblyLines.size(), numLines); + + auto checkForMultiLineInstruction = [](const QString& lastDisasm) { + // some instructions translate to multiple lines + // like 66 41 83 ba 00 80 ff 7f 00 which translates to: + // 0: 66 41 83 ba 00 80 ff cmp WORD PTR [r10+0x7fff8000],0x0 + // 7: 7f 00 + + const auto multiLineOpcodes = { + QLatin1String("movsbl"), QLatin1String("compb"), QLatin1String("movsd"), QLatin1String("%fs"), + QLatin1String("movabs"), QLatin1String("cs nopw"), QLatin1String("cmpq"), QLatin1String("cmpb"), + QLatin1String("cmpw"), QLatin1String("lea 0x0")}; + + return std::any_of(multiLineOpcodes.begin(), multiLineOpcodes.end(), + [lastDisasm](const auto& opcode) { return lastDisasm.contains(opcode); }); + }; + + QString lastOpcode; for (const auto& line : parsed.disassemblyLines) { if (line.fileLine.file.isEmpty()) { QCOMPARE(line.fileLine.line, -1); @@ -320,12 +340,17 @@ private slots: if (line.addr) { QVERIFY(line.addr >= minAddr); QVERIFY(line.addr <= maxAddr); - QVERIFY(!line.disassembly.isEmpty()); + QVERIFY(!line.disassembly.isEmpty() + || (line.disassembly.isEmpty() && checkForMultiLineInstruction(lastOpcode))); - if (!line.branchVisualisation.isEmpty()) { + if (!line.branchVisualisation.isEmpty()) + + { QVERIFY(std::all_of(line.branchVisualisation.begin(), line.branchVisualisation.end(), [](QChar c) { return QLatin1String(" |\\/->+X").contains(c); })); } + + lastOpcode = line.disassembly; } else { QVERIFY(line.branchVisualisation.isEmpty()); }