Files
qt-creator/src/plugins/debugger/gdb/gdbengine.cpp

4300 lines
156 KiB
C++
Raw Normal View History

/**************************************************************************
2008-12-02 12:01:29 +01:00
**
** This file is part of Qt Creator
**
2010-03-05 11:25:49 +01:00
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
2008-12-02 12:01:29 +01:00
**
** Contact: Nokia Corporation (qt-info@nokia.com)
2008-12-02 12:01:29 +01:00
**
** 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
2009-08-14 09:30:56 +02:00
** contact the sales department at http://qt.nokia.com/contact.
2008-12-02 12:01:29 +01:00
**
**************************************************************************/
2008-12-02 12:01:29 +01:00
2009-04-30 22:22:37 +02:00
#define QT_NO_CAST_FROM_ASCII
2008-12-02 12:01:29 +01:00
#include "gdbengine.h"
#include "gdboptionspage.h"
2010-03-22 16:17:06 +01:00
#include "debuggeruiswitcher.h"
#include "debuggermainwindow.h"
#include "attachgdbadapter.h"
#include "coregdbadapter.h"
#include "localplaingdbadapter.h"
#include "termgdbadapter.h"
#include "remotegdbserveradapter.h"
#include "remoteplaingdbadapter.h"
#include "trkgdbadapter.h"
2008-12-02 12:01:29 +01:00
#include "watchutils.h"
#include "debuggeractions.h"
#include "debuggeragents.h"
2008-12-02 12:01:29 +01:00
#include "debuggerconstants.h"
#include "debuggermanager.h"
#include "debuggertooltip.h"
#include "debuggerstringutils.h"
2008-12-02 12:01:29 +01:00
#include "gdbmi.h"
#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "snapshothandler.h"
2008-12-02 12:01:29 +01:00
#include "stackhandler.h"
#include "watchhandler.h"
#include "sourcefileswindow.h"
2008-12-02 12:01:29 +01:00
#include "debuggerdialogs.h"
2008-12-02 12:01:29 +01:00
2008-12-09 16:18:28 +01:00
#include <utils/qtcassert.h>
#include <utils/fancymainwindow.h>
#include <texteditor/itexteditor.h>
#include <projectexplorer/toolchain.h>
#include <coreplugin/icore.h>
2010-03-22 16:17:06 +01:00
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/progressmanager/futureprogress.h>
2008-12-09 16:18:28 +01:00
#include <QtCore/QCoreApplication>
2008-12-02 12:01:29 +01:00
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QMetaObject>
2008-12-02 12:01:29 +01:00
#include <QtCore/QTime>
#include <QtCore/QTimer>
#include <QtCore/QTemporaryFile>
#include <QtCore/QTextStream>
2008-12-02 12:01:29 +01:00
#include <QtGui/QAction>
#include <QtGui/QDialogButtonBox>
2008-12-02 12:01:29 +01:00
#include <QtGui/QLabel>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
#include <QtGui/QPushButton>
2008-12-02 12:01:29 +01:00
#ifdef Q_OS_UNIX
2008-12-02 12:01:29 +01:00
#include <unistd.h>
#include <dlfcn.h>
#endif
#include <ctype.h>
2008-12-02 12:01:29 +01:00
namespace Debugger {
namespace Internal {
2008-12-02 12:01:29 +01:00
//#define DEBUG_PENDING 1
static const char winPythonVersionC[] = "python2.5";
2008-12-02 12:01:29 +01:00
#if DEBUG_PENDING
# define PENDING_DEBUG(s) qDebug() << s
#else
2009-02-16 18:56:20 +01:00
# define PENDING_DEBUG(s)
2008-12-02 12:01:29 +01:00
#endif
#define PENDING_DEBUGX(s) qDebug() << s
2008-12-02 12:01:29 +01:00
#define CB(callback) &GdbEngine::callback, STRINGIFY(callback)
QByteArray GdbEngine::tooltipINameForExpression(const QByteArray &exp)
{
// FIXME: 'exp' can contain illegal characters
//return "tooltip." + exp;
Q_UNUSED(exp)
return "tooltip.x";
}
static bool stateAcceptsGdbCommands(DebuggerState state)
2009-09-22 09:27:19 +02:00
{
switch (state) {
case AdapterStarting:
case AdapterStarted:
case AdapterStartFailed:
case InferiorUnrunnable:
case InferiorStarting:
case InferiorStartFailed:
case InferiorRunningRequested:
case InferiorRunningRequested_Kill:
case InferiorRunning:
case InferiorStopping:
case InferiorStopping_Kill:
case InferiorStopped:
case InferiorShuttingDown:
case InferiorShutDown:
case InferiorShutdownFailed:
return true;
case DebuggerNotReady:
case EngineStarting:
case InferiorStopFailed:
case EngineShuttingDown:
break;
}
return false;
}
2009-09-22 09:27:19 +02:00
2008-12-02 12:01:29 +01:00
static int &currentToken()
{
static int token = 0;
return token;
}
static QByteArray parsePlainConsoleStream(const GdbResponse &response)
{
GdbMi output = response.data.findChild("consolestreamoutput");
QByteArray out = output.data();
// FIXME: proper decoding needed
if (out.endsWith("\\n"))
out.chop(2);
while (out.endsWith('\n') || out.endsWith(' '))
out.chop(1);
int pos = out.indexOf(" = ");
return out.mid(pos + 3);
}
2008-12-02 12:01:29 +01:00
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////
GdbEngine::GdbEngine(DebuggerManager *manager) :
IDebuggerEngine(manager),
m_gdbBinaryToolChainMap(DebuggerSettings::instance()->gdbBinaryToolChainMap())
2008-12-02 12:01:29 +01:00
{
2009-09-21 11:09:38 +02:00
m_gdbAdapter = 0;
2010-03-22 16:17:06 +01:00
m_progress = 0;
m_commandTimer = new QTimer(this);
m_commandTimer->setSingleShot(true);
connect(m_commandTimer, SIGNAL(timeout()), SLOT(commandTimeout()));
// Needs no resetting in initializeVariables()
m_busy = false;
connect(theDebuggerAction(AutoDerefPointers), SIGNAL(valueChanged(QVariant)),
this, SLOT(setAutoDerefPointers(QVariant)));
connect(theDebuggerAction(CreateFullBacktrace), SIGNAL(triggered()),
this, SLOT(createFullBacktrace()));
}
void GdbEngine::connectDebuggingHelperActions()
{
connect(theDebuggerAction(UseDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
this, SLOT(setUseDebuggingHelpers(QVariant)));
connect(theDebuggerAction(DebugDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
this, SLOT(setDebugDebuggingHelpersClassic(QVariant)));
connect(theDebuggerAction(RecheckDebuggingHelpers), SIGNAL(triggered()),
this, SLOT(recheckDebuggingHelperAvailabilityClassic()));
}
void GdbEngine::disconnectDebuggingHelperActions()
{
disconnect(theDebuggerAction(UseDebuggingHelpers), 0, this, 0);
disconnect(theDebuggerAction(DebugDebuggingHelpers), 0, this, 0);
disconnect(theDebuggerAction(RecheckDebuggingHelpers), 0, this, 0);
}
DebuggerStartMode GdbEngine::startMode() const
{
QTC_ASSERT(!m_startParameters.isNull(), return NoStartMode);
return m_startParameters->startMode;
2008-12-02 12:01:29 +01:00
}
QMainWindow *GdbEngine::mainWindow() const
{
return DebuggerUISwitcher::instance()->mainWindow();
}
AbstractGdbProcess *GdbEngine::gdbProc() const
{
return m_gdbAdapter->gdbProc();
}
2008-12-02 12:01:29 +01:00
GdbEngine::~GdbEngine()
{
// Prevent sending error messages afterwards.
if (m_gdbAdapter)
disconnect(gdbProc(), 0, this, 0);
2009-10-05 14:59:39 +02:00
delete m_gdbAdapter;
m_gdbAdapter = 0;
2009-09-21 11:09:38 +02:00
}
void GdbEngine::connectAdapter()
2008-12-02 12:01:29 +01:00
{
2009-09-21 11:09:38 +02:00
connect(m_gdbAdapter, SIGNAL(adapterStarted()),
this, SLOT(handleAdapterStarted()));
connect(m_gdbAdapter, SIGNAL(adapterStartFailed(QString,QString)),
this, SLOT(handleAdapterStartFailed(QString,QString)));
connect(m_gdbAdapter, SIGNAL(inferiorPrepared()),
this, SLOT(handleInferiorPrepared()));
2009-09-21 11:09:38 +02:00
connect(m_gdbAdapter, SIGNAL(inferiorStartFailed(QString)),
this, SLOT(handleInferiorStartFailed(QString)));
connect(m_gdbAdapter, SIGNAL(adapterCrashed(QString)),
this, SLOT(handleAdapterCrashed(QString)));
}
void GdbEngine::initializeVariables()
{
m_debuggingHelperState = DebuggingHelperUninitialized;
m_gdbVersion = 100;
2009-02-23 16:06:23 +01:00
m_gdbBuildVersion = -1;
m_isMacGdb = false;
m_hasPython = false;
m_registerNamesListed = false;
m_fullToShortName.clear();
m_shortToFullName.clear();
invalidateSourcesList();
m_sourcesListUpdating = false;
m_oldestAcceptableToken = -1;
m_outputCodec = QTextCodec::codecForLocale();
m_pendingWatchRequests = 0;
m_pendingBreakpointRequests = 0;
m_commandsDoneCallback = 0;
2009-02-16 18:28:15 +01:00
m_commandsToRunOnTemporaryBreak.clear();
m_cookieForToken.clear();
m_pendingConsoleStreamOutput.clear();
m_pendingLogStreamOutput.clear();
m_inbuffer.clear();
m_commandTimer->stop();
2009-10-05 17:56:24 +02:00
// ConverterState has no reset() function.
m_outputCodecState.~ConverterState();
new (&m_outputCodecState) QTextCodec::ConverterState();
m_currentFunctionArgs.clear();
m_currentFrame.clear();
m_dumperHelper.clear();
#ifdef Q_OS_LINUX
m_entryPoint.clear();
#endif
2010-03-22 16:17:06 +01:00
delete m_progress;
m_progress = 0;
}
QString GdbEngine::errorMessage(QProcess::ProcessError error)
2008-12-02 12:01:29 +01:00
{
switch (error) {
case QProcess::FailedToStart:
return tr("The Gdb process failed to start. Either the "
2008-12-02 12:01:29 +01:00
"invoked program '%1' is missing, or you may have insufficient "
"permissions to invoke the program.")
.arg(m_gdb);
2008-12-02 12:01:29 +01:00
case QProcess::Crashed:
return tr("The Gdb process crashed some time after starting "
2008-12-02 12:01:29 +01:00
"successfully.");
case QProcess::Timedout:
return tr("The last waitFor...() function timed out. "
2008-12-02 12:01:29 +01:00
"The state of QProcess is unchanged, and you can try calling "
"waitFor...() again.");
case QProcess::WriteError:
return tr("An error occurred when attempting to write "
2008-12-02 12:01:29 +01:00
"to the Gdb process. For example, the process may not be running, "
"or it may have closed its input channel.");
case QProcess::ReadError:
return tr("An error occurred when attempting to read from "
2008-12-02 12:01:29 +01:00
"the Gdb process. For example, the process may not be running.");
default:
return tr("An unknown error in the Gdb process occurred. ");
2008-12-02 12:01:29 +01:00
}
}
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
QByteArray ba(first, middle - first);
Q_UNUSED(to)
2008-12-02 12:01:29 +01:00
// 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)
{
m_manager->messageAvailable(m_outputCodec->toUnicode(
data.constData(), data.length(), &m_outputCodecState), true);
}
2009-02-16 13:29:57 +01:00
void GdbEngine::debugMessage(const QString &msg)
{
showDebuggerOutput(LogDebug, msg);
2009-02-16 13:29:57 +01:00
}
void GdbEngine::handleResponse(const QByteArray &buff)
2008-12-02 12:01:29 +01:00
{
static QTime lastTime;
if (theDebuggerBoolSetting(LogTimeStamps))
showDebuggerOutput(LogTime, currentTime());
showDebuggerOutput(LogOutput, QString::fromLocal8Bit(buff, buff.length()));
2008-12-02 12:01:29 +01:00
#if 0
qDebug() // << "#### start response handling #### "
<< currentTime()
<< lastTime.msecsTo(QTime::currentTime()) << "ms,"
<< "buf:" << buff.left(1500) << "..."
//<< "buf:" << buff
<< "size:" << buff.size();
2008-12-02 12:01:29 +01:00
#else
//qDebug() << "buf:" << buff;
2008-12-02 12:01:29 +01:00
#endif
lastTime = QTime::currentTime();
if (buff.isEmpty() || buff == "(gdb) ")
return;
2008-12-02 12:01:29 +01:00
const char *from = buff.constData();
const char *to = from + buff.size();
const char *inner;
2008-12-02 12:01:29 +01:00
int token = -1;
// token is a sequence of numbers
for (inner = from; inner != to; ++inner)
if (*inner < '0' || *inner > '9')
2008-12-02 12:01:29 +01:00
break;
if (from != inner) {
token = QByteArray(from, inner - from).toInt();
from = inner;
//qDebug() << "found token" << token;
}
2008-12-02 12:01:29 +01:00
// next char decides kind of response
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;
2008-12-02 12:01:29 +01:00
GdbMi result;
while (from != to) {
GdbMi data;
2009-04-07 14:14:43 +02:00
if (*from != ',') {
// happens on archer where we get
// 23^running <NL> *running,thread-id="all" <NL> (gdb)
result.m_type = GdbMi::Tuple;
2009-04-07 14:14:43 +02:00
break;
}
++from; // skip ','
data.parseResultOrValue(from, to);
if (data.isValid()) {
//qDebug() << "parsed result:" << data.toString();
result.m_children += data;
result.m_type = GdbMi::Tuple;
2008-12-02 12:01:29 +01:00
}
}
if (asyncClass == "stopped") {
handleStopResponse(result);
m_pendingLogStreamOutput.clear();
m_pendingConsoleStreamOutput.clear();
} 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"
QByteArray id = result.findChild("id").data();
if (!id.isEmpty())
showStatusMessage(tr("Library %1 loaded").arg(_(id)), 1000);
2010-03-22 16:17:06 +01:00
int progress = m_progress->progressValue();
m_progress->setProgressValue(qMin(70, progress + 1));
invalidateSourcesList();
} 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"
QByteArray id = result.findChild("id").data();
showStatusMessage(tr("Library %1 unloaded").arg(_(id)), 1000);
invalidateSourcesList();
} else if (asyncClass == "thread-group-added") {
// 7.1-symbianelf has "{id="i1"}"
} else if (asyncClass == "thread-group-created"
|| asyncClass == "thread-group-started") {
// Archer had only "{id="28902"}" at some point of 6.8.x.
// *-started seems to be standard in 7.1, but in early
// 7.0.x, there was a *-created instead.
2010-03-22 16:17:06 +01:00
int progress = m_progress->progressValue();
m_progress->setProgressValue(qMin(70, progress + 1));
QByteArray id = result.findChild("id").data();
showStatusMessage(tr("Thread group %1 created").arg(_(id)), 1000);
int pid = id.toInt();
if (pid != inferiorPid())
handleInferiorPidChanged(pid);
} else if (asyncClass == "thread-created") {
//"{id="1",group-id="28902"}"
QByteArray id = result.findChild("id").data();
showStatusMessage(tr("Thread %1 created").arg(_(id)), 1000);
} else if (asyncClass == "thread-group-exited") {
// Archer has "{id="28902"}"
QByteArray id = result.findChild("id").data();
showStatusMessage(tr("Thread group %1 exited").arg(_(id)), 1000);
} else if (asyncClass == "thread-exited") {
//"{id="1",group-id="28902"}"
QByteArray id = result.findChild("id").data();
QByteArray groupid = result.findChild("group-id").data();
showStatusMessage(tr("Thread %1 in group %2 exited")
.arg(_(id)).arg(_(groupid)), 1000);
2009-04-07 09:51:21 +02:00
} else if (asyncClass == "thread-selected") {
QByteArray id = result.findChild("id").data();
showStatusMessage(tr("Thread %1 selected").arg(_(id)), 1000);
//"{id="2"}"
} else if (m_isMacGdb && asyncClass == "shlibs-updated") {
// Apple's gdb announces updated libs.
invalidateSourcesList();
} else if (m_isMacGdb && asyncClass == "shlibs-added") {
// Apple's gdb 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=""}}
invalidateSourcesList();
} else if (m_isMacGdb && asyncClass == "resolve-pending-breakpoint") {
// Apple's gdb announces resolved breakpoints.
// new_bp="1",pended_bp="1",new_expr="\"gdbengine.cpp\":1584",
// bkpt={number="1",type="breakpoint",disp="keep",enabled="y",
// addr="0x0000000115cc3ddf",func="foo()",file="../foo.cpp",
// line="1584",shlib="/../libFoo_debug.dylib",times="0"}
const GdbMi bkpt = result.findChild("bkpt");
const int number = bkpt.findChild("number").data().toInt();
BreakHandler *handler = manager()->breakHandler();
BreakpointData *data = handler->findBreakpoint(number);
breakpointDataFromOutput(data, bkpt);
handler->updateMarkers();
} else {
qDebug() << "IGNORED ASYNC OUTPUT"
<< asyncClass << result.toString();
}
break;
}
case '~': {
QByteArray data = GdbMi::parseCString(from, to);
m_pendingConsoleStreamOutput += data;
// Parse pid from noise.
if (!inferiorPid()) {
// Linux/Mac gdb: [New [Tt]hread 0x545 (LWP 4554)]
static QRegExp re1(_("New .hread 0x[0-9a-f]+ \\(LWP ([0-9]*)\\)"));
// MinGW 6.8: [New thread 2437.0x435345]
static QRegExp re2(_("New .hread ([0-9]+)\\.0x[0-9a-f]*"));
2009-10-08 16:14:25 +02:00
// Mac: [Switching to process 9294 local thread 0x2e03] or
// [Switching to process 31773]
static QRegExp re3(_("Switching to process ([0-9]+)"));
QTC_ASSERT(re1.isValid() && re2.isValid(), return);
if (re1.indexIn(_(data)) != -1)
maybeHandleInferiorPidChanged(re1.cap(1));
else if (re2.indexIn(_(data)) != -1)
maybeHandleInferiorPidChanged(re2.cap(1));
else if (re3.indexIn(_(data)) != -1)
maybeHandleInferiorPidChanged(re3.cap(1));
}
// Show some messages to give the impression something happens.
2009-10-22 20:04:59 +02:00
if (data.startsWith("Reading symbols from ")) {
showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))), 1000);
invalidateSourcesList();
2009-10-22 20:04:59 +02:00
} else if (data.startsWith("[New ") || data.startsWith("[Thread ")) {
if (data.endsWith('\n'))
data.chop(1);
showStatusMessage(_(data), 1000);
2009-10-22 20:04:59 +02:00
}
break;
}
case '@': {
readDebugeeOutput(GdbMi::parseCString(from, to));
break;
2008-12-02 12:01:29 +01:00
}
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:"))
manager()->messageAvailable(_(data.mid(9)), true); // cut "warning: "
2008-12-02 12:01:29 +01:00
break;
}
case '^': {
GdbResponse response;
2008-12-02 12:01:29 +01:00
response.token = token;
2008-12-02 12:01:29 +01:00
for (inner = from; inner != to; ++inner)
if (*inner < 'a' || *inner > 'z')
break;
2008-12-02 12:01:29 +01:00
2009-05-04 18:22:13 +02:00
QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
if (resultClass == "done") {
response.resultClass = GdbResultDone;
} else if (resultClass == "running") {
2010-03-22 16:17:06 +01:00
if (m_progress) {
m_progress->setProgressValue(100);
m_progress->reportFinished();
}
if (state() == InferiorStopped) { // Result of manual command.
m_manager->resetLocation();
setTokenBarrier();
setState(InferiorRunningRequested);
}
setState(InferiorRunning);
showStatusMessage(tr("Running..."));
response.resultClass = GdbResultRunning;
} else if (resultClass == "connected") {
response.resultClass = GdbResultConnected;
} else if (resultClass == "error") {
response.resultClass = GdbResultError;
} else if (resultClass == "exit") {
response.resultClass = GdbResultExit;
} else {
response.resultClass = GdbResultUnknown;
}
2008-12-02 12:01:29 +01:00
from = inner;
if (from != to) {
2009-04-07 09:51:21 +02:00
if (*from == ',') {
++from;
response.data.parseTuple_helper(from, to);
response.data.m_type = GdbMi::Tuple;
response.data.m_name = "data";
2009-04-07 09:51:21 +02:00
} else {
// Archer has this
response.data.m_type = GdbMi::Tuple;
response.data.m_name = "data";
2008-12-02 12:01:29 +01:00
}
}
//qDebug() << "\nLOG STREAM:" + m_pendingLogStreamOutput;
//qDebug() << "\nCONSOLE STREAM:" + m_pendingConsoleStreamOutput;
response.data.setStreamOutput("logstreamoutput",
m_pendingLogStreamOutput);
response.data.setStreamOutput("consolestreamoutput",
m_pendingConsoleStreamOutput);
m_pendingLogStreamOutput.clear();
m_pendingConsoleStreamOutput.clear();
2009-10-23 21:32:08 +02:00
handleResultRecord(&response);
break;
}
default: {
qDebug() << "UNKNOWN RESPONSE TYPE" << c;
break;
2008-12-02 12:01:29 +01:00
}
}
}
void GdbEngine::readGdbStandardError()
{
QByteArray err = gdbProc()->readAllStandardError();
debugMessage(_("UNEXPECTED GDB STDERR: " + err));
if (err == "Undefined command: \"bb\". Try \"help\".\n")
return;
if (err.startsWith("BFD: reopening"))
return;
qWarning() << "Unexpected gdb stderr:" << err;
2008-12-02 12:01:29 +01:00
}
void GdbEngine::readGdbStandardOutput()
{
if (m_commandTimer->isActive())
m_commandTimer->start(); // Retrigger
2009-02-24 22:36:36 +01:00
int newstart = 0;
int scan = m_inbuffer.size();
2008-12-02 12:01:29 +01:00
m_inbuffer.append(gdbProc()->readAllStandardOutput());
2008-12-02 12:01:29 +01:00
// This can trigger when a dialog starts a nested event loop
if (m_busy)
return;
while (newstart < m_inbuffer.size()) {
int start = newstart;
2009-02-24 22:36:36 +01:00
int end = m_inbuffer.indexOf('\n', scan);
if (end < 0) {
m_inbuffer.remove(0, start);
return;
}
newstart = end + 1;
2009-02-24 22:36:36 +01:00
scan = newstart;
if (end == start)
continue;
#if defined(Q_OS_WIN)
if (m_inbuffer.at(end - 1) == '\r') {
--end;
if (end == start)
continue;
}
2009-02-24 22:36:36 +01:00
#endif
m_busy = true;
handleResponse(QByteArray::fromRawData(m_inbuffer.constData() + start, end - start));
m_busy = false;
2008-12-02 12:01:29 +01:00
}
m_inbuffer.clear();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::interruptInferior()
{
QTC_ASSERT(state() == InferiorRunning, qDebug() << state(); return);
2008-12-02 12:01:29 +01:00
setState(InferiorStopping);
showStatusMessage(tr("Stop requested..."), 5000);
debugMessage(_("TRYING TO INTERRUPT INFERIOR"));
m_gdbAdapter->interruptInferior();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::interruptInferiorTemporarily()
{
interruptInferior();
foreach (const GdbCommand &cmd, m_commandsToRunOnTemporaryBreak)
if (cmd.flags & LosesChild) {
setState(InferiorStopping_Kill);
break;
}
}
2008-12-02 12:01:29 +01:00
void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
{
const qint64 pid = pid0.toLongLong();
2008-12-02 12:01:29 +01:00
if (pid == 0) {
debugMessage(_("Cannot parse PID from %1").arg(pid0));
2008-12-02 12:01:29 +01:00
return;
}
if (pid == inferiorPid())
2008-12-02 12:01:29 +01:00
return;
debugMessage(_("FOUND PID %1").arg(pid));
handleInferiorPidChanged(pid);
2008-12-02 12:01:29 +01:00
}
void GdbEngine::postCommand(const QByteArray &command, AdapterCallback callback,
2009-09-21 11:09:38 +02:00
const char *callbackName, const QVariant &cookie)
{
postCommand(command, NoFlags, callback, callbackName, cookie);
}
void GdbEngine::postCommand(const QByteArray &command, GdbCommandFlags flags,
AdapterCallback callback,
const char *callbackName, const QVariant &cookie)
2009-09-21 11:09:38 +02:00
{
GdbCommand cmd;
cmd.command = command;
cmd.flags = flags;
2009-09-21 11:09:38 +02:00
cmd.adapterCallback = callback;
cmd.callbackName = callbackName;
cmd.cookie = cookie;
postCommandHelper(cmd);
}
void GdbEngine::postCommand(const QByteArray &command, GdbCommandCallback callback,
const char *callbackName, const QVariant &cookie)
{
postCommand(command, NoFlags, callback, callbackName, cookie);
}
void GdbEngine::postCommand(const QByteArray &command, GdbCommandFlags flags,
GdbCommandCallback callback, const char *callbackName,
const QVariant &cookie)
2009-09-21 11:09:38 +02:00
{
GdbCommand cmd;
cmd.command = command;
cmd.flags = flags;
cmd.callback = callback;
cmd.callbackName = callbackName;
cmd.cookie = cookie;
postCommandHelper(cmd);
}
void GdbEngine::postCommandHelper(const GdbCommand &cmd)
2008-12-02 12:01:29 +01:00
{
if (!stateAcceptsGdbCommands(state())) {
PENDING_DEBUG(_("NO GDB PROCESS RUNNING, CMD IGNORED: " + cmd.command));
debugMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: %1 %2")
.arg(_(cmd.command)).arg(state()));
2008-12-02 12:01:29 +01:00
return;
}
if (cmd.flags & RebuildWatchModel) {
++m_pendingWatchRequests;
PENDING_DEBUG(" WATCH MODEL:" << cmd.command << "=>" << cmd.callbackName
<< "INCREMENTS PENDING TO" << m_pendingWatchRequests);
} else if (cmd.flags & RebuildBreakpointModel) {
++m_pendingBreakpointRequests;
PENDING_DEBUG(" BRWAKPOINT MODEL:" << cmd.command << "=>" << cmd.callbackName
<< "INCREMENTS PENDING TO" << m_pendingBreakpointRequests);
2008-12-02 12:01:29 +01:00
} else {
2009-10-27 14:52:29 +01:00
PENDING_DEBUG(" OTHER (IN):" << cmd.command << "=>" << cmd.callbackName
<< "LEAVES PENDING WATCH AT" << m_pendingWatchRequests
<< "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests);
2008-12-02 12:01:29 +01:00
}
// FIXME: clean up logic below
if (cmd.flags & Immediate) {
// This should always be sent.
flushCommand(cmd);
} else if ((cmd.flags & NeedsStop)
|| !m_commandsToRunOnTemporaryBreak.isEmpty()) {
if (state() == InferiorStopped || state() == InferiorUnrunnable
|| state() == InferiorStarting || state() == AdapterStarted) {
// Can be safely sent now.
flushCommand(cmd);
} else {
// Queue the commands that we cannot send at once.
debugMessage(_("QUEUING COMMAND " + cmd.command));
m_commandsToRunOnTemporaryBreak.append(cmd);
if (state() == InferiorStopping) {
if (cmd.flags & LosesChild)
setState(InferiorStopping_Kill);
debugMessage(_("CHILD ALREADY BEING INTERRUPTED. STILL HOPING."));
// Calling shutdown() here breaks all situations where two
// NeedsStop commands are issued in quick succession.
} else if (state() == InferiorStopping_Kill) {
debugMessage(_("CHILD ALREADY BEING INTERRUPTED (KILL PENDING)"));
// FIXME
shutdown();
} else if (state() == InferiorRunningRequested) {
if (cmd.flags & LosesChild)
setState(InferiorRunningRequested_Kill);
debugMessage(_("RUNNING REQUESTED; POSTPONING INTERRUPT"));
} else if (state() == InferiorRunningRequested_Kill) {
debugMessage(_("RUNNING REQUESTED; POSTPONING INTERRUPT (KILL PENDING)"));
} else if (state() == InferiorRunning) {
showStatusMessage(tr("Stopping temporarily"), 1000);
interruptInferiorTemporarily();
} else {
qDebug() << "ATTEMPTING TO QUEUE COMMAND IN INAPPROPRIATE STATE" << state();
}
}
2009-09-21 11:09:38 +02:00
} else if (!cmd.command.isEmpty()) {
flushCommand(cmd);
2008-12-02 12:01:29 +01:00
}
}
void GdbEngine::flushQueuedCommands()
{
showStatusMessage(tr("Processing queued commands"), 1000);
while (!m_commandsToRunOnTemporaryBreak.isEmpty()) {
GdbCommand cmd = m_commandsToRunOnTemporaryBreak.takeFirst();
debugMessage(_("RUNNING QUEUED COMMAND " + cmd.command + ' '
+ cmd.callbackName));
flushCommand(cmd);
}
}
2009-09-21 11:09:38 +02:00
void GdbEngine::flushCommand(const GdbCommand &cmd0)
{
2009-09-21 11:09:38 +02:00
GdbCommand cmd = cmd0;
if (state() == DebuggerNotReady) {
showDebuggerInput(LogInput, _(cmd.command));
debugMessage(_("GDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: " + cmd.command));
return;
}
++currentToken();
cmd.postTime = QTime::currentTime();
m_cookieForToken[currentToken()] = cmd;
cmd.command = QByteArray::number(currentToken()) + cmd.command;
showDebuggerInput(LogInput, _(cmd.command));
m_gdbAdapter->write(cmd.command + "\r\n");
m_commandTimer->start();
if (cmd.flags & LosesChild)
setState(InferiorShuttingDown);
}
int GdbEngine::commandTimeoutTime() const
{
int time = theDebuggerAction(GdbWatchdogTimeout)->value().toInt();
return 1000 * qMax(20, time);
}
void GdbEngine::commandTimeout()
{
QList<int> keys = m_cookieForToken.keys();
qSort(keys);
bool killIt = false;
foreach (int key, keys) {
const GdbCommand &cmd = m_cookieForToken.value(key);
if (!(cmd.flags & NonCriticalResponse))
killIt = true;
QByteArray msg = QByteArray::number(key);
msg += ": " + cmd.command + " => ";
QTC_ASSERT(cmd.callbackName, /**/);
msg += cmd.callbackName;
debugMessage(_(msg));
}
if (killIt) {
debugMessage(_("TIMED OUT WAITING FOR GDB REPLY. COMMANDS STILL IN PROGRESS:"));
int timeOut = m_commandTimer->interval();
//m_commandTimer->stop();
const QString msg = tr("The gdb process has not responded "
"to a command within %1 seconds. This could mean it is stuck "
"in an endless loop or taking longer than expected to perform "
"the operation.\nYou can choose between waiting "
2010-01-12 13:51:46 +01:00
"longer or abort debugging.").arg(timeOut / 1000);
QMessageBox *mb = showMessageBox(QMessageBox::Critical,
tr("Gdb not responding"), msg,
QMessageBox::Ok | QMessageBox::Cancel);
mb->button(QMessageBox::Cancel)->setText(tr("Give gdb more time"));
mb->button(QMessageBox::Ok)->setText(tr("Stop debugging"));
if (mb->exec() == QMessageBox::Ok) {
debugMessage(_("KILLING DEBUGGER AS REQUESTED BY USER"));
// This is an undefined state, so we just pull the emergency brake.
manager()->watchHandler()->endCycle();
gdbProc()->kill();
} else {
debugMessage(_("CONTINUE DEBUGGER AS REQUESTED BY USER"));
}
} else {
debugMessage(_("\nNON-CRITICAL TIMEOUT\n"));
}
}
2009-10-23 21:32:08 +02:00
void GdbEngine::handleResultRecord(GdbResponse *response)
2008-12-02 12:01:29 +01:00
{
//qDebug() << "TOKEN:" << response.token
// << " ACCEPTABLE:" << m_oldestAcceptableToken;
//qDebug() << "\nRESULT" << response.token << response.toString();
2008-12-02 12:01:29 +01:00
2009-10-23 21:32:08 +02:00
int token = response->token;
2008-12-02 12:01:29 +01:00
if (token == -1)
return;
if (!m_cookieForToken.contains(token)) {
// In theory this should not happen (rather the error should be
// reported in the "first" response to the command) in practice it
// does. We try to handle a few situations we are aware of gracefully.
// Ideally, this code should not be present at all.
debugMessage(_("COOKIE FOR TOKEN %1 ALREADY EATEN. "
"TWO RESPONSES FOR ONE COMMAND?").arg(token));
2009-10-23 21:32:08 +02:00
if (response->resultClass == GdbResultError) {
QByteArray msg = response->data.findChild("msg").data();
if (msg == "Cannot find new threads: generic error") {
// Handle a case known to occur on Linux/gdb 6.8 when debugging moc
// with helpers enabled. In this case we get a second response with
// msg="Cannot find new threads: generic error"
debugMessage(_("APPLYING WORKAROUND #1"));
showMessageBox(QMessageBox::Critical,
tr("Executable failed"), QString::fromLocal8Bit(msg));
showStatusMessage(tr("Process failed to start"));
shutdown();
} else if (msg == "\"finish\" not meaningful in the outermost frame.") {
// Handle a case known to appear on gdb 6.4 symbianelf when
// the stack is cut due to access to protected memory.
debugMessage(_("APPLYING WORKAROUND #2"));
setState(InferiorStopping);
setState(InferiorStopped);
} else if (msg.startsWith("Cannot find bounds of current function")) {
// Happens when running "-exec-next" in a function for which
// there is no debug information. Divert to "-exec-next-step"
debugMessage(_("APPLYING WORKAROUND #3"));
setState(InferiorStopping);
setState(InferiorStopped);
2010-03-10 16:17:40 +01:00
executeNextI();
} else if (msg.startsWith("Couldn't get registers: No such process.")) {
// Happens on archer-tromey-python 6.8.50.20090910-cvs
// There might to be a race between a process shutting down
// and library load messages.
debugMessage(_("APPLYING WORKAROUND #4"));
setState(InferiorStopping);
setState(InferiorStopped);
setState(InferiorShuttingDown);
setState(InferiorShutDown);
showStatusMessage(tr("Executable failed: %1")
.arg(QString::fromLocal8Bit(msg)));
shutdown();
showMessageBox(QMessageBox::Critical,
tr("Executable failed"), QString::fromLocal8Bit(msg));
} else {
showMessageBox(QMessageBox::Critical,
tr("Executable failed"), QString::fromLocal8Bit(msg));
showStatusMessage(tr("Executable failed: %1")
.arg(QString::fromLocal8Bit(msg)));
}
}
return;
}
GdbCommand cmd = m_cookieForToken.take(token);
if (theDebuggerBoolSetting(LogTimeStamps)) {
showDebuggerOutput(LogTime, _("Response time: %1: %2 s")
.arg(_(cmd.command))
.arg(cmd.postTime.msecsTo(QTime::currentTime()) / 1000.));
}
2008-12-02 12:01:29 +01:00
2009-10-23 21:32:08 +02:00
if (response->token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
//debugMessage(_("### SKIPPING OLD RESULT") + response.toString());
2008-12-02 12:01:29 +01:00
return;
}
2009-10-23 21:32:08 +02:00
response->cookie = cmd.cookie;
2009-10-23 21:32:08 +02:00
if (response->resultClass != GdbResultError &&
response->resultClass != ((cmd.flags & RunRequest) ? GdbResultRunning :
(cmd.flags & ExitRequest) ? GdbResultExit :
GdbResultDone)) {
#ifdef Q_OS_WIN
// Ignore spurious 'running' responses to 'attach'
const bool warning = !(m_startParameters->startMode == AttachExternal
&& cmd.command.startsWith("attach"));
#else
const bool warning = true;
#endif
if (warning) {
QByteArray rsp = GdbResponse::stringFromResultClass(response->resultClass);
rsp = "UNEXPECTED RESPONSE '" + rsp + "' TO COMMAND '" + cmd.command + "'";
qWarning() << rsp << " AT " __FILE__ ":" STRINGIFY(__LINE__);
debugMessage(_(rsp));
}
} else {
if (cmd.callback)
2009-10-23 21:32:08 +02:00
(this->*cmd.callback)(*response);
else if (cmd.adapterCallback)
2009-10-23 21:32:08 +02:00
(m_gdbAdapter->*cmd.adapterCallback)(*response);
}
2008-12-02 12:01:29 +01:00
if (cmd.flags & RebuildWatchModel) {
--m_pendingWatchRequests;
2009-10-27 14:52:29 +01:00
PENDING_DEBUG(" WATCH" << cmd.command << "=>" << cmd.callbackName
<< "DECREMENTS PENDING WATCH TO" << m_pendingWatchRequests);
if (m_pendingWatchRequests <= 0) {
PENDING_DEBUG("\n\n ... AND TRIGGERS WATCH MODEL UPDATE\n");
rebuildWatchModel();
}
} else if (cmd.flags & RebuildBreakpointModel) {
--m_pendingBreakpointRequests;
PENDING_DEBUG(" BREAKPOINT" << cmd.command << "=>" << cmd.callbackName
<< "DECREMENTS PENDING TO" << m_pendingWatchRequests);
if (m_pendingBreakpointRequests <= 0) {
PENDING_DEBUG("\n\n ... AND TRIGGERS BREAKPOINT MODEL UPDATE\n");
attemptBreakpointSynchronization();
}
2008-12-02 12:01:29 +01:00
} else {
2009-10-27 14:52:29 +01:00
PENDING_DEBUG(" OTHER (OUT):" << cmd.command << "=>" << cmd.callbackName
<< "LEAVES PENDING WATCH AT" << m_pendingWatchRequests
<< "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests);
2008-12-02 12:01:29 +01:00
}
2009-05-07 12:18:45 +02:00
// Commands were queued, but we were in RunningRequested state, so the interrupt
// was postponed.
// This is done after the command callbacks so the running-requesting commands
// can assert on the right state.
if (state() == InferiorRunning && !m_commandsToRunOnTemporaryBreak.isEmpty())
interruptInferiorTemporarily();
// Continue only if there are no commands wire anymore, so this will
// be fully synchroneous.
2009-05-07 12:18:45 +02:00
// This is somewhat inefficient, as it makes the last command synchronous.
// An optimization would be requesting the continue immediately when the
// event loop is entered, and let individual commands have a flag to suppress
// that behavior.
if (m_commandsDoneCallback && m_cookieForToken.isEmpty()) {
debugMessage(_("ALL COMMANDS DONE; INVOKING CALLBACK"));
CommandsDoneCallback cont = m_commandsDoneCallback;
m_commandsDoneCallback = 0;
2009-09-21 11:09:38 +02:00
(this->*cont)();
} else {
PENDING_DEBUG("MISSING TOKENS: " << m_cookieForToken.keys());
2009-05-07 12:18:45 +02:00
}
if (m_cookieForToken.isEmpty())
m_commandTimer->stop();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::executeDebuggerCommand(const QString &command)
{
if (state() == DebuggerNotReady) {
debugMessage(_("GDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: ") + command);
2008-12-02 12:01:29 +01:00
return;
}
m_gdbAdapter->write(command.toLatin1() + "\r\n");
2008-12-02 12:01:29 +01:00
}
// Called from CoreAdapter and AttachAdapter
void GdbEngine::updateAll()
{
if (hasPython())
updateAllPython();
else
updateAllClassic();
}
void GdbEngine::handleQuerySources(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
m_sourcesListUpdating = false;
m_sourcesListOutdated = false;
if (response.resultClass == GdbResultDone) {
QMap<QString, QString> oldShortToFull = m_shortToFullName;
2008-12-02 12:01:29 +01:00
m_shortToFullName.clear();
m_fullToShortName.clear();
// "^done,files=[{file="../../../../bin/gdbmacros/gdbmacros.cpp",
// fullname="/data5/dev/ide/main/bin/gdbmacros/gdbmacros.cpp"},
GdbMi files = response.data.findChild("files");
2008-12-02 12:01:29 +01:00
foreach (const GdbMi &item, files.children()) {
GdbMi fullName = item.findChild("fullname");
GdbMi fileName = item.findChild("file");
QString file = QString::fromLocal8Bit(fileName.data());
2009-10-22 20:04:59 +02:00
if (fullName.isValid()) {
QString full = cleanupFullName(QString::fromLocal8Bit(fullName.data()));
m_shortToFullName[file] = full;
m_fullToShortName[full] = file;
} else if (fileName.isValid()) {
m_shortToFullName[file] = tr("<unknown>");
2008-12-02 12:01:29 +01:00
}
}
if (m_shortToFullName != oldShortToFull)
manager()->sourceFileWindow()->setSourceFiles(m_shortToFullName);
2008-12-02 12:01:29 +01:00
}
}
2009-04-30 22:32:01 +02:00
#if 0
void GdbEngine::handleExecJumpToLine(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
// 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"
setState(InferiorStopped);
showStatusMessage(tr("Jumped. Stopped"));
QByteArray output = response.data.findChild("logstreamoutput").data();
2009-04-30 22:32:01 +02:00
if (output.isEmpty())
2008-12-02 12:01:29 +01:00
return;
int idx1 = output.indexOf(' ') + 1;
if (idx1 > 0) {
int idx2 = output.indexOf(':', idx1);
if (idx2 > 0) {
QString file = QString::fromLocal8Bit(output.mid(idx1, idx2 - idx1));
int line = output.mid(idx2 + 1).toInt();
2009-09-17 16:51:27 +02:00
gotoLocation(file, line, true);
}
}
2008-12-02 12:01:29 +01:00
}
2009-04-30 22:32:01 +02:00
#endif
2008-12-02 12:01:29 +01:00
//void GdbEngine::handleExecRunToFunction(const GdbResponse &response)
//{
// // 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"}
// QTC_ASSERT(state() == InferiorStopping, qDebug() << state())
// setState(InferiorStopped);
// showStatusMessage(tr("Function reached. Stopped"));
// GdbMi frame = response.data.findChild("frame");
// StackFrame f = parseStackFrame(frame, 0);
// gotoLocation(f, true);
//}
2008-12-02 12:01:29 +01:00
static bool isExitedReason(const QByteArray &reason)
2008-12-02 12:01:29 +01:00
{
return reason == "exited-normally" // inferior exited normally
|| reason == "exited-signalled" // inferior exited because of a signal
//|| reason == "signal-received" // inferior received signal
|| reason == "exited"; // inferior exited
2008-12-02 12:01:29 +01:00
}
#if 0
2009-02-18 15:27:56 +01:00
void GdbEngine::handleAqcuiredInferior()
{
// Reverse debugging. FIXME: Should only be used when available.
//if (theDebuggerBoolSetting(EnableReverseDebugging))
// postCommand("target response");
tryLoadDebuggingHelpers();
2009-02-18 15:27:56 +01:00
#ifndef Q_OS_MAC
// intentionally after tryLoadDebuggingHelpers(),
2010-01-11 10:22:55 +01:00
// otherwise we'd interrupt solib loading.
2009-03-19 10:54:27 +01:00
if (theDebuggerBoolSetting(AllPluginBreakpoints)) {
postCommand("set auto-solib-add on");
postCommand("set stop-on-solib-events 0");
postCommand("sharedlibrary .*");
2009-03-19 10:54:27 +01:00
} else if (theDebuggerBoolSetting(SelectedPluginBreakpoints)) {
postCommand("set auto-solib-add on");
postCommand("set stop-on-solib-events 1");
postCommand("sharedlibrary "
2009-03-19 10:54:27 +01:00
+ theDebuggerStringSetting(SelectedPluginBreakpointsPattern));
} else if (theDebuggerBoolSetting(NoPluginBreakpoints)) {
2009-02-18 15:27:56 +01:00
// should be like that already
postCommand("set auto-solib-add off");
postCommand("set stop-on-solib-events 0");
2009-02-18 15:27:56 +01:00
}
#endif
// It's nicer to see a bit of the world we live in.
reloadModulesInternal();
2009-02-18 15:27:56 +01:00
}
#endif
2009-02-18 15:27:56 +01:00
#ifdef Q_OS_UNIX
# define STOP_SIGNAL "SIGINT"
# define CROSS_STOP_SIGNAL "SIGTRAP"
#else
# define STOP_SIGNAL "SIGTRAP"
# define CROSS_STOP_SIGNAL "SIGINT"
#endif
void GdbEngine::handleStopResponse(const GdbMi &data)
2008-12-02 12:01:29 +01:00
{
// This is gdb 7+'s initial *stopped in response to attach.
// For consistency, we just discard it.
if (state() == InferiorStarting)
return;
2009-09-21 11:09:38 +02:00
const QByteArray reason = data.findChild("reason").data();
2008-12-02 12:01:29 +01:00
if (isExitedReason(reason)) {
if (state() == InferiorRunning) {
setState(InferiorStopping);
} else {
// The user triggered a stop, but meanwhile the app simply exited ...
QTC_ASSERT(state() == InferiorStopping || state() == InferiorStopping_Kill,
qDebug() << state());
}
setState(InferiorStopped);
QString msg;
if (reason == "exited") {
msg = tr("Application exited with exit code %1")
2009-04-30 22:22:37 +02:00
.arg(_(data.findChild("exit-code").toString()));
} else if (reason == "exited-signalled" || reason == "signal-received") {
msg = tr("Application exited after receiving signal %1")
2009-04-30 22:22:37 +02:00
.arg(_(data.findChild("signal-name").toString()));
} else {
msg = tr("Application exited normally");
}
showStatusMessage(msg);
setState(InferiorShuttingDown);
setState(InferiorShutDown);
shutdown();
return;
}
2009-02-16 18:28:15 +01:00
if (!m_commandsToRunOnTemporaryBreak.isEmpty()) {
QTC_ASSERT(state() == InferiorStopping || state() == InferiorStopping_Kill,
qDebug() << state())
setState(InferiorStopped);
flushQueuedCommands();
if (state() == InferiorStopped) {
QTC_ASSERT(m_commandsDoneCallback == 0, /**/);
m_commandsDoneCallback = &GdbEngine::autoContinueInferior;
} else {
QTC_ASSERT(state() == InferiorShuttingDown, qDebug() << state())
2009-02-16 18:28:15 +01:00
}
return;
}
if (state() == InferiorRunning) {
// Stop triggered by a breakpoint or otherwise not directly
// initiated by the user.
setState(InferiorStopping);
} else {
QTC_ASSERT(state() == InferiorStopping, qDebug() << state());
}
setState(InferiorStopped);
#if 0 // See http://vladimir_prus.blogspot.com/2007/12/debugger-stories-pending-breakpoints.html
// Due to LD_PRELOADing the dumpers, these events can occur even before
// reaching the entry point. So handle it before the entry point hacks below.
if (reason.isEmpty() && m_gdbVersion < 70000 && !m_isMacGdb) {
// On Linux it reports "Stopped due to shared library event\n", but
// on Windows it simply forgets about it. Thus, we identify the response
// based on it having no frame information.
if (!data.findChild("frame").isValid()) {
invalidateSourcesList();
// Each stop causes a roundtrip and button flicker, so prevent
// a flood of useless stops. Will be automatically re-enabled.
postCommand("set stop-on-solib-events 0");
#if 0
// The related code (handleAqcuiredInferior()) is disabled as well.
if (theDebuggerBoolSetting(SelectedPluginBreakpoints)) {
QString dataStr = _(data.toString());
debugMessage(_("SHARED LIBRARY EVENT: ") + dataStr);
QString pat = theDebuggerStringSetting(SelectedPluginBreakpointsPattern);
debugMessage(_("PATTERN: ") + pat);
postCommand("sharedlibrary " + pat.toLocal8Bit());
showStatusMessage(tr("Loading %1...").arg(dataStr));
}
#endif
continueInferiorInternal();
return;
}
}
#endif
2010-05-05 17:07:08 +02:00
// FIXME: Replace the #ifdef by the "target" architecture
#ifdef Q_OS_LINUX
if (!m_entryPoint.isEmpty()) {
GdbMi frameData = data.findChild("frame");
if (frameData.findChild("addr").data() == m_entryPoint) {
// There are two expected reasons for getting here:
// 1) For some reason, attaching to a stopped process causes *two* SIGSTOPs
// when trying to continue (kernel i386 2.6.24-23-ubuntu, gdb 6.8).
// Interestingly enough, on MacOSX no signal is delivered at all.
// 2) The explicit tbreak at the entry point we set to query the PID.
// Gdb <= 6.8 reports a frame but no reason, 6.8.50+ reports everything.
// The case of the user really setting a breakpoint at _start is simply
// unsupported.
if (!inferiorPid()) // For programs without -pthread under gdb <= 6.8.
postCommand("info proc", CB(handleInfoProc));
continueInferiorInternal();
return;
2008-12-02 12:01:29 +01:00
}
// We are past the initial stop(s). No need to waste time on further checks.
m_entryPoint.clear();
2008-12-02 12:01:29 +01:00
}
#endif
2008-12-02 12:01:29 +01:00
// seen on XP after removing a breakpoint while running
// >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"}
2009-12-18 16:25:59 +01:00
// also seen on gdb 6.8-symbianelf without qXfer:libraries:read+;
// FIXME: remote.c parses "loaded" reply. It should be turning
// that into a TARGET_WAITKIND_LOADED. Does it?
// The bandaid here has the problem that it breaks for 'next' over a
// statement that indirectly loads shared libraries
// 6.1.2010: Breaks interrupting inferiors, disabled:
// if (reason == "signal-received"
// && data.findChild("signal-name").data() == "SIGTRAP") {
// continueInferiorInternal();
// return;
// }
// Jump over well-known frames.
2008-12-02 12:01:29 +01:00
static int stepCounter = 0;
2009-03-19 10:54:27 +01:00
if (theDebuggerBoolSetting(SkipKnownFrames)) {
2008-12-02 12:01:29 +01:00
if (reason == "end-stepping-range" || reason == "function-finished") {
GdbMi frame = data.findChild("frame");
2009-02-16 13:29:57 +01:00
//debugMessage(frame.toString());
QString funcName = _(frame.findChild("func").data());
QString fileName = QString::fromLocal8Bit(frame.findChild("file").data());
2008-12-02 12:01:29 +01:00
if (isLeavableFunction(funcName, fileName)) {
//debugMessage(_("LEAVING ") + funcName);
2008-12-02 12:01:29 +01:00
++stepCounter;
2010-03-10 16:17:40 +01:00
m_manager->executeStepOut();
2008-12-02 12:01:29 +01:00
return;
}
if (isSkippableFunction(funcName, fileName)) {
//debugMessage(_("SKIPPING ") + funcName);
2008-12-02 12:01:29 +01:00
++stepCounter;
2010-03-10 16:17:40 +01:00
m_manager->executeStep();
2008-12-02 12:01:29 +01:00
return;
}
//if (stepCounter)
// qDebug() << "STEPCOUNTER:" << stepCounter;
stepCounter = 0;
}
}
bool initHelpers = m_debuggingHelperState == DebuggingHelperUninitialized
|| m_debuggingHelperState == DebuggingHelperLoadTried;
// Don't load helpers on stops triggered by signals unless it's
// an intentional trap.
if (initHelpers
&& m_gdbAdapter->dumperHandling() != AbstractGdbAdapter::DumperLoadedByGdbPreload
&& reason == "signal-received") {
QByteArray name = data.findChild("signal-name").data();
if (name != STOP_SIGNAL
&& (startParameters().startMode != AttachToRemote
|| name != CROSS_STOP_SIGNAL))
initHelpers = false;
2008-12-02 12:01:29 +01:00
}
if (isSynchroneous())
initHelpers = false;
if (initHelpers) {
tryLoadDebuggingHelpersClassic();
QVariant var = QVariant::fromValue<GdbMi>(data);
postCommand("p 4", CB(handleStop1), var); // dummy
} else {
handleStop1(data);
2008-12-02 12:01:29 +01:00
}
// Dumper loading is sequenced, as otherwise the display functions
// will start requesting data without knowing that dumpers are available.
2008-12-02 12:01:29 +01:00
}
void GdbEngine::handleStop1(const GdbResponse &response)
2009-09-21 11:09:38 +02:00
{
handleStop1(response.cookie.value<GdbMi>());
}
void GdbEngine::handleStop1(const GdbMi &data)
{
QByteArray reason = data.findChild("reason").data();
if (0 && m_gdbAdapter->isTrkAdapter()
&& reason == "signal-received"
&& data.findChild("signal-name").data() == "SIGTRAP") {
// Caused by "library load" message.
debugMessage(_("INTERNAL CONTINUE"));
continueInferiorInternal();
return;
}
// This is for display only.
if (m_modulesListOutdated)
reloadModulesInternal();
// This needs to be done before fullName() may need it.
if (m_sourcesListOutdated && theDebuggerBoolSetting(UsePreciseBreakpoints))
reloadSourceFilesInternal();
if (m_breakListOutdated) {
reloadBreakListInternal();
} else {
// Older gdb versions do not produce "library loaded" messages
// so the breakpoint update is not triggered.
if (m_gdbVersion < 70000 && !m_isMacGdb
&& manager()->breakHandler()->size() > 0)
reloadBreakListInternal();
}
2009-09-21 11:09:38 +02:00
if (reason == "breakpoint-hit") {
QByteArray bpNumber = data.findChild("bkptno").data();
QByteArray threadId = data.findChild("thread-id").data();
showStatusMessage(tr("Stopped at breakpoint %1 in thread %2")
.arg(_(bpNumber), _(threadId)));
2009-09-21 11:09:38 +02:00
} else {
QString reasontr = tr("Stopped: \"%1\"").arg(_(reason));
2009-09-21 11:09:38 +02:00
if (reason == "signal-received"
&& theDebuggerBoolSetting(UseMessageBoxForSignals)) {
QByteArray name = data.findChild("signal-name").data();
QByteArray meaning = data.findChild("signal-meaning").data();
// Ignore these as they are showing up regularly when
2009-09-21 11:09:38 +02:00
// stopping debugging.
if (name != STOP_SIGNAL
&& (startParameters().startMode != AttachToRemote
|| name != CROSS_STOP_SIGNAL)) {
2009-09-21 11:09:38 +02:00
QString msg = tr("<p>The inferior stopped because it received a "
"signal from the Operating System.<p>"
"<table><tr><td>Signal name : </td><td>%1</td></tr>"
"<tr><td>Signal meaning : </td><td>%2</td></tr></table>")
.arg(name.isEmpty() ? tr(" <Unknown> ", "name") : _(name))
.arg(meaning.isEmpty() ? tr(" <Unknown> ", "meaning") : _(meaning));
showMessageBox(QMessageBox::Information,
tr("Signal received"), msg);
if (!name.isEmpty() && !meaning.isEmpty())
reasontr = tr("Stopped: %1 by signal %2")
.arg(_(meaning)).arg(_(name));
2009-09-21 11:09:38 +02:00
}
}
if (reason.isEmpty())
showStatusMessage(tr("Stopped."));
else
showStatusMessage(reasontr);
2009-09-21 11:09:38 +02:00
}
const GdbMi gdbmiFrame = data.findChild("frame");
m_currentFrame = _(gdbmiFrame.findChild("addr").data() + '%' +
gdbmiFrame.findChild("func").data() + '%');
// Quick shot: Jump to stack frame #0.
StackFrame frame;
if (gdbmiFrame.isValid()) {
frame = parseStackFrame(gdbmiFrame, 0);
gotoLocation(frame, true);
}
2008-12-02 12:01:29 +01:00
//
// Stack
//
manager()->stackHandler()->setCurrentIndex(0);
updateLocals(qVariantFromValue(frame)); // Quick shot
2008-12-02 12:01:29 +01:00
reloadStack(false);
2009-09-23 13:59:55 +02:00
if (supportsThreads()) {
int currentId = data.findChild("thread-id").data().toInt();
if (m_gdbAdapter->isTrkAdapter())
m_gdbAdapter->trkReloadThreads();
else
postCommand("-thread-list-ids", CB(handleStackListThreads), currentId);
2009-09-23 13:59:55 +02:00
}
2008-12-02 12:01:29 +01:00
//
// Registers
//
manager()->reloadRegisters();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::handleInfoProc(const GdbResponse &response)
{
if (response.resultClass == GdbResultDone) {
static QRegExp re(_("\\bprocess ([0-9]+)\n"));
QTC_ASSERT(re.isValid(), return);
if (re.indexIn(_(response.data.findChild("consolestreamoutput").data())) != -1)
maybeHandleInferiorPidChanged(re.cap(1));
}
}
void GdbEngine::handleShowVersion(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
2009-02-23 16:06:23 +01:00
//qDebug () << "VERSION 2:" << response.data.findChild("consolestreamoutput").data();
//qDebug () << "VERSION:" << response.toString();
debugMessage(_("VERSION: " + response.toString()));
2008-12-02 12:01:29 +01:00
if (response.resultClass == GdbResultDone) {
m_gdbVersion = 100;
2009-02-23 16:06:23 +01:00
m_gdbBuildVersion = -1;
m_isMacGdb = false;
GdbMi version = response.data.findChild("consolestreamoutput");
QString msg = QString::fromLocal8Bit(version.data());
bool foundIt = false;
QRegExp supported(_("GNU gdb(.*) (\\d+)\\.(\\d+)(\\.(\\d+))?(-(\\d+))?"));
if (supported.indexIn(msg) >= 0) {
debugMessage(_("SUPPORTED GDB VERSION ") + msg);
m_gdbVersion = 10000 * supported.cap(2).toInt()
+ 100 * supported.cap(3).toInt()
+ 1 * supported.cap(5).toInt();
m_gdbBuildVersion = supported.cap(7).toInt();
m_isMacGdb = msg.contains(__("Apple version"));
foundIt = true;
}
// OpenSUSE managed to ship "GNU gdb (GDB) SUSE (6.8.91.20090930-2.4).
if (!foundIt && msg.startsWith(_("GNU gdb (GDB) SUSE "))) {
supported.setPattern(_("[^\\d]*(\\d+).(\\d+).(\\d+).*"));
if (supported.indexIn(msg) >= 0) {
debugMessage(_("SUSE PATCHED GDB VERSION ") + msg);
m_gdbVersion = 10000 * supported.cap(1).toInt()
+ 100 * supported.cap(2).toInt()
+ 1 * supported.cap(3).toInt();
m_gdbBuildVersion = -1;
m_isMacGdb = false;
foundIt = true;
} else {
debugMessage(_("UNPARSABLE SUSE PATCHED GDB VERSION ") + msg);
}
}
if (!foundIt) {
debugMessage(_("UNSUPPORTED GDB VERSION ") + msg);
2009-10-21 16:50:00 +02:00
#if 0
QStringList list = msg.split(_c('\n'));
2008-12-02 12:01:29 +01:00
while (list.size() > 2)
list.removeLast();
msg = tr("The debugger you are using identifies itself as:")
+ _("<p><p>") + list.join(_("<br>")) + _("<p><p>")
2008-12-02 12:01:29 +01:00
+ tr("This version is not officially supported by Qt Creator.\n"
"Debugging will most likely not work well.\n"
"Using gdb 7.1 or later is strongly recommended.");
2008-12-02 12:01:29 +01:00
#if 0
// ugly, but 'Show again' check box...
static QErrorMessage *err = new QErrorMessage(mainWindow());
2008-12-02 12:01:29 +01:00
err->setMinimumSize(400, 300);
err->showMessage(msg);
#else
//showMessageBox(QMessageBox::Information, tr("Warning"), msg);
2009-10-21 16:50:00 +02:00
#endif
2008-12-02 12:01:29 +01:00
#endif
}
debugMessage(_("USING GDB VERSION: %1, BUILD: %2%3").arg(m_gdbVersion)
.arg(m_gdbBuildVersion).arg(_(m_isMacGdb ? " (APPLE)" : "")));
2009-02-23 16:06:23 +01:00
//qDebug () << "VERSION 3:" << m_gdbVersion << m_gdbBuildVersion;
2008-12-02 12:01:29 +01:00
}
}
void GdbEngine::handleHasPython(const GdbResponse &response)
2009-10-14 09:41:14 +02:00
{
if (response.resultClass == GdbResultDone) {
m_hasPython = true;
2009-10-14 09:41:14 +02:00
} else {
m_hasPython = false;
if (m_gdbAdapter->dumperHandling() == AbstractGdbAdapter::DumperLoadedByGdbPreload
&& checkDebuggingHelpersClassic()) {
QByteArray cmd = "set environment ";
cmd += Debugger::Constants::Internal::LD_PRELOAD_ENV_VAR;
cmd += ' ';
cmd += manager()->qtDumperLibraryName().toLocal8Bit();
postCommand(cmd);
m_debuggingHelperState = DebuggingHelperLoadTried;
}
2009-10-14 09:41:14 +02:00
}
}
2010-03-10 16:17:40 +01:00
void GdbEngine::handleExecuteContinue(const GdbResponse &response)
{
if (response.resultClass == GdbResultRunning) {
// The "running" state is picked up in handleResponse()
QTC_ASSERT(state() == InferiorRunning, /**/);
} else {
if (state() == InferiorRunningRequested_Kill) {
setState(InferiorStopped);
shutdown();
return;
}
QTC_ASSERT(state() == InferiorRunningRequested, /**/);
setState(InferiorStopped);
QByteArray msg = response.data.findChild("msg").data();
if (msg.startsWith("Cannot find bounds of current function")
|| msg.startsWith("\"finish\" not meaningful in the outermost frame")) {
if (!m_commandsToRunOnTemporaryBreak.isEmpty())
flushQueuedCommands();
showStatusMessage(tr("Stopped."), 5000);
reloadStack(true);
//showStatusMessage(tr("No debug information available. "
// "Leaving function..."));
2010-03-10 16:17:40 +01:00
//executeStepOut();
2008-12-02 12:01:29 +01:00
} else {
showMessageBox(QMessageBox::Critical, tr("Execution Error"),
tr("Cannot continue debugged process:\n") + QString::fromLocal8Bit(msg));
shutdown();
2008-12-02 12:01:29 +01:00
}
}
}
QString GdbEngine::fullName(const QString &fileName)
{
if (fileName.isEmpty())
return QString();
//QTC_ASSERT(!m_sourcesListOutdated, /* */)
QTC_ASSERT(!m_sourcesListUpdating, /* */)
return m_shortToFullName.value(fileName, QString());
2008-12-02 12:01:29 +01:00
}
#ifdef Q_OS_WIN
QString GdbEngine::cleanupFullName(const QString &fileName)
{
QTC_ASSERT(!fileName.isEmpty(), return QString())
// Gdb on windows often delivers "fullnames" which
// a) have no drive letter and b) are not normalized.
QFileInfo fi(fileName);
if (!fi.isReadable())
return QString();
return QDir::cleanPath(fi.absoluteFilePath());
}
#endif
2008-12-02 12:01:29 +01:00
void GdbEngine::shutdown()
{
debugMessage(_("INITIATE GDBENGINE SHUTDOWN"));
if (m_progress) {
m_progress->setProgressValue(90);
2010-03-22 16:17:06 +01:00
m_progress->reportCanceled();
m_progress->reportFinished();
}
switch (state()) {
case DebuggerNotReady: // Nothing to do! :)
case EngineStarting: // We can't get here, really
case InferiorShuttingDown: // Will auto-trigger further shutdown steps
case EngineShuttingDown: // Do not disturb! :)
case InferiorRunningRequested_Kill:
case InferiorStopping_Kill:
break;
case AdapterStarting: // GDB is up, adapter is "doing something"
setState(AdapterStartFailed);
m_gdbAdapter->shutdown();
// fall-through
case AdapterStartFailed: // Adapter "did something", but it did not help
if (gdbProc()->state() == QProcess::Running) {
m_commandsToRunOnTemporaryBreak.clear();
postCommand("-gdb-exit", GdbEngine::ExitRequest, CB(handleGdbExit));
} else {
setState(DebuggerNotReady);
}
break;
case InferiorRunningRequested:
case InferiorRunning:
case InferiorStopping:
case InferiorStopped:
m_commandsToRunOnTemporaryBreak.clear();
postCommand(m_gdbAdapter->inferiorShutdownCommand(),
NeedsStop | LosesChild, CB(handleInferiorShutdown));
break;
case AdapterStarted: // We can't get here, really
case InferiorStartFailed:
case InferiorShutDown:
case InferiorShutdownFailed: // Whatever
case InferiorUnrunnable:
m_commandsToRunOnTemporaryBreak.clear();
postCommand("-gdb-exit", GdbEngine::ExitRequest, CB(handleGdbExit));
setState(EngineShuttingDown); // Do it after posting the command!
break;
case InferiorStarting: // This may take some time, so just short-circuit it
setState(InferiorStartFailed);
// fall-through
case InferiorStopFailed: // Tough luck, I guess. But unreachable as of now anyway.
setState(EngineShuttingDown);
gdbProc()->kill();
break;
}
}
void GdbEngine::handleInferiorShutdown(const GdbResponse &response)
{
QTC_ASSERT(state() == InferiorShuttingDown, qDebug() << state());
if (response.resultClass == GdbResultDone) {
debugMessage(_("INFERIOR SUCCESSFULLY SHUT DOWN"));
setState(InferiorShutDown);
} else {
QByteArray ba = response.data.findChild("msg").data();
if (ba.contains(": No such file or directory.")) {
// This happens when someone removed the binary behind our back.
// It is not really an error from a user's point of view.
debugMessage(_("INFERIOR SUCCESSFULLY SHUT DOWN"));
debugMessage(_("NOTE: " + ba));
setState(InferiorShutDown);
} else {
debugMessage(_("INFERIOR SHUTDOWN FAILED"));
setState(InferiorShutdownFailed);
showMessageBox(QMessageBox::Critical,
tr("Failed to shut down application"),
m_gdbAdapter->msgInferiorStopFailed(QString::fromLocal8Bit(ba)));
}
}
shutdown(); // re-iterate...
}
void GdbEngine::handleGdbExit(const GdbResponse &response)
{
if (response.resultClass == GdbResultExit) {
debugMessage(_("GDB CLAIMS EXIT; WAITING"));
m_commandsDoneCallback = 0;
// Don't set state here, this will be handled in handleGdbFinished()
} else {
QString msg = m_gdbAdapter->msgGdbStopFailed(
QString::fromLocal8Bit(response.data.findChild("msg").data()));
debugMessage(_("GDB WON'T EXIT (%1); KILLING IT").arg(msg));
gdbProc()->kill();
}
2008-12-02 12:01:29 +01:00
}
void GdbEngine::detachDebugger()
{
2009-09-30 13:07:14 +02:00
QTC_ASSERT(state() == InferiorStopped, /**/);
QTC_ASSERT(startMode() != AttachCore, /**/);
postCommand("detach");
2009-09-30 13:07:14 +02:00
setState(InferiorShuttingDown);
setState(InferiorShutDown);
shutdown();
}
void GdbEngine::exitDebugger() // called from the manager
2008-12-02 12:01:29 +01:00
{
disconnectDebuggingHelperActions();
shutdown();
2009-09-16 13:43:03 +02:00
}
void GdbEngine::abortDebugger() // called from the manager
{
disconnectDebuggingHelperActions();
shutdown();
gdbProc()->kill();
}
2008-12-02 12:01:29 +01:00
int GdbEngine::currentFrame() const
{
return manager()->stackHandler()->currentIndex();
2008-12-02 12:01:29 +01:00
}
static inline QString msgNoBinaryForToolChain(int tc)
{
const ProjectExplorer::ToolChain::ToolChainType toolChain = static_cast<ProjectExplorer::ToolChain::ToolChainType>(tc);
const QString toolChainName = ProjectExplorer::ToolChain::toolChainName(toolChain);
return GdbEngine::tr("There is no gdb binary available for '%1'").arg(toolChainName);
}
bool GdbEngine::checkConfiguration(int toolChain, QString *errorMessage, QString *settingsPage) const
{
if (m_gdbBinaryToolChainMap->key(toolChain).isEmpty()) {
*errorMessage = msgNoBinaryForToolChain(toolChain);
*settingsPage = GdbOptionsPage::settingsId();
return false;
}
return true;
}
2009-10-05 14:59:39 +02:00
AbstractGdbAdapter *GdbEngine::createAdapter(const DebuggerStartParametersPtr &sp)
{
switch (sp->toolChainType) {
case ProjectExplorer::ToolChain::WINSCW: // S60
case ProjectExplorer::ToolChain::GCCE:
case ProjectExplorer::ToolChain::RVCT_ARMV5:
case ProjectExplorer::ToolChain::RVCT_ARMV6:
case ProjectExplorer::ToolChain::RVCT_ARMV5_GNUPOC:
case ProjectExplorer::ToolChain::GCCE_GNUPOC:
return new TrkGdbAdapter(this);
default:
break;
}
// @todo: remove testing hack
if (sp->processArgs.size() == 3 && sp->processArgs.at(0) == _("@sym@"))
return new TrkGdbAdapter(this);
switch (sp->startMode) {
case AttachCore:
2009-10-05 14:59:39 +02:00
return new CoreGdbAdapter(this);
case AttachToRemote:
return new RemoteGdbServerAdapter(this, sp->toolChainType);
case StartRemoteGdb:
return new RemotePlainGdbAdapter(this);
case AttachExternal:
2009-10-05 14:59:39 +02:00
return new AttachGdbAdapter(this);
default:
if (sp->useTerminal)
return new TermGdbAdapter(this);
return new LocalPlainGdbAdapter(this);
}
}
2009-09-17 14:38:15 +02:00
void GdbEngine::startDebugger(const DebuggerStartParametersPtr &sp)
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == EngineStarting, qDebug() << state());
// This should be set by the constructor or in exitDebugger()
2009-09-22 12:10:12 +02:00
// via initializeVariables()
//QTC_ASSERT(m_debuggingHelperState == DebuggingHelperUninitialized,
// initializeVariables());
//QTC_ASSERT(m_gdbAdapter == 0, delete m_gdbAdapter; m_gdbAdapter = 0);
initializeVariables();
2010-03-22 16:17:06 +01:00
m_progress = new QFutureInterface<void>();
m_progress->setProgressRange(0, 100);
Core::FutureProgress *fp = Core::ICore::instance()->progressManager()
->addTask(m_progress->future(), tr("Launching"), _("Debugger.Launcher"));
fp->setKeepOnFinish(false);
m_progress->reportStarted();
m_startParameters = sp;
2009-10-05 14:59:39 +02:00
delete m_gdbAdapter;
m_gdbAdapter = createAdapter(sp);
connectAdapter();
if (m_gdbAdapter->dumperHandling() != AbstractGdbAdapter::DumperNotAvailable)
connectDebuggingHelperActions();
2010-03-22 16:17:06 +01:00
m_progress->setProgressValue(20);
m_gdbAdapter->startAdapter();
2008-12-02 12:01:29 +01:00
}
unsigned GdbEngine::debuggerCapabilities() const
{
2010-02-11 13:21:38 +01:00
return ReverseSteppingCapability | SnapshotCapability
| AutoDerefPointersCapability | DisassemblerCapability
| RegisterCapability | ShowMemoryCapability
| JumpToLineCapability | ReloadModuleCapability
| ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability
| ReturnFromFunctionCapability
| CreateFullBacktraceCapability
| AddWatcherCapability;
}
void GdbEngine::continueInferiorInternal()
2008-12-02 12:01:29 +01:00
{
2009-10-22 17:53:36 +02:00
QTC_ASSERT(state() == InferiorStopped || state() == InferiorStarting,
qDebug() << state());
setState(InferiorRunningRequested);
2010-03-10 16:17:40 +01:00
postCommand("-exec-continue", RunRequest, CB(handleExecuteContinue));
2008-12-02 12:01:29 +01:00
}
void GdbEngine::autoContinueInferior()
{
continueInferiorInternal();
showStatusMessage(tr("Continuing after temporary stop..."), 1000);
}
void GdbEngine::continueInferior()
{
m_manager->resetLocation();
setTokenBarrier();
continueInferiorInternal();
showStatusMessage(tr("Running requested..."), 5000);
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeStep()
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
2008-12-02 12:01:29 +01:00
setTokenBarrier();
setState(InferiorRunningRequested);
showStatusMessage(tr("Step requested..."), 5000);
StackHandler *stackHandler = manager()->stackHandler();
if (m_gdbAdapter->isTrkAdapter() && stackHandler->stackSize() > 0)
postCommand("sal step," + stackHandler->topAddress().toLatin1());
if (manager()->isReverseDebugging())
postCommand("reverse-step", RunRequest, CB(handleExecuteStep));
else
2010-03-10 16:17:40 +01:00
postCommand("-exec-step", RunRequest, CB(handleExecuteStep));
}
2010-03-10 16:17:40 +01:00
void GdbEngine::handleExecuteStep(const GdbResponse &response)
{
if (response.resultClass == GdbResultRunning) {
// The "running" state is picked up in handleResponse().
QTC_ASSERT(state() == InferiorRunning, /**/);
} else {
if (state() == InferiorRunningRequested_Kill) {
setState(InferiorStopped);
shutdown();
return;
}
QTC_ASSERT(state() == InferiorRunningRequested, /**/);
setState(InferiorStopped);
QByteArray msg = response.data.findChild("msg").data();
if (msg.startsWith("Cannot find bounds of current function")) {
if (!m_commandsToRunOnTemporaryBreak.isEmpty())
flushQueuedCommands();
2010-03-10 16:17:40 +01:00
executeStepI(); // Fall back to instruction-wise stepping.
} else {
showMessageBox(QMessageBox::Critical, tr("Execution Error"),
tr("Cannot continue debugged process:\n")
+ QString::fromLocal8Bit(msg));
shutdown();
}
}
2008-12-02 12:01:29 +01:00
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeStepI()
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
2008-12-02 12:01:29 +01:00
setTokenBarrier();
setState(InferiorRunningRequested);
showStatusMessage(tr("Step by instruction requested..."), 5000);
if (manager()->isReverseDebugging())
postCommand("reverse-stepi", RunRequest, CB(handleExecuteContinue));
else
2010-03-10 16:17:40 +01:00
postCommand("-exec-step-instruction", RunRequest, CB(handleExecuteContinue));
2008-12-02 12:01:29 +01:00
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeStepOut()
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
2008-12-02 12:01:29 +01:00
setTokenBarrier();
setState(InferiorRunningRequested);
showStatusMessage(tr("Finish function requested..."), 5000);
2010-03-10 16:17:40 +01:00
postCommand("-exec-finish", RunRequest, CB(handleExecuteContinue));
2008-12-02 12:01:29 +01:00
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeNext()
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
2008-12-02 12:01:29 +01:00
setTokenBarrier();
setState(InferiorRunningRequested);
showStatusMessage(tr("Step next requested..."), 5000);
StackHandler *stackHandler = manager()->stackHandler();
if (m_gdbAdapter->isTrkAdapter() && stackHandler->stackSize() > 0)
postCommand("sal next," + stackHandler->topAddress().toLatin1());
if (manager()->isReverseDebugging())
postCommand("reverse-next", RunRequest, CB(handleExecuteNext));
else
2010-03-10 16:17:40 +01:00
postCommand("-exec-next", RunRequest, CB(handleExecuteNext));
}
2010-03-10 16:17:40 +01:00
void GdbEngine::handleExecuteNext(const GdbResponse &response)
{
if (response.resultClass == GdbResultRunning) {
// The "running" state is picked up in handleResponse()
QTC_ASSERT(state() == InferiorRunning, /**/);
} else {
if (state() == InferiorRunningRequested_Kill) {
setState(InferiorStopped);
shutdown();
return;
}
QTC_ASSERT(state() == InferiorRunningRequested, /**/);
setState(InferiorStopped);
QByteArray msg = response.data.findChild("msg").data();
if (msg.startsWith("Cannot find bounds of current function")) {
if (!m_commandsToRunOnTemporaryBreak.isEmpty())
flushQueuedCommands();
2010-03-10 16:17:40 +01:00
executeNextI(); // Fall back to instruction-wise stepping.
} else {
showMessageBox(QMessageBox::Critical, tr("Execution Error"),
tr("Cannot continue debugged process:\n") + QString::fromLocal8Bit(msg));
shutdown();
}
}
2008-12-02 12:01:29 +01:00
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeNextI()
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
2008-12-02 12:01:29 +01:00
setTokenBarrier();
setState(InferiorRunningRequested);
showStatusMessage(tr("Step next instruction requested..."), 5000);
if (manager()->isReverseDebugging())
postCommand("reverse-nexti", RunRequest, CB(handleExecuteContinue));
else
2010-03-10 16:17:40 +01:00
postCommand("-exec-next-instruction", RunRequest, CB(handleExecuteContinue));
2008-12-02 12:01:29 +01:00
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeRunToLine(const QString &fileName, int lineNumber)
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
2008-12-02 12:01:29 +01:00
setTokenBarrier();
setState(InferiorRunningRequested);
showStatusMessage(tr("Run to line %1 requested...").arg(lineNumber), 5000);
#if 1
QByteArray loc = '"' + breakLocation(fileName).toLocal8Bit() + '"' + ':'
+ QByteArray::number(lineNumber);
postCommand("tbreak " + loc);
postCommand("continue", RunRequest);
#else
// Seems to jump to unpredicatable places. Observed in the manual
// tests in the Foo::Foo() constructor with both gdb 6.8 and 7.1.
QByteArray args = '"' + breakLocation(fileName).toLocal8Bit() + '"' + ':'
+ QByteArray::number(lineNumber);
2010-03-10 16:17:40 +01:00
postCommand("-exec-until " + args, RunRequest, CB(handleExecuteContinue));
#endif
2008-12-02 12:01:29 +01:00
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeRunToFunction(const QString &functionName)
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
2008-12-02 12:01:29 +01:00
setTokenBarrier();
postCommand("-break-insert -t " + functionName.toLatin1());
continueInferiorInternal();
//setState(InferiorRunningRequested);
//postCommand("-exec-continue", handleExecRunToFunction);
showStatusMessage(tr("Run to function %1 requested...").arg(functionName), 5000);
2008-12-02 12:01:29 +01:00
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeJumpToLine(const QString &fileName, int lineNumber)
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
StackFrame frame;
frame.file = fileName;
frame.line = lineNumber;
2008-12-02 12:01:29 +01:00
#if 1
QByteArray loc = '"' + breakLocation(fileName).toLocal8Bit() + '"' + ':'
+ QByteArray::number(lineNumber);
postCommand("tbreak " + loc);
2009-10-22 22:55:09 +02:00
setState(InferiorRunningRequested);
postCommand("jump " + loc, RunRequest);
2008-12-02 12:01:29 +01:00
// will produce something like
// &"jump \"/home/apoenitz/dev/work/test1/test1.cpp\":242"
2008-12-02 12:01:29 +01:00
// ~"Continuing at 0x4058f3."
// ~"run1 (argc=1, argv=0x7fffbf1f5538) at test1.cpp:242"
// ~"242\t x *= 2;"
// 23^done"
2009-09-17 16:51:27 +02:00
gotoLocation(frame, true);
2008-12-02 12:01:29 +01:00
//setBreakpoint();
//postCommand("jump " + loc);
2008-12-02 12:01:29 +01:00
#else
2009-09-17 16:51:27 +02:00
gotoLocation(frame, true);
2008-12-02 12:01:29 +01:00
setBreakpoint(fileName, lineNumber);
2009-10-22 22:55:09 +02:00
setState(InferiorRunningRequested);
postCommand("jump " + loc, RunRequest);
2008-12-02 12:01:29 +01:00
#endif
}
2010-03-10 16:17:40 +01:00
void GdbEngine::executeReturn()
{
QTC_ASSERT(state() == InferiorStopped, qDebug() << state());
setTokenBarrier();
setState(InferiorRunningRequested);
showStatusMessage(tr("Immediate return from function requested..."), 5000);
2010-03-10 16:17:40 +01:00
postCommand("-exec-finish", RunRequest, CB(handleExecuteReturn));
}
2010-03-10 16:17:40 +01:00
void GdbEngine::handleExecuteReturn(const GdbResponse &response)
{
if (response.resultClass == GdbResultDone) {
updateAll();
}
}
2008-12-02 12:01:29 +01:00
/*!
\fn void GdbEngine::setTokenBarrier()
\brief Discard the results of all pending watch-updating commands.
2008-12-02 12:01:29 +01:00
2009-02-18 10:28:31 +01:00
This method is called at the beginning of all step/next/finish etc.
2008-12-02 12:01:29 +01:00
debugger functions.
If non-watch-updating commands with call-backs are still in the pipe,
it will complain.
2008-12-02 12:01:29 +01:00
*/
void GdbEngine::setTokenBarrier()
{
foreach (const GdbCommand &cookie, m_cookieForToken) {
QTC_ASSERT(!cookie.callback || (cookie.flags & Discardable),
qDebug() << "CMD:" << cookie.command
<< " FLAGS:" << cookie.flags
<< " CALLBACK:" << cookie.callbackName;
return
);
}
PENDING_DEBUG("\n--- token barrier ---\n");
showDebuggerInput(LogMisc, _("--- token barrier ---"));
if (theDebuggerBoolSetting(LogTimeStamps))
showDebuggerInput(LogMisc, currentTime());
2008-12-02 12:01:29 +01:00
m_oldestAcceptableToken = currentToken();
}
//////////////////////////////////////////////////////////////////////
//
// 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->bpEnabled = true;
2008-12-02 12:01:29 +01:00
data->bpCondition.clear();
QByteArray file, fullName;
2008-12-02 12:01:29 +01:00
foreach (const GdbMi &child, bkpt.children()) {
if (child.hasName("number")) {
data->bpNumber = child.data();
2008-12-02 12:01:29 +01:00
} else if (child.hasName("func")) {
data->bpFuncName = _(child.data());
2008-12-02 12:01:29 +01:00
} 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();
2008-12-02 12:01:29 +01:00
} else if (child.hasName("file")) {
file = child.data();
2008-12-02 12:01:29 +01:00
} else if (child.hasName("fullname")) {
fullName = child.data();
2008-12-02 12:01:29 +01:00
} else if (child.hasName("line")) {
data->bpLineNumber = child.data();
if (child.data().toInt() && data->bpCorrectedLineNumber.isEmpty())
data->setMarkerLineNumber(child.data().toInt());
2008-12-02 12:01:29 +01:00
} else if (child.hasName("cond")) {
data->bpCondition = child.data();
2008-12-02 12:01:29 +01:00
// 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("enabled")) {
data->bpEnabled = (child.data() == "y");
} else if (child.hasName("pending")) {
2008-12-02 12:01:29 +01:00
data->pending = true;
// Any content here would be interesting only if we did accept
// spontaneously appearing breakpoints (user using gdb commands).
} else if (child.hasName("at")) {
// Happens with (e.g.?) gdb 6.4 symbianelf
QByteArray ba = child.data();
if (ba.startsWith('<') && ba.endsWith('>'))
ba = ba.mid(1, ba.size() - 2);
data->bpFuncName = _(ba);
} else if (child.hasName("thread")) {
data->bpThreadSpec = child.data();
2008-12-02 12:01:29 +01:00
}
}
// 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;
if (!fullName.isEmpty()) {
name = cleanupFullName(QFile::decodeName(fullName));
if (data->markerFileName().isEmpty())
data->setMarkerFileName(name);
} else {
name = QFile::decodeName(file);
// Use fullName() once we have a mapping which is more complete than
// gdb's own. No point in assigning markerFileName for now.
}
if (!name.isEmpty())
data->bpFileName = name;
2008-12-02 12:01:29 +01:00
}
QString GdbEngine::breakLocation(const QString &file) const
{
//QTC_ASSERT(!m_breakListOutdated, /* */)
QString where = m_fullToShortName.value(file);
if (where.isEmpty())
return QFileInfo(file).fileName();
return where;
2008-12-02 12:01:29 +01:00
}
QByteArray GdbEngine::breakpointLocation(const BreakpointData *data)
2008-12-02 12:01:29 +01:00
{
if (!data->funcName.isEmpty())
return data->funcName.toLatin1();
// In this case, data->funcName is something like '*0xdeadbeef'
if (data->lineNumber.toInt() == 0)
return data->funcName.toLatin1();
QString loc = data->useFullPath ? data->fileName : breakLocation(data->fileName);
// The argument is simply a C-quoted version of the argument to the
// non-MI "break" command, including the "original" quoting it wants.
return "\"\\\"" + GdbMi::escapeCString(loc).toLocal8Bit() + "\\\":"
+ data->lineNumber + '"';
}
2008-12-02 12:01:29 +01:00
void GdbEngine::sendInsertBreakpoint(int index)
{
const BreakpointData *data = manager()->breakHandler()->at(index);
// Set up fallback in case of pending breakpoints which aren't handled
// by the MI interface.
QByteArray cmd;
if (m_isMacGdb) {
cmd = "-break-insert -l -1 -f ";
} else if (m_gdbAdapter->isTrkAdapter()) {
cmd = "-break-insert -h -f ";
} else if (m_gdbVersion >= 70000) {
cmd = "-break-insert ";
if (!data->threadSpec.isEmpty())
cmd += "-p " + data->threadSpec;
cmd += " -f ";
} else if (m_gdbVersion >= 60800) {
// Probably some earlier version would work as well.
cmd = "-break-insert -f ";
} else {
cmd = "-break-insert ";
}
2008-12-02 12:01:29 +01:00
//if (!data->condition.isEmpty())
// cmd += "-c " + data->condition + ' ';
cmd += breakpointLocation(data);
postCommand(cmd, NeedsStop | RebuildBreakpointModel,
CB(handleBreakInsert1), index);
}
void GdbEngine::handleBreakInsert1(const GdbResponse &response)
{
int index = response.cookie.toInt();
BreakpointData *data = manager()->breakHandler()->at(index);
if (response.resultClass == GdbResultDone) {
// Interesting only on Mac?
GdbMi bkpt = response.data.findChild("bkpt");
breakpointDataFromOutput(data, bkpt);
} else {
// Some versions of gdb like "GNU gdb (GDB) SUSE (6.8.91.20090930-2.4)"
// know how to do pending breakpoints using CLI but not MI. So try
// again with MI.
QByteArray cmd = "break " + breakpointLocation(data);
postCommand(cmd, NeedsStop | RebuildBreakpointModel,
CB(handleBreakInsert2), index);
}
}
void GdbEngine::handleBreakInsert2(const GdbResponse &response)
{
if (response.resultClass == GdbResultError) {
if (m_gdbVersion < 60800 && !m_isMacGdb) {
// This gdb version doesn't "do" pending breakpoints.
// Not much we can do about it except implementing the
// logic on top of shared library events, and that's not
// worth the effort.
} else {
QTC_ASSERT(false, /**/);
}
}
2008-12-02 12:01:29 +01:00
}
void GdbEngine::reloadBreakListInternal()
2008-12-02 12:01:29 +01:00
{
postCommand("-break-list",
NeedsStop | RebuildBreakpointModel,
CB(handleBreakList));
}
void GdbEngine::handleBreakList(const GdbResponse &response)
{
2008-12-02 12:01:29 +01:00
// 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 (response.resultClass == GdbResultDone) {
GdbMi table = response.data.findChild("BreakpointTable");
2008-12-02 12:01:29 +01:00
if (table.isValid())
handleBreakList(table);
}
}
void GdbEngine::handleBreakList(const GdbMi &table)
{
GdbMi body = table.findChild("body");
QList<GdbMi> bkpts;
if (body.isValid()) {
// Non-Mac
bkpts = body.children();
} else {
// Mac
bkpts = table.children();
2009-10-02 13:24:41 +02:00
// Remove the 'hdr' and artificial items.
2008-12-02 12:01:29 +01:00
for (int i = bkpts.size(); --i >= 0; ) {
int num = bkpts.at(i).findChild("number").data().toInt();
2009-10-02 13:24:41 +02:00
if (num <= 0)
2008-12-02 12:01:29 +01:00
bkpts.removeAt(i);
}
//qDebug() << "LEFT" << bkpts.size() << "BREAKPOINTS";
2008-12-02 12:01:29 +01:00
}
BreakHandler *handler = manager()->breakHandler();
2008-12-02 12:01:29 +01:00
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();
2008-12-02 12:01:29 +01:00
}
m_breakListOutdated = false;
2008-12-02 12:01:29 +01:00
}
void GdbEngine::handleBreakDisable(const GdbResponse &response)
{
BreakHandler *handler = manager()->breakHandler();
if (response.resultClass == GdbResultDone) {
handler->updateMarkers();
}
}
void GdbEngine::handleBreakIgnore(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
int bpNumber = response.cookie.toInt();
2008-12-02 12:01:29 +01:00
// 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 = manager()->breakHandler();
BreakpointData *data = handler->findBreakpoint(bpNumber);
if (response.resultClass == GdbResultDone && data) {
QString msg = _(response.data.findChild("consolestreamoutput").data());
//if (msg.contains(__("Will stop next time breakpoint"))) {
// data->bpIgnoreCount = _("0");
//} else if (msg.contains(__("Will ignore next"))) {
2008-12-02 12:01:29 +01:00
// data->bpIgnoreCount = data->ignoreCount;
//}
// FIXME: this assumes it is doing the right thing...
data->bpIgnoreCount = data->ignoreCount;
handler->updateMarkers();
}
}
void GdbEngine::handleBreakCondition(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
int bpNumber = response.cookie.toInt();
BreakHandler *handler = manager()->breakHandler();
BreakpointData *data = handler->findBreakpoint(bpNumber);
if (response.resultClass == GdbResultDone) {
2009-10-02 13:24:41 +02:00
// We just assume it was successful. Otherwise we had to parse
// the output stream data.
//qDebug() << "HANDLE BREAK CONDITION" << bpNumber << data->condition;
2008-12-02 12:01:29 +01:00
data->bpCondition = data->condition;
} else {
QByteArray msg = response.data.findChild("msg").data();
2008-12-02 12:01:29 +01:00
// happens on Mac
if (1 || msg.startsWith("Error parsing breakpoint condition. "
" Will try again when we hit the breakpoint.")) {
//qDebug() << "ERROR BREAK CONDITION" << bpNumber << data->condition;
2008-12-02 12:01:29 +01:00
data->bpCondition = data->condition;
}
}
handler->updateMarkers();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::extractDataFromInfoBreak(const QString &output, BreakpointData *data)
{
data->bpFileName = _("<MULTIPLE>");
2008-12-02 12:01:29 +01:00
//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
2009-02-16 18:56:20 +01:00
// - or -
2008-12-02 12:01:29 +01:00
// everything on a single line on Windows for constructors of classes
// within namespaces.
// Sometimes the path is relative too.
2009-02-16 18:56:20 +01:00
// 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]|$)"));
2008-12-02 12:01:29 +01:00
re.setMinimal(true);
if (re.indexIn(output) != -1) {
data->bpAddress = re.cap(1).toLatin1();
2008-12-02 12:01:29 +01:00
data->bpFuncName = re.cap(2).trimmed();
data->bpLineNumber = re.cap(4).toLatin1();
2008-12-02 12:01:29 +01:00
QString full = fullName(re.cap(3));
if (full.isEmpty()) {
2010-01-11 10:22:55 +01:00
// FIXME: This happens without UsePreciseBreakpoints regularly.
// We need to revive that "fill full name mapping bit by bit"
// approach of 1.2.x
//qDebug() << "NO FULL NAME KNOWN FOR" << re.cap(3);
full = cleanupFullName(re.cap(3));
if (full.isEmpty()) {
qDebug() << "FILE IS NOT RESOLVABLE" << re.cap(3);
full = re.cap(3); // FIXME: wrong, but prevents recursion
}
}
// The variable full could still contain, say "foo.cpp" when we asked
// for "/full/path/to/foo.cpp". In this case, using the requested
// instead of the acknowledged name makes sense as it will allow setting
// the marker in more cases.
if (data->fileName.endsWith(full))
full = data->fileName;
data->setMarkerLineNumber(data->bpLineNumber.toInt());
if (data->markerFileName().isEmpty()) {
qDebug() << "111";
data->setMarkerFileName(full);
}
2008-12-02 12:01:29 +01:00
data->bpFileName = full;
} else {
qDebug() << "COULD NOT MATCH " << re.pattern() << " AND " << output;
data->bpNumber = "<unavailable>";
2008-12-02 12:01:29 +01:00
}
}
void GdbEngine::handleBreakInfo(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
if (response.resultClass == GdbResultDone) {
2008-12-02 12:01:29 +01:00
// Old-style output for multiple breakpoints, presumably in a
// constructor.
const int bpNumber = response.cookie.toInt();
const BreakHandler *handler = manager()->breakHandler();
BreakpointData *data = handler->findBreakpoint(bpNumber);
if (data) {
QString str = QString::fromLocal8Bit(
response.data.findChild("consolestreamoutput").data());
extractDataFromInfoBreak(str, data);
}
}
}
void GdbEngine::handleInfoLine(const GdbResponse &response)
{
if (response.resultClass == GdbResultDone) {
// Old-style output: "Line 1102 of \"simple/app.cpp\" starts
// at address 0x80526aa <_Z10...+131> and ends at 0x80526b5
// <_Z10testQStackv+142>.\n"
const int bpNumber = response.cookie.toInt();
const BreakHandler *handler = manager()->breakHandler();
BreakpointData *data = handler->findBreakpoint(bpNumber);
if (!data)
return;
QByteArray ba = response.data.findChild("consolestreamoutput").data();
const int pos = ba.indexOf(' ', 5);
if (ba.startsWith("Line ") && pos != -1) {
const QByteArray line = ba.mid(5, pos - 5);
data->bpCorrectedLineNumber = line;
data->setMarkerLineNumber(line.toInt());
2008-12-02 12:01:29 +01:00
}
}
}
void GdbEngine::attemptBreakpointSynchronization()
{
QTC_ASSERT(!m_sourcesListUpdating,
qDebug() << "SOURCES LIST CURRENTLY UPDATING"; return);
debugMessage(tr("ATTEMPT BREAKPOINT SYNC"));
switch (state()) {
case InferiorStarting:
case InferiorRunningRequested:
case InferiorRunning:
case InferiorStopping:
case InferiorStopped:
break;
default:
//qDebug() << "attempted breakpoint sync in state" << state();
return;
}
// For best results, we rely on an up-to-date fullname mapping.
// The listing completion will retrigger us, so no futher action is needed.
if (m_sourcesListOutdated && theDebuggerBoolSetting(UsePreciseBreakpoints)) {
if (state() == InferiorRunning) {
// FIXME: this is a hack
// The hack solves the problem that we want both commands
// (reloadSourceFiles and reloadBreakList) to be executed
// within the same stop-executecommand-continue cycle.
// Just calling reloadSourceFiles and reloadBreakList doesn't work
// in this case, because a) stopping the executable is asynchronous,
// b) we wouldn't want to stop-exec-continue twice
m_sourcesListUpdating = true;
GdbCommand cmd;
cmd.command = "-file-list-exec-source-files";
cmd.flags = NoFlags;
cmd.callback = &GdbEngine::handleQuerySources;
cmd.callbackName = "";
m_commandsToRunOnTemporaryBreak.append(cmd);
} else {
reloadSourceFilesInternal();
}
reloadBreakListInternal();
return;
}
if (m_breakListOutdated) {
reloadBreakListInternal();
return;
}
BreakHandler *handler = manager()->breakHandler();
2008-12-02 12:01:29 +01:00
foreach (BreakpointData *data, handler->takeDisabledBreakpoints()) {
QByteArray bpNumber = data->bpNumber;
if (!bpNumber.trimmed().isEmpty()) {
postCommand("-break-disable " + bpNumber,
NeedsStop | RebuildBreakpointModel);
data->bpEnabled = false;
}
}
foreach (BreakpointData *data, handler->takeEnabledBreakpoints()) {
QByteArray bpNumber = data->bpNumber;
if (!bpNumber.trimmed().isEmpty()) {
postCommand("-break-enable " + bpNumber,
NeedsStop | RebuildBreakpointModel);
data->bpEnabled = true;
}
}
2008-12-02 12:01:29 +01:00
foreach (BreakpointData *data, handler->takeRemovedBreakpoints()) {
QByteArray bpNumber = data->bpNumber;
debugMessage(_("DELETING BP " + bpNumber + " IN "
+ data->markerFileName().toLocal8Bit()));
2008-12-02 12:01:29 +01:00
if (!bpNumber.trimmed().isEmpty())
postCommand("-break-delete " + bpNumber,
NeedsStop | RebuildBreakpointModel);
2008-12-02 12:01:29 +01:00
delete data;
}
for (int index = 0; index != handler->size(); ++index) {
BreakpointData *data = handler->at(index);
if (data->bpNumber.isEmpty()) { // Unset breakpoint?
data->bpNumber = " "; // Sent, but no feedback yet.
2008-12-02 12:01:29 +01:00
sendInsertBreakpoint(index);
} else if (data->bpNumber.toInt()) {
if (data->bpMultiple && data->bpFileName.isEmpty()) {
postCommand("info break " + data->bpNumber,
NeedsStop | RebuildBreakpointModel,
CB(handleBreakInfo), data->bpNumber.toInt());
continue;
}
if (data->condition != data->bpCondition && !data->conditionsMatch()) {
// Update conditions if needed.
postCommand("condition " + data->bpNumber + ' ' + data->condition,
NeedsStop | RebuildBreakpointModel,
CB(handleBreakCondition), data->bpNumber.toInt());
}
else // Because gdb won't do both changes at a time anyway.
if (data->ignoreCount != data->bpIgnoreCount) {
// Update ignorecount if needed.
QByteArray ic = QByteArray::number(data->ignoreCount.toInt());
postCommand("ignore " + data->bpNumber + ' ' + ic,
NeedsStop | RebuildBreakpointModel,
CB(handleBreakIgnore), data->bpNumber.toInt());
continue;
}
if (!data->enabled && data->bpEnabled) {
postCommand("-break-disable " + data->bpNumber,
NeedsStop | RebuildBreakpointModel,
CB(handleBreakInfo));
data->bpEnabled = false;
continue;
}
if (data->threadSpec != data->bpThreadSpec && !data->bpNumber.isEmpty()) {
// The only way to change this seems to be to re-set the bp completely.
//qDebug() << "FIXME: THREAD: " << data->threadSpec << data->bpThreadSpec;
data->bpThreadSpec.clear();
postCommand("-break-delete " + data->bpNumber,
NeedsStop | RebuildBreakpointModel);
sendInsertBreakpoint(index);
continue;
}
2010-03-26 15:22:20 +01:00
if (data->bpAddress.startsWith("0x")
&& data->bpCorrectedLineNumber.isEmpty()) {
// Prevent endless loop.
data->bpCorrectedLineNumber = " ";
postCommand("info line *" + data->bpAddress,
NeedsStop | RebuildBreakpointModel,
CB(handleInfoLine), data->bpNumber.toInt());
}
2008-12-02 12:01:29 +01:00
}
}
handler->updateMarkers();
2008-12-02 12:01:29 +01:00
}
//////////////////////////////////////////////////////////////////////
//
// Modules specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::loadSymbols(const QString &moduleName)
{
// FIXME: gdb does not understand quoted names here (tested with 6.8)
postCommand("sharedlibrary " + dotEscape(moduleName.toLocal8Bit()));
reloadModulesInternal();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::loadAllSymbols()
{
postCommand("sharedlibrary .*");
reloadModulesInternal();
2008-12-02 12:01:29 +01:00
}
2010-05-03 19:12:52 +02:00
void GdbEngine::requestModuleSymbols(const QString &moduleName)
{
QList<Symbol> rc;
bool success = false;
QString errorMessage;
do {
const QString nmBinary = _("nm");
QProcess proc;
proc.start(nmBinary, QStringList() << _("-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(_("([0-9a-f]+)?\\s+([^\\s]+)\\s+([^\\s]+)"));
Q_ASSERT(re.isValid());
foreach (const QString &line, contents.split(_c('\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));
2010-05-03 19:12:52 +02:00
manager()->showModuleSymbols(moduleName, rc);
}
2008-12-02 12:01:29 +01:00
void GdbEngine::reloadModules()
{
if (state() == InferiorRunning || state() == InferiorStopped)
reloadModulesInternal();
}
void GdbEngine::reloadModulesInternal()
2008-12-02 12:01:29 +01:00
{
m_modulesListOutdated = false;
postCommand("info shared", NeedsStop, CB(handleModulesList));
#if 0
if (m_gdbVersion < 70000 && !m_isMacGdb)
postCommand("set stop-on-solib-events 1");
#endif
2008-12-02 12:01:29 +01:00
}
void GdbEngine::handleModulesList(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
QList<Module> modules;
if (response.resultClass == GdbResultDone) {
2009-10-02 13:24:41 +02:00
// That's console-based output, likely Linux or Windows,
// but we can avoid the #ifdef here.
QString data = QString::fromLocal8Bit(response.data.findChild("consolestreamoutput").data());
2008-12-02 12:01:29 +01:00
QTextStream ts(&data, QIODevice::ReadOnly);
while (!ts.atEnd()) {
QString line = ts.readLine();
Module module;
QString symbolsRead;
QTextStream ts(&line, QIODevice::ReadOnly);
if (line.startsWith(__("0x"))) {
ts >> module.startAddress >> module.endAddress >> symbolsRead;
module.moduleName = ts.readLine().trimmed();
module.symbolsRead = (symbolsRead == __("Yes"));
modules.append(module);
} else if (line.trimmed().startsWith(__("No"))) {
// gdb 6.4 symbianelf
ts >> symbolsRead;
QTC_ASSERT(symbolsRead == __("No"), continue);
module.moduleName = ts.readLine().trimmed();
modules.append(module);
}
2008-12-02 12:01:29 +01:00
}
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, response.data.children()) {
Module module;
module.moduleName = QString::fromLocal8Bit(item.findChild("path").data());
module.symbolsRead = (item.findChild("state").data() == "Y");
module.startAddress = _(item.findChild("loaded_addr").data());
2009-04-30 22:22:37 +02:00
//: End address of loaded module
module.endAddress = tr("<unknown>", "address");
modules.append(module);
}
}
2008-12-02 12:01:29 +01:00
}
manager()->modulesHandler()->setModules(modules);
2008-12-02 12:01:29 +01:00
}
//////////////////////////////////////////////////////////////////////
//
// Source files specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::invalidateSourcesList()
{
m_modulesListOutdated = true;
m_sourcesListOutdated = true;
m_breakListOutdated = true;
}
void GdbEngine::reloadSourceFiles()
{
if ((state() == InferiorRunning || state() == InferiorStopped)
&& !m_sourcesListUpdating)
reloadSourceFilesInternal();
}
void GdbEngine::reloadSourceFilesInternal()
{
QTC_ASSERT(!m_sourcesListUpdating, /**/);
m_sourcesListUpdating = true;
postCommand("-file-list-exec-source-files", NeedsStop, CB(handleQuerySources));
#if 0
if (m_gdbVersion < 70000 && !m_isMacGdb)
postCommand("set stop-on-solib-events 1");
#endif
}
2008-12-02 12:01:29 +01:00
//////////////////////////////////////////////////////////////////////
//
// Stack specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::selectThread(int index)
{
ThreadsHandler *threadsHandler = manager()->threadsHandler();
threadsHandler->setCurrentThread(index);
QList<ThreadData> threads = threadsHandler->threads();
QTC_ASSERT(index < threads.size(), return);
int id = threads.at(index).id;
showStatusMessage(tr("Retrieving data for stack view..."), 10000);
postCommand("-thread-select " + QByteArray::number(id), CB(handleStackSelectThread));
}
void GdbEngine::handleStackSelectThread(const GdbResponse &)
2008-12-02 12:01:29 +01:00
{
QTC_ASSERT(state() == InferiorUnrunnable || state() == InferiorStopped, /**/);
//qDebug("FIXME: StackHandler::handleOutput: SelectThread");
showStatusMessage(tr("Retrieving data for stack view..."), 3000);
manager()->reloadRegisters();
reloadStack(true);
updateLocals();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::reloadFullStack()
{
PENDING_DEBUG("RELOAD FULL STACK");
postCommand("-stack-list-frames", CB(handleStackListFrames),
QVariant::fromValue<StackCookie>(StackCookie(true, true)));
}
void GdbEngine::reloadStack(bool forceGotoLocation)
{
PENDING_DEBUG("RELOAD STACK");
QByteArray cmd = "-stack-list-frames";
int stackDepth = theDebuggerAction(MaximalStackDepth)->value().toInt();
if (stackDepth && !m_gdbAdapter->isTrkAdapter())
cmd += " 0 " + QByteArray::number(stackDepth);
// FIXME: gdb 6.4 symbianelf likes to be asked twice. The first time it
// returns with "^error,msg="Previous frame identical to this frame
// (corrupt stack?)". Might be related to the fact that we can't
// access the memory belonging to the lower frames. But as we know
// this sometimes happens, ask the second time immediately instead
// of waiting for the first request to fail.
// FIXME: Seems to work with 6.8.
if (m_gdbAdapter->isTrkAdapter() && m_gdbVersion < 6.8)
postCommand(cmd);
postCommand(cmd, CB(handleStackListFrames),
QVariant::fromValue<StackCookie>(StackCookie(false, forceGotoLocation)));
}
2008-12-02 12:01:29 +01:00
StackFrame GdbEngine::parseStackFrame(const GdbMi &frameMi, int level)
{
//qDebug() << "HANDLING FRAME:" << frameMi.toString();
StackFrame frame;
frame.level = level;
GdbMi fullName = frameMi.findChild("fullname");
if (fullName.isValid())
frame.file = cleanupFullName(QFile::decodeName(fullName.data()));
else
frame.file = QFile::decodeName(frameMi.findChild("file").data());
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());
return frame;
}
void GdbEngine::handleStackListFrames(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
bool handleIt = (m_isMacGdb || response.resultClass == GdbResultDone);
if (!handleIt) {
// That always happens on symbian gdb with
// ^error,data={msg="Previous frame identical to this frame (corrupt stack?)"
// logstreamoutput="Previous frame identical to this frame (corrupt stack?)\n"
//qDebug() << "LISTING STACK FAILED: " << response.toString();
return;
}
2008-12-02 12:01:29 +01:00
StackCookie cookie = response.cookie.value<StackCookie>();
QList<StackFrame> stackFrames;
2009-09-14 09:46:34 +02:00
GdbMi stack = response.data.findChild("stack");
if (!stack.isValid()) {
qDebug() << "FIXME: stack:" << stack.toString();
return;
}
2009-09-14 09:46:34 +02:00
int targetFrame = -1;
2009-09-14 09:46:34 +02:00
int n = stack.childCount();
for (int i = 0; i != n; ++i) {
stackFrames.append(parseStackFrame(stack.childAt(i), i));
const StackFrame &frame = stackFrames.back();
2008-12-02 12:01:29 +01:00
#if defined(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 (targetFrame == -1 && isBogus) {
setTokenBarrier();
setState(InferiorRunningRequested);
2010-03-10 16:51:06 +01:00
postCommand("-exec-finish", RunRequest, CB(handleExecuteContinue));
showStatusMessage(tr("Jumping out of bogus frame..."), 1000);
return;
2009-09-14 09:46:34 +02:00
}
#endif
// Initialize top frame to the first valid frame.
const bool isValid = frame.isUsable() && !frame.function.isEmpty();
if (isValid && targetFrame == -1)
targetFrame = i;
2008-12-02 12:01:29 +01:00
}
bool canExpand = !cookie.isFull
&& (n >= theDebuggerAction(MaximalStackDepth)->value().toInt());
theDebuggerAction(ExpandStack)->setEnabled(canExpand);
manager()->stackHandler()->setFrames(stackFrames, canExpand);
2008-12-02 12:01:29 +01:00
// We can't jump to any file if we don't have any frames.
if (stackFrames.isEmpty())
return;
2008-12-02 12:01:29 +01:00
// targetFrame contains the top most frame for which we have source
// information. That's typically the frame we'd like to jump to, with
// a few exceptions:
// Always jump to frame #0 when stepping by instruction.
if (theDebuggerBoolSetting(OperateByInstruction))
targetFrame = 0;
// If there is no frame with source, jump to frame #0.
if (targetFrame == -1)
targetFrame = 0;
// Mac gdb does not add the location to the "stopped" message,
// so the early gotoLocation() was not triggered. Force it here.
// For targetFrame == 0 we already issued a 'gotoLocation'
// when reading the *stopped message.
bool jump = (m_isMacGdb || targetFrame != 0);
manager()->stackHandler()->setCurrentIndex(targetFrame);
if (jump || cookie.gotoLocation)
activateFrame(targetFrame);
2008-12-02 12:01:29 +01:00
}
void GdbEngine::activateFrame(int frameIndex)
{
m_manager->resetLocation();
if (state() != InferiorStopped && state() != InferiorUnrunnable)
2008-12-02 12:01:29 +01:00
return;
StackHandler *stackHandler = manager()->stackHandler();
2008-12-02 12:01:29 +01:00
int oldIndex = stackHandler->currentIndex();
if (frameIndex == stackHandler->stackSize()) {
reloadFullStack();
return;
}
2008-12-09 12:08:56 +01:00
QTC_ASSERT(frameIndex < stackHandler->stackSize(), return);
2008-12-02 12:01:29 +01:00
if (oldIndex != frameIndex) {
// Assuming the command always succeeds this saves a roundtrip.
2008-12-02 12:01:29 +01:00
// Otherwise the lines below would need to get triggered
// after a response to this -stack-select-frame here.
stackHandler->setCurrentIndex(frameIndex);
postCommand("-stack-select-frame " + QByteArray::number(frameIndex),
CB(handleStackSelectFrame));
2008-12-02 12:01:29 +01:00
}
gotoLocation(stackHandler->currentFrame(), true);
2008-12-02 12:01:29 +01:00
}
void GdbEngine::handleStackSelectFrame(const GdbResponse &response)
{
Q_UNUSED(response);
updateLocals();
reloadRegisters();
}
void GdbEngine::handleStackListThreads(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
int id = response.cookie.toInt();
2008-12-02 12:01:29 +01:00
// "72^done,{thread-ids={thread-id="2",thread-id="1"},number-of-threads="2"}
const QList<GdbMi> items = response.data.findChild("thread-ids").children();
2008-12-02 12:01:29 +01:00
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:" << response.toString();
2008-12-02 12:01:29 +01:00
currentIndex = index;
}
}
ThreadsHandler *threadsHandler = manager()->threadsHandler();
2008-12-02 12:01:29 +01:00
threadsHandler->setThreads(threads);
threadsHandler->setCurrentThread(currentIndex);
}
//////////////////////////////////////////////////////////////////////
//
// Snapshot specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::makeSnapshot()
{
QString fileName;
QTemporaryFile tf(QDir::tempPath() + _("/gdbsnapshot"));
if (tf.open()) {
fileName = tf.fileName();
tf.close();
postCommand("gcore " + fileName.toLocal8Bit(),
NeedsStop, CB(handleMakeSnapshot), fileName);
} else {
showMessageBox(QMessageBox::Critical, tr("Snapshot Creation Error"),
tr("Cannot create snapshot file."));
}
}
void GdbEngine::handleMakeSnapshot(const GdbResponse &response)
{
if (response.resultClass == GdbResultDone) {
SnapshotData snapshot;
snapshot.setDate(QDateTime::currentDateTime());
snapshot.setLocation(response.cookie.toString());
snapshot.setFrames(manager()->stackHandler()->frames());
manager()->snapshotHandler()->appendSnapshot(snapshot);
} else {
QByteArray msg = response.data.findChild("msg").data();
showMessageBox(QMessageBox::Critical, tr("Snapshot Creation Error"),
tr("Cannot create snapshot:\n") + QString::fromLocal8Bit(msg));
}
}
void GdbEngine::activateSnapshot(int index)
{
SnapshotData snapshot = m_manager->snapshotHandler()->setCurrentIndex(index);
m_startParameters->startMode = AttachCore;
m_startParameters->coreFile = snapshot.location();
if (state() == InferiorUnrunnable) {
// All is well. We are looking at another core file.
setState(EngineShuttingDown);
setState(DebuggerNotReady);
activateSnapshot2();
} else if (state() != DebuggerNotReady) {
QMessageBox *mb = showMessageBox(QMessageBox::Critical,
tr("Snapshot Reloading"),
tr("In order to load snapshots the debugged process needs "
"to be stopped. Continuation will not be possible afterwards.\n"
"Do you want to stop the debugged process and load the selected "
"snapshot?"), QMessageBox::Ok | QMessageBox::Cancel);
if (mb->exec() == QMessageBox::Cancel)
return;
debugMessage(_("KILLING DEBUGGER AS REQUESTED BY USER"));
delete m_gdbAdapter;
m_gdbAdapter = createAdapter(m_startParameters);
postCommand("kill", NeedsStop, CB(handleActivateSnapshot));
} else {
activateSnapshot2();
}
}
void GdbEngine::handleActivateSnapshot(const GdbResponse &response)
{
Q_UNUSED(response);
setState(InferiorShuttingDown);
setState(InferiorShutDown);
setState(EngineShuttingDown);
setState(DebuggerNotReady);
activateSnapshot2();
}
void GdbEngine::activateSnapshot2()
{
// Otherwise the stack data might be stale.
// See http://sourceware.org/bugzilla/show_bug.cgi?id=1124.
setState(EngineStarting);
setState(AdapterStarting);
postCommand("set stack-cache off");
handleAdapterStarted();
}
2008-12-02 12:01:29 +01:00
//////////////////////////////////////////////////////////////////////
//
// Register specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::reloadRegisters()
{
if (state() != InferiorStopped && state() != InferiorUnrunnable)
return;
if (!m_registerNamesListed) {
postCommand("-data-list-register-names", CB(handleRegisterListNames));
m_registerNamesListed = true;
// FIXME: Maybe better completely re-do this logic in TRK adapter.
if (m_gdbAdapter->isTrkAdapter())
return;
}
if (m_gdbAdapter->isTrkAdapter()) {
m_gdbAdapter->trkReloadRegisters();
} else {
postCommand("-data-list-register-values x",
Discardable, CB(handleRegisterListValues));
}
2008-12-02 12:01:29 +01:00
}
void GdbEngine::setRegisterValue(int nr, const QString &value)
{
Register reg = manager()->registerHandler()->registers().at(nr);
//qDebug() << "NOT IMPLEMENTED: CHANGE REGISTER " << nr << reg.name << ":"
// << value;
postCommand("-var-delete \"R@\"");
postCommand("-var-create \"R@\" * $" + reg.name);
postCommand("-var-assign \"R@\" " + value.toLatin1());
postCommand("-var-delete \"R@\"");
//postCommand("-data-list-register-values d",
// Discardable, CB(handleRegisterListValues));
reloadRegisters();
}
void GdbEngine::handleRegisterListNames(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
if (response.resultClass != GdbResultDone) {
m_registerNamesListed = false;
2008-12-02 12:01:29 +01:00
return;
}
2008-12-02 12:01:29 +01:00
Registers registers;
foreach (const GdbMi &item, response.data.findChild("register-names").children())
registers.append(Register(item.data()));
2008-12-02 12:01:29 +01:00
manager()->registerHandler()->setRegisters(registers);
if (m_gdbAdapter->isTrkAdapter())
m_gdbAdapter->trkReloadRegisters();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::handleRegisterListValues(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
if (response.resultClass != GdbResultDone)
2008-12-02 12:01:29 +01:00
return;
Registers registers = manager()->registerHandler()->registers();
2008-12-02 12:01:29 +01:00
// 24^done,register-values=[{number="0",value="0xf423f"},...]
const GdbMi values = response.data.findChild("register-values");
foreach (const GdbMi &item, values.children()) {
const int index = item.findChild("number").data().toInt();
2008-12-02 12:01:29 +01:00
if (index < registers.size()) {
Register &reg = registers[index];
GdbMi val = item.findChild("value");
QByteArray ba;
bool handled = false;
if (val.data().startsWith("{")) {
int pos1 = val.data().indexOf("v2_int32");
if (pos1 == -1)
pos1 = val.data().indexOf("v4_int32");
if (pos1 != -1) {
// FIXME: This block wastes cycles.
pos1 = val.data().indexOf("{", pos1 + 1) + 1;
int pos2 = val.data().indexOf("}", pos1);
QByteArray ba2 = val.data().mid(pos1, pos2 - pos1);
foreach (QByteArray ba3, ba2.split(',')) {
ba3 = ba3.trimmed();
QTC_ASSERT(ba3.size() >= 3, continue);
QTC_ASSERT(ba3.size() <= 10, continue);
ba.prepend(QByteArray(10 - ba3.size(), '0'));
ba.prepend(ba3.mid(2));
}
ba.prepend("0x");
handled = true;
}
}
const QString value = _(handled ? ba : val.data());
2008-12-02 12:01:29 +01:00
reg.changed = (value != reg.value);
if (reg.changed)
reg.value = value;
}
}
manager()->registerHandler()->setRegisters(registers);
2008-12-02 12:01:29 +01:00
}
//////////////////////////////////////////////////////////////////////
//
// Thread specific stuff
//
//////////////////////////////////////////////////////////////////////
bool GdbEngine::supportsThreads() const
{
// FSF gdb 6.3 crashes happily on -thread-list-ids. So don't use it.
2008-12-02 12:01:29 +01:00
// The test below is a semi-random pick, 6.8 works fine
return m_isMacGdb || m_gdbVersion > 60500;
2008-12-02 12:01:29 +01:00
}
2008-12-02 12:01:29 +01:00
//////////////////////////////////////////////////////////////////////
//
// Tooltip specific stuff
//
//////////////////////////////////////////////////////////////////////
QString GdbEngine::m_toolTipExpression;
QPoint GdbEngine::m_toolTipPos;
2008-12-02 12:01:29 +01:00
bool GdbEngine::showToolTip()
{
WatchHandler *handler = manager()->watchHandler();
WatchModel *model = handler->model(TooltipsWatch);
QByteArray iname = tooltipINameForExpression(m_toolTipExpression.toLatin1());
WatchItem *item = model->findItem(iname, model->rootItem());
if (!item) {
hideDebuggerToolTip();
return false;
}
QModelIndex index = model->watchIndex(item);
showDebuggerToolTip(m_toolTipPos, model, index, m_toolTipExpression);
return true;
}
void GdbEngine::setToolTipExpression(const QPoint &mousePos,
TextEditor::ITextEditor *editor, int cursorPos)
2008-12-02 12:01:29 +01:00
{
if (state() != InferiorStopped || !isCppEditor(editor)) {
//qDebug() << "SUPPRESSING DEBUGGER TOOLTIP, INFERIOR NOT STOPPED/Non Cpp editor";
2008-12-02 12:01:29 +01:00
return;
}
2009-02-16 18:56:20 +01:00
if (theDebuggerBoolSetting(DebugDebuggingHelpers)) {
2008-12-02 12:01:29 +01:00
// minimize interference
return;
}
m_toolTipPos = mousePos;
int line, column;
QString exp = cppExpressionAt(editor, cursorPos, &line, &column);
if (exp == m_toolTipExpression)
return;
m_toolTipExpression = exp;
// FIXME: enable caching
//if (showToolTip())
// return;
2008-12-02 12:01:29 +01:00
if (exp.isEmpty() || exp.startsWith(_c('#'))) {
//QToolTip::hideText();
2008-12-02 12:01:29 +01:00
return;
}
if (!hasLetterOrNumber(exp)) {
//QToolTip::showText(m_toolTipPos,
// tr("'%1' contains no identifier").arg(exp));
2008-12-02 12:01:29 +01:00
return;
}
if (isKeyWord(exp))
return;
if (exp.startsWith(_c('"')) && exp.endsWith(_c('"'))) {
//QToolTip::showText(m_toolTipPos, tr("String literal %1").arg(exp));
2008-12-02 12:01:29 +01:00
return;
}
if (exp.startsWith(__("++")) || exp.startsWith(__("--")))
2008-12-02 12:01:29 +01:00
exp = exp.mid(2);
if (exp.endsWith(__("++")) || exp.endsWith(__("--")))
2008-12-02 12:01:29 +01:00
exp = exp.mid(2);
if (exp.startsWith(_c('<')) || exp.startsWith(_c('[')))
2008-12-02 12:01:29 +01:00
return;
if (hasSideEffects(exp)) {
//QToolTip::showText(m_toolTipPos,
// tr("Cowardly refusing to evaluate expression '%1' "
// "with potential side effects").arg(exp));
2008-12-02 12:01:29 +01:00
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;
2008-12-02 12:01:29 +01:00
if (m_currentLocals.childAt(i).type.startsWith(exp)) {
QToolTip::showText(m_toolTipPos,
tr("%1: type of current 'this'").arg(exp));
2008-12-02 12:01:29 +01:00
qDebug() << " TOOLTIP CRASH SUPPRESSED";
return;
}
break;
}
}
*/
if (isSynchroneous()) {
updateLocals(QVariant());
return;
}
WatchData toolTip;
toolTip.exp = exp.toLatin1();
toolTip.name = exp;
toolTip.iname = tooltipINameForExpression(toolTip.exp);
manager()->watchHandler()->removeData(toolTip.iname);
manager()->watchHandler()->insertData(toolTip);
2008-12-02 12:01:29 +01:00
}
//////////////////////////////////////////////////////////////////////
//
// Watch specific stuff
//
//////////////////////////////////////////////////////////////////////
void GdbEngine::setAutoDerefPointers(const QVariant &on)
{
Q_UNUSED(on)
setTokenBarrier();
updateLocals();
}
bool GdbEngine::hasDebuggingHelperForType(const QString &type) const
2008-12-02 12:01:29 +01:00
{
if (!theDebuggerBoolSetting(UseDebuggingHelpers))
2008-12-02 12:01:29 +01:00
return false;
if (m_gdbAdapter->dumperHandling() == AbstractGdbAdapter::DumperNotAvailable) {
// "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)
&& manager()->stackHandler()->isDebuggingDebuggingHelpers())
2008-12-02 12:01:29 +01:00
return false;
if (m_debuggingHelperState != DebuggingHelperAvailable)
2008-12-02 12:01:29 +01:00
return false;
// simple types
return m_dumperHelper.type(type) != QtDumperHelper::UnknownType;
2008-12-02 12:01:29 +01:00
}
void GdbEngine::updateWatchData(const WatchData &data)
2009-08-28 17:31:45 +02:00
{
if (isSynchroneous()) {
// This should only be called for fresh expanded items, not for
// items that had their children retrieved earlier.
//qDebug() << "\nUPDATE WATCH DATA: " << data.toString() << "\n";
#if 0
WatchData data1 = data;
data1.setAllUnneeded();
insertData(data1);
rebuildModel();
#else
if (data.iname.endsWith("."))
return;
// Avoid endless loops created by faulty dumpers.
QByteArray processedName = "1-" + data.iname;
//qDebug() << "PROCESSED NAMES: " << processedName << m_processedNames;
if (m_processedNames.contains(processedName)) {
WatchData data1 = data;
showDebuggerInput(LogStatus,
_("<Breaking endless loop for " + data.iname + '>'));
data1.setAllUnneeded();
data1.setValue(_("<unavailable>"));
data1.setHasChildren(false);
insertData(data1);
return;
}
m_processedNames.insert(processedName);
updateLocals();
#endif
} else {
// Bump requests to avoid model rebuilding during the nested
// updateWatchModel runs.
++m_pendingWatchRequests;
PENDING_DEBUG("UPDATE WATCH BUMPS PENDING UP TO " << m_pendingWatchRequests);
#if 1
QMetaObject::invokeMethod(this, "updateWatchDataHelper",
Qt::QueuedConnection, Q_ARG(WatchData, data));
#else
updateWatchDataHelper(data);
#endif
}
2009-08-28 17:31:45 +02:00
}
void GdbEngine::updateWatchDataHelper(const WatchData &data)
2008-12-02 12:01:29 +01:00
{
//m_pendingRequests = 0;
PENDING_DEBUG("UPDATE WATCH DATA");
#if DEBUG_PENDING
//qDebug() << "##############################################";
qDebug() << "UPDATE MODEL, FOUND INCOMPLETE:";
//qDebug() << data.toString();
#endif
updateSubItemClassic(data);
//PENDING_DEBUG("INTERNAL TRIGGERING UPDATE WATCH MODEL");
--m_pendingWatchRequests;
PENDING_DEBUG("UPDATE WATCH DONE BUMPS PENDING DOWN TO " << m_pendingWatchRequests);
if (m_pendingWatchRequests <= 0)
rebuildWatchModel();
2008-12-02 12:01:29 +01:00
}
void GdbEngine::rebuildWatchModel()
2008-12-02 12:01:29 +01:00
{
static int count = 0;
++count;
if (!isSynchroneous())
m_processedNames.clear();
PENDING_DEBUG("REBUILDING MODEL" << count);
if (theDebuggerBoolSetting(LogTimeStamps))
showDebuggerInput(LogMisc, currentTime());
showDebuggerInput(LogStatus, _("<Rebuild Watchmodel %1>").arg(count));
showStatusMessage(tr("Finished retrieving data"), 400);
manager()->watchHandler()->endCycle();
showToolTip();
2008-12-02 12:01:29 +01:00
}
static QByteArray arrayFillCommand(const char *array, const QByteArray &params)
2008-12-02 12:01:29 +01:00
{
char buf[50];
sprintf(buf, "set {char[%d]} &%s = {", params.size(), array);
2008-12-02 12:01:29 +01:00
QByteArray encoded;
encoded.append(buf);
const int size = params.size();
for (int i = 0; i != size; ++i) {
2008-12-02 12:01:29 +01:00
sprintf(buf, "%d,", int(params[i]));
encoded.append(buf);
}
encoded[encoded.size() - 1] = '}';
return encoded;
}
void GdbEngine::sendWatchParameters(const QByteArray &params0)
{
QByteArray params = params0;
params.append('\0');
const QByteArray inBufferCmd = arrayFillCommand("qDumpInBuffer", params);
2008-12-02 12:01:29 +01:00
params.replace('\0','!');
showDebuggerInput(LogMisc, QString::fromUtf8(params));
params.clear();
params.append('\0');
const QByteArray outBufferCmd = arrayFillCommand("qDumpOutBuffer", params);
postCommand(inBufferCmd);
postCommand(outBufferCmd);
2008-12-02 12:01:29 +01:00
}
void GdbEngine::handleVarAssign(const GdbResponse &)
2008-12-02 12:01:29 +01:00
{
// Everything might have changed, force re-evaluation.
setTokenBarrier();
2008-12-02 12:01:29 +01:00
updateLocals();
}
void GdbEngine::handleVarCreate(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
WatchData data = response.cookie.value<WatchData>();
// Happens e.g. when we already issued a var-evaluate command.
2008-12-02 12:01:29 +01:00
if (!data.isValid())
return;
//qDebug() << "HANDLE VARIABLE CREATION:" << data.toString();
if (response.resultClass == GdbResultDone) {
2008-12-02 12:01:29 +01:00
data.variable = data.iname;
setWatchDataType(data, response.data.findChild("type"));
if (manager()->watchHandler()->isExpandedIName(data.iname)
&& !response.data.findChild("children").isValid())
data.setChildrenNeeded();
else
data.setChildrenUnneeded();
setWatchDataChildCount(data, response.data.findChild("numchild"));
insertData(data);
} else {
data.setError(QString::fromLocal8Bit(response.data.findChild("msg").data()));
2008-12-02 12:01:29 +01:00
if (data.isWatcher()) {
data.value = WatchData::msgNotInScope();
data.type = _(" ");
2008-12-02 12:01:29 +01:00
data.setAllUnneeded();
data.setHasChildren(false);
data.valueEnabled = false;
data.valueEditable = false;
2008-12-02 12:01:29 +01:00
insertData(data);
}
}
}
void GdbEngine::handleDebuggingHelperSetup(const GdbResponse &response)
2008-12-02 12:01:29 +01:00
{
if (response.resultClass == GdbResultDone) {
} else {
QString msg = QString::fromLocal8Bit(response.data.findChild("msg").data());
showStatusMessage(tr("Custom dumper setup: %1").arg(msg), 10000);
2008-12-02 12:01:29 +01:00
}
}
void GdbEngine::updateLocals(const QVariant &cookie)
2008-12-02 12:01:29 +01:00
{
m_pendingWatchRequests = 0;
m_pendingBreakpointRequests = 0;
if (hasPython())
updateLocalsPython(QByteArray());
else
updateLocalsClassic(cookie);
}
2008-12-03 16:51:56 +01:00
// Parse a local variable from GdbMi.
WatchData GdbEngine::localVariable(const GdbMi &item,
const QStringList &uninitializedVariables,
QMap<QByteArray, int> *seen)
2009-02-16 18:56:20 +01:00
{
// 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:
QByteArray name;
if (m_isMacGdb) {
int numExps = 0;
foreach (const GdbMi &child, item.children())
numExps += int(child.name() == "exp");
if (numExps > 1)
return WatchData();
name = item.findChild("exp").data();
} else {
name = item.findChild("name").data();
}
const QMap<QByteArray, int>::iterator it = seen->find(name);
if (it != seen->end()) {
const int n = it.value();
++(it.value());
WatchData data;
QString nam = _(name);
data.iname = "local." + name + QByteArray::number(n + 1);
//: Variable %1 is the variable name, %2 is a simple count.
data.name = WatchData::shadowedName(nam, n);
if (uninitializedVariables.contains(data.name)) {
data.setError(WatchData::msgNotInScope());
return data;
}
//: Type of local variable or parameter shadowed by another
//: variable of the same name in a nested block.
setWatchDataValue(data, item);
data.setType(GdbEngine::tr("<shadowed>"));
data.setHasChildren(false);
return data;
}
seen->insert(name, 1);
WatchData data;
QString nam = _(name);
data.iname = "local." + name;
data.name = nam;
data.exp = name;
data.framekey = m_currentFrame + data.name;
setWatchDataType(data, item.findChild("type"));
if (uninitializedVariables.contains(data.name)) {
data.setError(WatchData::msgNotInScope());
return data;
}
if (isSynchroneous()) {
setWatchDataValue(data, item);
// We know that the complete list of children is
// somewhere in the response.
data.setChildrenUnneeded();
} else {
// 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);
if (isSymbianIntType(data.type)) {
setWatchDataValue(data, item);
data.setHasChildren(false);
2008-12-02 12:01:29 +01:00
}
}
if (!m_manager->watchHandler()->isExpandedIName(data.iname))
data.setChildrenUnneeded();
GdbMi t = item.findChild("numchild");
if (t.isValid())
data.setHasChildren(t.data().toInt() > 0);
else if (isPointerType(data.type) || data.name == __("this"))
data.setHasChildren(true);
return data;
2008-12-02 12:01:29 +01:00
}
void GdbEngine::insertData(const WatchData &data0)
{
PENDING_DEBUG("INSERT DATA" << data0.toString());
2008-12-02 12:01:29 +01:00
WatchData data = data0;
if (data.value.startsWith(__("mi_cmd_var_create:"))) {
qDebug() << "BOGUS VALUE:" << data.toString();
2008-12-02 12:01:29 +01:00
return;
}
manager()->watchHandler()->insertData(data);
2008-12-02 12:01:29 +01:00
}
void GdbEngine::assignValueInDebugger(const QString &expression, const QString &value)
{
postCommand("-var-delete assign");
postCommand("-var-create assign * " + expression.toLatin1());
postCommand("-var-assign assign " + GdbMi::escapeCString(value.toLatin1()),
2010-02-11 13:21:38 +01:00
Discardable, CB(handleVarAssign));
2008-12-02 12:01:29 +01:00
}
QString GdbEngine::qtDumperLibraryName() const
{
return m_manager->qtDumperLibraryName();
}
void GdbEngine::watchPoint(const QPoint &pnt)
{
//qDebug() << "WATCH " << pnt;
QByteArray x = QByteArray::number(pnt.x());
QByteArray y = QByteArray::number(pnt.y());
postCommand("call (void*)watchPoint(" + x + ',' + y + ')',
NeedsStop, CB(handleWatchPoint));
}
void GdbEngine::handleWatchPoint(const GdbResponse &response)
{
//qDebug() << "HANDLE WATCH POINT:" << response.toString();
if (response.resultClass == GdbResultDone) {
GdbMi contents = response.data.findChild("consolestreamoutput");
// "$5 = (void *) 0xbfa7ebfc\n"
QString str = _(parsePlainConsoleStream(response));
// "(void *) 0xbfa7ebfc"
QString addr = str.mid(9);
QString ns = m_dumperHelper.qtNamespace();
QString type = ns.isEmpty() ? _("QWidget*") : _("'%1QWidget'*").arg(ns);
QString exp = _("(*(%1)%2)").arg(type).arg(addr);
theDebuggerAction(WatchExpression)->trigger(exp);
}
}
struct MemoryAgentCookie
{
MemoryAgentCookie() : agent(0), token(0), address(0) {}
MemoryAgentCookie(MemoryViewAgent *agent_, QObject *token_, quint64 address_)
: agent(agent_), token(token_), address(address_)
{}
QPointer<MemoryViewAgent> agent;
QPointer<QObject> token;
quint64 address;
};
void GdbEngine::fetchMemory(MemoryViewAgent *agent, QObject *token, quint64 addr,
quint64 length)
{
//qDebug() << "GDB MEMORY FETCH" << agent << addr << length;
postCommand("-data-read-memory " + QByteArray::number(addr) + " x 1 1 "
+ QByteArray::number(length),
NeedsStop, CB(handleFetchMemory),
QVariant::fromValue(MemoryAgentCookie(agent, token, addr)));
}
void GdbEngine::handleFetchMemory(const GdbResponse &response)
{
// ^done,addr="0x08910c88",nr-bytes="16",total-bytes="16",
// next-row="0x08910c98",prev-row="0x08910c78",next-page="0x08910c98",
// prev-page="0x08910c78",memory=[{addr="0x08910c88",
// data=["1","0","0","0","5","0","0","0","0","0","0","0","0","0","0","0"]}]
MemoryAgentCookie ac = response.cookie.value<MemoryAgentCookie>();
QTC_ASSERT(ac.agent, return);
QByteArray ba;
GdbMi memory = response.data.findChild("memory");
QTC_ASSERT(memory.children().size() <= 1, return);
if (memory.children().isEmpty())
return;
GdbMi memory0 = memory.children().at(0); // we asked for only one 'row'
GdbMi data = memory0.findChild("data");
foreach (const GdbMi &child, data.children()) {
bool ok = true;
unsigned char c = '?';
c = child.data().toUInt(&ok, 0);
QTC_ASSERT(ok, return);
ba.append(c);
}
ac.agent->addLazyData(ac.token, ac.address, ba);
}
class DisassemblerAgentCookie
{
public:
DisassemblerAgentCookie() : agent(0), attempts(0) {}
DisassemblerAgentCookie(DisassemblerViewAgent *agent_)
: agent(agent_), attempts(0)
{}
public:
QPointer<DisassemblerViewAgent> agent;
int attempts;
};
// FIXME: add agent->frame() accessor and use that
void GdbEngine::fetchDisassembler(DisassemblerViewAgent *agent)
{
fetchDisassemblerByCli(agent, agent->isMixed());
/*
if (agent->isMixed()) {
// Disassemble full function:
const StackFrame &frame = agent->frame();
QByteArray cmd = "-data-disassemble"
" -f " + frame.file.toLocal8Bit() +
" -l " + QByteArray::number(frame.line) + " -n -1 -- 1";
postCommand(cmd, Discardable, CB(handleFetchDisassemblerByLine),
QVariant::fromValue(DisassemblerAgentCookie(agent)));
} else {
fetchDisassemblerByAddress(agent, true);
}
*/
}
void GdbEngine::fetchDisassemblerByAddress(const DisassemblerAgentCookie &ac0,
bool useMixedMode)
{
DisassemblerAgentCookie ac = ac0;
QTC_ASSERT(ac.agent, return);
bool ok = true;
quint64 address = ac.agent->address().toULongLong(&ok, 0);
QTC_ASSERT(ok, qDebug() << "ADDRESS: " << ac.agent->address() << address; return);
QByteArray start = QByteArray::number(address - 20, 16);
QByteArray end = QByteArray::number(address + 100, 16);
// -data-disassemble [ -s start-addr -e end-addr ]
// | [ -f filename -l linenum [ -n lines ] ] -- mode
++ac.attempts;
if (useMixedMode)
postCommand("-data-disassemble -s 0x" + start + " -e 0x" + end + " -- 1",
Discardable, CB(handleFetchDisassemblerByAddress1),
QVariant::fromValue(ac));
else
postCommand("-data-disassemble -s 0x" + start + " -e 0x" + end + " -- 0",
Discardable, CB(handleFetchDisassemblerByAddress0),
QVariant::fromValue(ac));
}
void GdbEngine::fetchDisassemblerByCli(const DisassemblerAgentCookie &ac0,
bool useMixedMode)
{
DisassemblerAgentCookie ac = ac0;
QTC_ASSERT(ac.agent, return);
bool ok = false;
quint64 address = ac.agent->address().toULongLong(&ok, 0);
QByteArray cmd = "disassemble ";
if (useMixedMode && m_gdbVersion >= 60850)
cmd += "/m ";
cmd += " 0x";
cmd += QByteArray::number(address, 16);
++ac.attempts;
postCommand(cmd, Discardable, CB(handleFetchDisassemblerByCli),
QVariant::fromValue(ac));
}
void GdbEngine::fetchDisassemblerByAddressCli(const DisassemblerAgentCookie &ac0)
{
DisassemblerAgentCookie ac = ac0;
QTC_ASSERT(ac.agent, return);
bool ok = false;
quint64 address = ac.agent->address().toULongLong(&ok, 0);
QByteArray start = QByteArray::number(address - 20, 16);
QByteArray end = QByteArray::number(address + 100, 16);
// There have been changes to the syntax sometime between 7.0 and 7.1.
// As it is unclear how a given incarnation of gdb behaves without
// actually calling the command, try both.
const char sep = (ac.attempts % 2) ? ',' : ' ';
QByteArray cmd = "disassemble 0x" + start + sep + "0x" + end;
++ac.attempts;
postCommand(cmd, Discardable, CB(handleFetchDisassemblerByCli),
QVariant::fromValue(ac));
}
static QByteArray parseLine(const GdbMi &line)
{
QByteArray ba;
ba.reserve(200);
QByteArray address = line.findChild("address").data();
//QByteArray funcName = line.findChild("func-name").data();
//QByteArray offset = line.findChild("offset").data();
QByteArray inst = line.findChild("inst").data();
ba += address + QByteArray(15 - address.size(), ' ');
//ba += funcName + "+" + offset + " ";
//ba += QByteArray(30 - funcName.size() - offset.size(), ' ');
ba += inst;
ba += '\n';
return ba;
}
QString GdbEngine::parseDisassembler(const GdbMi &lines)
{
// ^done,data={asm_insns=[src_and_asm_line={line="1243",file=".../app.cpp",
// line_asm_insn=[{address="0x08054857",func-name="main",offset="27",
// inst="call 0x80545b0 <_Z13testQFileInfov>"}]},
// src_and_asm_line={line="1244",file=".../app.cpp",
// line_asm_insn=[{address="0x0805485c",func-name="main",offset="32",
//inst="call 0x804cba1 <_Z11testObject1v>"}]}]}
// - or -
// ^done,asm_insns=[
// {address="0x0805acf8",func-name="...",offset="25",inst="and $0xe8,%al"},
// {address="0x0805acfa",func-name="...",offset="27",inst="pop %esp"},
QList<QByteArray> fileContents;
bool fileLoaded = false;
QByteArray ba;
ba.reserve(200 * lines.children().size());
// FIXME: Performance?
foreach (const GdbMi &child, lines.children()) {
if (child.hasName("src_and_asm_line")) {
// mixed mode
if (!fileLoaded) {
2009-10-22 20:04:59 +02:00
QString fileName = QFile::decodeName(child.findChild("file").data());
fileName = cleanupFullName(fileName);
QFile file(fileName);
file.open(QIODevice::ReadOnly);
fileContents = file.readAll().split('\n');
fileLoaded = true;
}
2009-10-22 20:04:59 +02:00
int line = child.findChild("line").data().toInt();
if (line >= 1 && line <= fileContents.size())
ba += " " + fileContents.at(line - 1) + '\n';
GdbMi insn = child.findChild("line_asm_insn");
foreach (const GdbMi &line, insn.children())
ba += parseLine(line);
} else {
// the non-mixed version
ba += parseLine(child);
}
}
return _(ba);
}
void GdbEngine::handleFetchDisassemblerByLine(const GdbResponse &response)
{
DisassemblerAgentCookie ac = response.cookie.value<DisassemblerAgentCookie>();
QTC_ASSERT(ac.agent, return);
if (response.resultClass == GdbResultDone) {
GdbMi lines = response.data.findChild("asm_insns");
if (lines.children().isEmpty())
fetchDisassemblerByAddress(ac, true);
else if (lines.children().size() == 1
&& lines.childAt(0).findChild("line").data() == "0")
fetchDisassemblerByAddress(ac, true);
else {
QString contents = parseDisassembler(lines);
if (ac.agent->contentsCoversAddress(contents)) {
// All is well.
ac.agent->setContents(contents);
} else {
// Can happen e.g. for initializer list on symbian/rvct where
// we get a file name and line number but where the 'fully
// disassembled function' does not cover the code in the
// initializer list. Fall back needed:
//fetchDisassemblerByAddress(ac.agent, true);
fetchDisassemblerByCli(ac, true);
}
}
} else {
// 536^error,msg="mi_cmd_disassemble: Invalid line number"
QByteArray msg = response.data.findChild("msg").data();
if (msg == "mi_cmd_disassemble: Invalid line number"
|| msg.startsWith("Cannot access memory at address"))
fetchDisassemblerByAddress(ac, true);
else
showStatusMessage(tr("Disassembler failed: %1")
.arg(QString::fromLocal8Bit(msg)), 5000);
}
}
void GdbEngine::handleFetchDisassemblerByAddress1(const GdbResponse &response)
{
DisassemblerAgentCookie ac = response.cookie.value<DisassemblerAgentCookie>();
QTC_ASSERT(ac.agent, return);
if (response.resultClass == GdbResultDone) {
GdbMi lines = response.data.findChild("asm_insns");
if (lines.children().isEmpty())
fetchDisassemblerByAddress(ac, false);
else {
QString contents = parseDisassembler(lines);
if (ac.agent->contentsCoversAddress(contents)) {
ac.agent->setContents(parseDisassembler(lines));
} else {
debugMessage(_("FALL BACK TO NON-MIXED"));
fetchDisassemblerByAddress(ac, false);
}
}
} else {
// 26^error,msg="Cannot access memory at address 0x801ca308"
QByteArray msg = response.data.findChild("msg").data();
showStatusMessage(tr("Disassembler failed: %1")
.arg(QString::fromLocal8Bit(msg)), 5000);
}
}
void GdbEngine::handleFetchDisassemblerByAddress0(const GdbResponse &response)
{
DisassemblerAgentCookie ac = response.cookie.value<DisassemblerAgentCookie>();
QTC_ASSERT(ac.agent, return);
if (response.resultClass == GdbResultDone) {
GdbMi lines = response.data.findChild("asm_insns");
ac.agent->setContents(parseDisassembler(lines));
} else {
QByteArray msg = response.data.findChild("msg").data();
showStatusMessage(tr("Disassembler failed: %1")
.arg(QString::fromLocal8Bit(msg)), 5000);
}
}
void GdbEngine::handleFetchDisassemblerByCli(const GdbResponse &response)
{
DisassemblerAgentCookie ac = response.cookie.value<DisassemblerAgentCookie>();
QTC_ASSERT(ac.agent, return);
if (response.resultClass == GdbResultDone) {
// Apple's gdb produces MI output even for CLI commands.
GdbMi lines = response.data.findChild("asm_insns");
if (lines.isValid()) {
ac.agent->setContents(parseDisassembler(lines));
} else {
const QString someSpace = _(" ");
// First line is something like
// "Dump of assembler code from 0xb7ff598f to 0xb7ff5a07:"
GdbMi output = response.data.findChild("consolestreamoutput");
QStringList res;
foreach (QByteArray line, output.data().split('\n')) {
line = line.trimmed();
if (line.startsWith("=> "))
line = line.mid(3);
if (line.startsWith("Current language:"))
continue;
if (line.startsWith("The current source"))
continue;
if (line.startsWith("0x")) {
int pos1 = line.indexOf('<');
int pos2 = line.indexOf('+', pos1);
int pos3 = line.indexOf('>', pos2);
if (pos3 >= 0) {
QByteArray ba = " <+" + line.mid(pos2 + 1, pos3 - pos2 - 1);
ba = line.left(pos1 - 1) + ba.rightJustified(4)
+ ">: " + line.mid(pos3 + 2);
res.append(_(ba));
} else {
res.append(_(line));
}
continue;
}
res.append(someSpace + _(line));
}
// Drop "End of assembler dump." line.
res.takeLast();
if (res.size() > 1)
ac.agent->setContents(res.join(_("\n")));
else
fetchDisassemblerByAddressCli(ac);
}
} else {
QByteArray msg = response.data.findChild("msg").data();
//76^error,msg="No function contains program counter for selected..."
//76^error,msg="No function contains specified address."
//>568^error,msg="Line number 0 out of range;
if (ac.attempts < 4 // Try once more.
&& (msg.startsWith("No function ") || msg.startsWith("Line number ")))
fetchDisassemblerByAddressCli(ac);
else
showStatusMessage(tr("Disassembler failed: %1")
.arg(QString::fromLocal8Bit(msg)), 5000);
}
}
2009-09-17 16:51:27 +02:00
void GdbEngine::gotoLocation(const StackFrame &frame, bool setMarker)
{
// qDebug() << "GOTO " << frame << setMarker;
2009-09-17 16:51:27 +02:00
m_manager->gotoLocation(frame, setMarker);
}
2009-09-21 11:09:38 +02:00
//
// Starting up & shutting down
//
bool GdbEngine::startGdb(const QStringList &args, const QString &gdb, const QString &settingsIdHint)
2009-09-21 11:09:38 +02:00
{
gdbProc()->disconnect(); // From any previous runs
m_gdb = QString::fromLatin1(qgetenv("QTC_DEBUGGER_PATH"));
if (m_gdb.isEmpty())
m_gdb = m_gdbBinaryToolChainMap->key(m_startParameters->toolChainType);
if (m_gdb.isEmpty())
m_gdb = gdb;
if (m_gdb.isEmpty()) {
handleAdapterStartFailed(msgNoBinaryForToolChain(m_startParameters->toolChainType),
GdbOptionsPage::settingsId());
return false;
}
debugMessage(_("STARTING GDB ") + m_gdb);
QStringList gdbArgs;
gdbArgs << _("-i");
gdbArgs << _("mi");
gdbArgs += args;
#ifdef Q_OS_WIN
// Set python path. By convention, python is located below gdb executable.
// Extend the environment set on the process in startAdapter().
const QFileInfo fi(m_gdb);
bool foundPython = false;
if (fi.isAbsolute()) {
const QString winPythonVersion = QLatin1String(winPythonVersionC);
const QDir dir = fi.absoluteDir();
if (dir.exists(winPythonVersion)) {
QProcessEnvironment environment = gdbProc()->processEnvironment();
const QString pythonPathVariable = QLatin1String("PYTHONPATH");
// Check for existing values.
if (environment.contains(pythonPathVariable)) {
const QString oldPythonPath = environment.value(pythonPathVariable);
manager()->showDebuggerOutput(LogMisc,
_("Using existing python path: %1").arg(oldPythonPath));
} else {
const QString pythonPath =
QDir::toNativeSeparators(dir.absoluteFilePath(winPythonVersion));
environment.insert(pythonPathVariable, pythonPath);
manager()->showDebuggerOutput(LogMisc,
_("Python path: %1").arg(pythonPath));
gdbProc()->setProcessEnvironment(environment);
}
foundPython = true;
}
}
if (!foundPython) {
debugMessage(_("UNSUPPORTED GDB %1 DOES NOT HAVE PYTHON.").arg(m_gdb));
showStatusMessage(_("Gdb at %1 does not have python").arg(m_gdb));
}
#endif
connect(gdbProc(), SIGNAL(error(QProcess::ProcessError)),
SLOT(handleGdbError(QProcess::ProcessError)));
connect(gdbProc(), SIGNAL(finished(int, QProcess::ExitStatus)),
SLOT(handleGdbFinished(int, QProcess::ExitStatus)));
connect(gdbProc(), SIGNAL(readyReadStandardOutput()),
SLOT(readGdbStandardOutput()));
connect(gdbProc(), SIGNAL(readyReadStandardError()),
SLOT(readGdbStandardError()));
gdbProc()->start(m_gdb, gdbArgs);
if (!gdbProc()->waitForStarted()) {
const QString msg = tr("Unable to start gdb '%1': %2")
.arg(m_gdb, gdbProc()->errorString());
handleAdapterStartFailed(msg, settingsIdHint);
return false;
}
const QByteArray dumperSourcePath =
Core::ICore::instance()->resourcePath().toLocal8Bit() + "/gdbmacros/";
debugMessage(_("GDB STARTED, INITIALIZING IT"));
m_commandTimer->setInterval(commandTimeoutTime());
2009-09-21 11:09:38 +02:00
postCommand("show version", CB(handleShowVersion));
//postCommand("-enable-timings");
postCommand("set print static-members off"); // Seemingly doesn't work.
//postCommand("set debug infrun 1");
//postCommand("define hook-stop\n-thread-list-ids\n-stack-list-frames\nend");
//postCommand("define hook-stop\nprint 4\nend");
//postCommand("define hookpost-stop\nprint 5\nend");
//postCommand("define hook-call\nprint 6\nend");
//postCommand("define hookpost-call\nprint 7\nend");
//postCommand("set print object on"); // works with CLI, but not MI
//postCommand("set step-mode on"); // we can't work with that yes
//postCommand("set exec-done-display on");
//postCommand("set print pretty on");
//postCommand("set confirm off");
//postCommand("set pagination off");
// The following does not work with 6.3.50-20050815 (Apple version gdb-1344)
// (Mac OS 10.6), but does so for gdb-966 (10.5):
//postCommand("set print inferior-events 1");
postCommand("set breakpoint pending on");
postCommand("set print elements 10000");
// Produces a few messages during symtab loading
//postCommand("set verbose on");
2009-09-21 11:09:38 +02:00
//postCommand("set substitute-path /var/tmp/qt-x11-src-4.5.0 "
// "/home/sandbox/qtsdk-2009.01/qt");
2009-09-21 11:09:38 +02:00
// 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:
postCommand("set overload-resolution off");
2009-09-21 11:09:38 +02:00
//postCommand(_("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.
2010-01-11 10:22:55 +01:00
// We need "print" as otherwise we will get no feedback whatsoever
// when Custom DebuggingHelper crash (which happen regularly when accessing
// uninitialized variables).
postCommand("handle SIGSEGV nopass stop print");
2009-09-21 11:09:38 +02:00
// This is useful to kill the inferior whenever gdb dies.
//postCommand(_("handle SIGTERM pass nostop print"));
postCommand("set unwindonsignal on");
//postCommand("pwd");
postCommand("set width 0");
postCommand("set height 0");
2009-09-21 11:09:38 +02:00
if (m_isMacGdb) {
postCommand("-gdb-set inferior-auto-start-cfm off");
postCommand("-gdb-set sharedLibrary load-rules "
2009-09-21 11:09:38 +02:00
"dyld \".*libSystem.*\" all "
"dyld \".*libauto.*\" all "
"dyld \".*AppKit.*\" all "
"dyld \".*PBGDBIntrospectionSupport.*\" all "
"dyld \".*Foundation.*\" all "
"dyld \".*CFDataFormatters.*\" all "
"dyld \".*libobjc.*\" all "
"dyld \".*CarbonDataFormatters.*\" all");
}
2009-09-21 11:09:38 +02:00
QString scriptFileName = theDebuggerStringSetting(GdbScriptFile);
if (!scriptFileName.isEmpty()) {
if (QFileInfo(scriptFileName).isReadable()) {
postCommand("source " + scriptFileName.toLocal8Bit());
2009-09-21 11:09:38 +02:00
} else {
showMessageBox(QMessageBox::Warning,
2009-09-21 11:09:38 +02:00
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));
}
}
postCommand("-interpreter-exec console "
"\"python execfile('" + dumperSourcePath + "dumper.py')\"",
NonCriticalResponse);
postCommand("-interpreter-exec console "
"\"python execfile('" + dumperSourcePath + "gdbmacros.py')\"",
NonCriticalResponse);
postCommand("-interpreter-exec console \"help bb\"",
CB(handleHasPython));
return true;
}
2009-10-13 09:53:15 +02:00
bool GdbEngine::checkDebuggingHelpers()
{
return !hasPython() && checkDebuggingHelpersClassic();
}
void GdbEngine::handleGdbError(QProcess::ProcessError error)
{
debugMessage(_("HANDLE GDB ERROR"));
switch (error) {
case QProcess::Crashed:
break; // will get a processExited() as well
// impossible case QProcess::FailedToStart:
case QProcess::ReadError:
case QProcess::WriteError:
case QProcess::Timedout:
default:
gdbProc()->kill();
setState(EngineShuttingDown, true);
showMessageBox(QMessageBox::Critical, tr("Gdb I/O Error"),
errorMessage(error));
break;
}
2009-09-21 11:09:38 +02:00
}
void GdbEngine::handleGdbFinished(int code, QProcess::ExitStatus type)
2009-09-21 11:09:38 +02:00
{
debugMessage(_("GDB PROCESS FINISHED, status %1, code %2").arg(type).arg(code));
if (!m_gdbAdapter) {
debugMessage(_("NO ADAPTER PRESENT"));
} else if (state() == EngineShuttingDown) {
debugMessage(_("GOING TO SHUT DOWN ADAPTER"));
m_gdbAdapter->shutdown();
} else if (state() != AdapterStartFailed) {
QString msg = tr("The gdb process exited unexpectedly (%1).")
.arg((type == QProcess::CrashExit)
? tr("crashed") : tr("code %1").arg(code));
showMessageBox(QMessageBox::Critical, tr("Unexpected Gdb Exit"), msg);
showStatusMessage(msg);
m_gdbAdapter->shutdown();
}
initializeVariables();
setState(DebuggerNotReady, true);
2009-09-21 11:09:38 +02:00
}
void GdbEngine::handleAdapterStartFailed(const QString &msg, const QString &settingsIdHint)
2009-09-21 11:09:38 +02:00
{
setState(AdapterStartFailed);
debugMessage(_("ADAPTER START FAILED"));
if (!msg.isEmpty()) {
const QString title = tr("Adapter start failed");
if (settingsIdHint.isEmpty()) {
Core::ICore::instance()->showWarningWithOptions(title, msg);
} else {
Core::ICore::instance()->showWarningWithOptions(title, msg, QString(),
_(Debugger::Constants::DEBUGGER_SETTINGS_CATEGORY), settingsIdHint);
}
}
shutdown();
2009-09-21 11:09:38 +02:00
}
void GdbEngine::handleAdapterStarted()
2009-09-21 11:09:38 +02:00
{
setState(AdapterStarted);
2010-03-22 16:17:06 +01:00
if (m_progress)
m_progress->setProgressValue(25);
debugMessage(_("ADAPTER SUCCESSFULLY STARTED"));
showStatusMessage(tr("Starting inferior..."));
setState(InferiorStarting);
m_gdbAdapter->startInferior();
2009-09-21 11:09:38 +02:00
}
void GdbEngine::handleInferiorPrepared()
2009-09-21 11:09:38 +02:00
{
const QByteArray qtInstallPath = m_startParameters->qtInstallPath.toLocal8Bit();
2009-11-09 16:25:24 +01:00
if (!qtInstallPath.isEmpty()) {
QByteArray qtBuildPath;
#if defined(Q_OS_WIN)
qtBuildPath = "C:/qt-greenhouse/Trolltech/Code_less_create_more/Trolltech/Code_less_create_more/Troll/4.6/qt";
postCommand("set substitute-path " + qtBuildPath + ' ' + qtInstallPath);
qtBuildPath = "C:/iwmake/build_mingw_opensource";
postCommand("set substitute-path " + qtBuildPath + ' ' + qtInstallPath);
#elif defined(Q_OS_UNIX) && !defined (Q_OS_MAC)
qtBuildPath = "/var/tmp/qt-src";
postCommand("set substitute-path " + qtBuildPath + ' ' + qtInstallPath);
#endif
2009-11-09 16:25:24 +01:00
}
// Initial attempt to set breakpoints
showStatusMessage(tr("Setting breakpoints..."));
debugMessage(tr("Setting breakpoints..."));
2009-09-21 11:09:38 +02:00
attemptBreakpointSynchronization();
2009-10-13 09:53:15 +02:00
if (m_cookieForToken.isEmpty()) {
startInferiorPhase2();
} else {
QTC_ASSERT(m_commandsDoneCallback == 0, /**/);
m_commandsDoneCallback = &GdbEngine::startInferiorPhase2;
}
}
void GdbEngine::startInferiorPhase2()
{
debugMessage(_("BREAKPOINTS SET, CONTINUING INFERIOR STARTUP"));
m_gdbAdapter->startInferiorPhase2();
2009-09-21 11:09:38 +02:00
}
void GdbEngine::handleInferiorStartFailed(const QString &msg)
{
showStatusMessage(tr("Failed to start application: ") + msg);
if (state() == AdapterStartFailed) {
debugMessage(_("INFERIOR START FAILED, BUT ADAPTER DIED ALREADY"));
return; // Adapter crashed meanwhile, so this notification is meaningless.
}
2009-09-21 11:09:38 +02:00
debugMessage(_("INFERIOR START FAILED"));
showMessageBox(QMessageBox::Critical, tr("Failed to start application"), msg);
setState(InferiorStartFailed);
shutdown();
}
void GdbEngine::handleAdapterCrashed(const QString &msg)
{
debugMessage(_("ADAPTER CRASHED"));
2009-09-21 11:09:38 +02:00
// The adapter is expected to have cleaned up after itself when we get here,
// so the effect is about the same as AdapterStartFailed => use it.
// Don't bother with state transitions - this can happen in any state and
// the end result is always the same, so it makes little sense to find a
// "path" which does not assert.
setState(AdapterStartFailed, true);
// No point in being friendly here ...
gdbProc()->kill();
if (!msg.isEmpty())
showMessageBox(QMessageBox::Critical, tr("Adapter crashed"), msg);
}
void GdbEngine::addOptionPages(QList<Core::IOptionsPage *> *opts) const
2008-12-02 12:01:29 +01:00
{
opts->push_back(new GdbOptionsPage(m_gdbBinaryToolChainMap));
2008-12-02 12:01:29 +01:00
}
QMessageBox *GdbEngine::showMessageBox(int icon, const QString &title,
const QString &text, int buttons)
{
return m_manager->showMessageBox(icon, title, text, buttons);
}
void GdbEngine::setUseDebuggingHelpers(const QVariant &on)
{
//qDebug() << "SWITCHING ON/OFF DUMPER DEBUGGING:" << on;
Q_UNUSED(on)
setTokenBarrier();
updateLocals();
}
bool GdbEngine::hasPython() const
{
return m_hasPython;
}
void GdbEngine::createFullBacktrace()
{
postCommand("thread apply all bt", NeedsStop, CB(handleCreateFullBacktrace));
}
void GdbEngine::handleCreateFullBacktrace(const GdbResponse &response)
{
if (response.resultClass == GdbResultDone) {
m_manager->openTextEditor(_("Backtrace $"),
_(response.data.findChild("consolestreamoutput").data()));
}
}
//
// Factory
//
IDebuggerEngine *createGdbEngine(DebuggerManager *manager)
{
return new GdbEngine(manager);
}
} // namespace Internal
} // namespace Debugger
Q_DECLARE_METATYPE(Debugger::Internal::MemoryAgentCookie);
Q_DECLARE_METATYPE(Debugger::Internal::DisassemblerAgentCookie);
Q_DECLARE_METATYPE(Debugger::Internal::GdbMi);