Debugger[CDB]: Use new disassembler structures.

Parse CDB's ASM output, set it up with source line.
Move convenience to add source lines into DisassemblerLines
(cached).
This commit is contained in:
Friedemann Kleint
2011-04-13 13:52:03 +02:00
parent d34c81a2a6
commit 292119bb6a
6 changed files with 200 additions and 26 deletions

View File

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

View File

@@ -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, &currentFunction, &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

View File

@@ -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
{

View File

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

View File

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

View File

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