/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
**************************************************************************/

#include "gdbengine.h"

#include "watchutils.h"
#include "debuggeractions.h"
#include "debuggerconstants.h"
#include "debuggermanager.h"
#include "gdbmi.h"
#include "procinterrupt.h"

#include "disassemblerhandler.h"
#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "stackhandler.h"
#include "watchhandler.h"
#include "sourcefileswindow.h"

#include "debuggerdialogs.h"

#include <utils/qtcassert.h>
#include <coreplugin/icore.h>

#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QTime>
#include <QtCore/QTimer>

#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QLabel>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
#include <QtGui/QToolTip>
#include <QtGui/QDialogButtonBox>
#include <QtGui/QPushButton>

#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
#include <unistd.h>
#include <dlfcn.h>
#endif
#include <ctype.h>

using namespace Debugger;
using namespace Debugger::Internal;
using namespace Debugger::Constants;

Q_DECLARE_METATYPE(Debugger::Internal::GdbMi);

//#define DEBUG_PENDING  1
//#define DEBUG_SUBITEM  1

#if DEBUG_PENDING
#   define PENDING_DEBUG(s) qDebug() << s
#else
#   define PENDING_DEBUG(s)
#endif

static const QString tooltipIName = "tooltip";

///////////////////////////////////////////////////////////////////////
//
// GdbCommandType
//
///////////////////////////////////////////////////////////////////////

enum GdbCommandType
{
    GdbInvalidCommand = 0,

    GdbShowVersion = 100,
    GdbFileExecAndSymbols,
    GdbQueryPwd,
    GdbQuerySources,
    GdbAsyncOutput2,
    GdbStart,
    GdbExit,
    GdbAttached,
    GdbStubAttached,
    GdbExecRun,
    GdbExecRunToFunction,
    GdbExecStep,
    GdbExecNext,
    GdbExecStepI,
    GdbExecNextI,
    GdbExecContinue,
    GdbExecFinish,
    GdbExecJumpToLine,
    GdbExecInterrupt,
    GdbInfoShared,
    GdbInfoProc,
    GdbInfoThreads,
    GdbQueryDebuggingHelper,
    GdbTemporaryContinue,
    GdbTargetCore,

    BreakCondition = 200,
    BreakEnablePending,
    BreakSetAnnotate,
    BreakDelete,
    BreakList,
    BreakIgnore,
    BreakInfo,
    BreakInsert,
    BreakInsert1,

    DisassemblerList = 300,

    ModulesList = 400,

    RegisterListNames = 500,
    RegisterListValues,

    StackSelectThread = 600,
    StackListThreads,
    StackListFrames,
    StackListLocals,
    StackListArguments,

    WatchVarAssign = 700,             // data changed by user
    WatchVarListChildren,
    WatchVarCreate,
    WatchEvaluateExpression,
    WatchToolTip,
    WatchDebuggingHelperSetup,
    WatchDebuggingHelperValue1,           // waiting for gdb ack
    WatchDebuggingHelperValue2,           // waiting for actual data
    WatchDebuggingHelperValue3,           // macro based
    WatchDebuggingHelperEditValue,
};

static int &currentToken()
{
    static int token = 0;
    return token;
}

static bool isSkippable(int type)
{
    return type == RegisterListValues
        && type == StackListThreads
        && type == StackListFrames
        && type == StackListLocals
        && type == StackListArguments
        && type == WatchVarAssign
        && type == WatchVarListChildren
        && type == WatchVarCreate
        && type == WatchEvaluateExpression
        && type == WatchToolTip;
}

///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

GdbEngine::GdbEngine(DebuggerManager *parent)
{
    q = parent;
    qq = parent->engineInterface();
    m_stubProc.setDebug(true);
    initializeVariables();
    initializeConnections();
}

GdbEngine::~GdbEngine()
{
    // prevent sending error messages afterwards
    m_gdbProc.disconnect(this);
}

void GdbEngine::initializeConnections()
{
    // Gdb Process interaction
    connect(&m_gdbProc, SIGNAL(error(QProcess::ProcessError)), this,
        SLOT(gdbProcError(QProcess::ProcessError)));
    connect(&m_gdbProc, SIGNAL(readyReadStandardOutput()), this,
        SLOT(readGdbStandardOutput()));
    connect(&m_gdbProc, SIGNAL(readyReadStandardError()), this,
        SLOT(readGdbStandardError()));
    connect(&m_gdbProc, SIGNAL(finished(int, QProcess::ExitStatus)), q,
        SLOT(exitDebugger()));

    connect(&m_stubProc, SIGNAL(processError(QString)), SLOT(stubError(QString)));
    connect(&m_stubProc, SIGNAL(processStarted()), SLOT(stubStarted()));
    connect(&m_stubProc, SIGNAL(wrapperStopped()), q, SLOT(exitDebugger()));

    // Output
    connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
            SLOT(readDebugeeOutput(QByteArray)));

    connect(this, SIGNAL(gdbOutputAvailable(QString,QString)),
        q, SLOT(showDebuggerOutput(QString,QString)),
        Qt::QueuedConnection);
    connect(this, SIGNAL(gdbInputAvailable(QString,QString)),
        q, SLOT(showDebuggerInput(QString,QString)),
        Qt::QueuedConnection);
    connect(this, SIGNAL(applicationOutputAvailable(QString)),
        q, SLOT(showApplicationOutput(QString)),
        Qt::QueuedConnection);

    connect(theDebuggerAction(UseDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setUseDebuggingHelpers(QVariant)));
    connect(theDebuggerAction(DebugDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setDebugDebuggingHelpers(QVariant)));
    connect(theDebuggerAction(RecheckDebuggingHelpers), SIGNAL(triggered()),
        this, SLOT(recheckDebuggingHelperAvailability()));

    connect(theDebuggerAction(FormatHexadecimal), SIGNAL(triggered()),
        this, SLOT(reloadRegisters()));
    connect(theDebuggerAction(FormatDecimal), SIGNAL(triggered()),
        this, SLOT(reloadRegisters()));
    connect(theDebuggerAction(FormatOctal), SIGNAL(triggered()),
        this, SLOT(reloadRegisters()));
    connect(theDebuggerAction(FormatBinary), SIGNAL(triggered()),
        this, SLOT(reloadRegisters()));
    connect(theDebuggerAction(FormatRaw), SIGNAL(triggered()),
        this, SLOT(reloadRegisters()));
    connect(theDebuggerAction(FormatNatural), SIGNAL(triggered()),
        this, SLOT(reloadRegisters()));

    connect(theDebuggerAction(ExpandStack), SIGNAL(triggered()),
        this, SLOT(reloadFullStack()));
    connect(theDebuggerAction(MaximalStackDepth), SIGNAL(triggered()),
        this, SLOT(reloadFullStack()));
}

void GdbEngine::initializeVariables()
{
    m_debuggingHelperState = DebuggingHelperUninitialized;
    m_gdbVersion = 100;
    m_gdbBuildVersion = -1;

    m_fullToShortName.clear();
    m_shortToFullName.clear();
    m_varToType.clear();

    m_modulesListOutdated = true;
    m_oldestAcceptableToken = -1;
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingRequests = 0;
    m_waitingForBreakpointSynchronizationToContinue = false;
    m_waitingForFirstBreakpointToBeHit = false;
    m_commandsToRunOnTemporaryBreak.clear();
}

void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
    switch (error) {
        case QProcess::FailedToStart:
            msg = QString(tr("The Gdb process failed to start. Either the "
                "invoked program '%1' is missing, or you may have insufficient "
                "permissions to invoke the program."))
                .arg(theDebuggerStringSetting(GdbLocation));
            break;
        case QProcess::Crashed:
            msg = tr("The Gdb process crashed some time after starting "
                "successfully.");
            break;
        case QProcess::Timedout:
            msg = tr("The last waitFor...() function timed out. "
                "The state of QProcess is unchanged, and you can try calling "
                "waitFor...() again.");
            break;
        case QProcess::WriteError:
            msg = tr("An error occurred when attempting to write "
                "to the Gdb process. For example, the process may not be running, "
                "or it may have closed its input channel.");
            break;
        case QProcess::ReadError:
            msg = tr("An error occurred when attempting to read from "
                "the Gdb process. For example, the process may not be running.");
            break;
        default:
            msg = tr("An unknown error in the Gdb process occurred. "
                "This is the default return value of error().");
    }

    q->showStatusMessage(msg);
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
    // act as if it was closed by the core
    q->exitDebugger();
}

#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
    Q_UNUSED(to);
    // note that qDebug cuts off output after a certain size... (bug?)
    qDebug("\n>>>>> %s\n%s\n====\n%s\n<<<<<\n",
        qPrintable(currentTime()),
        qPrintable(QString(ba).trimmed()),
        qPrintable(to.trimmed()));
    //qDebug() << "";
    //qDebug() << qPrintable(currentTime())
    //    << " Reading response:  " << QString(ba).trimmed() << "\n";
}
#endif

void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
    emit applicationOutputAvailable(m_outputCodec->toUnicode(
            data.constData(), data.length(), &m_outputCodecState));
}

void GdbEngine::debugMessage(const QString &msg)
{
    emit gdbOutputAvailable("debug:", msg);
}

void GdbEngine::handleResponse(const QByteArray &buff)
{
    static QTime lastTime;

    emit gdbOutputAvailable("            ", currentTime());
    emit gdbOutputAvailable("stdout:", buff);

#if 0
    qDebug() // << "#### start response handling #### "
        << currentTime()
        << lastTime.msecsTo(QTime::currentTime()) << "ms,"
        << "buf: " << buff.left(1500) << "..."
        //<< "buf: " << buff
        << "size:" << buff.size();
#else
    //qDebug() << "buf: " << buff;
#endif

    lastTime = QTime::currentTime();

    if (buff.isEmpty() || buff == "(gdb) ")
        return;

    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;

    int token = -1;
    // token is a sequence of numbers
    for (inner = from; inner != to; ++inner)
        if (*inner < '0' || *inner > '9')
            break;
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
        //qDebug() << "found token " << token;
    }

    // next char decides kind of record
    const char c = *from++;
    //qDebug() << "CODE:" << c;
    switch (c) {
        case '*':
        case '+':
        case '=': {
            QByteArray asyncClass;
            for (; from != to; ++from) {
                const char c = *from;
                if (!isNameChar(c))
                    break;
                asyncClass += *from;
            }
            //qDebug() << "ASYNCCLASS" << asyncClass;

            GdbMi record;
            while (from != to) {
                GdbMi data;
                if (*from != ',') {
                    // happens on archer where we get 
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb) 
                    record.m_type = GdbMi::Tuple;
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
                    //qDebug() << "parsed response: " << data.toString();
                    record.m_children += data;
                    record.m_type = GdbMi::Tuple;
                }
            }
            if (asyncClass == "stopped") {
                handleAsyncOutput(record);
            } else if (asyncClass == "running") {
                // Archer has 'thread-id="all"' here
            } else if (asyncClass == "library-loaded") {
                // Archer has 'id="/usr/lib/libdrm.so.2",
                // target-name="/usr/lib/libdrm.so.2",
                // host-name="/usr/lib/libdrm.so.2",
                // symbols-loaded="0"
            } else if (asyncClass == "library-unloaded") {
                // Archer has 'id="/usr/lib/libdrm.so.2",
                // target-name="/usr/lib/libdrm.so.2",
                // host-name="/usr/lib/libdrm.so.2"
            } else if (asyncClass == "thread-group-created") {
                // Archer has "{id="28902"}" 
            } else if (asyncClass == "thread-created") {
                //"{id="1",group-id="28902"}" 
            } else if (asyncClass == "thread-group-exited") {
                // Archer has "{id="28902"}" 
            } else if (asyncClass == "thread-exited") {
                //"{id="1",group-id="28902"}" 
            } else if (asyncClass == "thread-selected") {
                //"{id="2"}" 
            #ifdef Q_OS_MAC
            } else if (asyncClass == "shlibs-updated") {
                // MAC announces updated libs
            } else if (asyncClass == "shlibs-added") {
                // MAC announces added libs
                // {shlib-info={num="2", name="libmathCommon.A_debug.dylib",
                // kind="-", dyld-addr="0x7f000", reason="dyld", requested-state="Y",
                // state="Y", path="/usr/lib/system/libmathCommon.A_debug.dylib",
                // description="/usr/lib/system/libmathCommon.A_debug.dylib",
                // loaded_addr="0x7f000", slide="0x7f000", prefix=""}}
            #endif
            } else {
                qDebug() << "IGNORED ASYNC OUTPUT "
                    << asyncClass << record.toString();
            }
            break;
        }

        case '~': {
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingConsoleStreamOutput += data;
            if (data.startsWith("Reading symbols from ")) {
                q->showStatusMessage(tr("Reading ")
                    + QString::fromLatin1(data.mid(21)));
            }
            break;
        }

        case '@': {
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingTargetStreamOutput += data;
            break;
        }

        case '&': {
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingLogStreamOutput += data;
            // On Windows, the contents seem to depend on the debugger
            // version and/or OS version used.
            if (data.startsWith("warning:"))
                qq->showApplicationOutput(data.mid(9)); // cut "warning: "
            break;
        }

        case '^': {
            GdbResultRecord record;

            record.token = token;

            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;

            QByteArray resultClass(from, inner - from);

            if (resultClass == "done")
                record.resultClass = GdbResultDone;
            else if (resultClass == "running")
                record.resultClass = GdbResultRunning;
            else if (resultClass == "connected")
                record.resultClass = GdbResultConnected;
            else if (resultClass == "error")
                record.resultClass = GdbResultError;
            else if (resultClass == "exit")
                record.resultClass = GdbResultExit;
            else
                record.resultClass = GdbResultUnknown;

            from = inner;
            if (from != to) {
                if (*from == ',') {
                    ++from;
                    record.data.parseTuple_helper(from, to);
                    record.data.m_type = GdbMi::Tuple;
                    record.data.m_name = "data";
                } else {
                    // Archer has this
                    record.data.m_type = GdbMi::Tuple;
                    record.data.m_name = "data";
                }
            }

            //qDebug() << "\nLOG STREAM:" + m_pendingLogStreamOutput;
            //qDebug() << "\nTARGET STREAM:" + m_pendingTargetStreamOutput;
            //qDebug() << "\nCONSOLE STREAM:" + m_pendingConsoleStreamOutput;
            record.data.setStreamOutput("logstreamoutput",
                m_pendingLogStreamOutput);
            record.data.setStreamOutput("targetstreamoutput",
                m_pendingTargetStreamOutput);
            record.data.setStreamOutput("consolestreamoutput",
                m_pendingConsoleStreamOutput);
            QByteArray custom = m_customOutputForToken[token];
            if (!custom.isEmpty())
                record.data.setStreamOutput("customvaluecontents",
                    '{' + custom + '}');
            //m_customOutputForToken.remove(token);
            m_pendingLogStreamOutput.clear();
            m_pendingTargetStreamOutput.clear();
            m_pendingConsoleStreamOutput.clear();

            handleResultRecord(record);
            break;
        }
        default: {
            qDebug() << "UNKNOWN RESPONSE TYPE" << c;
            break;
        }
    }
}

void GdbEngine::handleStubAttached()
{
    qq->notifyInferiorStopped();
    m_waitingForBreakpointSynchronizationToContinue = true;
    handleAqcuiredInferior();
}

void GdbEngine::stubStarted()
{
    q->m_attachedPID = m_stubProc.applicationPID();
    qq->notifyInferiorPidChanged(q->m_attachedPID);
    sendCommand("attach " + QString::number(q->m_attachedPID), GdbStubAttached);
}

void GdbEngine::stubError(const QString &msg)
{
    QMessageBox::critical(q->mainWindow(), tr("Debugger Error"), msg);
}

void GdbEngine::readGdbStandardError()
{
    qWarning() << "Unexpected gdb stderr:" << m_gdbProc.readAllStandardError();
}

void GdbEngine::readGdbStandardOutput()
{
    int newstart = 0;
    int scan = m_inbuffer.size();

    m_inbuffer.append(m_gdbProc.readAllStandardOutput());

    while (newstart < m_inbuffer.size()) {
        int start = newstart;
        int end = m_inbuffer.indexOf('\n', scan);
        if (end < 0) {
            m_inbuffer.remove(0, start);
            return;
        }
        newstart = end + 1;
        scan = newstart;
        if (end == start)
            continue;
        #ifdef Q_OS_WIN
        if (m_inbuffer.at(end - 1) == '\r') {
            --end;
            if (end == start)
                continue;
        }
        #endif
        handleResponse(QByteArray::fromRawData(m_inbuffer.constData() + start, end - start));
    }
    m_inbuffer.clear();
}

void GdbEngine::interruptInferior()
{
    qq->notifyInferiorStopRequested();
    if (m_gdbProc.state() == QProcess::NotRunning) {
        debugMessage("TRYING TO INTERRUPT INFERIOR WITHOUT RUNNING GDB");
        qq->notifyInferiorExited();
        return;
    }

    if (q->m_attachedPID <= 0) {
        debugMessage("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED");
        return;
    }

    if (!interruptProcess(q->m_attachedPID))
        debugMessage(QString("CANNOT INTERRUPT %1").arg(q->m_attachedPID));
}

void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
{
    int pid = pid0.toInt();
    if (pid == 0) {
        debugMessage(QString("Cannot parse PID from %1").arg(pid0));
        return;
    }
    if (pid == q->m_attachedPID)
        return;
    debugMessage(QString("FOUND PID %1").arg(pid));
    q->m_attachedPID = pid;
    qq->notifyInferiorPidChanged(pid);
}

void GdbEngine::sendSynchronizedCommand(const QString & command,
    int type, const QVariant &cookie, StopNeeded needStop)
{
    sendCommand(command, type, cookie, needStop, Synchronized);
}

