Make CDB load custom dumpers.

Load in a 'well-defined' (temporary) breakpoint
at main().
This commit is contained in:
Friedemann Kleint
2009-04-22 17:28:26 +02:00
parent ef8e69d96a
commit 52915776cd
14 changed files with 594 additions and 157 deletions

View File

@@ -32,11 +32,18 @@
#include "cdbdebugengine_p.h"
#include "cdbdebugoutput.h"
#include "cdbdebugeventcallback.h"
#include "watchutils.h"
#include <QtCore/QRegExp>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>
enum { loadDebug = 0 };
static const char *dumperModuleNameC = "gdbmacros";
static const char *qtCoreModuleNameC = "QtCore";
static const ULONG waitTimeOutMS = 30000;
namespace Debugger {
namespace Internal {
@@ -52,7 +59,7 @@ static bool allocDebuggeeMemory(CIDebugControl *ctl,
OutputRedirector redir(client, &stringHandler);
if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, allocCmd, errorMessage))
return false;
// "Allocated 1000 bytes starting at 003a0000" .. hopefully never localized
// "Allocated 1000 bytes starting at 003a0000" .. hopefully never localized
bool ok = false;
const QString output = stringHandler.result();
const int lastBlank = output.lastIndexOf(QLatin1Char(' '));
@@ -61,6 +68,8 @@ static bool allocDebuggeeMemory(CIDebugControl *ctl,
if (ok)
*address = addri;
}
if (loadDebug)
qDebug() << Q_FUNC_INFO << '\n' << output << *address << ok;
if (!ok) {
*errorMessage = QString::fromLatin1("Failed to parse output '%1'").arg(output);
return false;
@@ -88,25 +97,6 @@ static bool createDebuggeeAscIIString(CIDebugControl *ctl,
return true;
}
// Locate 'qstrdup' in the (potentially namespaced) corelib. For some
// reason, the symbol is present in QtGui as well without type information.
static inline QString resolveStrdup(CIDebugSymbols *syms, QString *errorMessage)
{
QStringList matches;
const QString pattern = QLatin1String("*qstrdup");
const QRegExp corelibPattern(QLatin1String("QtCore[d]*4!"));
Q_ASSERT(corelibPattern.isValid());
if (!searchSymbols(syms, pattern, &matches, errorMessage))
return QString();
QStringList corelibStrdup = matches.filter(corelibPattern);
if (corelibStrdup.isEmpty()) {
*errorMessage = QString::fromLatin1("Unable to locate '%1' in '%2' (%3)").
arg(pattern, corelibPattern.pattern(), matches.join(QString(QLatin1Char(','))));
return QString();
}
return corelibStrdup.front();
}
// Load a library into the debuggee. Currently requires
// the QtCored4.pdb file to be present as we need "qstrdup"
// as dummy symbol. This is ok ATM since dumpers only
@@ -132,9 +122,10 @@ static bool debuggeeLoadLibrary(CIDebugControl *ctl,
// server, the debugger refuses to recognize it as a function.
// Set up the call stack with a function of same signature (qstrdup)
// and change the call register to LoadLibraryA() before executing "g".
// Prepare call.
const QString dummyFunc = resolveStrdup(syms, errorMessage);
if (dummyFunc.isEmpty())
// Prepare call: Locate 'qstrdup' in the (potentially namespaced) corelib. For some
// reason, the symbol is present in QtGui as well without type information.
QString dummyFunc = QLatin1String("*qstrdup");
if (resolveSymbol(syms, QLatin1String("QtCore[d]*4!"), &dummyFunc, errorMessage) != ResolveSymbolOk)
return false;
QString callCmd = QLatin1String(".call ");
callCmd += dummyFunc;
@@ -145,80 +136,293 @@ static bool debuggeeLoadLibrary(CIDebugControl *ctl,
return false;
if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, QLatin1String("r eip=Kernel32!LoadLibraryA"), errorMessage))
return false;
// This will hit a breakpoint
if (loadDebug)
qDebug() << " executing 'g'";
// This will hit a breakpoint.
if (!CdbDebugEnginePrivate::executeDebuggerCommand(ctl, QString(QLatin1Char('g')), errorMessage))
return false;
const HRESULT hr = ctl->WaitForEvent(0, waitTimeOutMS);
if (FAILED(hr)) {
*errorMessage = msgComFailed("WaitForEvent", hr);
return false;
// @Todo: We cannot evaluate output here as it is asynchronous
}
return true;
}
CdbDumperHelper::CdbDumperHelper(CdbComInterfaces *cif) :
m_state(NotLoaded),
m_cif(cif)
// ------------------- CdbDumperHelper::DumperInputParameters
struct CdbDumperHelper::DumperInputParameters {
DumperInputParameters(int protocolVersion_ = 1,
int token_ = 1,
quint64 Address_ = 0,
bool dumpChildren_ = false,
int extraInt0_ = 0,
int extraInt1_ = 0,
int extraInt2_ = 0,
int extraInt3_ = 0);
QString command(const QString &dumpFunction) const;
int protocolVersion;
int token;
quint64 address;
bool dumpChildren;
int extraInt0;
int extraInt1;
int extraInt2;
int extraInt3;
};
CdbDumperHelper::DumperInputParameters::DumperInputParameters(int protocolVersion_,
int token_,
quint64 address_,
bool dumpChildren_,
int extraInt0_,
int extraInt1_,
int extraInt2_,
int extraInt3_) :
protocolVersion(protocolVersion_),
token(token_),
address(address_),
dumpChildren(dumpChildren_),
extraInt0(extraInt0_),
extraInt1(extraInt1_),
extraInt2(extraInt2_),
extraInt3(extraInt3_)
{
}
QString CdbDumperHelper::DumperInputParameters::command(const QString &dumpFunction) const
{
QString rc;
QTextStream str(&rc);
str.setIntegerBase(16);
str << ".call " << dumpFunction << '(' << protocolVersion << ',' << token << ',';
str.setIntegerBase(16);
str << "0x" << address;
str.setIntegerBase(10);
str << ',' << (dumpChildren ? 1 : 0) << ',' << extraInt0 << ',' << extraInt1
<< ',' << extraInt2 << ',' << extraInt3 << ')';
return rc;
}
// ------------------- CdbDumperHelper
CdbDumperHelper::CdbDumperHelper(CdbComInterfaces *cif) :
m_state(NotLoaded),
m_cif(cif),
m_inBufferAddress(0),
m_inBufferSize(0),
m_outBufferAddress(0),
m_outBufferSize(0),
m_buffer(0)
{
}
CdbDumperHelper::~CdbDumperHelper()
{
clearBuffer();
}
void CdbDumperHelper::clearBuffer()
{
if (m_buffer) {
delete [] m_buffer;
m_buffer = 0;
}
}
void CdbDumperHelper::reset(const QString &library, bool enabled)
{
m_library = library;
m_state = enabled ? NotLoaded : Disabled;
m_dumpObjectSymbol = QLatin1String("qDumpObjectData440");
m_errorMessage.clear();
m_knownTypes.clear();
m_qtVersion.clear();
m_qtNamespace.clear();
m_inBufferAddress = m_outBufferAddress = 0;
m_inBufferSize = m_outBufferSize = 0;
clearBuffer();
}
bool CdbDumperHelper::moduleLoadHook(const QString &name, bool *ignoreNextBreakPoint)
// Attempt to load and initialize dumpers, give feedback
// to user.
void CdbDumperHelper::load(DebuggerManager *manager, IDebuggerManagerAccessForEngines *access)
{
*ignoreNextBreakPoint = false;
bool ok = true; // report failure only once
switch (m_state) {
case Disabled:
break;
case NotLoaded:
// Load once QtCore is there.
if (name.contains(QLatin1String("QtCore"))) {
if (loadDebug)
qDebug() << Q_FUNC_INFO << '\n' << name << m_state << executionStatusString(m_cif->debugControl);
ok = debuggeeLoadLibrary(m_cif->debugControl, m_cif->debugClient, m_cif->debugSymbols, m_cif->debugDataSpaces,
m_library, &m_errorMessage);
if (ok) {
m_state = Loading;
*ignoreNextBreakPoint = true;
} else {
m_state = Failed;
}
if (loadDebug)
qDebug() << m_state << executionStatusString(m_cif->debugControl);
enum Result { Failed, Succeeded, NoQtApp };
if (m_state != NotLoaded)
return;
manager->showStatusMessage(QCoreApplication::translate("CdbDumperHelper", "Loading dumpers..."), 10000);
const QString messagePrefix = QLatin1String("dumper:");
QString message;
Result result = Failed;
do {
// Do we have Qt and are we already loaded by accident?
QStringList modules;
if (!getModuleNameList(m_cif->debugSymbols, &modules, &message))
break;
if (modules.filter(QLatin1String(qtCoreModuleNameC)).isEmpty()) {
message = QCoreApplication::translate("CdbDumperHelper", "The debugger does not appear to be Qt application.");
result = NoQtApp;
}
break;
case Loading:
// Hurray, loaded. Now resolve the symbols we need
if (name.contains(QLatin1String("gdbmacros"))) {
ok = resolveSymbols(&m_errorMessage);
if (ok) {
m_state = Loaded;
} else {
m_state = Failed;
// Make sure the dumper lib is loaded.
if (modules.filter(QLatin1String(dumperModuleNameC), Qt::CaseInsensitive).isEmpty()) {
// Try to load
if (!debuggeeLoadLibrary(m_cif->debugControl, m_cif->debugClient, m_cif->debugSymbols, m_cif->debugDataSpaces,
m_library, &message)) {
break;
}
} else {
access->showDebuggerOutput(messagePrefix, QCoreApplication::translate("CdbDumperHelper", "The dumper module appears to be already loaded."));
}
break;
case Loaded:
break;
// Resolve symbols and do call to get types
if (!resolveSymbols(&message))
break;
if (!getKnownTypes(&message))
break;
message = QCoreApplication::translate("CdbDumperHelper", "Dumper library '%1' loaded.").arg(m_library);
result = Succeeded;
} while (false);
// eval state and notify user
switch (result) {
case Failed:
message = QCoreApplication::translate("CdbDumperHelper", "The dumper library '%1' could not be loaded:\n%2").arg(m_library, message);
access->showDebuggerOutput(messagePrefix, message);
access->showQtDumperLibraryWarning(message);
m_state = Disabled;
break;
};
return ok;
case Succeeded:
access->showDebuggerOutput(messagePrefix, message);
access->showDebuggerOutput(messagePrefix, statusMessage());
m_state = Loaded;
break;
case NoQtApp:
access->showDebuggerOutput(messagePrefix, message);
m_state = Disabled;
break;
}
if (loadDebug)
qDebug() << Q_FUNC_INFO << "\n<" << result << '>' << m_state << message;
}
QString CdbDumperHelper::statusMessage() const
{
const QString nameSpace = m_qtNamespace.isEmpty() ? QCoreApplication::translate("CdbDumperHelper", "<none>") : m_qtNamespace;
return QCoreApplication::translate("CdbDumperHelper",
"%n known types, Qt version: %1, Qt namespace: %2",
0, QCoreApplication::CodecForTr,
m_knownTypes.size()).arg(m_qtVersion, nameSpace);
}
// Retrieve address and optionally size of a symbol.
static inline bool getSymbolAddress(CIDebugSymbols *sg,
const QString &name,
quint64 *address,
ULONG *size /* = 0*/,
QString *errorMessage)
{
// Get address
HRESULT hr = sg->GetOffsetByNameWide(name.utf16(), address);
if (FAILED(hr)) {
*errorMessage = msgComFailed("GetOffsetByNameWide", hr);
return false;
}
// Get size. Even works for arrays
if (size) {
ULONG64 moduleAddress;
ULONG type;
hr = sg->GetOffsetTypeId(*address, &type, &moduleAddress);
if (FAILED(hr)) {
*errorMessage = msgComFailed("GetOffsetTypeId", hr);
return false;
}
hr = sg->GetTypeSize(moduleAddress, type, size);
if (FAILED(hr)) {
*errorMessage = msgComFailed("GetTypeSize", hr);
return false;
}
} // size desired
return true;
}
bool CdbDumperHelper::resolveSymbols(QString *errorMessage)
{
// Resolve the symbols we need
m_dumpObjectSymbol = QLatin1String("qDumpObjectData440");
const bool rc = resolveSymbol(m_cif->debugSymbols, &m_dumpObjectSymbol, errorMessage) == ResolveSymbolOk;
// Resolve the symbols we need (potentially namespaced).
// There is a 'qDumpInBuffer' in QtCore as well.
m_dumpObjectSymbol = QLatin1String("*qDumpObjectData440");
QString inBufferSymbol = QLatin1String("*qDumpInBuffer");
QString outBufferSymbol = QLatin1String("*qDumpOutBuffer");
const QString dumperModuleName = QLatin1String(dumperModuleNameC);
bool rc = resolveSymbol(m_cif->debugSymbols, &m_dumpObjectSymbol, errorMessage) == ResolveSymbolOk
&& resolveSymbol(m_cif->debugSymbols, dumperModuleName, &inBufferSymbol, errorMessage) == ResolveSymbolOk
&& resolveSymbol(m_cif->debugSymbols, dumperModuleName, &outBufferSymbol, errorMessage) == ResolveSymbolOk;
if (!rc)
return false;
// Determine buffer addresses, sizes and alloc buffer
rc = getSymbolAddress(m_cif->debugSymbols, inBufferSymbol, &m_inBufferAddress, &m_inBufferSize, errorMessage)
&& getSymbolAddress(m_cif->debugSymbols, outBufferSymbol, &m_outBufferAddress, &m_outBufferSize, errorMessage);
if (!rc)
return false;
m_buffer = new char[qMax(m_inBufferSize, m_outBufferSize)];
if (loadDebug)
qDebug() << Q_FUNC_INFO << '\n' << rc << m_dumpObjectSymbol;
return rc;
qDebug().nospace() << Q_FUNC_INFO << '\n' << rc << m_dumpObjectSymbol
<< " buffers at 0x" << QString::number(m_inBufferAddress, 16) << ','
<< m_inBufferSize << "; 0x"
<< QString::number(m_outBufferAddress, 16) << ',' << m_outBufferSize << '\n';
return true;
}
bool CdbDumperHelper::getKnownTypes(QString *errorMessage)
{
QByteArray output;
if (!callDumper(DumperInputParameters(1), &output, errorMessage)) {
return false;
}
if (!parseQueryDumperOutput(output, &m_knownTypes, &m_qtVersion, &m_qtNamespace)) {
*errorMessage = QString::fromLatin1("Unable to parse the dumper output: '%1'").arg(QString::fromAscii(output));
}
if (loadDebug)
qDebug() << Q_FUNC_INFO << m_knownTypes << m_qtVersion << m_qtNamespace;
return true;
}
bool CdbDumperHelper::callDumper(const DumperInputParameters &p, QByteArray *output, QString *errorMessage)
{
IgnoreDebugEventCallback devNull;
EventCallbackRedirector eventRedir(m_cif->debugClient, &devNull);
const QString callCmd = p.command(m_dumpObjectSymbol);
// Set up call and a temporary breakpoint after it.
if (!CdbDebugEnginePrivate::executeDebuggerCommand(m_cif->debugControl, callCmd, errorMessage))
return false;
// Go and filter away break event
if (!CdbDebugEnginePrivate::executeDebuggerCommand(m_cif->debugControl, QString(QLatin1Char('g')), errorMessage))
return false;
HRESULT hr = m_cif->debugControl->WaitForEvent(0, waitTimeOutMS);
if (FAILED(hr)) {
*errorMessage = msgComFailed("WaitForEvent", hr);
return false;
}
// Read output
hr = m_cif->debugDataSpaces->ReadVirtual(m_outBufferAddress, m_buffer, m_outBufferSize, 0);
if (FAILED(hr)) {
*errorMessage = msgComFailed("ReadVirtual", hr);
return false;
}
// see QDumper implementation
const char result = m_buffer[0];
switch (result) {
case 't':
break;
case '+':
*errorMessage = QString::fromLatin1("Dumper call '%1' resulted in output overflow.").arg(callCmd);
return false;
case 'f':
*errorMessage = QString::fromLatin1("Dumper call '%1' failed.").arg(callCmd);
return false;
default:
*errorMessage = QString::fromLatin1("Dumper call '%1' failed ('%2').").arg(callCmd).arg(QLatin1Char(result));
return false;
}
*output = QByteArray(m_buffer + 1);
return true;
}
} // namespace Internal