Commit 1d64e169 authored by hjk's avatar hjk
Browse files

debugger: rework startup logic

parent ae2cfdaa
......@@ -33,6 +33,8 @@
#include <QtCore/QObject>
#include <QtCore/QProcess>
#include "gdbengine.h"
namespace Debugger {
namespace Internal {
......@@ -48,15 +50,10 @@ class AbstractGdbAdapter : public QObject
Q_OBJECT
public:
AbstractGdbAdapter(QObject *parent = 0) : QObject(parent) {}
virtual void setEngine(GdbEngine *engine) { m_engine = engine; }
virtual void start(const QString &program, const QStringList &args,
QIODevice::OpenMode mode = QIODevice::ReadWrite) = 0;
virtual void kill() = 0;
virtual void terminate() = 0;
//virtual bool waitForStarted(int msecs = 30000) = 0;
virtual bool waitForFinished(int msecs = 30000) = 0;
AbstractGdbAdapter(GdbEngine *engine, QObject *parent = 0)
: QObject(parent), m_engine(engine)
{}
virtual QProcess::ProcessState state() const = 0;
virtual QString errorString() const = 0;
virtual QByteArray readAllStandardError() = 0;
......@@ -66,18 +63,35 @@ public:
virtual void setEnvironment(const QStringList &env) = 0;
virtual bool isAdapter() const = 0;
virtual void attach() = 0;
virtual void startAdapter(const DebuggerStartParametersPtr &sp) = 0;
virtual void prepareInferior() = 0;
virtual void startInferior() = 0;
virtual void shutdownInferior() = 0;
virtual void shutdownAdapter() = 0;
virtual void interruptInferior() = 0;
signals:
void adapterStarted();
void adapterStartFailed(const QString &msg);
void adapterShutDown();
void adapterShutdownFailed(const QString &msg);
void adapterCrashed();
void inferiorPrepared();
void inferiorPreparationFailed(const QString &msg);
void inferiorStarted();
void inferiorStartFailed(const QString &msg);
void inferiorShutDown();
void inferiorShutdownFailed(const QString &msg);
void inferiorPidChanged(qint64 pid);
void error(QProcess::ProcessError);
void started();
void readyReadStandardOutput();
void readyReadStandardError();
void finished(int, QProcess::ExitStatus);
protected:
GdbEngine *m_engine;
GdbEngine * const m_engine;
};
} // namespace Internal
......
......@@ -2,6 +2,7 @@ include(../../../shared/trk/trk.pri)
HEADERS += \
$$PWD/abstractgdbadapter.h \
$$PWD/plaingdbadapter.h \
$$PWD/gdbmi.h \
$$PWD/gdbengine.h \
$$PWD/gdboptionspage.h \
......@@ -14,10 +15,11 @@ SOURCES += \
$$PWD/gdbmi.cpp \
$$PWD/gdbengine.cpp \
$$PWD/gdboptionspage.cpp \
$$PWD/trkgdbadapter.cpp \
$$PWD/plaingdbadapter.cpp \
$$PWD/trkoptions.cpp \
$$PWD/trkoptionswidget.cpp \
$$PWD/trkoptionspage.cpp
$$PWD/trkoptionspage.cpp \
$$PWD/trkgdbadapter.cpp
FORMS += $$PWD/gdboptionspage.ui \
$$PWD/trkoptionswidget.ui
......
......@@ -33,6 +33,7 @@
#include "gdboptionspage.h"
#include "trkoptions.h"
#include "trkoptionspage.h"
#include "plaingdbadapter.h"
#include "trkgdbadapter.h"
#include "watchutils.h"
......@@ -82,6 +83,9 @@
#endif
#include <ctype.h>
static QString lastFile;
static int lastLine;
namespace Debugger {
namespace Internal {
using namespace Debugger::Constants;
......@@ -141,44 +145,13 @@ static QByteArray parsePlainConsoleStream(const GdbResultRecord &record)
return out.mid(pos + 3);
}
///////////////////////////////////////////////////////////////////////
//
// PlainGdbAdapter
//
///////////////////////////////////////////////////////////////////////
void PlainGdbAdapter::attach()
{
QFileInfo fi(m_engine->startParameters().executable);
m_engine->postCommand(_("-file-exec-and-symbols \"%1\"").arg(fi.absoluteFilePath()),
&GdbEngine::handleFileExecAndSymbols, "handleFileExecAndSymbols");
}
void PlainGdbAdapter::interruptInferior()
{
if (m_engine->startMode() == StartRemote) {
m_engine->postCommand(_("-exec-interrupt"));
return;
}
const qint64 attachedPID = m_engine->inferiorPid();
if (attachedPID <= 0) {
m_engine->debugMessage(
_("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED"));
return;
}
if (!interruptProcess(attachedPID))
m_engine->debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
}
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////
GdbEngine::GdbEngine(DebuggerManager *parent, AbstractGdbAdapter *gdbAdapter) :
GdbEngine::GdbEngine(DebuggerManager *parent) :
#ifdef Q_OS_WIN // Do injection loading with MinGW (call loading does not work with 64bit)
m_dumperInjectionLoad(true),
#else
......@@ -187,21 +160,24 @@ GdbEngine::GdbEngine(DebuggerManager *parent, AbstractGdbAdapter *gdbAdapter) :
m_manager(parent),
qq(parent->engineInterface())
{
m_gdbAdapter = gdbAdapter;
m_gdbAdapter->setEngine(this);
m_stubProc.setMode(Core::Utils::ConsoleProcess::Debug);
#ifdef Q_OS_UNIX
m_stubProc.setSettings(Core::ICore::instance()->settings());
#endif
initializeVariables();
initializeConnections();
m_gdbAdapter = 0;
}
GdbEngine::~GdbEngine()
{
// prevent sending error messages afterwards
if (m_gdbAdapter) {
m_gdbAdapter->disconnect(this);
delete m_gdbAdapter;
m_gdbAdapter = 0;
}
}
void GdbEngine::setGdbAdapter(AbstractGdbAdapter *gdbAdapter)
{
m_gdbAdapter = gdbAdapter;
initializeVariables();
initializeConnections();
}
void GdbEngine::initializeConnections()
......@@ -213,16 +189,31 @@ void GdbEngine::initializeConnections()
this, SLOT(readGdbStandardOutput()));
connect(m_gdbAdapter, SIGNAL(readyReadStandardError()),
this, SLOT(readGdbStandardError()));
connect(m_gdbAdapter, SIGNAL(finished(int, QProcess::ExitStatus)),
m_manager, SLOT(exitDebugger()));
connect(m_gdbAdapter, SIGNAL(started()),
this, SLOT(startDebugger2()));
connect(&m_stubProc, SIGNAL(processError(QString)),
this, SLOT(stubError(QString)));
connect(&m_stubProc, SIGNAL(processStarted()),
this, SLOT(stubStarted()));
connect(&m_stubProc, SIGNAL(wrapperStopped()),
connect(m_gdbAdapter, SIGNAL(adapterStarted()),
this, SLOT(handleAdapterStarted()));
connect(m_gdbAdapter, SIGNAL(adapterStartFailed(QString)),
this, SLOT(handleAdapterStartFailed(QString)));
connect(m_gdbAdapter, SIGNAL(adapterShutDown()),
this, SLOT(handleAdapterShutDown()));
connect(m_gdbAdapter, SIGNAL(adapterShutdownFailed(QString)),
this, SLOT(handleAdapterShutdownFailed(QString)));
connect(m_gdbAdapter, SIGNAL(inferiorPrepared()),
this, SLOT(handleInferiorPrepared()));
connect(m_gdbAdapter, SIGNAL(inferiorPreparationFailed(QString)),
this, SLOT(handleInferiorPreparationFailed(QString)));
connect(m_gdbAdapter, SIGNAL(inferiorStarted()),
this, SLOT(handleInferiorStarted()));
connect(m_gdbAdapter, SIGNAL(inferiorStartFailed(QString)),
this, SLOT(handleInferiorStartFailed(QString)));
connect(m_gdbAdapter, SIGNAL(inferiorShutDown()),
this, SLOT(handleInferiorShutDown()));
connect(m_gdbAdapter, SIGNAL(inferiorShutdownFailed(QString)),
this, SLOT(handleInferiorShutdownFailed(QString)));
connect(m_gdbAdapter, SIGNAL(adapterCrashed()),
m_manager, SLOT(exitDebugger()));
connect(&m_uploadProc, SIGNAL(error(QProcess::ProcessError)),
......@@ -269,7 +260,7 @@ void GdbEngine::initializeVariables()
m_oldestAcceptableToken = -1;
m_outputCodec = QTextCodec::codecForLocale();
m_pendingRequests = 0;
m_autoContinue = false;
m_continuationAfterDone = 0;
m_waitingForFirstBreakpointToBeHit = false;
m_commandsToRunOnTemporaryBreak.clear();
m_cookieForToken.clear();
......@@ -287,10 +278,8 @@ void GdbEngine::initializeVariables()
// FIXME: unhandled:
//m_outputCodecState = QTextCodec::ConverterState();
//OutputCollector m_outputCollector;
//QProcess m_gdbAdapter;
//QProcess m_uploadProc;
//Core::Utils::ConsoleProcess m_stubProc;
}
void GdbEngine::gdbProcError(QProcess::ProcessError error)
......@@ -544,11 +533,13 @@ void GdbEngine::handleResponse(const QByteArray &buff)
}
case '~': {
static QRegExp re(_("New .hread 0x[0-9a-f]* \\(LWP ([0-9]*)\\)"));
QByteArray data = GdbMi::parseCString(from, to);
m_pendingConsoleStreamOutput += data;
if (data.startsWith("Reading symbols from ")) {
if (re.indexIn(_(data)) != -1)
maybeHandleInferiorPidChanged(re.cap(1));
if (data.startsWith("Reading symbols from "))
showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))));
}
break;
}
......@@ -633,25 +624,6 @@ void GdbEngine::handleResponse(const QByteArray &buff)
}
}
void GdbEngine::handleStubAttached(const GdbResultRecord &, const QVariant &)
{
qq->notifyInferiorStopped();
handleAqcuiredInferior();
m_autoContinue = true;
}
void GdbEngine::stubStarted()
{
const qint64 attachedPID = m_stubProc.applicationPID();
qq->notifyInferiorPidChanged(attachedPID);
postCommand(_("attach %1").arg(attachedPID), CB(handleStubAttached));
}
void GdbEngine::stubError(const QString &msg)
{
QMessageBox::critical(mainWindow(), tr("Debugger Error"), msg);
}
void GdbEngine::readGdbStandardError()
{
qWarning() << "Unexpected gdb stderr:" << m_gdbAdapter->readAllStandardError();
......@@ -717,6 +689,18 @@ void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
tryLoadDebuggingHelpers();
}
void GdbEngine::postCommand(const QString &command, AdapterCallback callback,
const char *callbackName, const QVariant &cookie)
{
GdbCommand cmd;
cmd.command = command;
//cmd.flags = flags;
cmd.adapterCallback = callback;
cmd.callbackName = callbackName;
cmd.cookie = cookie;
postCommandHelper(cmd);
}
void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
const char *callbackName, const QVariant &cookie)
{
......@@ -726,29 +710,33 @@ void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
void GdbEngine::postCommand(const QString &command, GdbCommandFlags flags,
GdbCommandCallback callback, const char *callbackName,
const QVariant &cookie)
{
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)
{
if (m_gdbAdapter->state() == QProcess::NotRunning) {
debugMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: ") + command);
debugMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: ") + cmd.command);
return;
}
if (flags & RebuildModel) {
if (cmd.flags & RebuildModel) {
++m_pendingRequests;
PENDING_DEBUG(" CALLBACK" << callbackName << "INCREMENTS PENDING TO:"
<< m_pendingRequests << command);
PENDING_DEBUG(" CALLBACK" << cmd.callbackName
<< "INCREMENTS PENDING TO:" << m_pendingRequests << cmd.command);
} else {
PENDING_DEBUG(" UNKNOWN CALLBACK" << callbackName << "LEAVES PENDING AT:"
<< m_pendingRequests << command);
PENDING_DEBUG(" UNKNOWN CALLBACK" << cmd.callbackName
<< "LEAVES PENDING AT:" << m_pendingRequests << cmd.command);
}
GdbCommand cmd;
cmd.command = command;
cmd.flags = flags;
cmd.callback = callback;
cmd.callbackName = callbackName;
cmd.cookie = cookie;
if ((flags & NeedsStop) && status() != DebuggerInferiorStopped
if ((cmd.flags & NeedsStop) && status() != DebuggerInferiorStopped
&& status() != DebuggerProcessStartingUp) {
// queue the commands that we cannot send at once
QTC_ASSERT(status() == DebuggerInferiorRunning,
......@@ -757,13 +745,14 @@ void GdbEngine::postCommand(const QString &command, GdbCommandFlags flags,
debugMessage(_("QUEUING COMMAND ") + cmd.command);
m_commandsToRunOnTemporaryBreak.append(cmd);
interruptInferior();
} else if (!command.isEmpty()) {
} else if (!cmd.command.isEmpty()) {
flushCommand(cmd);
}
}
void GdbEngine::flushCommand(GdbCommand &cmd)
void GdbEngine::flushCommand(const GdbCommand &cmd0)
{
GdbCommand cmd = cmd0;
if (m_gdbAdapter->state() != QProcess::Running) {
emit gdbInputAvailable(LogInput, cmd.command);
debugMessage(_("GDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: ") + cmd.command);
......@@ -834,7 +823,9 @@ void GdbEngine::handleResultRecord(const GdbResultRecord &record)
// << "\n data: " << record.data.toString(true);
if (cmd.callback)
(this->*(cmd.callback))(record, cmd.cookie);
(this->*cmd.callback)(record, cmd.cookie);
if (cmd.adapterCallback)
(m_gdbAdapter->*cmd.adapterCallback)(record, cmd.cookie);
if (cmd.flags & RebuildModel) {
--m_pendingRequests;
......@@ -855,10 +846,13 @@ void GdbEngine::handleResultRecord(const GdbResultRecord &record)
// 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_cookieForToken.isEmpty() && m_autoContinue) {
m_autoContinue = false;
continueInferior();
showStatusMessage(tr("Continuing after temporary stop."));
if (m_continuationAfterDone && m_cookieForToken.isEmpty()) {
Continuation cont = m_continuationAfterDone;
m_continuationAfterDone = 0;
(this->*cont)();
//showStatusMessage(tr("Continuing after temporary stop."));
} else {
PENDING_DEBUG("MISSING TOKENS: " << m_cookieForToken.keys());
}
}
......@@ -1077,7 +1071,7 @@ void GdbEngine::handleAqcuiredInferior()
void GdbEngine::handleAsyncOutput(const GdbMi &data)
{
const QByteArray &reason = data.findChild("reason").data();
const QByteArray reason = data.findChild("reason").data();
if (isExitedReason(reason)) {
qq->notifyInferiorExited();
......@@ -1107,7 +1101,7 @@ void GdbEngine::handleAsyncOutput(const GdbMi &data)
qq->notifyInferiorStopped();
handleAqcuiredInferior();
m_autoContinue = true;
// FIXME: m_continuationAfterDone = true;
return;
}
......@@ -1123,7 +1117,7 @@ void GdbEngine::handleAsyncOutput(const GdbMi &data)
flushCommand(cmd);
}
showStatusMessage(tr("Processing queued commands."));
m_autoContinue = true;
// FIXME: m_continuationAfterDone = true;
return;
}
......@@ -1185,6 +1179,69 @@ void GdbEngine::handleAsyncOutput(const GdbMi &data)
}
if (isStoppedReason(reason) || reason.isEmpty()) {
QVariant var = QVariant::fromValue<GdbMi>(data);
if (m_debuggingHelperState == DebuggingHelperUninitialized) {
tryLoadDebuggingHelpers();
postCommand(_("p 4"), CB(handleStop1), var); // dummy
} else {
handleStop1(GdbResultRecord(), var);
}
return;
}
debugMessage(_("STOPPED FOR UNKNOWN REASON: " + data.toString()));
// Ignore it. Will be handled with full response later in the
// JumpToLine or RunToFunction handlers
#if 1
// FIXME: remove this special case as soon as there's a real
// reason given when the temporary breakpoint is hit.
// reight now we get:
// 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4",
// func="foo",args=[{name="str",value="@0x7fff0f450460"}],
// file="main.cpp",fullname="/tmp/g/main.cpp",line="37"}
//
// MAC yields sometimes:
// >3661*stopped,time={wallclock="0.00658",user="0.00142",
// system="0.00136",start="1218810678.805432",end="1218810678.812011"}
m_manager->resetLocation();
qq->notifyInferiorStopped();
showStatusMessage(tr("Run to Function finished. Stopped."));
GdbMi frame = data.findChild("frame");
StackFrame f;
f.file = QString::fromLocal8Bit(frame.findChild("fullname").data());
f.line = frame.findChild("line").data().toInt();
f.address = _(frame.findChild("addr").data());
gotoLocation(f, true);
#endif
}
void GdbEngine::reloadFullStack()
{
QString cmd = _("-stack-list-frames");
postCommand(cmd, WatchUpdate, CB(handleStackListFrames), true);
}
void GdbEngine::reloadStack()
{
QString cmd = _("-stack-list-frames");
int stackDepth = theDebuggerAction(MaximalStackDepth)->value().toInt();
if (stackDepth && !m_gdbAdapter->isAdapter())
cmd += _(" 0 ") + QString::number(stackDepth);
postCommand(cmd, WatchUpdate, CB(handleStackListFrames), false);
// 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 always happens, ask the second time immediately instead
// of waiting for the first request to fail.
if (m_gdbAdapter->isAdapter())
postCommand(cmd, WatchUpdate, CB(handleStackListFrames), false);
}
void GdbEngine::handleStop1(const GdbResultRecord &, const QVariant &cookie)
{
GdbMi data = cookie.value<GdbMi>();
QByteArray reason = data.findChild("reason").data();
if (m_modulesListOutdated) {
reloadModules();
m_modulesListOutdated = false;
......@@ -1201,7 +1258,7 @@ void GdbEngine::handleAsyncOutput(const GdbMi &data)
reloadSourceFiles();
postCommand(_("-break-list"), CB(handleBreakList));
QVariant var = QVariant::fromValue<GdbMi>(data);
postCommand(_("p 0"), CB(handleAsyncOutput2), var); // dummy
postCommand(_("p 0"), CB(handleStop2), var); // dummy
} else {
#ifdef Q_OS_LINUX
// For some reason, attaching to a stopped process causes *two* stops
......@@ -1242,66 +1299,16 @@ void GdbEngine::handleAsyncOutput(const GdbMi &data)
showStatusMessage(tr("Stopped."));
else
showStatusMessage(tr("Stopped: \"%1\"").arg(_(reason)));
handleAsyncOutput2(data);
}
return;
handleStop2(data);
}
debugMessage(_("STOPPED FOR UNKNOWN REASON: " + data.toString()));
// Ignore it. Will be handled with full response later in the
// JumpToLine or RunToFunction handlers
#if 1
// FIXME: remove this special case as soon as there's a real
// reason given when the temporary breakpoint is hit.
// reight now we get:
// 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4",
// func="foo",args=[{name="str",value="@0x7fff0f450460"}],
// file="main.cpp",fullname="/tmp/g/main.cpp",line="37"}
//
// MAC yields sometimes:
// >3661*stopped,time={wallclock="0.00658",user="0.00142",
// system="0.00136",start="1218810678.805432",end="1218810678.812011"}
m_manager->resetLocation();
qq->notifyInferiorStopped();
showStatusMessage(tr("Run to Function finished. Stopped."));
GdbMi frame = data.findChild("frame");
StackFrame f;
f.file = QString::fromLocal8Bit(frame.findChild("fullname").data());
f.line = frame.findChild("line").data().toInt();
f.address = _(frame.findChild("addr").data());
gotoLocation(f, true);
#endif
}
void GdbEngine::reloadFullStack()
{
QString cmd = _("-stack-list-frames");
postCommand(cmd, WatchUpdate, CB(handleStackListFrames), true);
}
void GdbEngine::reloadStack()
{
QString cmd = _("-stack-list-frames");
int stackDepth = theDebuggerAction(MaximalStackDepth)->value().toInt();
if (stackDepth && !m_gdbAdapter->isAdapter())
cmd += _(" 0 ") + QString::number(stackDepth);
postCommand(cmd, WatchUpdate, CB(handleStackListFrames), false);
// 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 always happens, ask the second time immediately instead
// of waiting for the first request to fail.
if (m_gdbAdapter->isAdapter())
postCommand(cmd, WatchUpdate, CB(handleStackListFrames), false);
}
void GdbEngine::handleAsyncOutput2(const GdbResultRecord &, const QVariant &cookie)
void GdbEngine::handleStop2(const GdbResultRecord &, const QVariant &cookie)
{
handleAsyncOutput2(cookie.value<GdbMi>());
handleStop2(cookie.value<GdbMi>());
}
void GdbEngine::handleAsyncOutput2(const GdbMi &data)
void GdbEngine::handleStop2(const GdbMi &data)
{
qq->notifyInferiorStopped();
......@@ -1399,6 +1406,7 @@ void GdbEngine::handleFileExecAndSymbols(const GdbResultRecord &response, const
}
}
#if 0
void GdbEngine::handleExecRun(const GdbResultRecord &response, const QVariant &)
{
if (response.resultClass == GdbResultRunning) {
......@@ -1413,6 +1421,7 @@ void GdbEngine::handleExecRun(const GdbResultRecord &response, const QVariant &)
qq->notifyInferiorExited();
}
}
#endif
void GdbEngine::handleExecContinue(const GdbResultRecord &response, const QVariant &)
{
......@@ -1519,6 +1528,7 @@ void GdbEngine::handleExitHelper(const GdbResultRecord &, const QVariant &)
void GdbEngine::exitDebugger2()
{
/*
postCommand(_("-gdb-exit"), CB(handleExit));
// 20s can easily happen when loading webkit debug information
if (!m_gdbAdapter->waitForFinished(20000)) {
......@@ -1533,6 +1543,7 @@ void GdbEngine::exitDebugger2()
.arg(m_gdbAdapter->state()));
m_gdbAdapter->kill();
}
*/
m_outputCollector.shutdown();
initializeVariables();
......@@ -1548,6 +1559,8 @@ int GdbEngine::currentFrame() const
void GdbEngine::startDebugger(const DebuggerStartParametersPtr &sp)
{
m_startParameters = sp;
m_gdbAdapter->startAdapter(sp);
/*
// This should be set by the constructor or in exitDebugger().