diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 7b93d25872a98d003a0a721f97acb3e19b7f052e..587c786b0d07fedff292b308e2913ebc710366fb 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -823,6 +823,7 @@ void CdbEngine::setupInferior() qDebug("setupInferior"); attemptBreakpointSynchronization(); postCommand("sxn 0x4000001f", 0); // Do not break on WowX86 exceptions. + postCommand(".asm source_line", 0); // Source line in assembly postExtensionCommand("pid", QByteArray(), 0, &CdbEngine::handlePid); } @@ -1507,10 +1508,7 @@ void CdbEngine::handleDisassembler(const CdbBuiltinCommandPtr &command) { QTC_ASSERT(qVariantCanConvert<DisassemblerAgent*>(command->cookie), return;) DisassemblerAgent *agent = qvariant_cast<DisassemblerAgent*>(command->cookie); - DisassemblerLines disassemblerLines; - foreach(const QByteArray &line, command->reply) - disassemblerLines.appendUnparsed(QString::fromLatin1(line)); - agent->setContents(disassemblerLines); + agent->setContents(parseCdbDisassembler(command->reply)); } void CdbEngine::fetchMemory(MemoryAgent *agent, QObject *editor, quint64 addr, quint64 length) diff --git a/src/plugins/debugger/cdb/cdbparsehelpers.cpp b/src/plugins/debugger/cdb/cdbparsehelpers.cpp index 446e50abf076d7d5a70ea59a191cfafed15a0e75..1695727178121de77751c215f60b8cff768bcb53 100644 --- a/src/plugins/debugger/cdb/cdbparsehelpers.cpp +++ b/src/plugins/debugger/cdb/cdbparsehelpers.cpp @@ -37,19 +37,22 @@ #include "registerhandler.h" #include "bytearrayinputstream.h" #include "gdb/gdbmi.h" +#include "disassemblerlines.h" #ifdef Q_OS_WIN # include "shared/dbgwinutils.h" #endif +#include <utils/qtcassert.h> + #include <QtCore/QByteArray> #include <QtCore/QVariant> #include <QtCore/QString> #include <QtCore/QDir> #include <QtCore/QDebug> -#include <utils/qtcassert.h> - #include <cctype> +enum { debugDisAsm = 0 }; + namespace Debugger { namespace Internal { @@ -435,5 +438,152 @@ QDebug operator<<(QDebug s, const WinException &e) return s; } +/*! + \fn DisassemblerLines parseCdbDisassembler(const QList<QByteArray> &a) + + \brief Parse CDB disassembler output into DisassemblerLines (with helpers) + + Expected options (prepend source file line): + \code + .asm source_line + .lines + \endcode + + should cause the 'u' command to produce: + + \code +gitgui!Foo::MainWindow::on_actionPtrs_triggered+0x1f9 [c:\qt\projects\gitgui\app\mainwindow.cpp @ 758]: + 225 00000001`3fcebfe9 488b842410050000 mov rax,qword ptr [rsp+510h] + 225 00000001`3fcebff1 8b4030 mov eax,dword ptr [rax+30h] + 226 00000001`3fcebff4 ffc0 inc eax + 00000001`3fcebff6 488b8c2410050000 mov rcx,qword ptr [rsp+510h] +... +QtCored4!QTextStreamPrivate::putString+0x34: + 10 00000000`6e5e7f64 90 nop +... +\endcode + + The algorithm checks for a function line and grabs the function name, offset and (optional) + source file from it. + Instruction lines are checked for address and source line number. + When the source line changes, the source instruction is inserted. +*/ + +// Parse a function header line: Match: 'nsp::foo::+0x<offset> [<file> @ <line>]:' +// or 'nsp::foo::+0x<offset>:' +// Do not use regexp here as it is hard for functions like operator+, operator[]. +bool parseCdbDisassemblerFunctionLine(const QString &l, + QString *currentFunction, quint64 *functionOffset, + QString *sourceFile) +{ + if (l.isEmpty() || !l.endsWith(QLatin1Char(':')) || l.at(0).isDigit() || l.at(0).isSpace()) + return false; + const int offsetPos = l.indexOf(QLatin1String("+0x")); + if (offsetPos == -1) + return false; + sourceFile->clear(); + *currentFunction = l.left(offsetPos); + if (!l.endsWith(QLatin1String("]:"))) { // No source file. + *functionOffset = l.mid(offsetPos + 3, l.size() - offsetPos - 4).trimmed().toULongLong(0, 16); + if (debugDisAsm) + qDebug() << "Function:" << l << currentFunction << functionOffset; + return true; + } + // Parse file and line. + const int filePos = l.indexOf(QLatin1Char('['), offsetPos + 1); + const int linePos = filePos != -1 ? l.indexOf(QLatin1String(" @ "), filePos + 1) : -1; + if (linePos == -1) + return false; + *functionOffset = l.mid(offsetPos + 3, filePos - offsetPos - 4).trimmed().toULongLong(0, 16); + *sourceFile = l.mid(filePos + 1, linePos - filePos - 1).trimmed(); + if (debugDisAsm) + qDebug() << "Function with source: " << l << currentFunction + << functionOffset << sourceFile; + return true; +} + +// Parse an instruction line: +// ' 21 00000001`3fcebff1 8b4030 mov eax,dword ptr [rax+30h]' +// '<source_line> <address> <raw data> <instruction> +bool parseCdbDisassemblerLine(const QString &lineIn, DisassemblerLine *dLine, uint *sourceLine) +{ + *sourceLine = 0; + if (lineIn.size() < 6) + return false; + // Check source line: right-padded 5 digits + const bool hasSourceLine = lineIn.at(4).isDigit(); + const QString line = lineIn.trimmed(); + int addressPos = 0; + const QChar blank = QLatin1Char(' '); + // Optional source line. + if (hasSourceLine) { + const int sourceLineEnd = line.indexOf(blank); + if (sourceLineEnd == -1) + return false; + *sourceLine = line.left(sourceLineEnd).toUInt(); + addressPos = sourceLineEnd + 1; + } + // Find positions of address/raw data/instruction + const int addressEnd = line.indexOf(blank, addressPos + 1); + if (addressEnd < 0) + return false; + const int rawDataPos = addressEnd + 1; + const int rawDataEnd = line.indexOf(blank, rawDataPos + 1); + if (rawDataEnd < 0) + return false; + const int instructionPos = rawDataEnd + 1; + bool ok; + QString addressS = line.mid(addressPos, addressEnd - addressPos); + if (addressS.size() > 9 && addressS.at(8) == QLatin1Char('`')) + addressS.remove(8, 1); + dLine->address = addressS.toULongLong(&ok, 16); + if (!ok) + return false; + dLine->rawData = QByteArray::fromHex(line.mid(rawDataPos, rawDataEnd - rawDataPos).toAscii()); + dLine->data = line.right(line.size() - instructionPos).trimmed(); + return true; +} + +DisassemblerLines parseCdbDisassembler(const QList<QByteArray> &a) +{ + DisassemblerLines result; + quint64 functionAddress = 0; + uint lastSourceLine = 0; + QString currentFunction; + quint64 functionOffset = 0; + QString sourceFile; + + foreach (const QByteArray &lineBA, a) { + const QString line = QString::fromLatin1(lineBA); + // New function? + if (parseCdbDisassemblerFunctionLine(line, ¤tFunction, &functionOffset, &sourceFile)) { + functionAddress = 0; + } else { + DisassemblerLine disassemblyLine; + uint sourceLine; + if (parseCdbDisassemblerLine(line, &disassemblyLine, &sourceLine)) { + // New source line: Add source code if available. + if (sourceLine && sourceLine != lastSourceLine) { + lastSourceLine = sourceLine; + result.appendSourceLine(sourceFile, sourceLine); + } + } else { + qWarning("Unable to parse assembly line '%s'", lineBA.constData()); + disassemblyLine.fromString(line); + } + // Determine address of function from the first assembler line after a + // function header line. + if (!functionAddress && disassemblyLine.address && functionOffset) { + functionAddress = disassemblyLine.address - functionOffset; + } + if (functionAddress && disassemblyLine.address) + disassemblyLine.offset = disassemblyLine.address - functionAddress; + disassemblyLine.function = currentFunction; + result.appendLine(disassemblyLine); + } + } + return result; +} + } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/cdb/cdbparsehelpers.h b/src/plugins/debugger/cdb/cdbparsehelpers.h index 65f9362706748272ffce88bfa8b895dca3e8c1ad..6f761c5e2ab52904625205cee6b88c968fa012ab 100644 --- a/src/plugins/debugger/cdb/cdbparsehelpers.h +++ b/src/plugins/debugger/cdb/cdbparsehelpers.h @@ -54,6 +54,7 @@ class BreakpointParameters; struct ThreadData; class Register; class GdbMi; +class DisassemblerLines; // Perform mapping on parts of the source tree as reported by/passed to debugger // in case the user has specified such mappings in the global settings. @@ -81,6 +82,8 @@ QByteArray cdbWriteMemoryCommand(quint64 addr, const QByteArray &data); QString debugByteArray(const QByteArray &a); QString StringFromBase64EncodedUtf16(const QByteArray &a); +DisassemblerLines parseCdbDisassembler(const QList<QByteArray> &a); + // Model EXCEPTION_RECORD + firstchance struct WinException { diff --git a/src/plugins/debugger/disassemblerlines.cpp b/src/plugins/debugger/disassemblerlines.cpp index c9cba4f896bc801299f4539ab1e26b3555441063..0f8523a89267899cbb4f18d2080acdb3e6bc60a1 100644 --- a/src/plugins/debugger/disassemblerlines.cpp +++ b/src/plugins/debugger/disassemblerlines.cpp @@ -36,6 +36,8 @@ #include <QtCore/QDebug> #include <QtCore/QRegExp> +#include <QtCore/QFile> +#include <QtCore/QTextStream> namespace Debugger { namespace Internal { @@ -95,6 +97,39 @@ void DisassemblerLines::appendLine(const DisassemblerLine &dl) m_rowCache[dl.address] = m_data.size(); } +// Append source line: Cache current file +struct SourceFileCache +{ + QString fileName; + QStringList lines; +}; + +Q_GLOBAL_STATIC(SourceFileCache, sourceFileCache) + +void DisassemblerLines::appendSourceLine(const QString &fileName, uint lineNumber) +{ + + if (fileName.isEmpty() || lineNumber == 0) + return; + lineNumber--; // fix 1..n range. + SourceFileCache *cache = sourceFileCache(); + if (fileName != cache->fileName) { + cache->fileName = fileName; + cache->lines.clear(); + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + QTextStream ts(&file); + cache->lines = ts.readAll().split(QLatin1Char('\n')); + } // open + } // different file + if (lineNumber >= uint(cache->lines.size())) + return; + DisassemblerLine dl; + dl.lineNumber = lineNumber; + dl.data = cache->lines.at(lineNumber); + appendLine(dl); +} + void DisassemblerLines::appendUnparsed(const QString &unparsed) { QString line = unparsed.trimmed(); diff --git a/src/plugins/debugger/disassemblerlines.h b/src/plugins/debugger/disassemblerlines.h index 9d17f4b943c16fb7b48dfedf318c8199d72d9c55..e175b9c5ef5a75e1c633cda1df0d8319912fdbbd 100644 --- a/src/plugins/debugger/disassemblerlines.h +++ b/src/plugins/debugger/disassemblerlines.h @@ -34,7 +34,7 @@ #ifndef DEBUGGER_DISASSEMBLERLINES_H #define DEBUGGER_DISASSEMBLERLINES_H -#include <QtCore/QString> +#include <QtCore/QStringList> #include <QtCore/QHash> #include <QtCore/QVector> @@ -65,6 +65,7 @@ public: QString function; // (ass) Function to which current instruction belongs. uint offset; // (ass) Offset of instruction in relation to current function. uint lineNumber; // (src) Line number in source. + QByteArray rawData; // (ass) Raw bytes of the instruction QString data; // (ass) Instruction text, (src) source text, (cmt) arbitrary. }; @@ -76,6 +77,9 @@ public: bool coversAddress(quint64 address) const; void appendUnparsed(const QString &line); void appendLine(const DisassemblerLine &dl); + // Mixed source/assembly: Retrieve contents of source (cached) + void appendSourceLine(const QString &fileName, uint line); + int size() const { return m_data.size(); } const DisassemblerLine &at(int i) const { return m_data.at(i); } int lineForAddress(quint64 address) const; diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 3e52db0fbced16439e99b253a17bc03e99cde7ea..52098edfe9b300fb0542afb4c2a387f152bc23b8 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -4137,30 +4137,14 @@ DisassemblerLines GdbEngine::parseMiDisassembler(const GdbMi &lines) // {address="0x0805acf8",func-name="...",offset="25",inst="and $0xe8,%al"}, // {address="0x0805acfa",func-name="...",offset="27",inst="pop %esp"}, - QStringList fileContents; - bool fileLoaded = false; DisassemblerLines result; // FIXME: Performance? foreach (const GdbMi &child, lines.children()) { if (child.hasName("src_and_asm_line")) { - // Mixed mode. - if (!fileLoaded) { - QString fileName = QFile::decodeName(child.findChild("file").data()); - fileName = cleanupFullName(fileName); - QFile file(fileName); - file.open(QIODevice::ReadOnly); - QTextStream ts(&file); - fileContents = ts.readAll().split(QLatin1Char('\n')); - fileLoaded = true; - } - int line = child.findChild("line").data().toInt(); - if (line >= 1 && line <= fileContents.size()) { - DisassemblerLine dl; - dl.lineNumber = line; - dl.data = fileContents.at(line - 1); - result.appendLine(dl); - } + const QString fileName = QFile::decodeName(child.findChild("file").data()); + const uint line = child.findChild("line").data().toUInt(); + result.appendSourceLine(fileName, line); GdbMi insn = child.findChild("line_asm_insn"); foreach (const GdbMi &item, insn.children()) result.appendLine(parseLine(item));