void GdbEngine::sendCommand(const QString &command, int type,
    const QVariant &cookie, StopNeeded needStop, Synchronization synchronized)
{
    if (m_gdbProc.state() == QProcess::NotRunning) {
        debugMessage("NO GDB PROCESS RUNNING, CMD IGNORED: " + command);
        return;
    }

    if (synchronized) {
        ++m_pendingRequests;
        PENDING_DEBUG("   TYPE " << type << " INCREMENTS PENDING TO: "
            << m_pendingRequests << command);
    } else {
        PENDING_DEBUG("   UNKNOWN TYPE " << type << " LEAVES PENDING AT: "
            << m_pendingRequests << command);
    }

    GdbCookie cmd;
    cmd.synchronized = synchronized;
    cmd.command = command;
    cmd.type = type;
    cmd.cookie = cookie;

    if (needStop && q->status() != DebuggerInferiorStopped
            && q->status() != DebuggerProcessStartingUp) {
        // queue the commands that we cannot send at once
        QTC_ASSERT(q->status() == DebuggerInferiorRunning,
            qDebug() << "STATUS: " << q->status());
        q->showStatusMessage(tr("Stopping temporarily."));
        debugMessage("QUEUING COMMAND " + cmd.command);
        m_commandsToRunOnTemporaryBreak.append(cmd);
        interruptInferior();
    } else if (!command.isEmpty()) {
        ++currentToken();
        m_cookieForToken[currentToken()] = cmd;
        cmd.command = QString::number(currentToken()) + cmd.command;
        if (cmd.command.contains("%1"))
            cmd.command = cmd.command.arg(currentToken());

        m_gdbProc.write(cmd.command.toLatin1() + "\r\n");
        //emit gdbInputAvailable(QString(), "         " +  currentTime());
        //emit gdbInputAvailable(QString(), "[" + currentTime() + "]    " + cmd.command);
        emit gdbInputAvailable(QString(), cmd.command);
    }
}

void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
    //qDebug() << "TOKEN: " << record.token
    //    << " ACCEPTABLE: " << m_oldestAcceptableToken;
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

    int token = record.token;
    if (token == -1)
        return;

    GdbCookie cmd = m_cookieForToken.take(token);

    if (record.token < m_oldestAcceptableToken && isSkippable(cmd.type)) {
        //qDebug() << "### SKIPPING OLD RESULT " << record.toString();
        //QMessageBox::information(m_mainWindow, tr("Skipped"), "xxx");
        return;
    }

#if 0
    qDebug() << "# handleOutput, "
        << "cmd type: " << cmd.type
        << "cmd synchronized: " << cmd.synchronized
        << "\n record: " << record.toString();
#endif

    // << "\n data: " << record.data.toString(true);

    if (cmd.type != GdbInvalidCommand)
        handleResult(record, cmd.type, cmd.cookie);

    if (cmd.synchronized) {
        --m_pendingRequests;
        PENDING_DEBUG("   TYPE " << cmd.type << " DECREMENTS PENDING TO: "
            << m_pendingRequests << cmd.command);
        if (m_pendingRequests <= 0) {
            PENDING_DEBUG(" ....  AND TRIGGERS MODEL UPDATE");
            updateWatchModel2();
        }
    } else {
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.type << " LEAVES PENDING AT: "
            << m_pendingRequests << cmd.command);
    }
}

void GdbEngine::handleResult(const GdbResultRecord & record, int type,
    const QVariant & cookie)
{
    switch (type) {
        case GdbStubAttached:
            handleStubAttached();
            break;
        case GdbExecNext:
        case GdbExecStep:
        case GdbExecNextI:
        case GdbExecStepI:
        case GdbExecContinue:
        case GdbExecFinish:
            // evil code sharing
            handleExecRun(record);
            break;

        case GdbStart:
            handleStart(record);
            break;
        case GdbAttached:
            handleAttach();
            break;
        case GdbInfoProc:
            handleInfoProc(record);
            break;
        case GdbInfoThreads:
            handleInfoThreads(record);
            break;
        case GdbExit:
            handleExit(record);
            break;

        case GdbShowVersion:
            handleShowVersion(record);
            break;
        case GdbFileExecAndSymbols:
            handleFileExecAndSymbols(record);
            break;
        case GdbExecRunToFunction:
            // that should be "^running". We need to handle the resulting
            // "Stopped"
            //handleExecRunToFunction(record);
            break;
        case GdbExecInterrupt:
            qq->notifyInferiorStopped();
            break;
        case GdbExecJumpToLine:
            handleExecJumpToLine(record);
            break;
        case GdbQueryPwd:
            handleQueryPwd(record);
            break;
        case GdbQuerySources:
            handleQuerySources(record);
            break;
        case GdbAsyncOutput2:
            handleAsyncOutput2(cookie.value<GdbMi>());
            break;
        case GdbInfoShared:
            handleInfoShared(record);
            break;
        case GdbQueryDebuggingHelper:
            handleQueryDebuggingHelper(record);
            break;
        case GdbTemporaryContinue:
            continueInferior();
            q->showStatusMessage(tr("Continuing after temporary stop."));
            break;
        case GdbTargetCore:
            handleTargetCore(record);
            break;

        case BreakList:
            handleBreakList(record);
            break;
        case BreakInsert:
            handleBreakInsert(record, cookie.toInt());
            break;
        case BreakInsert1:
            handleBreakInsert1(record, cookie.toInt());
            break;
        case BreakInfo:
            handleBreakInfo(record, cookie.toInt());
            break;
        case BreakEnablePending:
        case BreakDelete:
            // nothing
            break;
        case BreakIgnore:
            handleBreakIgnore(record, cookie.toInt());
            break;
        case BreakCondition:
            handleBreakCondition(record, cookie.toInt());
            break;

        case DisassemblerList:
            handleDisassemblerList(record, cookie.toString());
            break;

        case ModulesList:
            handleModulesList(record);
            break;

        case RegisterListNames:
            handleRegisterListNames(record);
            break;
        case RegisterListValues:
            handleRegisterListValues(record);
            break;

        case StackListFrames:
            handleStackListFrames(record, cookie.toBool());
            break;
        case StackListThreads:
            handleStackListThreads(record, cookie.toInt());
            break;
        case StackSelectThread:
            handleStackSelectThread(record, cookie.toInt());
            break;
        case StackListLocals:
            handleStackListLocals(record);
            break;
        case StackListArguments:
            handleStackListArguments(record);
            break;

        case WatchVarListChildren:
            handleVarListChildren(record, cookie.value<WatchData>());
            break;
        case WatchVarCreate:
            handleVarCreate(record, cookie.value<WatchData>());
            break;
        case WatchVarAssign:
            handleVarAssign();
            break;
        case WatchEvaluateExpression:
            handleEvaluateExpression(record, cookie.value<WatchData>());
            break;
        case WatchToolTip:
            handleToolTip(record, cookie.toString());
            break;
        case WatchDebuggingHelperValue1:
            handleDebuggingHelperValue1(record, cookie.value<WatchData>());
            break;
        case WatchDebuggingHelperValue2:
            handleDebuggingHelperValue2(record, cookie.value<WatchData>());
            break;

        case WatchDebuggingHelperValue3:
            handleDebuggingHelperValue3(record, cookie.value<WatchData>());
            break;

        case WatchDebuggingHelperSetup:
            handleDebuggingHelperSetup(record);
            break;

        default:
            debugMessage(QString("FIXME: GdbEngine::handleResult: "
                "should not happen %1").arg(type));
            break;
    }
}

void GdbEngine::executeDebuggerCommand(const QString &command)
{
    if (m_gdbProc.state() == QProcess::NotRunning) {
        debugMessage("NO GDB PROCESS RUNNING, PLAIN CMD IGNORED: " + command);
        return;
    }

    GdbCookie cmd;
    cmd.command = command;
    cmd.type = -1;

    m_gdbProc.write(cmd.command.toLatin1() + "\r\n");
}

void GdbEngine::handleTargetCore(const GdbResultRecord &record)
{
    Q_UNUSED(record);

    qq->notifyInferiorStopped();
    q->showStatusMessage(tr("Core file loaded."));

    q->resetLocation();

    //
    // Stack
    //
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot

    reloadStack();
    if (supportsThreads())
        sendSynchronizedCommand("-thread-list-ids", StackListThreads, 0);

    //
    // Disassembler
    //
    // XXX we have no data here ...
    //m_address = data.findChild("frame").findChild("addr").data();
    //qq->reloadDisassembler();

    //
    // Registers
    //
    qq->reloadRegisters();

    // Gdb-Macro based DebuggingHelpers
    sendCommand(
        "define qdumpqstring\n"
        "set $i = 0\n"
        "set $l = $arg0->d->size\n"
        "set $p = $arg0->d->data\n"
        "while $i < $l\n"
        "printf \"%d \",$p[$i++]\n"
        "end\n"
        "printf \"\\n\"\n"
        "end\n"
    );

    sendCommand(
        "define qdumpqstringlist\n"
        "set $i = $arg0->d->begin\n"
        "set $e = $arg0->d->end\n"
        "while $i < $e\n"
        "printf \"%d \",$arg0->d->array + $i++\n"
        "end\n"
        "printf \"\\n\"\n"
        "end\n"
    );
}

void GdbEngine::handleQueryPwd(const GdbResultRecord &record)
{
    // FIXME: remove this special case as soon as 'pwd'
    // is supported by MI
    //qDebug() << "PWD OUTPUT:" <<  record.toString();
    // Gdb responses _unless_ we get an error first.
    if (record.resultClass == GdbResultDone) {
#ifdef Q_OS_LINUX
        // "5^done,{logstreamoutput="pwd ",consolestreamoutput
        // ="Working directory /home/apoenitz/dev/work/test1.  "}
        m_pwd = record.data.findChild("consolestreamoutput").data();
        int pos = m_pwd.indexOf("Working directory");
        m_pwd = m_pwd.mid(pos + 18);
        m_pwd = m_pwd.trimmed();
        if (m_pwd.endsWith('.'))
            m_pwd.chop(1);
#endif
#ifdef Q_OS_WIN
        // ~"Working directory C:\\Users\\Thomas\\Documents\\WBTest3\\debug.\n"
        m_pwd = record.data.findChild("consolestreamoutput").data();
        m_pwd = m_pwd.trimmed();
#endif
        debugMessage("PWD RESULT: " + m_pwd);
    }
}

