Commit d4ccc16f authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Implement disassembler for cdb.

Filter debugging output correctly, some glitches. Extract
base class for debugging output to be able to intercept
debugging output for other purposes (such as disassembling).
parent b95af44c
...@@ -84,16 +84,17 @@ void BreakWindow::resizeEvent(QResizeEvent *ev) ...@@ -84,16 +84,17 @@ void BreakWindow::resizeEvent(QResizeEvent *ev)
void BreakWindow::contextMenuEvent(QContextMenuEvent *ev) void BreakWindow::contextMenuEvent(QContextMenuEvent *ev)
{ {
QMenu menu; QMenu menu;
QModelIndex index = indexAt(ev->pos()); const QModelIndex index = indexAt(ev->pos());
const bool indexIsValid = index.isValid();
QAction *act0 = new QAction(tr("Delete breakpoint"), &menu); QAction *act0 = new QAction(tr("Delete breakpoint"), &menu);
act0->setEnabled(index.isValid()); act0->setEnabled(indexIsValid);
QAction *act1 = new QAction(tr("Adjust column widths to contents"), &menu); QAction *act1 = new QAction(tr("Adjust column widths to contents"), &menu);
QAction *act2 = new QAction(tr("Always adjust column widths to contents"), &menu); QAction *act2 = new QAction(tr("Always adjust column widths to contents"), &menu);
act2->setCheckable(true); act2->setCheckable(true);
act2->setChecked(m_alwaysResizeColumnsToContents); act2->setChecked(m_alwaysResizeColumnsToContents);
QAction *act3 = new QAction(tr("Edit condition..."), &menu); QAction *act3 = new QAction(tr("Edit condition..."), &menu);
act0->setEnabled(index.isValid()); act3->setEnabled(indexIsValid);
QAction *act4 = new QAction(tr("Syncronize breakpoints"), &menu); QAction *act4 = new QAction(tr("Synchronize breakpoints"), &menu);
menu.addAction(act0); menu.addAction(act0);
menu.addAction(act3); menu.addAction(act3);
......
...@@ -28,15 +28,20 @@ ...@@ -28,15 +28,20 @@
**************************************************************************/ **************************************************************************/
#include "cdbassembler.h" #include "cdbassembler.h"
#include "registerhandler.h" #include "cdbdebugoutput.h"
#include "cdbdebugengine_p.h" #include "cdbdebugengine_p.h"
#include "cdbsymbolgroupcontext.h" #include "cdbsymbolgroupcontext.h"
#include "disassemblerhandler.h"
#include "registerhandler.h"
#include <QtCore/QVector> #include <QtCore/QVector>
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
typedef QList<DisassemblerLine> DisassemblerLineList;
bool getRegisters(IDebugControl4 *ctl, bool getRegisters(IDebugControl4 *ctl,
IDebugRegisters2 *ireg, IDebugRegisters2 *ireg,
QList<Register> *registers, QList<Register> *registers,
...@@ -79,5 +84,151 @@ bool getRegisters(IDebugControl4 *ctl, ...@@ -79,5 +84,151 @@ bool getRegisters(IDebugControl4 *ctl,
return true; return true;
} }
// Output parser for disassembler lines.
// It uses the source file lines as symbol until it encounters
// a C++ symbol (function entered), from which then on
// it uses that symbol.
class DisassemblerOutputParser
{
public:
explicit DisassemblerOutputParser(DisassemblerLineList *list);
void parse(const QStringList &l);
private:
enum ParseResult { ParseOk, ParseIgnore, ParseFailed };
ParseResult parseDisassembled(const QString &in, DisassemblerLine* l);
DisassemblerLineList *m_list;
QString m_sourceSymbol;
int m_sourceSymbolOffset;
};
DisassemblerOutputParser::DisassemblerOutputParser(DisassemblerLineList *list) :
m_list(list),
m_sourceSymbolOffset(0)
{
}
// Parse a disassembler line:
// module!class::foo:
// 004017cf cc int 3
// 77 mainwindow.cpp 004018ff 8d4da8 lea ecx,[ebp-0x58]
DisassemblerOutputParser::ParseResult
DisassemblerOutputParser::parseDisassembled(const QString &in, DisassemblerLine* l)
{
l->clear();
// Check if there is a source file
if (in.size() < 7)
return ParseIgnore;
const bool hasSourceFile = !in.at(6).isSpace();
// Sometimes, empty lines occur
const QString simplified = in.simplified();
if (simplified.isEmpty())
return ParseIgnore;
QStringList tokens = simplified.split(QLatin1Char(' '), QString::SkipEmptyParts);
// Check for symbols as 'module!class::foo:' (start of function encountered)
if (tokens.size() == 1) {
QString symbol = tokens.front();
if (symbol.endsWith(QLatin1Char(':')) && symbol.contains(QLatin1Char('!'))) {
symbol.truncate(symbol.size() - 1);
m_sourceSymbol = symbol;
m_sourceSymbolOffset = 0;
}
return ParseIgnore;
}
if (tokens.size() < 2)
return ParseIgnore;
// Symbol display: Do we know a symbol?
if (!m_sourceSymbol.isEmpty()) {
l->symbol = QString(QLatin1Char('<'));
l->symbol += m_sourceSymbol;
if (m_sourceSymbolOffset) {
l->symbol += QLatin1Char('+');
l->symbol += QString::number(m_sourceSymbolOffset);
}
l->symbol += QLatin1Char('>');
m_sourceSymbolOffset++;
}
// Read source file information: If we don't know a symbol yet,
// use the source file.
if (hasSourceFile) {
if (l->symbol.isEmpty()) {
l->symbol = tokens.at(1);
l->symbol += QLatin1Char('+');
l->symbol += tokens.front();
}
tokens.pop_front();
tokens.pop_front();
}
l->symbolDisplay = l->symbol;
// Get offset address and instruction
if (tokens.size() < 3)
return ParseFailed;
l->addressDisplay = l->address = tokens.front();
tokens.pop_front();
// The rest is effective address & instructions
if (tokens.size() > 1)
tokens.pop_front();
l->mnemonic = tokens.join(QString(QLatin1Char(' ')));
return ParseOk;
}
void DisassemblerOutputParser::parse(const QStringList &l)
{
DisassemblerLine dLine;
foreach(const QString &line, l) {
switch (parseDisassembled(line, &dLine)) {
case ParseOk:
m_list->push_back(dLine);
break;
case ParseIgnore:
break;
case ParseFailed:
qWarning("Failed to parse '%s'\n", qPrintable(line));
break;
}
}
}
bool dissassemble(IDebugClient5 *client,
IDebugControl4 *ctl,
ULONG64 offset,
unsigned long beforeLines,
unsigned long afterLines,
QList<DisassemblerLine> *lines,
QString *errorMessage)
{
if (debugCDB)
qDebug() << Q_FUNC_INFO << offset;
lines->clear();
const ULONG flags = DEBUG_DISASM_MATCHING_SYMBOLS|DEBUG_DISASM_SOURCE_LINE_NUMBER|DEBUG_DISASM_SOURCE_FILE_NAME;
// Catch the output by temporarily setting another handler.
// We use the method that outputs to the output handler as it
// conveniently provides the 'beforeLines' context (stepping back
// in assembler code). We build a complete string first as line breaks
// may occur in-between messages.
StringOutputHandler stringHandler;
IDebugOutputCallbacksWide *oldHandler = CdbDebugOutputBase::getOutputCallback(client);
client->SetOutputCallbacksWide(&stringHandler);
// For some reason, we need to output to "all clients"
const HRESULT hr = ctl->OutputDisassemblyLines(DEBUG_OUTCTL_ALL_CLIENTS,
beforeLines, beforeLines + afterLines,
offset, flags, 0, 0, 0, 0);
client->SetOutputCallbacksWide(oldHandler);
if (FAILED(hr)) {
*errorMessage= QString::fromLatin1("Unable to dissamble at 0x%1: %2").
arg(QString::number(offset, 16), msgComFailed("OutputDisassemblyLines", hr));
return false;
}
DisassemblerOutputParser parser(lines);
parser.parse(stringHandler.result().split(QLatin1Char('\n')));
return true;
}
} }
} }
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
class DisassemblerLine;
// Utilities related to assembler code. // Utilities related to assembler code.
class Register; class Register;
...@@ -47,6 +49,14 @@ bool getRegisters(IDebugControl4 *ctl, ...@@ -47,6 +49,14 @@ bool getRegisters(IDebugControl4 *ctl,
QList<Register> *registers, QList<Register> *registers,
QString *errorMessage, QString *errorMessage,
int base = 10 /* 16 for hex, etc */); int base = 10 /* 16 for hex, etc */);
bool dissassemble(IDebugClient5 *client,
IDebugControl4 *ctl,
ULONG64 offset,
unsigned long beforeLines,
unsigned long afterLines,
QList<DisassemblerLine> *lines,
QString *errorMessage);
} }
} }
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include "watchhandler.h" #include "watchhandler.h"
#include "registerhandler.h" #include "registerhandler.h"
#include "moduleshandler.h" #include "moduleshandler.h"
#include "disassemblerhandler.h"
#include "watchutils.h" #include "watchutils.h"
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
...@@ -56,6 +57,7 @@ ...@@ -56,6 +57,7 @@
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtGui/QMessageBox> #include <QtGui/QMessageBox>
#include <QtGui/QMainWindow> #include <QtGui/QMainWindow>
#include <QtGui/QApplication>
#define DBGHELP_TRANSLATE_TCHAR #define DBGHELP_TRANSLATE_TCHAR
#include <inc/Dbghelp.h> #include <inc/Dbghelp.h>
...@@ -195,7 +197,6 @@ CdbDebugEnginePrivate::CdbDebugEnginePrivate(DebuggerManager *parent, CdbDebugEn ...@@ -195,7 +197,6 @@ CdbDebugEnginePrivate::CdbDebugEnginePrivate(DebuggerManager *parent, CdbDebugEn
m_breakEventMode(BreakEventHandle), m_breakEventMode(BreakEventHandle),
m_watchTimer(-1), m_watchTimer(-1),
m_debugEventCallBack(engine), m_debugEventCallBack(engine),
m_debugOutputCallBack(engine),
m_pDebugClient(0), m_pDebugClient(0),
m_pDebugControl(0), m_pDebugControl(0),
m_pDebugSystemObjects(0), m_pDebugSystemObjects(0),
...@@ -225,7 +226,7 @@ bool CdbDebugEnginePrivate::init(QString *errorMessage) ...@@ -225,7 +226,7 @@ bool CdbDebugEnginePrivate::init(QString *errorMessage)
return false; return false;
} }
m_pDebugClient->SetOutputCallbacks(&m_debugOutputCallBack); m_pDebugClient->SetOutputCallbacksWide(&m_debugOutputCallBack);
m_pDebugClient->SetEventCallbacks(&m_debugEventCallBack); m_pDebugClient->SetEventCallbacks(&m_debugEventCallBack);
hr = lib.debugCreate( __uuidof(IDebugControl4), reinterpret_cast<void**>(&m_pDebugControl)); hr = lib.debugCreate( __uuidof(IDebugControl4), reinterpret_cast<void**>(&m_pDebugControl));
...@@ -317,6 +318,10 @@ CdbDebugEngine::CdbDebugEngine(DebuggerManager *parent) : ...@@ -317,6 +318,10 @@ CdbDebugEngine::CdbDebugEngine(DebuggerManager *parent) :
m_d->m_debuggerManager, SLOT(showDebuggerOutput(QString,QString))); m_d->m_debuggerManager, SLOT(showDebuggerOutput(QString,QString)));
connect(&m_d->m_debugOutputCallBack, SIGNAL(debuggerInputPrompt(QString,QString)), connect(&m_d->m_debugOutputCallBack, SIGNAL(debuggerInputPrompt(QString,QString)),
m_d->m_debuggerManager, SLOT(showDebuggerInput(QString,QString))); m_d->m_debuggerManager, SLOT(showDebuggerInput(QString,QString)));
connect(&m_d->m_debugOutputCallBack, SIGNAL(debuggeeOutput(QString)),
m_d->m_debuggerManager, SLOT(showApplicationOutput(QString)));
connect(&m_d->m_debugOutputCallBack, SIGNAL(debuggeeInputPrompt(QString)),
m_d->m_debuggerManager, SLOT(showApplicationOutput(QString)));
} }
CdbDebugEngine::~CdbDebugEngine() CdbDebugEngine::~CdbDebugEngine()
...@@ -1040,7 +1045,31 @@ void CdbDebugEngine::saveSessionData() ...@@ -1040,7 +1045,31 @@ void CdbDebugEngine::saveSessionData()
} }
void CdbDebugEngine::reloadDisassembler() void CdbDebugEngine::reloadDisassembler()
{ {
enum { ContextLines = 40 };
// Do we have a top stack frame?
const ULONG64 offset = m_d->m_currentStackTrace ? m_d->m_currentStackTrace->instructionOffset() : ULONG64(0);
if (debugCDB)
qDebug() << Q_FUNC_INFO << offset;
DisassemblerHandler *dh = m_d->m_debuggerManagerAccess->disassemblerHandler();
if (offset) {
QList<DisassemblerLine> lines;
QString errorMessage;
QApplication::setOverrideCursor(Qt::WaitCursor);
const bool drc = dissassemble(m_d->m_pDebugClient, m_d->m_pDebugControl, offset,
ContextLines, ContextLines, &lines, &errorMessage);
QApplication::restoreOverrideCursor();
if (drc) {
dh->setLines(lines);
if (lines.size() > ContextLines)
dh->setCurrentLine(ContextLines);
} else {
qWarning("reloadDisassembler: %s\n", qPrintable(errorMessage));
}
} else {
dh->setLines(QList<DisassemblerLine>());
}
} }
void CdbDebugEngine::reloadModules() void CdbDebugEngine::reloadModules()
......
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
** **
**************************************************************************/ **************************************************************************/
#include "cdbdebugoutput.h" #include "cdbdebugoutput.h"
#include "cdbdebugengine.h" #include "cdbdebugengine.h"
#include "cdbdebugengine_p.h" #include "cdbdebugengine_p.h"
...@@ -38,12 +37,11 @@ ...@@ -38,12 +37,11 @@
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
CdbDebugOutput::CdbDebugOutput(CdbDebugEngine* engine) : CdbDebugOutputBase::CdbDebugOutputBase()
m_pEngine(engine)
{ {
} }
STDMETHODIMP CdbDebugOutput::QueryInterface( STDMETHODIMP CdbDebugOutputBase::QueryInterface(
THIS_ THIS_
IN REFIID InterfaceId, IN REFIID InterfaceId,
OUT PVOID* Interface OUT PVOID* Interface
...@@ -52,9 +50,9 @@ STDMETHODIMP CdbDebugOutput::QueryInterface( ...@@ -52,9 +50,9 @@ STDMETHODIMP CdbDebugOutput::QueryInterface(
*Interface = NULL; *Interface = NULL;
if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) || if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) ||
IsEqualIID(InterfaceId, __uuidof(IDebugOutputCallbacks))) IsEqualIID(InterfaceId, __uuidof(IDebugOutputCallbacksWide)))
{ {
*Interface = (IDebugOutputCallbacks *)this; *Interface = (IDebugOutputCallbacksWide*)this;
AddRef(); AddRef();
return S_OK; return S_OK;
} else { } else {
...@@ -62,27 +60,43 @@ STDMETHODIMP CdbDebugOutput::QueryInterface( ...@@ -62,27 +60,43 @@ STDMETHODIMP CdbDebugOutput::QueryInterface(
} }
} }
STDMETHODIMP_(ULONG) CdbDebugOutput::AddRef(THIS) STDMETHODIMP_(ULONG) CdbDebugOutputBase::AddRef(THIS)
{ {
// This class is designed to be static so // This class is designed to be static so
// there's no true refcount. // there's no true refcount.
return 1; return 1;
} }
STDMETHODIMP_(ULONG) CdbDebugOutput::Release(THIS) STDMETHODIMP_(ULONG) CdbDebugOutputBase::Release(THIS)
{ {
// This class is designed to be static so // This class is designed to be static so
// there's no true refcount. // there's no true refcount.
return 0; return 0;
} }
STDMETHODIMP CdbDebugOutputBase::Output(
THIS_
IN ULONG mask,
IN PCWSTR text
)
{
output(mask, QString::fromUtf16(text));
return S_OK;
}
IDebugOutputCallbacksWide *CdbDebugOutputBase::getOutputCallback(IDebugClient5 *client)
{
IDebugOutputCallbacksWide *rc;
if (FAILED(client->GetOutputCallbacksWide(&rc)))
return 0;
return rc;
}
// ------------------------- CdbDebugOutput
// Return a prefix for debugger messages // Return a prefix for debugger messages
static QString prefix(ULONG mask) static QString prefix(ULONG mask)
{ {
if (mask & (DEBUG_OUTPUT_DEBUGGEE|DEBUG_OUTPUT_DEBUGGEE_PROMPT|DEBUG_OUTPUT_DEBUGGEE_PROMPT)) {
static const QString p = QLatin1String("target:");
return p;
}
if (mask & (DEBUG_OUTPUT_PROMPT_REGISTERS)) { if (mask & (DEBUG_OUTPUT_PROMPT_REGISTERS)) {
static const QString p = QLatin1String("registers:"); static const QString p = QLatin1String("registers:");
return p; return p;
...@@ -103,23 +117,42 @@ static QString prefix(ULONG mask) ...@@ -103,23 +117,42 @@ static QString prefix(ULONG mask)
return commonPrefix; return commonPrefix;
} }
STDMETHODIMP CdbDebugOutput::Output( enum OutputKind { DebuggerOutput, DebuggerPromptOutput, DebuggeeOutput, DebuggeePromptOutput };
THIS_
IN ULONG mask, static inline OutputKind outputKind(ULONG mask)
IN PCSTR text {
) if (mask & DEBUG_OUTPUT_DEBUGGEE)
return DebuggeeOutput;
if (mask & DEBUG_OUTPUT_DEBUGGEE_PROMPT)
return DebuggeePromptOutput;
if (mask & DEBUG_OUTPUT_PROMPT)
return DebuggerPromptOutput;
return DebuggerOutput;
}
CdbDebugOutput::CdbDebugOutput()
{ {
const QString msg = QString::fromLocal8Bit(text); }
void CdbDebugOutput::output(ULONG mask, const QString &msg)
{
if (debugCDB > 1) if (debugCDB > 1)
qDebug() << Q_FUNC_INFO << "\n " << msg; qDebug() << Q_FUNC_INFO << "\n " << msg;
if (mask & (DEBUG_OUTPUT_PROMPT|DEBUG_OUTPUT_DEBUGGEE_PROMPT)) { switch (outputKind(mask)) {
case DebuggerOutput:
debuggerOutput(prefix(mask), msg);
break;
case DebuggerPromptOutput:
emit debuggerInputPrompt(prefix(mask), msg); emit debuggerInputPrompt(prefix(mask), msg);
} else { break;
emit debuggerOutput(prefix(mask), msg); case DebuggeeOutput:
emit debuggeeOutput(msg);
break;
case DebuggeePromptOutput:
emit debuggeeInputPrompt(msg);
break;
} }
return S_OK;
} }
} // namespace Internal } // namespace Internal
......
...@@ -38,14 +38,12 @@ ...@@ -38,14 +38,12 @@
namespace Debugger { namespace Debugger {
namespace Internal { namespace Internal {
class CdbDebugEngine; // CdbDebugOutputBase is a base class for output handlers
// that takes care of the Active X magic and conversion to QString.
class CdbDebugOutput : public QObject, public IDebugOutputCallbacks class CdbDebugOutputBase : public IDebugOutputCallbacksWide
{ {
Q_OBJECT
public: public:
explicit CdbDebugOutput(CdbDebugEngine* engine);
// IUnknown. // IUnknown.
STDMETHOD(QueryInterface)( STDMETHOD(QueryInterface)(
THIS_ THIS_
...@@ -63,15 +61,48 @@ public: ...@@ -63,15 +61,48 @@ public:
STDMETHOD(Output)( STDMETHOD(Output)(
THIS_ THIS_
IN ULONG mask, IN ULONG mask,
IN PCSTR text IN PCWSTR text
); );
// Helpers to retrieve the output callbacks IF
static IDebugOutputCallbacksWide *getOutputCallback(IDebugClient5 *client);
protected:
CdbDebugOutputBase();
virtual void output(ULONG mask, const QString &message) = 0;
};
// Standard CDB output handler
class CdbDebugOutput : public QObject, public CdbDebugOutputBase
{
Q_OBJECT
public:
CdbDebugOutput();
protected:
virtual void output(ULONG mask, const QString &message);
signals: signals:
void debuggerOutput(const QString &prefix, const QString &message); void debuggerOutput(const QString &prefix, const QString &message);
void debuggerInputPrompt(const QString &prefix, const QString &message); void debuggerInputPrompt(const QString &prefix, const QString &message);
void debuggeeOutput(const QString &message);
void debuggeeInputPrompt(const QString &message);
};
// An output handler that adds lines to a string (to be
// used for cases in which linebreaks occur in-between calls
// to output).
class StringOutputHandler : public CdbDebugOutputBase
{
public:
StringOutputHandler() {}