Files
qt-creator/src/plugins/debugger/gdbengine.cpp
Friedemann Kleint 3695ee3646 Start on dumper support for CDB, bug fixes.
Make command execution output shown, use Wide version
of Debug Callbacks and output, provide bases classes
for other output and event handlers, move common
dumper functionality into debugger manager.
2009-04-20 16:40:50 +02:00

4196 lines
144 KiB
C++

/**************************************************************************
**
** 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 "gdboptionspage.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(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();
}
QList<Symbol> GdbEngine::moduleSymbols(const QString &moduleName)
{
QList<Symbol> rc;
bool success = false;
QString errorMessage;
do {
const QString nmBinary = QLatin1String("nm");
QProcess proc;
proc.start(nmBinary, QStringList() << QLatin1String("-D") << moduleName);
if (!proc.waitForFinished()) {
errorMessage = tr("Unable to run '%1': %2").arg(nmBinary, proc.errorString());
break;
}
const QString contents = QString::fromLocal8Bit(proc.readAllStandardOutput());
const QRegExp re(QLatin1String("([0-9a-f]+)?\\s+([^\\s]+)\\s+([^\\s]+)"));
Q_ASSERT(re.isValid());
foreach (const QString &line, contents.split(QLatin1Char('\n'))) {
if (re.indexIn(line) != -1) {
Symbol symbol;
symbol.address = re.cap(1);
symbol.state = re.cap(2);
symbol.name = re.cap(3);
rc.push_back(symbol);
} else {
qWarning("moduleSymbols: unhandled: %s", qPrintable(line));
}
}
success = true;
} while (false);
if (!success)
qWarning("moduleSymbols: %s\n", qPrintable(errorMessage));
return rc;
}
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
//
//////////////////////////////////////////////////////////////////////
static inline char registerFormatChar()
{
switch(checkedRegisterFormatAction()) {
case FormatHexadecimal:
return 'x';
case FormatDecimal:
return 'd';
case FormatOctal:
return 'o';
case FormatBinary:
return 't';
case FormatRaw:
return 'r';
default:
break;
}
return 'N';
}
void GdbEngine::reloadRegisters()
{
sendCommand(QLatin1String("-data-list-register-values ") + QLatin1Char(registerFormatChar()), 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("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);
}
void GdbEngine::tryLoadDebuggingHelpers()
{
if (m_debuggingHelperState != DebuggingHelperUninitialized)
return;
PENDING_DEBUG("TRY LOAD CUSTOM DUMPERS");
m_debuggingHelperState = DebuggingHelperUnavailable;
if (!q->qtDumperLibraryEnabled())
return;
const QString lib = q->qtDumperLibraryName();
//qDebug() << "DUMPERLIB: " << lib;
// @TODO: same in CDB engine...
const QFileInfo fi(lib);
if (!fi.exists()) {
const QString msg = tr("The dumper library '%1' does not exist.").arg(lib);
debugMessage(msg);
q->showQtDumperLibraryWarning(msg);
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 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 qDumpObjectData440(1,%1+1,0,0,0,0,0,0)");
sendCommand("p (char*)&qDumpOutBuffer", GdbQueryDebuggingHelper);
}
IDebuggerEngine *createGdbEngine(DebuggerManager *parent, QList<Core::IOptionsPage*> *opts)
{
opts->push_back(new GdbOptionsPage);
return new GdbEngine(parent);
}