void GdbEngine::handleQuerySources(const GdbResultRecord &record)
{
    if (record.resultClass == GdbResultDone) {
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
        m_shortToFullName.clear();
        m_fullToShortName.clear();
        // "^done,files=[{file="../../../../bin/gdbmacros/gdbmacros.cpp",
        // fullname="/data5/dev/ide/main/bin/gdbmacros/gdbmacros.cpp"},
        GdbMi files = record.data.findChild("files");
        foreach (const GdbMi &item, files.children()) {
            QString fileName = item.findChild("file").data();
            GdbMi fullName = item.findChild("fullname");
            QString full = fullName.data();
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
                //qDebug() << "STORING 2: " << fileName << full;
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
    }
}

void GdbEngine::handleInfoThreads(const GdbResultRecord &record)
{
    if (record.resultClass == GdbResultDone) {
        // FIXME: use something more robust
        // WIN:     * 3 Thread 2312.0x4d0  0x7c91120f in ?? ()
        // LINUX:   * 1 Thread 0x7f466273c6f0 (LWP 21455)  0x0000000000404542 in ...
        QRegExp re(QLatin1String("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
        QString data = record.data.findChild("consolestreamoutput").data();
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
    }
}

void GdbEngine::handleInfoProc(const GdbResultRecord &record)
{
    if (record.resultClass == GdbResultDone) {
        #if defined(Q_OS_MAC)
        //^done,process-id="85075"
        maybeHandleInferiorPidChanged(record.data.findChild("process-id").data());
        #endif

        #if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
        // FIXME: use something more robust
        QRegExp re(QLatin1String("process (\\d+)"));
        QString data = record.data.findChild("consolestreamoutput").data();
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

void GdbEngine::handleInfoShared(const GdbResultRecord &record)
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
        handleModulesList(record);
    }
}

void GdbEngine::handleExecJumpToLine(const GdbResultRecord &record)
{
    // FIXME: remove this special case as soon as 'jump'
    // is supported by MI
    // "&"jump /home/apoenitz/dev/work/test1/test1.cpp:242"
    // ~"Continuing at 0x4058f3."
    // ~"run1 (argc=1, argv=0x7fffb213a478) at test1.cpp:242"
    // ~"242\t x *= 2;"
    //109^done"
    qq->notifyInferiorStopped();
    q->showStatusMessage(tr("Jumped. Stopped."));
    QString output = record.data.findChild("logstreamoutput").data();
    if (!output.isEmpty())
        return;
    QString fileAndLine = output.section(' ', 1, 1);
    QString file = fileAndLine.section(':', 0, 0);
    int line = fileAndLine.section(':', 1, 1).toInt();
    q->gotoLocation(file, line, true);
}

void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record)
{
    // FIXME: remove this special case as soon as there's a real
    // reason given when the temporary breakpoint is hit.
    // reight now we get:
    // 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4",
    // func="foo",args=[{name="str",value="@0x7fff0f450460"}],
    // file="main.cpp",fullname="/tmp/g/main.cpp",line="37"}
    qq->notifyInferiorStopped();
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
    GdbMi frame = record.data.findChild("frame");
    QString file = frame.findChild("fullname").data();
    int line = frame.findChild("line").data().toInt();
    qDebug() << "HIT: " << file << line << " IN " << frame.toString()
        << " -- " << record.toString();
    q->gotoLocation(file, line, true);
}

static bool isExitedReason(const QString &reason)
{
    return reason == QLatin1String("exited-normally")   // inferior exited normally
        || reason == QLatin1String("exited-signalled")  // inferior exited because of a signal
        //|| reason == QLatin1String("signal-received") // inferior received signal
        || reason == QLatin1String("exited");           // inferior exited
}

static bool isStoppedReason(const QString &reason)
{
    return reason == QLatin1String("function-finished")  // -exec-finish
        || reason == QLatin1String("signal-received")  // handled as "isExitedReason"
        || reason == QLatin1String("breakpoint-hit")     // -exec-continue
        || reason == QLatin1String("end-stepping-range") // -exec-next, -exec-step
        || reason == QLatin1String("location-reached")   // -exec-until
        || reason == QLatin1String("access-watchpoint-trigger")
        || reason == QLatin1String("read-watchpoint-trigger")
        #ifdef Q_OS_MAC
        || reason.isEmpty()
        #endif
    ;
}

void GdbEngine::handleAqcuiredInferior()
{
    #if defined(Q_OS_WIN)
    sendCommand("info thread", GdbInfoThreads);
    #endif
    #if defined(Q_OS_LINUX)
    sendCommand("info proc", GdbInfoProc);
    #endif
    #if defined(Q_OS_MAC)
    sendCommand("info pid", GdbInfoProc, QVariant(), NeedsStop);
    #endif
    if (theDebuggerBoolSetting(ListSourceFiles))
        reloadSourceFiles();
    tryLoadDebuggingHelpers();

    #ifndef Q_OS_MAC
    // intentionally after tryLoadDebuggingHelpers(),
    // otherwise we'd interupt solib loading.
    if (theDebuggerBoolSetting(AllPluginBreakpoints)) {
        sendCommand("set auto-solib-add on");
        sendCommand("set stop-on-solib-events 0");
        sendCommand("sharedlibrary .*");
    } else if (theDebuggerBoolSetting(SelectedPluginBreakpoints)) {
        sendCommand("set auto-solib-add on");
        sendCommand("set stop-on-solib-events 1");
        sendCommand("sharedlibrary "
          + theDebuggerStringSetting(SelectedPluginBreakpointsPattern));
    } else if (theDebuggerBoolSetting(NoPluginBreakpoints)) {
        // should be like that already
        sendCommand("set auto-solib-add off");
        sendCommand("set stop-on-solib-events 0");
    }
    #endif

    // nicer to see a bit of the world we live in
    reloadModules();
    attemptBreakpointSynchronization();
}

void GdbEngine::handleAsyncOutput(const GdbMi &data)
{
    const QString reason = data.findChild("reason").data();

    if (isExitedReason(reason)) {
        qq->notifyInferiorExited();
        QString msg = "Program exited normally";
        if (reason == "exited") {
            msg = "Program exited with exit code "
                + data.findChild("exit-code").toString();
        } else if (reason == "exited-signalled") {
            msg = "Program exited after receiving signal "
                + data.findChild("signal-name").toString();
        } else if (reason == "signal-received") {
            msg = "Program exited after receiving signal "
                + data.findChild("signal-name").toString();
        }
        q->showStatusMessage(msg);
        sendCommand("-gdb-exit", GdbExit);
        return;
    }


    //MAC: bool isFirstStop = data.findChild("bkptno").data() == "1";
    //!MAC: startSymbolName == data.findChild("frame").findChild("func")
    if (m_waitingForFirstBreakpointToBeHit) {
        // If the executable dies already that early we might get something
        // like stdout:49*stopped,reason="exited",exit-code="0177"
        // This is handled now above.

        qq->notifyInferiorStopped();
        m_waitingForFirstBreakpointToBeHit = false;
        //
        // this will "continue" if done
        m_waitingForBreakpointSynchronizationToContinue = true;
        //
        // that's the "early stop"
        handleAqcuiredInferior();
        return;
    }

    if (!m_commandsToRunOnTemporaryBreak.isEmpty()) {
        QTC_ASSERT(q->status() == DebuggerInferiorStopRequested,
            qDebug() << "STATUS: " << q->status())
        qq->notifyInferiorStopped();
        q->showStatusMessage(tr("Temporarily stopped."));
        // FIXME: racy
        foreach (const GdbCookie &cmd, m_commandsToRunOnTemporaryBreak) {
            debugMessage(QString("RUNNING QUEUED COMMAND %1 %2")
                .arg(cmd.command).arg(cmd.type));
            sendCommand(cmd.command, cmd.type, cmd.cookie);
        }
        sendCommand("p temporaryStop", GdbTemporaryContinue);
        m_commandsToRunOnTemporaryBreak.clear();
        q->showStatusMessage(tr("Handling queued commands."));
        return;
    }

    QString msg = data.findChild("consolestreamoutput").data();
    if (msg.contains("Stopped due to shared library event") || reason.isEmpty()) {
        if (theDebuggerBoolSetting(SelectedPluginBreakpoints)) {
            debugMessage("SHARED LIBRARY EVENT: " + data.toString());
            QString pat = theDebuggerStringSetting(SelectedPluginBreakpointsPattern);
            debugMessage("PATTERN: " + pat);
            sendCommand("sharedlibrary " + pat);
            continueInferior();
            q->showStatusMessage(tr("Loading %1...").arg(QString(data.toString())));
            return;
        }
        m_modulesListOutdated = true;
        // fall through
    }

    // seen on XP after removing a breakpoint while running
    //  stdout:945*stopped,reason="signal-received",signal-name="SIGTRAP",
    //  signal-meaning="Trace/breakpoint trap",thread-id="2",
    //  frame={addr="0x7c91120f",func="ntdll!DbgUiConnectToDbg",
    //  args=[],from="C:\\WINDOWS\\system32\\ntdll.dll"}
    if (reason == "signal-received"
          && data.findChild("signal-name").toString() == "SIGTRAP") {
        continueInferior();
        return;
    }

    //tryLoadDebuggingHelpers();

    // jump over well-known frames
    static int stepCounter = 0;
    if (theDebuggerBoolSetting(SkipKnownFrames)) {
        if (reason == "end-stepping-range" || reason == "function-finished") {
            GdbMi frame = data.findChild("frame");
            //debugMessage(frame.toString());
            m_currentFrame = frame.findChild("addr").data() + '%' +
                 frame.findChild("func").data() + '%';

            QString funcName = frame.findChild("func").data();
            QString fileName = frame.findChild("file").data();
            if (isLeavableFunction(funcName, fileName)) {
                //debugMessage("LEAVING" + funcName);
                ++stepCounter;
                q->stepOutExec();
                //stepExec();
                return;
            }
            if (isSkippableFunction(funcName, fileName)) {
                //debugMessage("SKIPPING" + funcName);
                ++stepCounter;
                q->stepExec();
                return;
            }
            //if (stepCounter)
            //    qDebug() << "STEPCOUNTER:" << stepCounter;
            stepCounter = 0;
        }
    }

    if (isStoppedReason(reason) || reason.isEmpty()) {
        if (m_modulesListOutdated) {
            reloadModules();
            m_modulesListOutdated = false;
        }
        // Need another round trip
        if (reason == "breakpoint-hit") {
            q->showStatusMessage(tr("Stopped at breakpoint."));
            GdbMi frame = data.findChild("frame");
            //debugMessage("HIT BREAKPOINT: " + frame.toString());
            m_currentFrame = frame.findChild("addr").data() + '%' +
                 frame.findChild("func").data() + '%';

            QApplication::alert(q->mainWindow(), 3000);
            if (theDebuggerAction(ListSourceFiles)->value().toBool())
                reloadSourceFiles();
            sendCommand("-break-list", BreakList);
            QVariant var = QVariant::fromValue<GdbMi>(data);
            sendCommand("p 0", GdbAsyncOutput2, var);  // dummy
        } else {
#ifdef Q_OS_LINUX
            // For some reason, attaching to a stopped process causes *two* stops
            // when trying to continue (kernel 2.6.24-23-ubuntu).
            // Interestingly enough, on MacOSX no signal is delivered at all.
            if (reason == QLatin1String("signal-received")
                && data.findChild("signal-name").data() == "SIGSTOP") {
                GdbMi frameData = data.findChild("frame");
                if (frameData.findChild("func").data() == "_start"
                    && frameData.findChild("from").data() == "/lib/ld-linux.so.2") {
                    sendCommand("-exec-continue");
                    return;
                }
            }
#endif
            q->showStatusMessage(tr("Stopped: \"%1\"").arg(reason));
            handleAsyncOutput2(data);
        }
        return;
    }

    debugMessage("STOPPED FOR UNKNOWN REASON: " + data.toString());
    // Ignore it. Will be handled with full response later in the
    // JumpToLine or RunToFunction handlers
#if 1
    // FIXME: remove this special case as soon as there's a real
    // reason given when the temporary breakpoint is hit.
    // reight now we get:
    // 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4",
    // func="foo",args=[{name="str",value="@0x7fff0f450460"}],
    // file="main.cpp",fullname="/tmp/g/main.cpp",line="37"}
    //
    // MAC yields sometimes:
    // stdout:3661*stopped,time={wallclock="0.00658",user="0.00142",
    // system="0.00136",start="1218810678.805432",end="1218810678.812011"}
    q->resetLocation();
    qq->notifyInferiorStopped();
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
    GdbMi frame = data.findChild("frame");
    QString file = frame.findChild("fullname").data();
    int line = frame.findChild("line").data().toInt();
    qDebug() << "HIT: " << file << line << " IN " << frame.toString()
        << " -- " << data.toString();
    q->gotoLocation(file, line, true);
#endif
}

void GdbEngine::reloadFullStack()
{
    QString cmd = "-stack-list-frames";
    sendSynchronizedCommand(cmd, StackListFrames, true);
}

void GdbEngine::reloadStack()
{
    QString cmd = "-stack-list-frames";
    if (int stackDepth = theDebuggerAction(MaximalStackDepth)->value().toInt())
        cmd += " 0 " + QString::number(stackDepth);
    sendSynchronizedCommand(cmd, StackListFrames, false);
}

void GdbEngine::handleAsyncOutput2(const GdbMi &data)
{
    qq->notifyInferiorStopped();

    //
    // Stack
    //
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot

    int currentId = data.findChild("thread-id").data().toInt();
    reloadStack();
    if (supportsThreads())
        sendSynchronizedCommand("-thread-list-ids", StackListThreads, currentId);

    //
    // Disassembler
    //
    // Linux:
    //"79*stopped,reason="end-stepping-range",reason="breakpoint-hit",bkptno="1",
    //thread-id="1",frame={addr="0x0000000000405d8f",func="run1",
    //args=[{name="argc",value="1"},{name="argv",value="0x7fffb7c23058"}],
    //file="test1.cpp",fullname="/home/apoenitz/dev/work/test1/test1.cpp",line="261"}"
    // Mac: (but only sometimes)
    m_address = data.findChild("frame").findChild("addr").data();
    qq->reloadDisassembler();

    //
    // Registers
    //
    qq->reloadRegisters();
}

void GdbEngine::handleShowVersion(const GdbResultRecord &response)
{
    //qDebug () << "VERSION 2:" << response.data.findChild("consolestreamoutput").data();
    //qDebug () << "VERSION:" << response.toString();
    debugMessage("VERSION:" + response.toString());
    if (response.resultClass == GdbResultDone) {
        m_gdbVersion = 100;
        m_gdbBuildVersion = -1;
        QString msg = response.data.findChild("consolestreamoutput").data();
        QRegExp supported("GNU gdb(.*) (\\d+)\\.(\\d+)(\\.(\\d+))?(-(\\d+))?");
        if (supported.indexIn(msg) == -1) {
            debugMessage("UNSUPPORTED GDB VERSION " + msg);
            QStringList list = msg.split("\n");
            while (list.size() > 2)
                list.removeLast();
            msg = tr("The debugger you are using identifies itself as:")
                + "<p><p>" + list.join("<br>") + "<p><p>"
                + tr("This version is not officially supported by Qt Creator.\n"
                     "Debugging will most likely not work well.\n"
                     "Using gdb 6.7 or later is strongly recommended.");
#if 0
            // ugly, but 'Show again' check box...
            static QErrorMessage *err = new QErrorMessage(m_mainWindow);
            err->setMinimumSize(400, 300);
            err->showMessage(msg);
#else
            //QMessageBox::information(m_mainWindow, tr("Warning"), msg);
#endif
        } else {
            m_gdbVersion = 10000 * supported.cap(2).toInt()
                         +   100 * supported.cap(3).toInt()
                         +     1 * supported.cap(5).toInt();
            m_gdbBuildVersion = supported.cap(7).toInt();
            debugMessage(QString("GDB VERSION: %1").arg(m_gdbVersion));
        }
        //qDebug () << "VERSION 3:" << m_gdbVersion << m_gdbBuildVersion;
    }
}

void GdbEngine::handleFileExecAndSymbols
    (const GdbResultRecord &response)
{
    if (response.resultClass == GdbResultDone) {
        //m_breakHandler->clearBreakMarkers();
    } else if (response.resultClass == GdbResultError) {
        QString msg = response.data.findChild("msg").data();
        QMessageBox::critical(q->mainWindow(), tr("Error"),
            tr("Starting executable failed:\n") + msg);
        QTC_ASSERT(q->status() == DebuggerInferiorRunning, /**/);
        interruptInferior();
    }
}

void GdbEngine::handleExecRun(const GdbResultRecord &response)
{
    if (response.resultClass == GdbResultRunning) {
        qq->notifyInferiorRunning();
        q->showStatusMessage(tr("Running..."));
    } else if (response.resultClass == GdbResultError) {
        QString msg = response.data.findChild("msg").data();
        if (msg == "Cannot find bounds of current function") {
            qq->notifyInferiorStopped();
            //q->showStatusMessage(tr("No debug information available. "
            //  "Leaving function..."));
            //stepOutExec();
        } else {
            QMessageBox::critical(q->mainWindow(), tr("Error"),
                tr("Starting executable failed:\n") + msg);
            QTC_ASSERT(q->status() == DebuggerInferiorRunning, /**/);
            interruptInferior();
        }
    }
}

void GdbEngine::queryFullName(const QString &fileName, QString *full)
{
    *full = fullName(fileName);
}

QString GdbEngine::shortName(const QString &fullName)
{
    return m_fullToShortName.value(fullName, QString());
}

QString GdbEngine::fullName(const QString &fileName)
{
    //QString absName = m_manager->currentWorkingDirectory() + "/" + file; ??
    if (fileName.isEmpty())
        return QString();
    QString full = m_shortToFullName.value(fileName, QString());
    //debugMessage("RESOLVING: " + fileName + " " +  full);
    if (!full.isEmpty())
        return full;
    QFileInfo fi(fileName);
    if (!fi.isReadable())
        return QString();
    full = fi.absoluteFilePath();
    #ifdef Q_OS_WIN
    full = QDir::cleanPath(full);
    #endif
    //debugMessage("STORING: " + fileName + " " + full);
    m_shortToFullName[fileName] = full;
    m_fullToShortName[full] = fileName;
    return full;
}

QString GdbEngine::fullName(const QStringList &candidates)
{
    QString full;
    foreach (const QString &fileName, candidates) {
        full = fullName(fileName);
        if (!full.isEmpty())
            return full;
    }
    foreach (const QString &fileName, candidates) {
        if (!fileName.isEmpty())
            return fileName;
    }
    return full;
}

void GdbEngine::shutdown()
{
    exitDebugger();
}

void GdbEngine::exitDebugger()
{
    debugMessage(QString("GDBENGINE EXITDEBUFFER: %1").arg(m_gdbProc.state()));
    if (m_gdbProc.state() == QProcess::Starting) {
        debugMessage(QString("WAITING FOR GDB STARTUP TO SHUTDOWN: %1")
            .arg(m_gdbProc.state()));
        m_gdbProc.waitForStarted();
    }
    if (m_gdbProc.state() == QProcess::Running) {
        debugMessage(QString("WAITING FOR RUNNING GDB TO SHUTDOWN: %1")
            .arg(m_gdbProc.state()));
        if (q->status() != DebuggerInferiorStopped
            && q->status() != DebuggerProcessStartingUp) {
            QTC_ASSERT(q->status() == DebuggerInferiorRunning,
                qDebug() << "STATUS ON EXITDEBUGGER: " << q->status());
            interruptInferior();
        }
        if (q->startMode() == AttachExternal)
            sendCommand("detach");
        else
            sendCommand("kill");
        sendCommand("-gdb-exit", GdbExit);
        // 20s can easily happen when loading webkit debug information
        m_gdbProc.waitForFinished(20000);
        if (m_gdbProc.state() != QProcess::Running) {
            debugMessage(QString("FORCING TERMINATION: %1")
                .arg(m_gdbProc.state()));
            m_gdbProc.terminate();
            m_gdbProc.waitForFinished(20000);
        }
    }
    if (m_gdbProc.state() != QProcess::NotRunning)
        debugMessage("PROBLEM STOPPING DEBUGGER");

    m_outputCollector.shutdown();
    initializeVariables();
    //q->settings()->m_debugDebuggingHelpers = false;
}


int GdbEngine::currentFrame() const
{
    return qq->stackHandler()->currentIndex();
}

bool GdbEngine::startDebugger()
{
    debugMessage(DebuggerSettings::instance()->dump());
    QStringList gdbArgs;

    if (m_gdbProc.state() != QProcess::NotRunning) {
        debugMessage("GDB IS ALREADY RUNNING!");
        return false;
    }

    //gdbArgs.prepend(QLatin1String("--quiet"));
    gdbArgs.prepend(QLatin1String("mi"));
    gdbArgs.prepend(QLatin1String("-i"));

    if (q->startMode() == AttachCore || q->startMode() == AttachExternal) {
        // nothing to do
    } else if (q->m_useTerminal) {
        m_stubProc.stop(); // We leave the console open, so recycle it now.

        m_stubProc.setWorkingDirectory(q->m_workingDir);
        m_stubProc.setEnvironment(q->m_environment);
        if (!m_stubProc.start(q->m_executable, q->m_processArgs))
            return false; // Error message for user is delivered via a signal.
    } else {
        if (!m_outputCollector.listen()) {
            QMessageBox::critical(q->mainWindow(), tr("Debugger Startup Failure"),
                tr("Cannot set up communication with child process: %1")
                    .arg(m_outputCollector.errorString()));
            return false;
        }
        gdbArgs.prepend(QLatin1String("--tty=") + m_outputCollector.serverName());

        if (!q->m_workingDir.isEmpty())
            m_gdbProc.setWorkingDirectory(q->m_workingDir);
        if (!q->m_environment.isEmpty())
            m_gdbProc.setEnvironment(q->m_environment);
    }

    #if 0
    qDebug() << "Command: " << q->settings()->m_gdbCmd;
    qDebug() << "WorkingDirectory: " << m_gdbProc.workingDirectory();
    qDebug() << "ScriptFile: " << q->settings()->m_scriptFile;
    qDebug() << "Environment: " << m_gdbProc.environment();
    qDebug() << "Arguments: " << gdbArgs;
    qDebug() << "BuildDir: " << q->m_buildDir;
    qDebug() << "ExeFile: " << q->m_executable;
    #endif

    QString loc = theDebuggerStringSetting(GdbLocation);
    q->showStatusMessage(tr("Starting Debugger: ") + loc + ' ' + gdbArgs.join(" "));
    m_gdbProc.start(loc, gdbArgs);
    if (!m_gdbProc.waitForStarted()) {
        QMessageBox::critical(q->mainWindow(), tr("Debugger Startup Failure"),
                              tr("Cannot start debugger: %1").arg(m_gdbProc.errorString()));
        m_outputCollector.shutdown();
        m_stubProc.blockSignals(true);
        m_stubProc.stop();
        m_stubProc.blockSignals(false);
        return false;
    }

    q->showStatusMessage(tr("Gdb Running..."));

    sendCommand("show version", GdbShowVersion);
    //sendCommand("-enable-timings");
    sendCommand("set print static-members off"); // Seemingly doesn't work.
    //sendCommand("define hook-stop\n-thread-list-ids\n-stack-list-frames\nend");
    //sendCommand("define hook-stop\nprint 4\nend");
    //sendCommand("define hookpost-stop\nprint 5\nend");
    //sendCommand("define hook-call\nprint 6\nend");
    //sendCommand("define hookpost-call\nprint 7\nend");
    //sendCommand("set print object on"); // works with CLI, but not MI
    //sendCommand("set step-mode on");  // we can't work with that yes
    //sendCommand("set exec-done-display on");
    //sendCommand("set print pretty on");
    //sendCommand("set confirm off");
    //sendCommand("set pagination off");
    sendCommand("set breakpoint pending on", BreakEnablePending);
    sendCommand("set print elements 10000");
    sendCommand("-data-list-register-names", RegisterListNames);

    //sendCommand("set substitute-path /var/tmp/qt-x11-src-4.5.0 "
    //    "/home/sandbox/qtsdk-2009.01/qt");

    // one of the following is needed to prevent crashes in gdb on code like:
    //  template <class T> T foo() { return T(0); }
    //  int main() { return foo<int>(); }
    //  (gdb) call 'int foo<int>'()
    //  /build/buildd/gdb-6.8/gdb/valops.c:2069: internal-error:
    sendCommand("set overload-resolution off");
    //sendCommand("set demangle-style none");

    // From the docs:
    //  Stop means reenter debugger if this signal happens (implies print).
    //  Print means print a message if this signal happens.
    //  Pass means let program see this signal;
    //  otherwise program doesn't know.
    //  Pass and Stop may be combined.
    // We need "print" as otherwise we would get no feedback whatsoever
    // Custom DebuggingHelper crashs which happen regularily for when accessing
    // uninitialized variables.
    sendCommand("handle SIGSEGV nopass stop print");

    // This is useful to kill the inferior whenever gdb dies.
    //sendCommand("handle SIGTERM pass nostop print");

    sendCommand("set unwindonsignal on");
    sendCommand("pwd", GdbQueryPwd);
    sendCommand("set width 0");
    sendCommand("set height 0");

    #ifdef Q_OS_MAC
    sendCommand("-gdb-set inferior-auto-start-cfm off");
    sendCommand("-gdb-set sharedLibrary load-rules "
            "dyld \".*libSystem.*\" all "
            "dyld \".*libauto.*\" all "
            "dyld \".*AppKit.*\" all "
            "dyld \".*PBGDBIntrospectionSupport.*\" all "
            "dyld \".*Foundation.*\" all "
            "dyld \".*CFDataFormatters.*\" all "
            "dyld \".*libobjc.*\" all "
            "dyld \".*CarbonDataFormatters.*\" all");
    #endif

    QString scriptFileName = theDebuggerStringSetting(GdbScriptFile);
    if (!scriptFileName.isEmpty()) {
        QFile scriptFile(scriptFileName);
        if (scriptFile.open(QIODevice::ReadOnly)) {
            sendCommand("source " + scriptFileName);
        } else {
            QMessageBox::warning(q->mainWindow(),
            tr("Cannot find debugger initialization script"),
            tr("The debugger settings point to a script file at '%1' "
               "which is not accessible. If a script file is not needed, "
               "consider clearing that entry to avoid this warning. "
              ).arg(scriptFileName));
        }
    }

    if (q->startMode() == AttachExternal) {
        sendCommand("attach " + QString::number(q->m_attachedPID), GdbAttached);
    } else if (q->startMode() == AttachCore) {
        QFileInfo fi(q->m_executable);
        QString fileName = '"' + fi.absoluteFilePath() + '"';
        QFileInfo fi2(q->m_coreFile);
        // quoting core name below fails in gdb 6.8-debian
        QString coreName = fi2.absoluteFilePath();
        sendCommand("-file-exec-and-symbols " + fileName);
        sendCommand("target core " + coreName, GdbTargetCore);
    } else if (q->m_useTerminal) {
        // nothing needed, stub takes care
    } else if (q->startMode() == StartInternal || q->startMode() == StartExternal) {
        QFileInfo fi(q->m_executable);
        QString fileName = '"' + fi.absoluteFilePath() + '"';
        sendCommand("-file-exec-and-symbols " + fileName, GdbFileExecAndSymbols);
        //sendCommand("file " + fileName, GdbFileExecAndSymbols);
        #ifdef Q_OS_MAC
        sendCommand("sharedlibrary apply-load-rules all");
        #endif
        if (!q->m_processArgs.isEmpty())
            sendCommand("-exec-arguments " + q->m_processArgs.join(" "));
        #ifndef Q_OS_MAC
        sendCommand("set auto-solib-add off");
        sendCommand("info target", GdbStart);
        #else
        // On MacOS, breaking in at the entry point wreaks havoc.
        sendCommand("tbreak main");
        m_waitingForFirstBreakpointToBeHit = true;
        qq->notifyInferiorRunningRequested();
        sendCommand("-exec-run");
        #endif
    }

    // set all to "pending"
    if (q->startMode() == AttachExternal || q->startMode() == AttachCore)
        qq->breakHandler()->removeAllBreakpoints();
    else if (q->startMode() == StartInternal || q->startMode() == StartExternal)
        qq->breakHandler()->setAllPending();

    return true;
}

void GdbEngine::continueInferior()
{
    q->resetLocation();
    setTokenBarrier();
    qq->notifyInferiorRunningRequested();
    sendCommand("-exec-continue", GdbExecContinue);
}

void GdbEngine::handleStart(const GdbResultRecord &response)
{
#ifdef Q_OS_MAC
    Q_UNUSED(response);
#else
    if (response.resultClass == GdbResultDone) {
        // [some leading stdout here]
        // stdout:&"        Entry point: 0x80831f0  0x08048134 - 0x08048147 is .interp\n"
        // [some trailing stdout here]
        QString msg = response.data.findChild("consolestreamoutput").data();
        QRegExp needle("\\bEntry point: (0x[0-9a-f]+)\\b");
        if (needle.indexIn(msg) != -1) {
            //debugMessage("STREAM: " + msg + " " + needle.cap(1));
            sendCommand("tbreak *" + needle.cap(1));
            m_waitingForFirstBreakpointToBeHit = true;
            qq->notifyInferiorRunningRequested();
            sendCommand("-exec-run");
        } else {
            debugMessage("PARSING START ADDRESS FAILED: " + msg);
        }
    } else if (response.resultClass == GdbResultError) {
        debugMessage("FETCHING START ADDRESS FAILED: " + response.toString());
    }
#endif
}

void GdbEngine::handleAttach()
{
    qq->notifyInferiorStopped();
    q->showStatusMessage(tr("Attached to running process. Stopped."));
    handleAqcuiredInferior();

    q->resetLocation();
    recheckDebuggingHelperAvailability();

    //
    // Stack
    //
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot

    reloadStack();
    if (supportsThreads())
        sendSynchronizedCommand("-thread-list-ids", StackListThreads, 0);

    //
    // Disassembler
    //
    // XXX we have no data here ...
    //m_address = data.findChild("frame").findChild("addr").data();
    //qq->reloadDisassembler();

    //
    // Registers
    //
    qq->reloadRegisters();
}

void GdbEngine::handleExit(const GdbResultRecord &response)
{
    Q_UNUSED(response);
    q->showStatusMessage(tr("Debugger exited."));
}

void GdbEngine::stepExec()
{
    setTokenBarrier();
    qq->notifyInferiorRunningRequested();
    sendCommand("-exec-step", GdbExecStep);
}

void GdbEngine::stepIExec()
{
    setTokenBarrier();
    qq->notifyInferiorRunningRequested();
    sendCommand("-exec-step-instruction", GdbExecStepI);
}

void GdbEngine::stepOutExec()
{
    setTokenBarrier();
    qq->notifyInferiorRunningRequested();
    sendCommand("-exec-finish", GdbExecFinish);
}

void GdbEngine::nextExec()
{
    setTokenBarrier();
    qq->notifyInferiorRunningRequested();
    sendCommand("-exec-next", GdbExecNext);
}

void GdbEngine::nextIExec()
{
    setTokenBarrier();
    qq->notifyInferiorRunningRequested();
    sendCommand("-exec-next-instruction", GdbExecNextI);
}

void GdbEngine::runToLineExec(const QString &fileName, int lineNumber)
{
    setTokenBarrier();
    qq->notifyInferiorRunningRequested();
    sendCommand("-exec-until " + fileName + ":" + QString::number(lineNumber));
}

void GdbEngine::runToFunctionExec(const QString &functionName)
{
    setTokenBarrier();
    sendCommand("-break-insert -t " + functionName);
    qq->notifyInferiorRunningRequested();
    sendCommand("-exec-continue", GdbExecRunToFunction);
}

void GdbEngine::jumpToLineExec(const QString &fileName, int lineNumber)
{
#if 1
    // not available everywhere?
    //sendCliCommand("tbreak " + fileName + ":" + QString::number(lineNumber));
    sendCommand("-break-insert -t " + fileName + ":" + QString::number(lineNumber));
    sendCommand("jump " + fileName + ":" + QString::number(lineNumber));
    // will produce something like
    //  &"jump /home/apoenitz/dev/work/test1/test1.cpp:242"
    //  ~"Continuing at 0x4058f3."
    //  ~"run1 (argc=1, argv=0x7fffbf1f5538) at test1.cpp:242"
    //  ~"242\t x *= 2;"
    //  23^done"
    q->gotoLocation(fileName, lineNumber, true);
    //setBreakpoint();
    //sendCommand("jump " + fileName + ":" + QString::number(lineNumber));
#else
    q->gotoLocation(fileName, lineNumber, true);
    setBreakpoint(fileName, lineNumber);
    sendCommand("jump " + fileName + ":" + QString::number(lineNumber));
#endif
}

/*!
    \fn void GdbEngine::setTokenBarrier()
    \brief Discard the results of all pending watch-updating commands.

    This method is called at the beginning of all step/next/finish etc.
    debugger functions.
    If non-watch-updating commands with call-backs are still in the pipe,
    it will complain.
*/

void GdbEngine::setTokenBarrier()
{
    foreach (const GdbCookie &cookie, m_cookieForToken) {
        QTC_ASSERT(
            cookie.synchronized
                || cookie.type == GdbInvalidCommand
                // FIXME: use something like "command classes" for these cases:
                || cookie.type == GdbInfoProc
                || cookie.type == GdbStubAttached
                || cookie.type == ModulesList
                || cookie.type == WatchDebuggingHelperSetup
                || cookie.type == GdbQueryDebuggingHelper,
            qDebug() << "CMD: " << cookie.command << "TYPE: " << cookie.type
                << "SYNC: " << cookie.synchronized;
            return
        );
    }
    PENDING_DEBUG("\n--- token barrier ---\n");
    emit gdbInputAvailable(QString(), "--- token barrier ---");
    m_oldestAcceptableToken = currentToken();
}

void GdbEngine::setDebugDebuggingHelpers(const QVariant &on)
{
    if (on.toBool()) {
        debugMessage("SWITCHING ON DUMPER DEBUGGING");
        sendCommand("set unwindonsignal off");
        q->breakByFunction("qDumpObjectData440");
        //updateLocals();
    } else {
        debugMessage("SWITCHING OFF DUMPER DEBUGGING");
        sendCommand("set unwindonsignal on");
    }
}


//////////////////////////////////////////////////////////////////////
//
// Breakpoint specific stuff
//
//////////////////////////////////////////////////////////////////////

void GdbEngine::breakpointDataFromOutput(BreakpointData *data, const GdbMi &bkpt)
{
    if (!bkpt.isValid())
        return;
    if (!data)
        return;
    data->pending = false;
    data->bpMultiple = false;
    data->bpCondition.clear();
    QStringList files;
    foreach (const GdbMi &child, bkpt.children()) {
        if (child.hasName("number")) {
            data->bpNumber = child.data();
        } else if (child.hasName("func")) {
            data->bpFuncName = child.data();
        } else if (child.hasName("addr")) {
            // <MULTIPLE> happens in constructors. In this case there are
            // _two_ fields named "addr" in the response. On Linux that is...
            if (child.data() == "<MULTIPLE>")
                data->bpMultiple = true;
            else
                data->bpAddress = child.data();
        } else if (child.hasName("file")) {
            files.append(child.data());
        } else if (child.hasName("fullname")) {
            QString fullName = child.data();
            #ifdef Q_OS_WIN
            fullName = QDir::cleanPath(fullName);
            #endif
            files.prepend(fullName);
        } else if (child.hasName("line")) {
            data->bpLineNumber = child.data();
            if (child.data().toInt())
                data->markerLineNumber = child.data().toInt();
        } else if (child.hasName("cond")) {
            data->bpCondition = child.data();
            // gdb 6.3 likes to "rewrite" conditions. Just accept that fact.
            if (data->bpCondition != data->condition && data->conditionsMatch())
                data->condition = data->bpCondition;
        }
        else if (child.hasName("pending")) {
            data->pending = true;
            int pos = child.data().lastIndexOf(':');
            if (pos > 0) {
                data->bpLineNumber = child.data().mid(pos + 1);
                data->markerLineNumber = child.data().mid(pos + 1).toInt();
                QString file = child.data().left(pos);
                if (file.startsWith('"') && file.endsWith('"'))
                    file = file.mid(1, file.size() - 2);
                files.prepend(file);
            } else {
                files.prepend(child.data());
            }
        }
    }
    // This field is not present.  Contents needs to be parsed from
    // the plain "ignore" response.
    //else if (child.hasName("ignore"))
    //    data->bpIgnoreCount = child.data();

    QString name = fullName(files);
    if (data->bpFileName.isEmpty())
        data->bpFileName = name;
    if (data->markerFileName.isEmpty())
        data->markerFileName = name;
}

void GdbEngine::sendInsertBreakpoint(int index)
{
    const BreakpointData *data = qq->breakHandler()->at(index);
    QString where;
    if (data->funcName.isEmpty()) {
        where = data->fileName;
#ifdef Q_OS_MAC
        // full names do not work on Mac/MI
        QFileInfo fi(data->fileName);
        where = fi.fileName();
        //where = fi.absoluteFilePath();
#endif
#ifdef Q_OS_WIN
        // full names do not work on Mac/MI
        QFileInfo fi(data->fileName);
        where = fi.fileName();
    //where = m_manager->shortName(data->fileName);
        //if (where.isEmpty())
        //    where = data->fileName;
#endif
        // we need something like   "\"file name.cpp\":100"  to
        // survive the gdb command line parser with file names intact
        where = "\"\\\"" + where + "\\\":" + data->lineNumber + "\"";
    } else {
        where = data->funcName;
    }

    // set up fallback in case of pending breakpoints which aren't handled
    // by the MI interface
#ifdef Q_OS_LINUX
    QString cmd = "-break-insert -f ";
    //if (!data->condition.isEmpty())
    //    cmd += "-c " + data->condition + " ";
    cmd += where;
#endif
#ifdef Q_OS_MAC
    QString cmd = "-break-insert -l -1 ";
    //if (!data->condition.isEmpty())
    //    cmd += "-c " + data->condition + " ";
    cmd += where;
#endif
#ifdef Q_OS_WIN
    QString cmd = "-break-insert ";
    //if (!data->condition.isEmpty())
    //    cmd += "-c " + data->condition + " ";
    cmd += where;
#endif
    debugMessage(QString("Current state: %1").arg(q->status()));
    sendCommand(cmd, BreakInsert, index, NeedsStop);
}

void GdbEngine::handleBreakList(const GdbResultRecord &record)
{
    // 45^done,BreakpointTable={nr_rows="2",nr_cols="6",hdr=[
    // {width="3",alignment="-1",col_name="number",colhdr="Num"}, ...
    // body=[bkpt={number="1",type="breakpoint",disp="keep",enabled="y",
    //  addr="0x000000000040109e",func="main",file="app.cpp",
    //  fullname="/home/apoenitz/dev/work/plugintest/app/app.cpp",
    //  line="11",times="1"},
    //  bkpt={number="2",type="breakpoint",disp="keep",enabled="y",
    //  addr="<PENDING>",pending="plugin.cpp:7",times="0"}] ... }

    if (record.resultClass == GdbResultDone) {
        GdbMi table = record.data.findChild("BreakpointTable");
        if (table.isValid())
            handleBreakList(table);
    }
}

void GdbEngine::handleBreakList(const GdbMi &table)
{
    //qDebug() << "GdbEngine::handleOutput: table: "
    //  << table.toString();
    GdbMi body = table.findChild("body");
    //qDebug() << "GdbEngine::handleOutput: body: "
    //  << body.toString();
    QList<GdbMi> bkpts;
    if (body.isValid()) {
        // Non-Mac
        bkpts = body.children();
    } else {
        // Mac
        bkpts = table.children();
        // remove the 'hdr' and artificial items
        //qDebug() << "FOUND " << bkpts.size() << " BREAKPOINTS";
        for (int i = bkpts.size(); --i >= 0; ) {
            int num = bkpts.at(i).findChild("number").data().toInt();
            if (num <= 0) {
                //qDebug() << "REMOVING " << i << bkpts.at(i).toString();
                bkpts.removeAt(i);
            }
        }
        //qDebug() << "LEFT " << bkpts.size() << " BREAKPOINTS";
    }

    BreakHandler *handler = qq->breakHandler();
    for (int index = 0; index != bkpts.size(); ++index) {
        BreakpointData temp(handler);
        breakpointDataFromOutput(&temp, bkpts.at(index));
        int found = handler->findBreakpoint(temp);
        if (found != -1)
            breakpointDataFromOutput(handler->at(found), bkpts.at(index));
        //else
            //qDebug() << "CANNOT HANDLE RESPONSE " << bkpts.at(index).toString();
    }

    attemptBreakpointSynchronization();
    handler->updateMarkers();
}


void GdbEngine::handleBreakIgnore(const GdbResultRecord &record, int index)
{
    // gdb 6.8:
    // ignore 2 0:
    // ~"Will stop next time breakpoint 2 is reached.\n"
    // 28^done
    // ignore 2 12:
    // &"ignore 2 12\n"
    // ~"Will ignore next 12 crossings of breakpoint 2.\n"
    // 29^done
    //
    // gdb 6.3 does not produce any console output
    BreakHandler *handler = qq->breakHandler();
    if (record.resultClass == GdbResultDone && index < handler->size()) {
        QString msg = record.data.findChild("consolestreamoutput").data();
        BreakpointData *data = handler->at(index);
        //if (msg.contains("Will stop next time breakpoint")) {
        //    data->bpIgnoreCount = "0";
        //} else if (msg.contains("Will ignore next")) {
        //    data->bpIgnoreCount = data->ignoreCount;
        //}
        // FIXME: this assumes it is doing the right thing...
        data->bpIgnoreCount = data->ignoreCount;
        attemptBreakpointSynchronization();
        handler->updateMarkers();
    }
}

void GdbEngine::handleBreakCondition(const GdbResultRecord &record, int index)
{
    BreakHandler *handler = qq->breakHandler();
    if (record.resultClass == GdbResultDone) {
        // we just assume it was successful. otherwise we had to parse
        // the output stream data
        BreakpointData *data = handler->at(index);
        //qDebug() << "HANDLE BREAK CONDITION " << index << data->condition;
        data->bpCondition = data->condition;
        attemptBreakpointSynchronization();
        handler->updateMarkers();
    } else if (record.resultClass == GdbResultError) {
        QString msg = record.data.findChild("msg").data();
        // happens on Mac
        if (1 || msg.startsWith("Error parsing breakpoint condition. "
                " Will try again when we hit the breakpoint.")) {
            BreakpointData *data = handler->at(index);
            //qDebug() << "ERROR BREAK CONDITION " << index << data->condition;
            data->bpCondition = data->condition;
            attemptBreakpointSynchronization();
            handler->updateMarkers();
        }
    }
}

void GdbEngine::handleBreakInsert(const GdbResultRecord &record, int index)
{
    BreakHandler *handler = qq->breakHandler();
    if (record.resultClass == GdbResultDone) {
        //qDebug() << "HANDLE BREAK INSERT " << index;
//#ifdef Q_OS_MAC
        // interesting only on Mac?
        BreakpointData *data = handler->at(index);
        GdbMi bkpt = record.data.findChild("bkpt");
        //qDebug() << "BKPT: " << bkpt.toString() << " DATA" << data->toToolTip();
        breakpointDataFromOutput(data, bkpt);
//#endif
        attemptBreakpointSynchronization();
        handler->updateMarkers();
    } else if (record.resultClass == GdbResultError) {
        const BreakpointData *data = handler->at(index);
#ifdef Q_OS_LINUX
        //QString where = "\"\\\"" + data->fileName + "\\\":"
        //    + data->lineNumber + "\"";
        QString where = "\"" + data->fileName + "\":"
            + data->lineNumber;
        // Should not happen with -break-insert -f. gdb older than 6.8?
        QTC_ASSERT(false, /**/);
        sendCommand("break " + where, BreakInsert1, index);
#endif
#ifdef Q_OS_MAC
        QFileInfo fi(data->fileName);
        QString where = "\"" + fi.fileName() + "\":"
            + data->lineNumber;
        sendCommand("break " + where, BreakInsert1, index);
#endif
#ifdef Q_OS_WIN
        QFileInfo fi(data->fileName);
        QString where = "\"" + fi.fileName() + "\":"
            + data->lineNumber;
        //QString where = m_data->fileName + QLatin1Char(':') + data->lineNumber;
        sendCommand("break " + where, BreakInsert1, index);
#endif
    }
}

void GdbEngine::extractDataFromInfoBreak(const QString &output, BreakpointData *data)
{
    data->bpFileName = "<MULTIPLE>";

    //qDebug() << output;
    if (output.isEmpty())
        return;
    // "Num     Type           Disp Enb Address            What
    // 4       breakpoint     keep y   <MULTIPLE>         0x00000000004066ad
    // 4.1                         y     0x00000000004066ad in CTorTester
    //  at /data5/dev/ide/main/tests/manual/gdbdebugger/simple/app.cpp:124
    // - or -
    // everything on a single line on Windows for constructors of classes
    // within namespaces.
    // Sometimes the path is relative too.

    // 2    breakpoint     keep y   <MULTIPLE> 0x0040168e
    // 2.1    y     0x0040168e in MainWindow::MainWindow(QWidget*) at mainwindow.cpp:7
    // 2.2    y     0x00401792 in MainWindow::MainWindow(QWidget*) at mainwindow.cpp:7

    // tested in ../../../tests/auto/debugger/
    QRegExp re("MULTIPLE.*(0x[0-9a-f]+) in (.*)\\s+at (.*):([\\d]+)([^\\d]|$)");
    re.setMinimal(true);

    if (re.indexIn(output) != -1) {
        data->bpAddress = re.cap(1);
        data->bpFuncName = re.cap(2).trimmed();
        data->bpLineNumber = re.cap(4);
        QString full = fullName(re.cap(3));
        if (full.isEmpty()) {
            qDebug() << "NO FULL NAME KNOWN FOR" << re.cap(3);
            full = re.cap(3); // FIXME: wrong, but prevents recursion
        }
        data->markerLineNumber = data->bpLineNumber.toInt();
        data->markerFileName = full;
        data->bpFileName = full;
        //qDebug() << "FOUND BREAKPOINT\n" << output
        //    << re.cap(1) << "\n" << re.cap(2) << "\n"
        //    << re.cap(3) << "\n" << re.cap(4) << "\n";
    } else {
        qDebug() << "COULD NOT MATCH " << re.pattern() << " AND " << output;
        data->bpNumber = "<unavailable>";
    }
}

void GdbEngine::handleBreakInfo(const GdbResultRecord &record, int bpNumber)
{
    BreakHandler *handler = qq->breakHandler();
    if (record.resultClass == GdbResultDone) {
        // Old-style output for multiple breakpoints, presumably in a
        // constructor
        int found = handler->findBreakpoint(bpNumber);
        if (found != -1) {
            QString str = record.data.findChild("consolestreamoutput").data();
            extractDataFromInfoBreak(str, handler->at(found));
            handler->updateMarkers();
            attemptBreakpointSynchronization(); // trigger "ready"
        }
    }
}

void GdbEngine::handleBreakInsert1(const GdbResultRecord &record, int index)
{
    BreakHandler *handler = qq->breakHandler();
    if (record.resultClass == GdbResultDone) {
        // Pending breakpoints in dylibs on Mac only?
        BreakpointData *data = handler->at(index);
        GdbMi bkpt = record.data.findChild("bkpt");
        breakpointDataFromOutput(data, bkpt);
        attemptBreakpointSynchronization(); // trigger "ready"
        handler->updateMarkers();
    } else if (record.resultClass == GdbResultError) {
        qDebug() << "INSERTING BREAKPOINT WITH BASE NAME FAILED. GIVING UP";
        BreakpointData *data = handler->at(index);
        data->bpNumber = "<unavailable>";
        attemptBreakpointSynchronization(); // trigger "ready"
        handler->updateMarkers();
    }
}

void GdbEngine::attemptBreakpointSynchronization()
{
    // Non-lethal check for nested calls
    static bool inBreakpointSychronization = false;
    QTC_ASSERT(!inBreakpointSychronization, /**/);
    inBreakpointSychronization = true;

    BreakHandler *handler = qq->breakHandler();

    foreach (BreakpointData *data, handler->takeRemovedBreakpoints()) {
        QString bpNumber = data->bpNumber;
        debugMessage(QString("DELETING BP %1 IN %2").arg(bpNumber)
            .arg(data->markerFileName));
        if (!bpNumber.trimmed().isEmpty())
            sendCommand("-break-delete " + bpNumber, BreakDelete, QVariant(),
                NeedsStop);
        delete data;
    }

    bool updateNeeded = false;

    for (int index = 0; index != handler->size(); ++index) {
        BreakpointData *data = handler->at(index);
        // multiple breakpoints?
        if (data->bpMultiple && data->bpFileName.isEmpty()) {
            sendCommand(QString("info break %1").arg(data->bpNumber),
                BreakInfo, data->bpNumber.toInt());
            updateNeeded = true;
            break;
        }
    }

    for (int index = 0; index != handler->size(); ++index) {
        BreakpointData *data = handler->at(index);
        // unset breakpoints?
        if (data->bpNumber.isEmpty()) {
            data->bpNumber = " ";
            sendInsertBreakpoint(index);
            //qDebug() << "UPDATE NEEDED BECAUSE OF UNKNOWN BREAKPOINT";
            updateNeeded = true;
            break;
        }
    }

    if (!updateNeeded) {
        for (int index = 0; index != handler->size(); ++index) {
            BreakpointData *data = handler->at(index);
            // update conditions if needed
            if (data->bpNumber.toInt() && data->condition != data->bpCondition
                   && !data->conditionsMatch()) {
                sendCommand(QString("condition %1 %2").arg(data->bpNumber)
                    .arg(data->condition), BreakCondition, index);
                //qDebug() << "UPDATE NEEDED BECAUSE OF CONDITION"
                //    << data->condition << data->bpCondition;
                updateNeeded = true;
                break;
            }
            // update ignorecount if needed
            if (data->bpNumber.toInt() && data->ignoreCount != data->bpIgnoreCount) {
                sendCommand(QString("ignore %1 %2").arg(data->bpNumber)
                    .arg(data->ignoreCount), BreakIgnore, index);
                updateNeeded = true;
                break;
            }
        }
    }

    for (int index = 0; index != handler->size(); ++index) {
        // happens sometimes on Mac. Brush over symptoms
        BreakpointData *data = handler->at(index);
        if (data->markerFileName.startsWith("../")) {
            data->markerFileName = fullName(data->markerFileName);
            handler->updateMarkers();
        }
    }

    if (!updateNeeded && m_waitingForBreakpointSynchronizationToContinue) {
        m_waitingForBreakpointSynchronizationToContinue = false;
        // we continue the execution
        continueInferior();
    }

    inBreakpointSychronization = false;
}


//////////////////////////////////////////////////////////////////////
//
// Disassembler specific stuff
//
//////////////////////////////////////////////////////////////////////

void GdbEngine::reloadDisassembler()
{
    emit sendCommand("disassemble", DisassemblerList, m_address);
}

void GdbEngine::handleDisassemblerList(const GdbResultRecord &record,
    const QString &cookie)
{
    QList<DisassemblerLine> lines;
    static const QString pad = QLatin1String("    ");
    int currentLine = -1;
    if (record.resultClass == GdbResultDone) {
        QString res = record.data.findChild("consolestreamoutput").data();
        QTextStream ts(&res, QIODevice::ReadOnly);
        while (!ts.atEnd()) {
            //0x0000000000405fd8 <_ZN11QTextStreamD1Ev@plt+0>:
            //    jmpq   *2151890(%rip)    # 0x6135b0 <_GLOBAL_OFFSET_TABLE_+640>
            //0x0000000000405fde <_ZN11QTextStreamD1Ev@plt+6>:
            //    pushq  $0x4d
            //0x0000000000405fe3 <_ZN11QTextStreamD1Ev@plt+11>:
            //    jmpq   0x405af8 <_init+24>
            //0x0000000000405fe8 <_ZN9QHashData6rehashEi@plt+0>:
            //    jmpq   *2151882(%rip)    # 0x6135b8 <_GLOBAL_OFFSET_TABLE_+648>
            QString str = ts.readLine();
            if (!str.startsWith(QLatin1String("0x"))) {
                //qDebug() << "IGNORING DISASSEMBLER" << str;
                continue;
            }
            DisassemblerLine line;
            QTextStream ts(&str, QIODevice::ReadOnly);
            ts >> line.address >> line.symbol;
            line.mnemonic = ts.readLine().trimmed();
            if (line.symbol.endsWith(QLatin1Char(':')))
                line.symbol.chop(1);
            line.addressDisplay = line.address + pad;
            if (line.addressDisplay.startsWith(QLatin1String("0x00000000")))
                line.addressDisplay.replace(2, 8, QString());
            line.symbolDisplay = line.symbol + pad;

            if (line.address == cookie)
                currentLine = lines.size();

            lines.append(line);
        }
    } else {
        DisassemblerLine line;
        line.addressDisplay = tr("<could not retreive module information>");
        lines.append(line);
    }

    qq->disassemblerHandler()->setLines(lines);
    if (currentLine != -1)
        qq->disassemblerHandler()->setCurrentLine(currentLine);
}


//////////////////////////////////////////////////////////////////////
//
// Modules specific stuff
//
//////////////////////////////////////////////////////////////////////

void GdbEngine::loadSymbols(const QString &moduleName)
{
    // FIXME: gdb does not understand quoted names here (tested with 6.8)
    sendCommand("sharedlibrary " + dotEscape(moduleName));
    reloadModules();
}

void GdbEngine::loadAllSymbols()
{
    sendCommand("sharedlibrary .*");
    reloadModules();
}

void GdbEngine::reloadModules()
{
    sendCommand("info shared", ModulesList, QVariant());
}

void GdbEngine::handleModulesList(const GdbResultRecord &record)
{
    QList<Module> modules;
    if (record.resultClass == GdbResultDone) {
        // that's console-based output, likely Linux or Windows,
        // but we can avoid the #ifdef here
        QString data = record.data.findChild("consolestreamoutput").data();
        QTextStream ts(&data, QIODevice::ReadOnly);
        while (!ts.atEnd()) {
            QString line = ts.readLine();
            if (!line.startsWith("0x"))
                continue;
            Module module;
            QString symbolsRead;
            QTextStream ts(&line, QIODevice::ReadOnly);
            ts >> module.startAddress >> module.endAddress >> symbolsRead;
            module.moduleName = ts.readLine().trimmed();
            module.symbolsRead = (symbolsRead == "Yes");
            modules.append(module);
        }
        if (modules.isEmpty()) {
            // Mac has^done,shlib-info={num="1",name="dyld",kind="-",
            // dyld-addr="0x8fe00000",reason="dyld",requested-state="Y",
            // state="Y",path="/usr/lib/dyld",description="/usr/lib/dyld",
            // loaded_addr="0x8fe00000",slide="0x0",prefix="__dyld_"},
            // shlib-info={...}...
            foreach (const GdbMi &item, record.data.children()) {
                Module module;
                module.moduleName = item.findChild("path").data();
                module.symbolsRead = (item.findChild("state").data() == "Y");
                module.startAddress = item.findChild("loaded_addr").data();
                module.endAddress = "<unknown>";
                modules.append(module);
            }
        }
    }
    qq->modulesHandler()->setModules(modules);
}


//////////////////////////////////////////////////////////////////////
//
// Source files specific stuff
//
//////////////////////////////////////////////////////////////////////

void GdbEngine::reloadSourceFiles()
{
    sendCommand("-file-list-exec-source-files", GdbQuerySources);
}


//////////////////////////////////////////////////////////////////////
//
// Stack specific stuff
//
//////////////////////////////////////////////////////////////////////

void GdbEngine::handleStackSelectThread(const GdbResultRecord &record, int)
{
    Q_UNUSED(record);
    //qDebug("FIXME: StackHandler::handleOutput: SelectThread");
    q->showStatusMessage(tr("Retrieving data for stack view..."), 3000);
    reloadStack();
}


void GdbEngine::handleStackListFrames(const GdbResultRecord &record, bool isFull)
{
    QList<StackFrame> stackFrames;

    const GdbMi stack = record.data.findChild("stack");
    QString dummy = stack.toString();
    if (!stack.isValid()) {
        qDebug() << "FIXME: stack: " << stack.toString();
        return;
    }

    int topFrame = -1;

    int n = stack.childCount();
    for (int i = 0; i != n; ++i) {
        //qDebug() << "HANDLING FRAME: " << stack.childAt(i).toString();
        const GdbMi frameMi = stack.childAt(i);
        StackFrame frame(i);
        QStringList files;
        files.append(frameMi.findChild("fullname").data());
        files.append(frameMi.findChild("file").data());
        frame.file = fullName(files);
        frame.function = frameMi.findChild("func").data();
        frame.from = frameMi.findChild("from").data();
        frame.line = frameMi.findChild("line").data().toInt();
        frame.address = frameMi.findChild("addr").data();

        stackFrames.append(frame);

#ifdef Q_OS_WIN
        const bool isBogus =
            // Assume this is wrong and points to some strange stl_algobase
            // implementation. Happens on Karsten's XP system with Gdb 5.50
            (frame.file.endsWith("/bits/stl_algobase.h") && frame.line == 150)
            // Also wrong. Happens on Vista with Gdb 5.50
               || (frame.function == "operator new" && frame.line == 151);

        // immediately leave bogus frames
        if (topFrame == -1 && isBogus) {
            sendCommand("-exec-finish");
            return;
        }

#endif

        // Initialize top frame to the first valid frame
        const bool isValid = !frame.file.isEmpty() && !frame.function.isEmpty();
        if (isValid && topFrame == -1)
            topFrame = i;
    }

    bool canExpand = !isFull 
        && (n >= theDebuggerAction(MaximalStackDepth)->value().toInt());
    theDebuggerAction(ExpandStack)->setEnabled(canExpand);
    qq->stackHandler()->setFrames(stackFrames, canExpand);

    if (topFrame != -1) {
        // updates of locals already triggered early
        const StackFrame &frame = qq->stackHandler()->currentFrame();
        if (frame.isUsable())
            q->gotoLocation(frame.file, frame.line, true);
        else
            qDebug() << "FULL NAME NOT USABLE 0: " << frame.file << topFrame;
    }
}

void GdbEngine::selectThread(int index)
{
    //reset location arrow
    q->resetLocation();

    ThreadsHandler *threadsHandler = qq->threadsHandler();
    threadsHandler->setCurrentThread(index);

    QList<ThreadData> threads = threadsHandler->threads();
    QTC_ASSERT(index < threads.size(), return);
    int id = threads.at(index).id;
    q->showStatusMessage(tr("Retrieving data for stack view..."), 10000);
    sendCommand(QLatin1String("-thread-select ") + QString::number(id),
        StackSelectThread);
}

void GdbEngine::activateFrame(int frameIndex)
{
    if (q->status() != DebuggerInferiorStopped)
        return;

    StackHandler *stackHandler = qq->stackHandler();
    int oldIndex = stackHandler->currentIndex();
    //qDebug() << "ACTIVATE FRAME: " << frameIndex << oldIndex
    //    << stackHandler->currentIndex();

    if (frameIndex == stackHandler->stackSize()) {
        reloadFullStack();
        return;
    }
    QTC_ASSERT(frameIndex < stackHandler->stackSize(), return);

    if (oldIndex != frameIndex) {
        setTokenBarrier();

        // Assuming this always succeeds saves a roundtrip.
        // Otherwise the lines below would need to get triggered
        // after a response to this -stack-select-frame here.
        sendCommand("-stack-select-frame " + QString::number(frameIndex));

        stackHandler->setCurrentIndex(frameIndex);
        updateLocals();
    }

    const StackFrame &frame = stackHandler->currentFrame();

    if (frame.isUsable())
        q->gotoLocation(frame.file, frame.line, true);
    else
        qDebug() << "FULL NAME NOT USABLE: " << frame.file;
}

void GdbEngine::handleStackListThreads(const GdbResultRecord &record, int id)
{
    // "72^done,{thread-ids={thread-id="2",thread-id="1"},number-of-threads="2"}
    const QList<GdbMi> items = record.data.findChild("thread-ids").children();
    QList<ThreadData> threads;
    int currentIndex = -1;
    for (int index = 0, n = items.size(); index != n; ++index) {
        ThreadData thread;
        thread.id = items.at(index).data().toInt();
        threads.append(thread);
        if (thread.id == id) {
            //qDebug() << "SETTING INDEX TO: " << index << " ID: "<< id << "RECOD: "<< record.toString();
            currentIndex = index;
        }
    }
    ThreadsHandler *threadsHandler = qq->threadsHandler();
    threadsHandler->setThreads(threads);
    threadsHandler->setCurrentThread(currentIndex);
}


//////////////////////////////////////////////////////////////////////
//
// Register specific stuff
//
//////////////////////////////////////////////////////////////////////

void GdbEngine::reloadRegisters()
{
    QString format;
    if (theDebuggerAction(FormatHexadecimal)->isChecked())
        format = "x";
    else if (theDebuggerAction(FormatDecimal)->isChecked())
        format = "d";
    else if (theDebuggerAction(FormatOctal)->isChecked())
        format = "o";
    else if (theDebuggerAction(FormatBinary)->isChecked())
        format = "t";
    else if (theDebuggerAction(FormatRaw)->isChecked())
        format = "r";
    else
        format = "N";
    sendCommand("-data-list-register-values " + format, RegisterListValues);
}

void GdbEngine::handleRegisterListNames(const GdbResultRecord &record)
{
    if (record.resultClass != GdbResultDone)
        return;

    QList<Register> registers;
    foreach (const GdbMi &item, record.data.findChild("register-names").children())
        registers.append(Register(item.data()));

    qq->registerHandler()->setRegisters(registers);
}

void GdbEngine::handleRegisterListValues(const GdbResultRecord &record)
{
    if (record.resultClass != GdbResultDone)
        return;

    QList<Register> registers = qq->registerHandler()->registers();

    // 24^done,register-values=[{number="0",value="0xf423f"},...]
    foreach (const GdbMi &item, record.data.findChild("register-values").children()) {
        int index = item.findChild("number").data().toInt();
        if (index < registers.size()) {
            Register &reg = registers[index];
            QString value = item.findChild("value").data();
            reg.changed = (value != reg.value);
            if (reg.changed)
                reg.value = value;
        }
    }
    qq->registerHandler()->setRegisters(registers);
}


//////////////////////////////////////////////////////////////////////
//
// Thread specific stuff
//
//////////////////////////////////////////////////////////////////////

bool GdbEngine::supportsThreads() const
{
    // 6.3 crashes happily on -thread-list-ids. So don't use it.
    // The test below is a semi-random pick, 6.8 works fine
    return m_gdbVersion > 60500;
}

//////////////////////////////////////////////////////////////////////
//
// Tooltip specific stuff
//
//////////////////////////////////////////////////////////////////////

static WatchData m_toolTip;
static QString m_toolTipExpression;
static QPoint m_toolTipPos;
static QMap<QString, WatchData> m_toolTipCache;

void GdbEngine::setToolTipExpression(const QPoint &pos, const QString &exp0)
{
    //qDebug() << "SET TOOLTIP EXP" << pos << exp0;
    if (q->status() != DebuggerInferiorStopped) {
        //qDebug() << "SUPPRESSING DEBUGGER TOOLTIP, INFERIOR NOT STOPPED";
        return;
    }

    if (theDebuggerBoolSetting(DebugDebuggingHelpers)) {
        // minimize interference
        return;
    }

    m_toolTipPos = pos;
    m_toolTipExpression = exp0;
    QString exp = exp0;
/*
    if (m_toolTip.isTypePending()) {
        qDebug() << "suppressing duplicated tooltip creation";
        return;
    }
*/
    if (m_toolTipCache.contains(exp)) {
        const WatchData & data = m_toolTipCache[exp];
        // FIXME: qq->watchHandler()->collapseChildren(data.iname);
        insertData(data);
        return;
    }

    QToolTip::hideText();
    if (exp.isEmpty() || exp.startsWith("#"))  {
        QToolTip::hideText();
        return;
    }

    if (!hasLetterOrNumber(exp)) {
        QToolTip::showText(m_toolTipPos,
            tr("'%1' contains no identifier").arg(exp));
        return;
    }

    if (isKeyWord(exp))
        return;

    if (exp.startsWith('"') && exp.endsWith('"'))  {
        QToolTip::showText(m_toolTipPos, "String literal " + exp);
        return;
    }

    if (exp.startsWith("++") || exp.startsWith("--"))
        exp = exp.mid(2);

    if (exp.endsWith("++") || exp.endsWith("--"))
        exp = exp.mid(2);

    if (exp.startsWith("<") || exp.startsWith("["))
        return;

    if (hasSideEffects(exp)) {
        QToolTip::showText(m_toolTipPos,
            tr("Cowardly refusing to evaluate expression '%1' "
               "with potential side effects").arg(exp));
        return;
    }

    // Gdb crashes when creating a variable object with the name
    // of the type of 'this'
/*
    for (int i = 0; i != m_currentLocals.childCount(); ++i) {
        if (m_currentLocals.childAt(i).exp == "this") {
            qDebug() << "THIS IN ROW " << i;
            if (m_currentLocals.childAt(i).type.startsWith(exp)) {
                QToolTip::showText(m_toolTipPos,
                    tr("%1: type of current 'this'").arg(exp));
                qDebug() << " TOOLTIP CRASH SUPPRESSED";
                return;
            }
            break;
        }
    }
*/

    //if (m_manager->status() != DebuggerInferiorStopped)
    //    return;

    // FIXME: 'exp' can contain illegal characters
    m_toolTip = WatchData();
    //m_toolTip.level = 0;
   // m_toolTip.row = 0;
   // m_toolTip.parentIndex = 2;
    m_toolTip.exp = exp;
    m_toolTip.name = exp;
    m_toolTip.iname = tooltipIName;
    insertData(m_toolTip);
    updateWatchModel2();
}


//////////////////////////////////////////////////////////////////////
//
// Watch specific stuff
//
//////////////////////////////////////////////////////////////////////

static const QString strNotInScope = QLatin1String("<not in scope>");

static QString quoteUnprintableLatin1(const QByteArray &ba)
{
    QString res;
    char buf[10];
    for (int i = 0, n = ba.size(); i != n; ++i) {
        unsigned char c = ba.at(i);
        if (isprint(c)) {
            res += c;
        } else {
            qsnprintf(buf, sizeof(buf) - 1, "\\%x", int(c));
            res += buf;
        }
    }
    return res;
}

static QString decodeData(QByteArray ba, int encoding)
{
    switch (encoding) {
        case 0: // unencoded 8 bit data
            return quoteUnprintableLatin1(ba);
        case 1: //  base64 encoded 8 bit data, used for QByteArray
            ba = QByteArray::fromBase64(ba);
            return '"' + quoteUnprintableLatin1(ba) + '"';
        case 2: //  base64 encoded 16 bit data, used for QString
            ba = QByteArray::fromBase64(ba);
            return '"' + QString::fromUtf16((ushort *)ba.data(), ba.size() / 2) + '"';
        case 3: //  base64 encoded 32 bit data
            ba = QByteArray::fromBase64(ba);
            return '"' + QString::fromUcs4((uint *)ba.data(), ba.size() / 4) + '"';
            break;
        case 4: //  base64 encoded 16 bit data, without quotes (see 2)
            ba = QByteArray::fromBase64(ba);
            return QString::fromUtf16((ushort *)ba.data(), ba.size() / 2);
    }
    return "<Encoding error>";
}

static void setWatchDataValue(WatchData &data, const GdbMi &mi,
    int encoding = 0)
{
    if (mi.isValid())
        data.setValue(decodeData(mi.data(), encoding));
    else
        data.setValueNeeded();
}

static void setWatchDataEditValue(WatchData &data, const GdbMi &mi)
{
    if (mi.isValid())
        data.editvalue = mi.data();
}

static void setWatchDataValueToolTip(WatchData &data, const GdbMi &mi)
{
    if (mi.isValid())
        data.setValueToolTip(mi.data());
}

static void setWatchDataChildCount(WatchData &data, const GdbMi &mi)
{
    if (mi.isValid()) {
        data.childCount = mi.data().toInt();
        data.setChildCountUnneeded();
        if (data.childCount == 0)
            data.setChildrenUnneeded();
    } else {
        data.childCount = -1;
    }
}

static void setWatchDataValueDisabled(WatchData &data, const GdbMi &mi)
{
    if (mi.data() == "true")
        data.valuedisabled = true;
    else if (mi.data() == "false")
        data.valuedisabled = false;
}

static void setWatchDataExpression(WatchData &data, const GdbMi &mi)
{
    if (mi.isValid())
        data.exp = "(" + mi.data() + ")";
}

static void setWatchDataAddress(WatchData &data, const GdbMi &mi)
{
    if (mi.isValid()) {
        data.addr = mi.data();
        if (data.exp.isEmpty())
            data.exp = "(*(" + gdbQuoteTypes(data.type) + "*)" + data.addr + ")";
    }
}

static void setWatchDataSAddress(WatchData &data, const GdbMi &mi)
{
    if (mi.isValid())
        data.saddr = mi.data();
}

void GdbEngine::setUseDebuggingHelpers(const QVariant &on)
{
    qDebug() << "SWITCHING ON/OFF DUMPER DEBUGGING:" << on;
    // FIXME: a bit too harsh, but otherwise the treeview sometimes look funny
    //m_expandedINames.clear();
    setTokenBarrier();
    updateLocals();
}

bool GdbEngine::hasDebuggingHelperForType(const QString &type) const
{
    if (!theDebuggerBoolSetting(UseDebuggingHelpers))
        return false;

    if (q->startMode() == AttachCore) {
        // "call" is not possible in gdb when looking at core files
        return type == "QString" || type.endsWith("::QString")
            || type == "QStringList" || type.endsWith("::QStringList");
    }

    if (theDebuggerBoolSetting(DebugDebuggingHelpers)
            && qq->stackHandler()->isDebuggingDebuggingHelpers())
        return false;

    if (m_debuggingHelperState != DebuggingHelperAvailable)
        return false;

    // simple types
    if (m_availableSimpleDebuggingHelpers.contains(type))
        return true;

    // templates
    QString tmplate;
    QString inner;
    if (!extractTemplate(type, &tmplate, &inner))
        return false;
    return m_availableSimpleDebuggingHelpers.contains(tmplate);
}

void GdbEngine::runDirectDebuggingHelper(const WatchData &data, bool dumpChildren)
{
    Q_UNUSED(dumpChildren);
    QString type = data.type;
    QString cmd;

    if (type == "QString" || type.endsWith("::QString"))
        cmd = "qdumpqstring (&" + data.exp + ")";
    else if (type == "QStringList" || type.endsWith("::QStringList"))
        cmd = "qdumpqstringlist (&" + data.exp + ")";

    QVariant var;
    var.setValue(data);
    sendSynchronizedCommand(cmd, WatchDebuggingHelperValue3, var);

    q->showStatusMessage(
        tr("Retrieving data for watch view (%1 requests pending)...")
            .arg(m_pendingRequests + 1), 10000);
}

void GdbEngine::runDebuggingHelper(const WatchData &data0, bool dumpChildren)
{
    if (q->startMode() == AttachCore) {
        runDirectDebuggingHelper(data0, dumpChildren);
        return;
    }
    WatchData data = data0;
    QTC_ASSERT(!data.exp.isEmpty(), return);
    QString tmplate;
    QString inner;
    bool isTemplate = extractTemplate(data.type, &tmplate, &inner);
    QStringList inners = inner.split('@');
    if (inners.at(0).isEmpty())
        inners.clear();
    for (int i = 0; i != inners.size(); ++i)
        inners[i] = inners[i].simplified();

    QString outertype = isTemplate ? tmplate : data.type;
    // adjust the data extract
    if (outertype == m_namespace + "QWidget")
        outertype = m_namespace + "QObject";

    QString extraArgs[4];
    extraArgs[0] = "0";
    extraArgs[1] = "0";
    extraArgs[2] = "0";
    extraArgs[3] = "0";

    int extraArgCount = 0;

    // "generic" template dumpers: passing sizeof(argument)
    // gives already most information the dumpers need
    foreach (const QString &arg, inners)
        extraArgs[extraArgCount++] = sizeofTypeExpression(arg);

    // in rare cases we need more or less:
    if (outertype == m_namespace + "QObject") {
        extraArgs[0] = "(char*)&((('"
            + m_namespace + "QObjectPrivate'*)&"
            + data.exp + ")->children)-(char*)&" + data.exp;
    } else if (outertype == m_namespace + "QVector") {
        extraArgs[1] = "(char*)&(("
            + data.exp + ").d->array)-(char*)" + data.exp + ".d";
    } else if (outertype == m_namespace + "QObjectSlot"
            || outertype == m_namespace + "QObjectSignal") {
        // we need the number out of something like
        // iname="local.ob.slots.2" // ".deleteLater()"?
        int pos = data.iname.lastIndexOf('.');
        QString slotNumber = data.iname.mid(pos + 1);
        QTC_ASSERT(slotNumber.toInt() != -1, /**/);
        extraArgs[0] = slotNumber;
    } else if (outertype == m_namespace + "QMap" || outertype == m_namespace + "QMultiMap") {
        QString nodetype;
        if (m_qtVersion >= (4 << 16) + (5 << 8) + 0) {
            nodetype  = m_namespace + "QMapNode";
            nodetype += data.type.mid(outertype.size());
        } else {
            // FIXME: doesn't work for QMultiMap
            nodetype  = data.type + "::Node";
        }
        //qDebug() << "OUTERTYPE: " << outertype << " NODETYPE: " << nodetype
        //    << "QT VERSION" << m_qtVersion << ((4 << 16) + (5 << 8) + 0);
        extraArgs[2] = sizeofTypeExpression(nodetype);
        extraArgs[3] = "(size_t)&(('" + nodetype + "'*)0)->value";
    } else if (outertype == m_namespace + "QMapNode") {
        extraArgs[2] = sizeofTypeExpression(data.type);
        extraArgs[3] = "(size_t)&(('" + data.type + "'*)0)->value";
    } else if (outertype == "std::vector" || outertype == "vector") {
        //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners;
        if (inners.at(0) == "bool") {
            outertype = "std::vector::bool";
        } else {
            //extraArgs[extraArgCount++] = sizeofTypeExpression(data.type);
            //extraArgs[extraArgCount++] = "(size_t)&(('" + data.type + "'*)0)->value";
        }
    } else if (outertype == "std::deque" || outertype == "deque") {
        // remove 'std::allocator<...>':
        extraArgs[1] = "0";
    } else if (outertype == "std::stack" || outertype == "stack") {
        // remove 'std::allocator<...>':
        extraArgs[1] = "0";
    } else if (outertype == "std::set" || outertype == "set") {
        // remove 'std::less<...>':
        extraArgs[1] = "0";
        // remove 'std::allocator<...>':
        extraArgs[2] = "0";
    } else if (outertype == "std::map" || outertype == "map") {
        // We don't want the comparator and the allocator confuse gdb.
        // But we need the offset of the second item in the value pair.
        // We read the type of the pair from the allocator argument because
        // that gets the constness "right" (in the sense that gdb can
        // read it back;
        QString pairType = inners.at(3);
        // remove 'std::allocator<...>':
        pairType = pairType.mid(15, pairType.size() - 15 - 2);
        extraArgs[2] = "(size_t)&(('" + pairType + "'*)0)->second";
        extraArgs[3] = "0";
    } else if (outertype == "std::basic_string" || outertype == "basic_string") {
        //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners;
        if (inners.at(0) == "char") {
            outertype = "std::string";
        } else if (inners.at(0) == "wchar_t") {
            outertype = "std::wstring";
        }
        extraArgs[0] = "0";
        extraArgs[1] = "0";
        extraArgs[2] = "0";
        extraArgs[3] = "0";
    }

    //int protocol = (data.iname.startsWith("watch") && data.type == "QImage") ? 3 : 2;
    //int protocol = data.iname.startsWith("watch") ? 3 : 2;
    int protocol = 2;
    //int protocol = isDisplayedIName(data.iname) ? 3 : 2;

    QString addr;
    if (data.addr.startsWith("0x"))
        addr = "(void*)" + data.addr;
    else
        addr = "&(" + data.exp + ")";

    QByteArray params;
    params.append(outertype.toUtf8());
    params.append('\0');
    params.append(data.iname.toUtf8());
    params.append('\0');
    params.append(data.exp.toUtf8());
    params.append('\0');
    params.append(inner.toUtf8());
    params.append('\0');
    params.append(data.iname.toUtf8());
    params.append('\0');

    sendWatchParameters(params);

    QString cmd ="call "
            + QString("(void*)qDumpObjectData440(")
            + QString::number(protocol)
            + ',' + "%1+1"                // placeholder for token
            + ',' + addr
            + ',' + (dumpChildren ? "1" : "0")
            + ',' + extraArgs[0]
            + ',' + extraArgs[1]
            + ',' + extraArgs[2]
            + ',' + extraArgs[3] + ')';

    //qDebug() << "CMD: " << cmd;

    QVariant var;
    var.setValue(data);
    sendSynchronizedCommand(cmd, WatchDebuggingHelperValue1, var);

    q->showStatusMessage(
        tr("Retrieving data for watch view (%1 requests pending)...")
            .arg(m_pendingRequests + 1), 10000);

    // retrieve response
    sendSynchronizedCommand("p (char*)&qDumpOutBuffer", WatchDebuggingHelperValue2, var);
}

void GdbEngine::createGdbVariable(const WatchData &data)
{
    sendSynchronizedCommand("-var-delete \"" + data.iname + '"');
    QString exp = data.exp;
    if (exp.isEmpty() && data.addr.startsWith("0x"))
        exp = "*(" + gdbQuoteTypes(data.type) + "*)" + data.addr;
    QVariant val = QVariant::fromValue<WatchData>(data);
    sendSynchronizedCommand("-var-create \"" + data.iname + '"' + " * "
        + '"' + exp + '"', WatchVarCreate, val);
}

void GdbEngine::updateSubItem(const WatchData &data0)
{
    WatchData data = data0;
    #if DEBUG_SUBITEM
    qDebug() << "UPDATE SUBITEM: " << data.toString();
    #endif
    QTC_ASSERT(data.isValid(), return);

    // in any case we need the type first
    if (data.isTypeNeeded()) {
        // This should only happen if we don't have a variable yet.
        // Let's play safe, though.
        if (!data.variable.isEmpty()) {
            // Update: It does so for out-of-scope watchers.
            #if 1
            qDebug() << "FIXME: GdbEngine::updateSubItem: "
                 << data.toString() << "should not happen";
            #else
            data.setType("<out of scope>");
            data.setValue("<out of scope>");
            data.setChildCount(0);
            insertData(data);
            return;
            #endif
        }
        // The WatchVarCreate handler will receive type information
        // and re-insert a WatchData item with correct type, so
        // we will not re-enter this bit.
        // FIXME: Concurrency issues?
        createGdbVariable(data);
        return;
    }

    // we should have a type now. this is relied upon further below
    QTC_ASSERT(!data.type.isEmpty(), return);

    // a common case that can be easily solved
    if (data.isChildrenNeeded() && isPointerType(data.type)
        && !hasDebuggingHelperForType(data.type)) {
        // We sometimes know what kind of children pointers have
        #if DEBUG_SUBITEM
        qDebug() << "IT'S A POINTER";
        #endif
#if 1
        insertData(data.pointerChildPlaceHolder());
        data.setChildrenUnneeded();
        insertData(data);
#else
        // Try automatic dereferentiation
        data.exp = "*(" + data.exp + ")";
        data.type = data.type + "."; // FIXME: fragile HACK to avoid recursion
        insertData(data);
#endif
        return;
    }

    if (data.isValueNeeded() && hasDebuggingHelperForType(data.type)) {
        #if DEBUG_SUBITEM
        qDebug() << "UPDATE SUBITEM: CUSTOMVALUE";
        #endif
        runDebuggingHelper(data, qq->watchHandler()->isExpandedIName(data.iname));
        return;
    }

/*
    if (data.isValueNeeded() && data.exp.isEmpty()) {
        #if DEBUG_SUBITEM
        qDebug() << "UPDATE SUBITEM: NO EXPRESSION?";
        #endif
        data.setError("<no expression given>");
        insertData(data);
        return;
    }
*/

    if (data.isValueNeeded() && data.variable.isEmpty()) {
        #if DEBUG_SUBITEM
        qDebug() << "UPDATE SUBITEM: VARIABLE NEEDED FOR VALUE";
        #endif
        createGdbVariable(data);
        // the WatchVarCreate handler will re-insert a WatchData
        // item, with valueNeeded() set.
        return;
    }

    if (data.isValueNeeded()) {
        QTC_ASSERT(!data.variable.isEmpty(), return); // tested above
        #if DEBUG_SUBITEM
        qDebug() << "UPDATE SUBITEM: VALUE";
        #endif
        QString cmd = "-var-evaluate-expression \"" + data.iname + "\"";
        sendSynchronizedCommand(cmd, WatchEvaluateExpression,
            QVariant::fromValue(data));
        return;
    }

    if (data.isChildrenNeeded() && hasDebuggingHelperForType(data.type)) {
        #if DEBUG_SUBITEM
        qDebug() << "UPDATE SUBITEM: CUSTOMVALUE WITH CHILDREN";
        #endif
        runDebuggingHelper(data, true);
        return;
    }

    if (data.isChildrenNeeded() && data.variable.isEmpty()) {
        #if DEBUG_SUBITEM
        qDebug() << "UPDATE SUBITEM: VARIABLE NEEDED FOR CHILDREN";
        #endif
        createGdbVariable(data);
        // the WatchVarCreate handler will re-insert a WatchData
        // item, with childrenNeeded() set.
        return;
    }

    if (data.isChildrenNeeded()) {
        QTC_ASSERT(!data.variable.isEmpty(), return); // tested above
        QString cmd = "-var-list-children --all-values \"" + data.variable + "\"";
        sendSynchronizedCommand(cmd, WatchVarListChildren, QVariant::fromValue(data));
        return;
    }

    if (data.isChildCountNeeded() && hasDebuggingHelperForType(data.type)) {
        #if DEBUG_SUBITEM
        qDebug() << "UPDATE SUBITEM: CUSTOMVALUE WITH CHILDREN";
        #endif
        runDebuggingHelper(data, qq->watchHandler()->isExpandedIName(data.iname));
        return;
    }

    if (data.isChildCountNeeded() && data.variable.isEmpty()) {
        #if DEBUG_SUBITEM
        qDebug() << "UPDATE SUBITEM: VARIABLE NEEDED FOR CHILDCOUNT";
        #endif
        createGdbVariable(data);
        // the WatchVarCreate handler will re-insert a WatchData
        // item, with childrenNeeded() set.
        return;
    }

    if (data.isChildCountNeeded()) {
        QTC_ASSERT(!data.variable.isEmpty(), return); // tested above
        QString cmd = "-var-list-children --all-values \"" + data.variable + "\"";
        sendCommand(cmd, WatchVarListChildren, QVariant::fromValue(data));
        return;
    }

    qDebug() << "FIXME: UPDATE SUBITEM: " << data.toString();
    QTC_ASSERT(false, return);
}

void GdbEngine::updateWatchModel()
{
    m_pendingRequests = 0;
    PENDING_DEBUG("EXTERNAL TRIGGERING UPDATE WATCH MODEL");
    updateWatchModel2();
}

void GdbEngine::updateWatchModel2()
{
    PENDING_DEBUG("UPDATE WATCH MODEL");
    QList<WatchData> incomplete = qq->watchHandler()->takeCurrentIncompletes();
    //QTC_ASSERT(incomplete.isEmpty(), /**/);
    if (!incomplete.isEmpty()) {
        #if DEBUG_PENDING
        qDebug() << "##############################################";
        qDebug() << "UPDATE MODEL, FOUND INCOMPLETES:";
        foreach (const WatchData &data, incomplete)
            qDebug() << data.toString();
        #endif

        // Bump requests to avoid model rebuilding during the nested
        // updateWatchModel runs.
        ++m_pendingRequests;
        foreach (const WatchData &data, incomplete)
            updateSubItem(data);
        PENDING_DEBUG("INTERNAL TRIGGERING UPDATE WATCH MODEL");
        updateWatchModel2();
        --m_pendingRequests;

        return;
    }

    if (m_pendingRequests > 0) {
        PENDING_DEBUG("UPDATE MODEL, PENDING: " << m_pendingRequests);
        return;
    }

    PENDING_DEBUG("REBUILDING MODEL");
    emit gdbInputAvailable(QString(),
        "[" + currentTime() + "]    <Rebuild Watchmodel>");
    q->showStatusMessage(tr("Finished retrieving data."), 400);
    qq->watchHandler()->rebuildModel();

    if (!m_toolTipExpression.isEmpty()) {
        WatchData *data = qq->watchHandler()->findData(tooltipIName);
        if (data) {
            //m_toolTipCache[data->exp] = *data;
            QToolTip::showText(m_toolTipPos,
                    "(" + data->type + ") " + data->exp + " = " + data->value);
        } else {
            QToolTip::showText(m_toolTipPos,
                tr("Cannot evaluate expression: %1").arg(m_toolTipExpression));
        }
    }
}

void GdbEngine::handleQueryDebuggingHelper(const GdbResultRecord &record)
{
    //qDebug() << "DATA DUMPER TRIAL:" << record.toString();
    GdbMi output = record.data.findChild("consolestreamoutput");
    QByteArray out = output.data();
    out = out.mid(out.indexOf('"') + 2); // + 1 is success marker
    out = out.left(out.lastIndexOf('"'));
    //out.replace('\'', '"');
    out.replace("\\", "");
    out = "dummy={" + out + "}";
    //qDebug() << "OUTPUT: " << out;

    GdbMi contents;
    contents.fromString(out);
    GdbMi simple = contents.findChild("dumpers");
    m_namespace = contents.findChild("namespace").data();
    GdbMi qtversion = contents.findChild("qtversion");
    if (qtversion.children().size() == 3) {
        m_qtVersion = (qtversion.childAt(0).data().toInt() << 16)
                    + (qtversion.childAt(1).data().toInt() << 8)
                    + qtversion.childAt(2).data().toInt();
        //qDebug() << "FOUND QT VERSION: " << qtversion.toString() << m_qtVersion;
    } else {
        m_qtVersion = 0;
    }

    //qDebug() << "CONTENTS: " << contents.toString();
    //qDebug() << "SIMPLE DUMPERS: " << simple.toString();
    m_availableSimpleDebuggingHelpers.clear();
    foreach (const GdbMi &item, simple.children())
        m_availableSimpleDebuggingHelpers.append(item.data());
    if (m_availableSimpleDebuggingHelpers.isEmpty()) {
        m_debuggingHelperState = DebuggingHelperUnavailable;
        q->showStatusMessage(tr("Debugging helpers not found."));
        //QMessageBox::warning(q->mainWindow(),
        //    tr("Cannot find special data dumpers"),
        //    tr("The debugged binary does not contain information needed for "
        //            "nice display of Qt data types.\n\n"
        //            "You might want to try including the file\n\n"
        //            ".../share/qtcreator/gdbmacros/gdbmacros.cpp\n\n"
        //            "into your project directly.")
        //        );
    } else {
        m_debuggingHelperState = DebuggingHelperAvailable;
        q->showStatusMessage(tr("%1 custom dumpers found.")
            .arg(m_availableSimpleDebuggingHelpers.size()));
    }
    //qDebug() << "DATA DUMPERS AVAILABLE" << m_availableSimpleDebuggingHelpers;
}

void GdbEngine::sendWatchParameters(const QByteArray &params0)
{
    QByteArray params = params0;
    params.append('\0');
    char buf[50];
    sprintf(buf, "set {char[%d]} &qDumpInBuffer = {", params.size());
    QByteArray encoded;
    encoded.append(buf);
    for (int i = 0; i != params.size(); ++i) {
        sprintf(buf, "%d,", int(params[i]));
        encoded.append(buf);
    }
    encoded[encoded.size() - 1] = '}';

    sendCommand(encoded);
}

void GdbEngine::handleVarAssign()
{
    // everything might have changed, force re-evaluation
    // FIXME: Speed this up by re-using variables and only
    // marking values as 'unknown'
    setTokenBarrier();
    updateLocals();
}

void GdbEngine::setWatchDataType(WatchData &data, const GdbMi &mi)
{
    if (mi.isValid()) {
        if (!data.framekey.isEmpty())
            m_varToType[data.framekey] = mi.data();
        data.setType(mi.data());
    } else if (data.type.isEmpty()) {
        data.setTypeNeeded();
    }
}

void GdbEngine::handleVarCreate(const GdbResultRecord &record,
    const WatchData &data0)
{
    WatchData data = data0;
    // happens e.g. when we already issued a var-evaluate command
    if (!data.isValid())
        return;
    //qDebug() << "HANDLE VARIABLE CREATION: " << data.toString();
    if (record.resultClass == GdbResultDone) {
        data.variable = data.iname;
        setWatchDataType(data, record.data.findChild("type"));
        if (hasDebuggingHelperForType(data.type)) {
            // we do not trust gdb if we have a custom dumper
            if (record.data.findChild("children").isValid())
                data.setChildrenUnneeded();
            else if (qq->watchHandler()->isExpandedIName(data.iname))
                data.setChildrenNeeded();
            insertData(data);
        } else {
            if (record.data.findChild("children").isValid())
                data.setChildrenUnneeded();
            else if (qq->watchHandler()->isExpandedIName(data.iname))
                data.setChildrenNeeded();
            setWatchDataChildCount(data, record.data.findChild("numchild"));
            //if (data.isValueNeeded() && data.childCount > 0)
            //    data.setValue(QString());
            insertData(data);
        }
    } else if (record.resultClass == GdbResultError) {
        data.setError(record.data.findChild("msg").data());
        if (data.isWatcher()) {
            data.value = strNotInScope;
            data.type = " ";
            data.setAllUnneeded();
            data.setChildCount(0);
            data.valuedisabled = true;
            insertData(data);
        }
    }
}

void GdbEngine::handleEvaluateExpression(const GdbResultRecord &record,
    const WatchData &data0)
{
    WatchData data = data0;
    QTC_ASSERT(data.isValid(), qDebug() << "HUH?");
    if (record.resultClass == GdbResultDone) {
        //if (col == 0)
        //    data.name = record.data.findChild("value").data();
        //else
            setWatchDataValue(data, record.data.findChild("value"));
    } else if (record.resultClass == GdbResultError) {
        data.setError(record.data.findChild("msg").data());
    }
    //qDebug() << "HANDLE EVALUATE EXPRESSION: " << data.toString();
    insertData(data);
    //updateWatchModel2();
}

void GdbEngine::handleDebuggingHelperSetup(const GdbResultRecord &record)
{
    //qDebug() << "CUSTOM SETUP RESULT: " << record.toString();
    if (record.resultClass == GdbResultDone) {
    } else if (record.resultClass == GdbResultError) {
        QString msg = record.data.findChild("msg").data();
        //qDebug() << "CUSTOM DUMPER SETUP ERROR MESSAGE: " << msg;
        q->showStatusMessage(tr("Custom dumper setup: %1").arg(msg), 10000);
    }
}

void GdbEngine::handleDebuggingHelperValue1(const GdbResultRecord &record,
    const WatchData &data0)
{
    WatchData data = data0;
    QTC_ASSERT(data.isValid(), return);
    if (record.resultClass == GdbResultDone) {
        // ignore this case, data will follow
    } else if (record.resultClass == GdbResultError) {
        // Record an extra result, as the socket result will be lost
        // in transmission
        //--m_pendingRequests;
        QString msg = record.data.findChild("msg").data();
        //qDebug() << "CUSTOM DUMPER ERROR MESSAGE: " << msg;
#ifdef QT_DEBUG
        // Make debugging of dumpers easier
        if (theDebuggerBoolSetting(DebugDebuggingHelpers)
                && msg.startsWith("The program being debugged stopped while")
                && msg.contains("qDumpObjectData440")) {
            // Fake full stop
            sendCommand("p 0", GdbAsyncOutput2);  // dummy
            return;
        }
#endif
        //if (msg.startsWith("The program being debugged was sig"))
        //    msg = strNotInScope;
        //if (msg.startsWith("The program being debugged stopped while"))
        //    msg = strNotInScope;
        //data.setError(msg);
        //insertData(data);
    }
}

void GdbEngine::handleDebuggingHelperValue2(const GdbResultRecord &record,
    const WatchData &data0)
{
    WatchData data = data0;
    QTC_ASSERT(data.isValid(), return);
    //qDebug() << "CUSTOM VALUE RESULT: " << record.toString();
    //qDebug() << "FOR DATA: " << data.toString() << record.resultClass;
    if (record.resultClass != GdbResultDone) {
        qDebug() << "STRANGE CUSTOM DUMPER RESULT DATA: " << data.toString();
        return;
    }

    GdbMi output = record.data.findChild("consolestreamoutput");
    QByteArray out = output.data();

    int markerPos = out.indexOf('"') + 1; // position of 'success marker'
    if (markerPos == -1 || out.at(markerPos) == 'f') {  // 't' or 'f'
        // custom dumper produced no output
        data.setError(strNotInScope);
        insertData(data);
        return;
    }

    out = out.mid(markerPos +  1);
    out = out.left(out.lastIndexOf('"'));
    out.replace("\\", "");
    out = "dummy={" + out + "}";

    GdbMi contents;
    contents.fromString(out);
    //qDebug() << "CONTENTS" << contents.toString(true);
    if (!contents.isValid()) {
        data.setError(strNotInScope);
        insertData(data);
        return;
    }

    setWatchDataType(data, contents.findChild("type"));
    setWatchDataValue(data, contents.findChild("value"),
        contents.findChild("valueencoded").data().toInt());
    setWatchDataAddress(data, contents.findChild("addr"));
    setWatchDataSAddress(data, contents.findChild("saddr"));
    setWatchDataChildCount(data, contents.findChild("numchild"));
    setWatchDataValueToolTip(data, contents.findChild("valuetooltip"));
    setWatchDataValueDisabled(data, contents.findChild("valuedisabled"));
    setWatchDataEditValue(data, contents.findChild("editvalue"));
    if (qq->watchHandler()->isDisplayedIName(data.iname)) {
        GdbMi editvalue = contents.findChild("editvalue");
        if (editvalue.isValid()) {
            setWatchDataEditValue(data, editvalue);
            qq->watchHandler()->showEditValue(data);
        }
    }
    if (!qq->watchHandler()->isExpandedIName(data.iname))
        data.setChildrenUnneeded();
    GdbMi children = contents.findChild("children");
    if (children.isValid() || !qq->watchHandler()->isExpandedIName(data.iname))
        data.setChildrenUnneeded();
    data.setValueUnneeded();

    // try not to repeat data too often
    WatchData childtemplate;
    setWatchDataType(childtemplate, contents.findChild("childtype"));
    setWatchDataChildCount(childtemplate, contents.findChild("childnumchild"));
    //qDebug() << "DATA: " << data.toString();
    insertData(data);
    foreach (GdbMi item, children.children()) {
        WatchData data1 = childtemplate;
        data1.name = item.findChild("name").data();
        data1.iname = data.iname + "." + data1.name;
        if (!data1.name.isEmpty() && data1.name.at(0).isDigit())
            data1.name = '[' + data1.name + ']';
        QByteArray key = item.findChild("key").data();
        if (!key.isEmpty()) {
            int encoding = item.findChild("keyencoded").data().toInt();
            QString skey = decodeData(key, encoding);
            if (skey.size() > 13) {
                skey = skey.left(12);
                skey += "...";
            }
            //data1.name += " (" + skey + ")";
            data1.name = skey;
        }
        setWatchDataType(data1, item.findChild("type"));
        setWatchDataExpression(data1, item.findChild("exp"));
        setWatchDataChildCount(data1, item.findChild("numchild"));
        setWatchDataValue(data1, item.findChild("value"),
            item.findChild("valueencoded").data().toInt());
        setWatchDataAddress(data1, item.findChild("addr"));
        setWatchDataSAddress(data1, item.findChild("saddr"));
        setWatchDataValueToolTip(data1, item.findChild("valuetooltip"));
        setWatchDataValueDisabled(data1, item.findChild("valuedisabled"));
        if (!qq->watchHandler()->isExpandedIName(data1.iname))
            data1.setChildrenUnneeded();
        //qDebug() << "HANDLE CUSTOM SUBCONTENTS:" << data1.toString();
        insertData(data1);
    }
}

void GdbEngine::handleDebuggingHelperValue3(const GdbResultRecord &record,
    const WatchData &data0)
{
    WatchData data = data0;
    QByteArray out = record.data.findChild("consolestreamoutput").data();
    while (out.endsWith(' ') || out.endsWith('\n'))
        out.chop(1);
    QList<QByteArray> list = out.split(' ');
    //qDebug() << "RECEIVED" << record.toString() << " FOR " << data0.toString()
    //    <<  " STREAM: " << out;
    if (list.isEmpty()) {
        data.setValue("<unavailable>");
        data.setAllUnneeded();
        insertData(data);
    } else if (data.type == "QString" || data.type.endsWith("::QString")) {
        QList<QByteArray> list = out.split(' ');
        QString str;
        for (int i = 0; i < list.size(); ++i)
             str.append(list.at(i).toInt());
        data.setValue('"' + str + '"');
        data.setChildCount(0);
        data.setAllUnneeded();
        insertData(data);
    } else if (data.type == "QStringList" || data.type.endsWith("::QStringList")) {
        int l = list.size();
        data.setValue(tr("<%1 items>").arg(l));
        data.setChildCount(list.size());
        data.setAllUnneeded();
        insertData(data);
        for (int i = 0; i < l; ++i) {
            WatchData data1;
            data1.name = QString("[%1]").arg(i);
            data1.type = data.type.left(data.type.size() - 4);
            data1.iname = data.iname + QString(".%1").arg(i);
            data1.addr = list.at(i);
            data1.exp = "((" + gdbQuoteTypes(data1.type) + "*)" + data1.addr + ")";
            data1.setChildCount(0);
            data1.setValueNeeded();
            QString cmd = "qdumpqstring (" + data1.exp + ")";
            QVariant var;
            var.setValue(data1);
            sendSynchronizedCommand(cmd, WatchDebuggingHelperValue3, var);
        }
    } else {
        data.setValue("<unavailable>");
        data.setAllUnneeded();
        insertData(data);
    }
}

void GdbEngine::updateLocals()
{
    m_pendingRequests = 0;

    PENDING_DEBUG("\nRESET PENDING");
    m_toolTipCache.clear();
    m_toolTipExpression.clear();
    qq->watchHandler()->reinitializeWatchers();

    int level = currentFrame();
    // '2' is 'list with type and value'
    QString cmd = QString("-stack-list-arguments 2 %1 %2").arg(level).arg(level);
    sendSynchronizedCommand(cmd, StackListArguments);                 // stage 1/2
    // '2' is 'list with type and value'
    sendSynchronizedCommand("-stack-list-locals 2", StackListLocals); // stage 2/2
}

void GdbEngine::handleStackListArguments(const GdbResultRecord &record)
{
    // stage 1/2

    // Linux:
    // 12^done,stack-args=
    //   [frame={level="0",args=[
    //     {name="argc",type="int",value="1"},
    //     {name="argv",type="char **",value="(char **) 0x7..."}]}]
    // Mac:
    // 78^done,stack-args=
    //    {frame={level="0",args={
    //      varobj=
    //        {exp="this",value="0x38a2fab0",name="var21",numchild="3",
    //             type="CurrentDocumentFind *  const",typecode="PTR",
    //             dynamic_type="",in_scope="true",block_start_addr="0x3938e946",
    //             block_end_addr="0x3938eb2d"},
    //      varobj=
    //         {exp="before",value="@0xbfffb9f8: {d = 0x3a7f2a70}",
    //              name="var22",numchild="1",type="const QString  ...} }}}
    //
    // In both cases, iterating over the children of stack-args/frame/args
    // is ok.
    m_currentFunctionArgs.clear();
    if (record.resultClass == GdbResultDone) {
        const GdbMi list = record.data.findChild("stack-args");
        const GdbMi frame = list.findChild("frame");
        const GdbMi args = frame.findChild("args");
        m_currentFunctionArgs = args.children();
    } else if (record.resultClass == GdbResultError) {
        qDebug() << "FIXME: GdbEngine::handleStackListArguments: should not happen";
    }
}

void GdbEngine::handleStackListLocals(const GdbResultRecord &record)
{
    // stage 2/2

    // There could be shadowed variables
    QList<GdbMi> locals = record.data.findChild("locals").children();
    locals += m_currentFunctionArgs;

    setLocals(locals);
}

void GdbEngine::setLocals(const QList<GdbMi> &locals)
{
    //qDebug() << m_varToType;
    QMap<QString, int> seen;

    foreach (const GdbMi &item, locals) {
        // Local variables of inlined code are reported as
        // 26^done,locals={varobj={exp="this",value="",name="var4",exp="this",
        // numchild="1",type="const QtSharedPointer::Basic<CPlusPlus::..."
        // We do not want these at all. Current hypotheses is that those
        // "spurious" locals have _two_ "exp" field. Try to filter them:
        #ifdef Q_OS_MAC
        int numExps = 0;
        foreach (const GdbMi &child, item.children())
            numExps += int(child.name() == "exp");
        if (numExps > 1)
            continue;
        QString name = item.findChild("exp").data();
        #else
        QString name = item.findChild("name").data();
        #endif
        int n = seen.value(name);
        if (n) {
            seen[name] = n + 1;
            WatchData data;
            data.iname = "local." + name + QString::number(n + 1);
            data.name = name + QString(" <shadowed %1>").arg(n);
            //data.setValue("<shadowed>");
            setWatchDataValue(data, item.findChild("value"));
            data.setType("<shadowed>");
            data.setChildCount(0);
            insertData(data);
        } else {
            seen[name] = 1;
            WatchData data;
            data.iname = "local." + name;
            data.name = name;
            data.exp = name;
            data.framekey = m_currentFrame + data.name;
            setWatchDataType(data, item.findChild("type"));
            // set value only directly if it is simple enough, otherwise
            // pass through the insertData() machinery
            if (isIntOrFloatType(data.type) || isPointerType(data.type))
                setWatchDataValue(data, item.findChild("value"));
            if (!qq->watchHandler()->isExpandedIName(data.iname))
                data.setChildrenUnneeded();
            if (isPointerType(data.type) || data.name == "this")
                data.setChildCount(1);
            if (0 && m_varToType.contains(data.framekey)) {
                qDebug() << "RE-USING " << m_varToType.value(data.framekey);
                data.setType(m_varToType.value(data.framekey));
            }
            insertData(data);
        }
    }
}

void GdbEngine::insertData(const WatchData &data0)
{
    //qDebug() << "INSERT DATA" << data0.toString();
    WatchData data = data0;
    if (data.value.startsWith("mi_cmd_var_create:")) {
        qDebug() << "BOGUS VALUE: " << data.toString();
        return;
    }
    qq->watchHandler()->insertData(data);
}

void GdbEngine::handleTypeContents(const QString &output)
{
    // output.startsWith("type = ") == true
    // "type = int"
    // "type = class QString {"
    // "type = class QStringList : public QList<QString> {"
    QString tip;
    QString className;
    if (output.startsWith("type = class")) {
        int posBrace = output.indexOf('{');
        QString head = output.mid(13, posBrace - 13 - 1);
        int posColon = head.indexOf(": public");
        if (posColon == -1)
            posColon = head.indexOf(": protected");
        if (posColon == -1)
            posColon = head.indexOf(": private");
        if (posColon == -1) {
            className = head;
            tip = "class " + className + " { ... }";
        } else {
            className = head.left(posColon - 1);
            tip = "class " + head + " { ... }";
        }
        //qDebug() << "posColon: " << posColon;
        //qDebug() << "posBrace: " << posBrace;
        //qDebug() << "head: " << head;
    } else {
        className = output.mid(7);
        tip = className;
    }
    //qDebug() << "output: " << output.left(100) + "...";
    //qDebug() << "className: " << className;
    //qDebug() << "tip: " << tip;
    //m_toolTip.type = className;
    m_toolTip.type.clear();
    m_toolTip.value = tip;
}

void GdbEngine::handleVarListChildrenHelper(const GdbMi &item,
    const WatchData &parent)
{
    //qDebug() <<  "VAR_LIST_CHILDREN: PARENT 2" << parent.toString();
    //qDebug() <<  "VAR_LIST_CHILDREN: APPENDEE " << data.toString();
    QByteArray exp = item.findChild("exp").data();
    QByteArray name = item.findChild("name").data();
    if (isAccessSpecifier(exp)) {
        // suppress 'private'/'protected'/'public' level
        WatchData data;
        data.variable = name;
        data.iname = parent.iname;
        //data.iname = data.variable;
        data.exp = parent.exp;
        data.setTypeUnneeded();
        data.setValueUnneeded();
        data.setChildCountUnneeded();
        data.setChildrenUnneeded();
        //qDebug() << "DATA" << data.toString();
        QString cmd = "-var-list-children --all-values \"" + data.variable + "\"";
        //iname += '.' + exp;
        sendSynchronizedCommand(cmd, WatchVarListChildren, QVariant::fromValue(data));
    } else if (item.findChild("numchild").data() == "0") {
        // happens for structs without data, e.g. interfaces.
        WatchData data;
        data.iname = parent.iname + '.' + exp;
        data.name = exp;
        data.variable = name;
        setWatchDataType(data, item.findChild("type"));
        setWatchDataValue(data, item.findChild("value"));
        setWatchDataAddress(data, item.findChild("addr"));
        setWatchDataSAddress(data, item.findChild("saddr"));
        data.setChildCount(0);
        insertData(data);
    } else if (parent.iname.endsWith('.')) {
        // Happens with anonymous unions
        WatchData data;
        data.iname = name;
        QString cmd = "-var-list-children --all-values \"" + data.variable + "\"";
        sendSynchronizedCommand(cmd, WatchVarListChildren, QVariant::fromValue(data));
    } else if (exp == "staticMetaObject") {
        //    && item.findChild("type").data() == "const QMetaObject")
        // FIXME: Namespaces?
        // { do nothing }    FIXME: make coinfigurable?
        // special "clever" hack to avoid clutter in the GUI.
        // I am not sure this is a good idea...
    } else {
        WatchData data;
        data.iname = parent.iname + '.' + exp;
        data.variable = name;
        setWatchDataType(data, item.findChild("type"));
        setWatchDataValue(data, item.findChild("value"));
        setWatchDataAddress(data, item.findChild("addr"));
        setWatchDataSAddress(data, item.findChild("saddr"));
        setWatchDataChildCount(data, item.findChild("numchild"));
        if (!qq->watchHandler()->isExpandedIName(data.iname))
            data.setChildrenUnneeded();

        data.name = exp;
        if (isPointerType(parent.type) && data.type == exp) {
            data.exp = "*(" + parent.exp + ")";
            data.name = "*" + parent.name;
        } else if (data.type == exp) {
            // A type we derive from? gdb crashes when creating variables here
            data.exp = parent.exp;
        } else if (exp.startsWith("*")) {
            // A pointer
            data.exp = "*(" + parent.exp + ")";
        } else if (startsWithDigit(exp)) {
            // An array. No variables needed?
            data.name = "[" + data.name + "]";
            data.exp = parent.exp + "[" + exp + "]";
        } else if (0 && parent.name.endsWith('.')) {
            // Happens with anonymous unions
            data.exp = parent.exp + exp;
            //data.name = "<anonymous union>";
        } else if (exp.isEmpty()) {
            // Happens with anonymous unions
            data.exp = parent.exp;
            data.name = "<n/a>";
            data.iname = parent.iname + ".@";
            data.type = "<anonymous union>";
        } else {
            // A structure. Hope there's nothing else...
            data.exp = parent.exp + '.' + exp;
        }

        if (hasDebuggingHelperForType(data.type)) {
            // we do not trust gdb if we have a custom dumper
            data.setValueNeeded();
            data.setChildCountNeeded();
        }

        //qDebug() <<  "VAR_LIST_CHILDREN: PARENT 3" << parent.toString();
        //qDebug() <<  "VAR_LIST_CHILDREN: APPENDEE " << data.toString();
        insertData(data);
    }
}

void GdbEngine::handleVarListChildren(const GdbResultRecord &record,
    const WatchData &data0)
{
    //WatchResultCounter dummy(this, WatchVarListChildren);
    WatchData data = data0;
    if (!data.isValid())
        return;
    if (record.resultClass == GdbResultDone) {
        //qDebug() <<  "VAR_LIST_CHILDREN: PARENT " << data.toString();
        GdbMi children = record.data.findChild("children");

        foreach (const GdbMi &child, children.children())
            handleVarListChildrenHelper(child, data);

        if (children.children().isEmpty()) {
            // happens e.g. if no debug information is present or
            // if the class really has no children
            WatchData data1;
            data1.iname = data.iname + ".child";
            data1.value = tr("<no information>");
            data1.childCount = 0;
            data1.setAllUnneeded();
            insertData(data1);
            data.setAllUnneeded();
            insertData(data);
        } else if (!isAccessSpecifier(data.variable.split('.').takeLast())) {
            data.setChildrenUnneeded();
            insertData(data);
        } else {
            // this skips the spurious "public", "private" etc levels
            // gdb produces
        }
    } else if (record.resultClass == GdbResultError) {
        data.setError(record.data.findChild("msg").data());
    } else {
        data.setError("Unknown error: " + record.toString());
    }
}

void GdbEngine::handleToolTip(const GdbResultRecord &record,
        const QString &what)
{
    //qDebug() << "HANDLE TOOLTIP: " << what << m_toolTip.toString();
    //    << "record: " << record.toString();
    if (record.resultClass == GdbResultError) {
        QString msg = record.data.findChild("msg").data();
        if (what == "create") {
            sendCommand("ptype " + m_toolTip.exp, WatchToolTip, "ptype");
            return;
        }
        if (what == "evaluate") {
            if (msg.startsWith("Cannot look up value of a typedef")) {
                m_toolTip.value = m_toolTip.exp + " is a typedef.";
                //return;
            }
        }
    } else if (record.resultClass == GdbResultDone) {
        if (what == "create") {
            setWatchDataType(m_toolTip, record.data.findChild("type"));
            setWatchDataChildCount(m_toolTip, record.data.findChild("numchild"));
            if (hasDebuggingHelperForType(m_toolTip.type))
                runDebuggingHelper(m_toolTip, false);
            else
                q->showStatusMessage(tr("Retrieving data for tooltip..."), 10000);
                sendCommand("-data-evaluate-expression " + m_toolTip.exp,
                    WatchToolTip, "evaluate");
                //sendToolTipCommand("-var-evaluate-expression tooltip")
            return;
        }
        if (what == "evaluate") {
            m_toolTip.value = m_toolTip.type + ' ' + m_toolTip.exp
                   + " = " + record.data.findChild("value").data();
            //return;
        }
        if (what == "ptype") {
            GdbMi mi = record.data.findChild("consolestreamoutput");
            m_toolTip.value = extractTypeFromPTypeOutput(mi.data());
            //return;
        }
    }

    m_toolTip.iname = tooltipIName;
    m_toolTip.setChildrenUnneeded();
    m_toolTip.setChildCountUnneeded();
    insertData(m_toolTip);
    qDebug() << "DATA INSERTED";
    QTimer::singleShot(0, this, SLOT(updateWatchModel2()));
    qDebug() << "HANDLE TOOLTIP END";
}

#if 0
void GdbEngine::handleChangedItem(QStandardItem *item)
{
    // HACK: Just store the item for the slot
    //  handleChangedItem(QWidget *widget) below.
    QModelIndex index = item->index().sibling(item->index().row(), 0);
    //WatchData data = m_currentSet.takeData(iname);
    //m_editedData = inameFromItem(m_model.itemFromIndex(index)).exp;
    //qDebug() << "HANDLE CHANGED EXPRESSION: " << m_editedData;
}
#endif

void GdbEngine::assignValueInDebugger(const QString &expression, const QString &value)
{
    sendCommand("-var-delete assign");
    sendCommand("-var-create assign * " + expression);
    sendCommand("-var-assign assign " + value, WatchVarAssign);
}

QString GdbEngine::dumperLibraryName() const
{
    if (theDebuggerAction(UseCustomDebuggingHelperLocation)->value().toBool())
        return theDebuggerAction(CustomDebuggingHelperLocation)->value().toString();
    return q->m_dumperLib;
}

void GdbEngine::showDebuggingHelperWarning()
{
    QMessageBox dialog(q->mainWindow());
    QPushButton *qtPref = dialog.addButton(tr("Open Qt preferences"), QMessageBox::ActionRole);
    QPushButton *helperOff = dialog.addButton(tr("Turn helper usage off"), QMessageBox::ActionRole);
    QPushButton *justContinue = dialog.addButton(tr("Continue anyway"), QMessageBox::AcceptRole);
    dialog.setDefaultButton(justContinue);
    dialog.setWindowTitle(tr("Debugging helper missing"));
    dialog.setText(tr("The debugger did not find the debugging helper library."));
    dialog.setInformativeText(tr("The debugging helper is used to nicely format the values of Qt "
                                 "data types and some STL data types. "
                                 "It must be compiled for each Qt version, "
                                 "you can do this in the Qt preferences page by selecting "
                                 "a Qt installation and clicking on 'Rebuild' for the debugging "
                                 "helper."));
    dialog.exec();
    if (dialog.clickedButton() == qtPref) {
        Core::ICore::instance()->showOptionsDialog("Qt4", "Qt Versions");
    } else if (dialog.clickedButton() == helperOff) {
        theDebuggerAction(UseDebuggingHelpers)->setValue(qVariantFromValue(false), false);
    }
}

void GdbEngine::tryLoadDebuggingHelpers()
{
    if (m_debuggingHelperState != DebuggingHelperUninitialized)
        return;

    PENDING_DEBUG("TRY LOAD CUSTOM DUMPERS");
    m_debuggingHelperState = DebuggingHelperUnavailable;
    QString lib = dumperLibraryName();
    //qDebug() << "DUMPERLIB: " << lib;

    if (!QFileInfo(lib).exists()) {
        debugMessage(QString("DEBUG HELPER LIBRARY IS NOT USABLE: "
            " %1  EXISTS: %2, EXECUTABLE: %3").arg(lib)
            .arg(QFileInfo(lib).exists())
            .arg(QFileInfo(lib).isExecutable()));
        if (theDebuggerBoolSetting(UseDebuggingHelpers))
            showDebuggingHelperWarning();
        return;
    }

    m_debuggingHelperState = DebuggingHelperLoadTried;
#if defined(Q_OS_WIN)
    sendCommand("sharedlibrary .*"); // for LoadLibraryA
    //sendCommand("handle SIGSEGV pass stop print");
    //sendCommand("set unwindonsignal off");
    sendCommand("call LoadLibraryA(\"" + lib + "\")",
        WatchDebuggingHelperSetup);
    sendCommand("sharedlibrary " + dotEscape(lib));
#elif defined(Q_OS_MAC)
    //sendCommand("sharedlibrary libc"); // for malloc
    //sendCommand("sharedlibrary libdl"); // for dlopen
    QString flag = QString::number(RTLD_NOW);
    sendCommand("call (void)dlopen(\"" + lib + "\", " + flag + ")",
        WatchDebuggingHelperSetup);
    //sendCommand("sharedlibrary " + dotEscape(lib));
    m_debuggingHelperState = DebuggingHelperLoadTried;
#else
    //sendCommand("p dlopen");
    QString flag = QString::number(RTLD_NOW);
    sendCommand("sharedlibrary libc"); // for malloc
    sendCommand("sharedlibrary libdl"); // for dlopen
    sendCommand("call (void*)dlopen(\"" + lib + "\", " + flag + ")",
        WatchDebuggingHelperSetup);
    // some older systems like CentOS 4.6 prefer this:
    sendCommand("call (void*)__dlopen(\"" + lib + "\", " + flag + ")",
        WatchDebuggingHelperSetup);
    sendCommand("sharedlibrary " + dotEscape(lib));
#endif
    // retreive list of dumpable classes
    sendCommand("call (void*)qDumpObjectData440(1,%1+1,0,0,0,0,0,0)");
    sendCommand("p (char*)&qDumpOutBuffer", GdbQueryDebuggingHelper);
}

void GdbEngine::recheckDebuggingHelperAvailability()
{
    // retreive list of dumpable classes
    sendCommand("call (void*)qDumpObjectData440(1,%1+1,0,0,0,0,0,0)");
    sendCommand("p (char*)&qDumpOutBuffer", GdbQueryDebuggingHelper);
}

IDebuggerEngine *createGdbEngine(DebuggerManager *parent)
{
    return new GdbEngine(parent);
}
