/src/plugins/debugger/gdb/gdbengine.cpp
C++ | 5439 lines | 4438 code | 429 blank | 572 comment | 902 complexity | 456d16a2f4907e74a86b1b2081e849ef MD5 | raw file
Possible License(s): LGPL-2.1
Large files files are truncated, but you can click here to view the full file
- /**************************************************************************
- **
- ** This file is part of Qt Creator
- **
- ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
- **
- ** Contact: Nokia Corporation (qt-info@nokia.com)
- **
- **
- ** GNU Lesser General Public License Usage
- **
- ** 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.
- **
- ** In addition, as a special exception, Nokia gives you certain additional
- ** rights. These rights are described in the Nokia Qt LGPL Exception
- ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
- **
- ** Other Usage
- **
- ** Alternatively, this file may be used in accordance with the terms and
- ** conditions contained in a signed written agreement between you and Nokia.
- **
- ** If you have questions regarding the use of this file, please contact
- ** Nokia at qt-info@nokia.com.
- **
- **************************************************************************/
- #define QT_NO_CAST_FROM_ASCII
- #include "gdbengine.h"
- #include "debuggerstartparameters.h"
- #include "debuggerinternalconstants.h"
- #include "disassemblerlines.h"
- #include "attachgdbadapter.h"
- #include "coregdbadapter.h"
- #include "localplaingdbadapter.h"
- #include "termgdbadapter.h"
- #include "remotegdbserveradapter.h"
- #include "remoteplaingdbadapter.h"
- #include "codagdbadapter.h"
- #include "debuggeractions.h"
- #include "debuggerconstants.h"
- #include "debuggercore.h"
- #include "debuggerplugin.h"
- #include "debuggerrunner.h"
- #include "debuggerstringutils.h"
- #include "debuggertooltipmanager.h"
- #include "disassembleragent.h"
- #include "gdbmi.h"
- #include "gdboptionspage.h"
- #include "memoryagent.h"
- #include "watchutils.h"
- #include "breakhandler.h"
- #include "moduleshandler.h"
- #include "registerhandler.h"
- #include "snapshothandler.h"
- #include "sourcefileshandler.h"
- #include "stackhandler.h"
- #include "threadshandler.h"
- #include "watchhandler.h"
- #include "debuggersourcepathmappingwidget.h"
- #include "hostutils.h"
- #include "logwindow.h"
- #include "procinterrupt.h"
- #include <coreplugin/icore.h>
- #include <coreplugin/idocument.h>
- #include <extensionsystem/pluginmanager.h>
- #include <projectexplorer/abi.h>
- #include <projectexplorer/projectexplorerconstants.h>
- #include <projectexplorer/taskhub.h>
- #include <projectexplorer/itaskhandler.h>
- #include <texteditor/itexteditor.h>
- #include <utils/elfreader.h>
- #include <utils/qtcassert.h>
- #include <utils/qtcprocess.h>
- #include <QCoreApplication>
- #include <QDebug>
- #include <QDir>
- #include <QDirIterator>
- #include <QFileInfo>
- #include <QMetaObject>
- #include <QTime>
- #include <QTimer>
- #include <QTemporaryFile>
- #include <QTextStream>
- #include <QAction>
- #include <QDialogButtonBox>
- #include <QLabel>
- #include <QMainWindow>
- #include <QMessageBox>
- #include <QPushButton>
- #ifdef Q_OS_UNIX
- #include <unistd.h>
- #include <dlfcn.h>
- #endif
- #include <ctype.h>
- using namespace ProjectExplorer;
- using namespace Utils;
- namespace Debugger {
- namespace Internal {
- class GdbToolTipContext : public DebuggerToolTipContext
- {
- public:
- GdbToolTipContext(const DebuggerToolTipContext &c) :
- DebuggerToolTipContext(c), editor(0) {}
- QPoint mousePosition;
- QString expression;
- Core::IEditor *editor;
- };
- enum { debugPending = 0 };
- #define PENDING_DEBUG(s) do { if (debugPending) qDebug() << s; } while (0)
- #define CB(callback) &GdbEngine::callback, STRINGIFY(callback)
- QByteArray GdbEngine::tooltipIName(const QString &exp)
- {
- return "tooltip." + exp.toLatin1().toHex();
- }
- static bool stateAcceptsGdbCommands(DebuggerState state)
- {
- switch (state) {
- case EngineSetupRequested:
- case EngineSetupOk:
- case EngineSetupFailed:
- case InferiorUnrunnable:
- case InferiorSetupRequested:
- case InferiorSetupFailed:
- case EngineRunRequested:
- case InferiorRunRequested:
- case InferiorRunOk:
- case InferiorStopRequested:
- case InferiorStopOk:
- case InferiorShutdownRequested:
- case EngineShutdownRequested:
- case InferiorShutdownOk:
- case InferiorShutdownFailed:
- return true;
- case DebuggerNotReady:
- case InferiorStopFailed:
- case InferiorSetupOk:
- case EngineRunFailed:
- case InferiorExitOk:
- case InferiorRunFailed:
- case EngineShutdownOk:
- case EngineShutdownFailed:
- case DebuggerFinished:
- return false;
- }
- return false;
- }
- static int ¤tToken()
- {
- static int token = 0;
- return token;
- }
- static QByteArray parsePlainConsoleStream(const GdbResponse &response)
- {
- QByteArray out = response.consoleStreamOutput;
- // 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);
- }
- ///////////////////////////////////////////////////////////////////////
- //
- // Debuginfo Taskhandler
- //
- ///////////////////////////////////////////////////////////////////////
- class DebugInfoTask
- {
- public:
- QString command;
- };
- class DebugInfoTaskHandler : public ProjectExplorer::ITaskHandler
- {
- public:
- explicit DebugInfoTaskHandler(GdbEngine *engine)
- : m_engine(engine)
- {}
- bool canHandle(const Task &task) const
- {
- return m_debugInfoTasks.contains(task.taskId);
- }
- void handle(const Task &task)
- {
- m_engine->requestDebugInformation(m_debugInfoTasks.value(task.taskId));
- }
- void addTask(unsigned id, const DebugInfoTask &task)
- {
- m_debugInfoTasks[id] = task;
- }
- QAction *createAction(QObject *parent) const
- {
- QAction *action = new QAction(DebuggerPlugin::tr("Install &Debug Information"), parent);
- action->setToolTip(DebuggerPlugin::tr("This tries to install missing debug information."));
- return action;
- }
- private:
- GdbEngine *m_engine;
- QHash<unsigned, DebugInfoTask> m_debugInfoTasks;
- };
- ///////////////////////////////////////////////////////////////////////
- //
- // GdbEngine
- //
- ///////////////////////////////////////////////////////////////////////
- GdbEngine::GdbEngine(const DebuggerStartParameters &startParameters,
- DebuggerEngine *masterEngine)
- : DebuggerEngine(startParameters, CppLanguage, masterEngine)
- {
- setObjectName(_("GdbEngine"));
- m_busy = false;
- m_debuggingHelperState = DebuggingHelperUninitialized;
- m_gdbVersion = 100;
- m_gdbBuildVersion = -1;
- m_isMacGdb = false;
- m_isQnxGdb = false;
- m_hasBreakpointNotifications = false;
- m_hasPython = false;
- m_registerNamesListed = false;
- m_hasInferiorThreadList = false;
- m_sourcesListUpdating = false;
- m_oldestAcceptableToken = -1;
- m_nonDiscardableCount = 0;
- m_outputCodec = QTextCodec::codecForLocale();
- m_pendingBreakpointRequests = 0;
- m_commandsDoneCallback = 0;
- m_stackNeeded = false;
- m_preparedForQmlBreak = false;
- m_disassembleUsesComma = false;
- m_actingOnExpectedStop = false;
- m_fullStartDone = false;
- m_forceAsyncModel = false;
- invalidateSourcesList();
- m_debugInfoTaskHandler = new DebugInfoTaskHandler(this);
- ExtensionSystem::PluginManager::addObject(m_debugInfoTaskHandler);
- m_commandTimer.setSingleShot(true);
- connect(&m_commandTimer, SIGNAL(timeout()), SLOT(commandTimeout()));
- connect(debuggerCore()->action(AutoDerefPointers), SIGNAL(valueChanged(QVariant)),
- SLOT(reloadLocals()));
- connect(debuggerCore()->action(CreateFullBacktrace), SIGNAL(triggered()),
- SLOT(createFullBacktrace()));
- connect(debuggerCore()->action(UseDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
- SLOT(reloadLocals()));
- connect(debuggerCore()->action(UseDynamicType), SIGNAL(valueChanged(QVariant)),
- SLOT(reloadLocals()));
- }
- GdbEngine::~GdbEngine()
- {
- ExtensionSystem::PluginManager::removeObject(m_debugInfoTaskHandler);
- delete m_debugInfoTaskHandler;
- m_debugInfoTaskHandler = 0;
- // Prevent sending error messages afterwards.
- disconnect();
- }
- DebuggerStartMode GdbEngine::startMode() const
- {
- return startParameters().startMode;
- }
- QString GdbEngine::errorMessage(QProcess::ProcessError error)
- {
- switch (error) {
- case QProcess::FailedToStart:
- return tr("The gdb process failed to start. Either the "
- "invoked program '%1' is missing, or you may have insufficient "
- "permissions to invoke the program.\n%2")
- .arg(m_gdb, gdbProc()->errorString());
- case QProcess::Crashed:
- if (targetState() == DebuggerFinished)
- return tr("The gdb process crashed some time after starting "
- "successfully.");
- else
- return tr("The gdb process was ended forcefully");
- case QProcess::Timedout:
- return tr("The last waitFor...() function timed out. "
- "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 "
- "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 "
- "the gdb process. For example, the process may not be running.");
- default:
- return tr("An unknown error in the gdb process occurred. ");
- }
- }
- #if 0
- static void dump(const char *first, const char *middle, const QString & to)
- {
- QByteArray ba(first, middle - first);
- Q_UNUSED(to)
- // note that qDebug cuts off output after a certain size... (bug?)
- qDebug("\n>>>>> %s\n%s\n====\n%s\n<<<<<\n",
- qPrintable(currentTime()),
- qPrintable(QString(ba).trimmed()),
- qPrintable(to.trimmed()));
- //qDebug() << "";
- //qDebug() << qPrintable(currentTime())
- // << " Reading response: " << QString(ba).trimmed() << "\n";
- }
- #endif
- // Parse "~:gdb: unknown target exception 0xc0000139 at 0x77bef04e\n"
- // and return an exception message
- static inline QString msgWinException(const QByteArray &data)
- {
- const int exCodePos = data.indexOf("0x");
- const int blankPos = exCodePos != -1 ? data.indexOf(' ', exCodePos + 1) : -1;
- const int addressPos = blankPos != -1 ? data.indexOf("0x", blankPos + 1) : -1;
- if (addressPos < 0)
- return GdbEngine::tr("An exception was triggered.");
- const unsigned exCode = data.mid(exCodePos, blankPos - exCodePos).toUInt(0, 0);
- const quint64 address = data.mid(addressPos).trimmed().toULongLong(0, 0);
- QString rc;
- QTextStream str(&rc);
- str << GdbEngine::tr("An exception was triggered: ");
- #ifdef Q_OS_WIN
- formatWindowsException(exCode, address, 0, 0, 0, str);
- #else
- Q_UNUSED(exCode)
- Q_UNUSED(address)
- #endif
- str << '.';
- return rc;
- }
- void GdbEngine::readDebugeeOutput(const QByteArray &data)
- {
- QString msg = m_outputCodec->toUnicode(data.constData(), data.length(),
- &m_outputCodecState);
- showMessage(msg, AppOutput);
- }
- static bool isNameChar(char c)
- {
- // could be 'stopped' or 'shlibs-added'
- return (c >= 'a' && c <= 'z') || c == '-';
- }
- static bool contains(const QByteArray &message, const char *pattern, int size)
- {
- const int s = message.size();
- if (s < size)
- return false;
- const int pos = message.indexOf(pattern);
- if (pos == -1)
- return false;
- const bool beginFits = pos == 0 || message.at(pos - 1) == '\n';
- const bool endFits = pos + size == s || message.at(pos + size) == '\n';
- return beginFits && endFits;
- }
- static bool isGdbConnectionError(const QByteArray &message)
- {
- // Handle messages gdb client produces when the target exits (gdbserver)
- //
- // we get this as response either to a specific command, e.g.
- // 31^error,msg="Remote connection closed"
- // or as informative output:
- // &Remote connection closed
- const char msg1[] = "Remote connection closed";
- const char msg2[] = "Remote communication error. Target disconnected.: No error.";
- const char msg3[] = "Quit";
- return contains(message, msg1, sizeof(msg1) - 1)
- || contains(message, msg2, sizeof(msg2) - 1)
- || contains(message, msg3, sizeof(msg3) - 1);
- }
- void GdbEngine::handleResponse(const QByteArray &buff)
- {
- showMessage(QString::fromLocal8Bit(buff, buff.length()), LogOutput);
- if (buff.isEmpty() || buff == "(gdb) ")
- return;
- const char *from = buff.constData();
- const char *to = from + buff.size();
- const char *inner;
- int token = -1;
- // Token is a sequence of numbers.
- for (inner = from; inner != to; ++inner)
- if (*inner < '0' || *inner > '9')
- break;
- if (from != inner) {
- token = QByteArray(from, inner - from).toInt();
- from = inner;
- }
- // Next char decides kind of response.
- const char c = *from++;
- switch (c) {
- case '*':
- case '+':
- case '=': {
- QByteArray asyncClass;
- for (; from != to; ++from) {
- const char c = *from;
- if (!isNameChar(c))
- break;
- asyncClass += *from;
- }
- GdbMi result;
- while (from != to) {
- GdbMi data;
- if (*from != ',') {
- // happens on archer where we get
- // 23^running <NL> *running,thread-id="all" <NL> (gdb)
- result.m_type = GdbMi::Tuple;
- break;
- }
- ++from; // skip ','
- data.parseResultOrValue(from, to);
- if (data.isValid()) {
- //qDebug() << "parsed result:" << data.toString();
- result.m_children += data;
- result.m_type = GdbMi::Tuple;
- }
- }
- 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"
- // id="/lib/i386-linux-gnu/libc.so.6"
- // target-name="/lib/i386-linux-gnu/libc.so.6"
- // host-name="/lib/i386-linux-gnu/libc.so.6"
- // symbols-loaded="0",thread-group="i1"
- QByteArray id = result.findChild("id").data();
- if (!id.isEmpty())
- showStatusMessage(tr("Library %1 loaded").arg(_(id)), 1000);
- progressPing();
- invalidateSourcesList();
- Module module;
- module.startAddress = 0;
- module.endAddress = 0;
- module.hostPath = _(result.findChild("host-name").data());
- module.modulePath = _(result.findChild("target-name").data());
- module.moduleName = QFileInfo(module.hostPath).baseName();
- modulesHandler()->updateModule(module);
- } 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();
- progressPing();
- 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.
- progressPing();
- // 7.1.50 has thread-group-started,id="i1",pid="3529"
- QByteArray id = result.findChild("id").data();
- showStatusMessage(tr("Thread group %1 created").arg(_(id)), 1000);
- int pid = id.toInt();
- if (!pid) {
- id = result.findChild("pid").data();
- pid = id.toInt();
- }
- if (pid)
- notifyInferiorPid(pid);
- handleThreadGroupCreated(result);
- } 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);
- handleThreadGroupExited(result);
- } 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);
- } 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 BreakpointResponseId rid(bkpt.findChild("number").data());
- if (!isQmlStepBreakpoint(rid)) {
- BreakHandler *handler = breakHandler();
- BreakpointModelId id = handler->findBreakpointByResponseId(rid);
- BreakpointResponse br = handler->response(id);
- updateResponse(br, bkpt);
- handler->setResponse(id, br);
- attemptAdjustBreakpointLocation(id);
- }
- } else if (asyncClass == "breakpoint-modified") {
- // New in FSF gdb since 2011-04-27.
- // "{bkpt={number="3",type="breakpoint",disp="keep",
- // enabled="y",addr="<MULTIPLE>",times="1",
- // original-location="\\",simple_gdbtest_app.cpp\\":135"},
- // {number="3.1",enabled="y",addr="0x0805ff68",
- // func="Vector<int>::Vector(int)",
- // file="simple_gdbtest_app.cpp",
- // fullname="/data/...line="135"},{number="3.2"...}}"
- // Note the leading comma in original-location. Filter it out.
- // We don't need the field anyway.
- QByteArray ba = result.toString();
- ba = '[' + ba.mid(6, ba.size() - 7) + ']';
- const int pos1 = ba.indexOf(",original-location");
- const int pos2 = ba.indexOf("\":", pos1 + 2);
- const int pos3 = ba.indexOf('"', pos2 + 2);
- ba.remove(pos1, pos3 - pos1 + 1);
- result = GdbMi();
- result.fromString(ba);
- BreakHandler *handler = breakHandler();
- BreakpointModelId id;
- BreakpointResponse br;
- foreach (const GdbMi &bkpt, result.children()) {
- const QByteArray nr = bkpt.findChild("number").data();
- BreakpointResponseId rid(nr);
- if (!isHiddenBreakpoint(rid)) {
- if (nr.contains('.')) {
- // A sub-breakpoint.
- BreakpointResponse sub;
- updateResponse(sub, bkpt);
- sub.id = rid;
- sub.type = br.type;
- handler->insertSubBreakpoint(id, sub);
- } else {
- // A primary breakpoint.
- id = handler->findBreakpointByResponseId(rid);
- //qDebug() << "NR: " << nr << "RID: " << rid
- // << "ID: " << id;
- //BreakpointModelId id =
- // handler->findBreakpointByResponseId(rid);
- br = handler->response(id);
- updateResponse(br, bkpt);
- handler->setResponse(id, br);
- }
- }
- }
- m_hasBreakpointNotifications = true;
- } else if (asyncClass == "breakpoint-created") {
- // "{bkpt={number="1",type="breakpoint",disp="del",enabled="y",
- // addr="<PENDING>",pending="main",times="0",
- // original-location="main"}}" -- or --
- // {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y",
- // what="*0xbfffed48",times="0",original-location="*0xbfffed48"
- BreakHandler *handler = breakHandler();
- foreach (const GdbMi &bkpt, result.children()) {
- BreakpointResponse br;
- br.type = BreakpointByFileAndLine;
- updateResponse(br, bkpt);
- handler->handleAlienBreakpoint(br, this);
- }
- } else if (asyncClass == "breakpoint-deleted") {
- // "breakpoint-deleted" "{id="1"}"
- // New in FSF gdb since 2011-04-27.
- BreakHandler *handler = breakHandler();
- QByteArray nr = result.findChild("id").data();
- BreakpointResponseId rid(nr);
- BreakpointModelId id = handler->findBreakpointByResponseId(rid);
- if (id.isValid())
- handler->removeAlienBreakpoint(id);
- } 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]*"));
- // 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.
- if (data.startsWith("Reading symbols from ")) {
- showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))), 1000);
- progressPing();
- invalidateSourcesList();
- } else if (data.startsWith("[New ") || data.startsWith("[Thread ")) {
- if (data.endsWith('\n'))
- data.chop(1);
- progressPing();
- showStatusMessage(_(data), 1000);
- } else if (data.startsWith("gdb: unknown target exception 0x")) {
- // [Windows, most likely some DLL/Entry point not found]:
- // "gdb: unknown target exception 0xc0000139 at 0x77bef04e"
- // This may be fatal and cause the target to exit later
- m_lastWinException = msgWinException(data);
- showMessage(m_lastWinException, LogMisc);
- }
- if (data.startsWith("QMLBP:")) {
- int pos1 = 6;
- int pos2 = data.indexOf(' ', pos1);
- m_qmlBreakpointResponseId2 = BreakpointResponseId(data.mid(pos1, pos2 - pos1));
- //qDebug() << "FOUND QMLBP: " << m_qmlBreakpointNumbers[2];
- //qDebug() << "CURRENT: " << m_qmlBreakpointNumbers;
- }
- break;
- }
- case '@': {
- readDebugeeOutput(GdbMi::parseCString(from, to));
- break;
- }
- case '&': {
- QByteArray data = GdbMi::parseCString(from, to);
- m_pendingLogStreamOutput += data;
- // On Windows, the contents seem to depend on the debugger
- // version and/or OS version used.
- if (data.startsWith("warning:"))
- showMessage(_(data.mid(9)), AppStuff); // Cut "warning: "
- if (isGdbConnectionError(data)) {
- notifyInferiorExited();
- break;
- }
- // From SuSE's gdb: >&"Missing separate debuginfo for ...\n"
- // ">&"Try: zypper install -C \"debuginfo(build-id)=c084ee5876ed1ac12730181c9f07c3e027d8e943\"\n"
- if (data.startsWith("Missing separate debuginfo for ")) {
- m_lastMissingDebugInfo = QString::fromLocal8Bit(data.mid(32));
- } else if (data.startsWith("Try: zypper")) {
- QString cmd = QString::fromLocal8Bit(data.mid(4));
- Task task(Task::Warning,
- tr("Missing debug information for %1\nTry: %2")
- .arg(m_lastMissingDebugInfo).arg(cmd),
- FileName(), 0, Core::Id("Debuginfo"));
- taskHub()->addTask(task);
- DebugInfoTask dit;
- dit.command = cmd;
- m_debugInfoTaskHandler->addTask(task.taskId, dit);
- }
- break;
- }
- case '^': {
- GdbResponse response;
- response.token = token;
- for (inner = from; inner != to; ++inner)
- if (*inner < 'a' || *inner > 'z')
- break;
- QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
- if (resultClass == "done") {
- response.resultClass = GdbResultDone;
- } else if (resultClass == "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;
- }
- from = inner;
- if (from != to) {
- if (*from == ',') {
- ++from;
- response.data.parseTuple_helper(from, to);
- response.data.m_type = GdbMi::Tuple;
- response.data.m_name = "data";
- } else {
- // Archer has this.
- response.data.m_type = GdbMi::Tuple;
- response.data.m_name = "data";
- }
- }
- //qDebug() << "\nLOG STREAM:" + m_pendingLogStreamOutput;
- //qDebug() << "\nCONSOLE STREAM:" + m_pendingConsoleStreamOutput;
- response.logStreamOutput = m_pendingLogStreamOutput;
- response.consoleStreamOutput = m_pendingConsoleStreamOutput;
- m_pendingLogStreamOutput.clear();
- m_pendingConsoleStreamOutput.clear();
- handleResultRecord(&response);
- break;
- }
- default: {
- qDebug() << "UNKNOWN RESPONSE TYPE '" << c << "'. REST: " << from;
- break;
- }
- }
- }
- void GdbEngine::readGdbStandardError()
- {
- QByteArray err = gdbProc()->readAllStandardError();
- showMessage(_("UNEXPECTED GDB STDERR: " + err));
- if (err == "Undefined command: \"bb\". Try \"help\".\n")
- return;
- if (err.startsWith("BFD: reopening"))
- return;
- qWarning() << "Unexpected GDB stderr:" << err;
- }
- void GdbEngine::readGdbStandardOutput()
- {
- m_commandTimer.start(); // Restart timer.
- int newstart = 0;
- int scan = m_inbuffer.size();
- QByteArray out = gdbProc()->readAllStandardOutput();
- m_inbuffer.append(out);
- // This can trigger when a dialog starts a nested event loop.
- if (m_busy)
- return;
- while (newstart < m_inbuffer.size()) {
- int start = newstart;
- int end = m_inbuffer.indexOf('\n', scan);
- if (end < 0) {
- m_inbuffer.remove(0, start);
- return;
- }
- newstart = end + 1;
- scan = newstart;
- if (end == start)
- continue;
- if (m_inbuffer.at(end - 1) == '\r') {
- --end;
- if (end == start)
- continue;
- }
- m_busy = true;
- QByteArray ba = QByteArray::fromRawData(m_inbuffer.constData() + start, end - start);
- handleResponse(ba);
- m_busy = false;
- }
- m_inbuffer.clear();
- }
- void GdbEngine::interruptInferior()
- {
- QTC_ASSERT(state() == InferiorStopRequested,
- qDebug() << "INTERRUPT INFERIOR: " << state(); return);
- if (usesExecInterrupt()) {
- postCommand("-exec-interrupt", Immediate);
- } else {
- showStatusMessage(tr("Stop requested..."), 5000);
- showMessage(_("TRYING TO INTERRUPT INFERIOR"));
- interruptInferior2();
- }
- }
- void GdbEngine::interruptInferiorTemporarily()
- {
- foreach (const GdbCommand &cmd, m_commandsToRunOnTemporaryBreak) {
- if (cmd.flags & LosesChild) {
- notifyInferiorIll();
- return;
- }
- }
- requestInterruptInferior();
- }
- void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
- {
- const qint64 pid = pid0.toLongLong();
- if (pid == 0) {
- showMessage(_("Cannot parse PID from %1").arg(pid0));
- return;
- }
- if (pid == inferiorPid())
- return;
- showMessage(_("FOUND PID %1").arg(pid));
- notifyInferiorPid(pid);
- }
- 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)
- {
- 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 (!stateAcceptsGdbCommands(state())) {
- PENDING_DEBUG(_("NO GDB PROCESS RUNNING, CMD IGNORED: " + cmd.command));
- showMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: %1 %2")
- .arg(_(cmd.command)).arg(state()));
- return;
- }
- if (cmd.flags & RebuildBreakpointModel) {
- ++m_pendingBreakpointRequests;
- PENDING_DEBUG(" BRWAKPOINT MODEL:" << cmd.command << "=>" << cmd.callbackName
- << "INCREMENTS PENDING TO" << m_pendingBreakpointRequests);
- } else {
- PENDING_DEBUG(" OTHER (IN):" << cmd.command << "=>" << cmd.callbackName
- << "LEAVES PENDING WATCH AT" << m_uncompleted.size()
- << "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests);
- }
- if (!(cmd.flags & Discardable))
- ++m_nonDiscardableCount;
- // 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() == InferiorStopOk || state() == InferiorUnrunnable
- || state() == InferiorSetupRequested || state() == EngineSetupOk
- || state() == InferiorShutdownRequested) {
- // Can be safely sent now.
- flushCommand(cmd);
- } else {
- // Queue the commands that we cannot send at once.
- showMessage(_("QUEUING COMMAND " + cmd.command));
- m_commandsToRunOnTemporaryBreak.append(cmd);
- if (state() == InferiorStopRequested) {
- if (cmd.flags & LosesChild) {
- notifyInferiorIll();
- }
- showMessage(_("CHILD ALREADY BEING INTERRUPTED. STILL HOPING."));
- // Calling shutdown() here breaks all situations where two
- // NeedsStop commands are issued in quick succession.
- } else if (state() == InferiorRunOk) {
- showStatusMessage(tr("Stopping temporarily"), 1000);
- interruptInferiorTemporarily();
- } else {
- qDebug() << "ATTEMPTING TO QUEUE COMMAND "
- << cmd.command << "IN INAPPROPRIATE STATE" << state();
- }
- }
- } else if (!cmd.command.isEmpty()) {
- flushCommand(cmd);
- }
- }
- void GdbEngine::flushQueuedCommands()
- {
- showStatusMessage(tr("Processing queued commands"), 1000);
- while (!m_commandsToRunOnTemporaryBreak.isEmpty()) {
- GdbCommand cmd = m_commandsToRunOnTemporaryBreak.takeFirst();
- showMessage(_("RUNNING QUEUED COMMAND " + cmd.command + ' '
- + (cmd.callbackName ? cmd.callbackName : "<unnamed callback>")));
- flushCommand(cmd);
- }
- }
- void GdbEngine::flushCommand(const GdbCommand &cmd0)
- {
- if (!stateAcceptsGdbCommands(state())) {
- showMessage(_(cmd0.command), LogInput);
- showMessage(_("GDB PROCESS ACCEPTS NO CMD IN STATE %1 ").arg(state()));
- return;
- }
- QTC_ASSERT(gdbProc()->state() == QProcess::Running, return);
- const int token = ++currentToken();
- GdbCommand cmd = cmd0;
- cmd.postTime = QTime::currentTime();
- m_cookieForToken[token] = cmd;
- if (cmd.flags & ConsoleCommand)
- cmd.command = "-interpreter-exec console \"" + cmd.command + '"';
- cmd.command = QByteArray::number(token) + cmd.command;
- showMessage(_(cmd.command), LogInput);
- if (m_scheduledTestResponses.contains(token)) {
- // Fake response for test cases.
- QByteArray buffer = m_scheduledTestResponses.value(token);
- buffer.replace("@TOKEN@", QByteArray::number(token));
- m_scheduledTestResponses.remove(token);
- showMessage(_("FAKING TEST RESPONSE (TOKEN: %2, RESPONSE: '%3')")
- .arg(token).arg(_(buffer)));
- QMetaObject::invokeMethod(this, "handleResponse",
- Q_ARG(QByteArray, buffer));
- } else {
- write(cmd.command + "\r\n");
- // Start Watchdog.
- if (m_commandTimer.interval() <= 20000)
- m_commandTimer.setInterval(commandTimeoutTime());
- // The process can die for external reason between the "-gdb-exit" was
- // sent and a response could be retrieved. We don't want the watchdog
- // to bark in that case since the only possible outcome is a dead
- // process anyway.
- if (!cmd.command.endsWith("-gdb-exit"))
- m_commandTimer.start();
- //if (cmd.flags & LosesChild)
- // notifyInferiorIll();
- }
- }
- int GdbEngine::commandTimeoutTime() const
- {
- int time = debuggerCore()->action(GdbWatchdogTimeout)->value().toInt();
- return 1000 * qMax(40, 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 + " => ";
- msg += cmd.callbackName ? cmd.callbackName : "<unnamed callback>";
- showMessage(_(msg));
- }
- if (killIt) {
- QStringList commands;
- foreach (const GdbCommand &cookie, m_cookieForToken)
- commands << QString(_("\"%1\"")).arg(
- QString::fromLatin1(cookie.command));
- showMessage(_("TIMED OUT WAITING FOR GDB REPLY. "
- "COMMANDS STILL IN PROGRESS: ") + commands.join(_(", ")));
- int timeOut = m_commandTimer.interval();
- //m_commandTimer.stop();
- const QString msg = tr("The gdb process has not responded "
- "to a command within %n second(s). This could mean it is stuck "
- "in an endless loop or taking longer than expected to perform "
- "the operation.\nYou can choose between waiting "
- "longer or abort debugging.", 0, 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) {
- showMessage(_("KILLING DEBUGGER AS REQUESTED BY USER"));
- // This is an undefined state, so we just pull the emergency brake.
- gdbProc()->kill();
- } else {
- showMessage(_("CONTINUE DEBUGGER AS REQUESTED BY USER"));
- }
- } else {
- showMessage(_("\nNON-CRITICAL TIMEOUT\n"));
- }
- }
- void GdbEngine::handleResultRecord(GdbResponse *response)
- {
- //qDebug() << "TOKEN:" << response->token
- // << " ACCEPTABLE:" << m_oldestAcceptableToken;
- //qDebug() << "\nRESULT" << response->token << response->toString();
- int token = response->token;
- 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.
- showMessage(_("COOKIE FOR TOKEN %1 ALREADY EATEN (%2). "
- "TWO RESPONSES FOR ONE COMMAND?").arg(token).
- arg(QString::fromLatin1(stateName(state()))));
- 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"
- showMessage(_("APPLYING WORKAROUND #1"));
- showMessageBox(QMessageBox::Critical,
- tr("Executable failed"), QString::fromLocal8Bit(msg));
- showStatusMessage(tr("Process failed to start"));
- //shutdown();
- notifyInferiorIll();
- } 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.
- //showMessage(_("APPLYING WORKAROUND #2"));
- notifyInferiorStopOk();
- } 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"
- showMessage(_("APPLYING WORKAROUND #3"));
- notifyInferiorStopOk();
- 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.
- showMessage(_("APPLYING WORKAROUND #4"));
- notifyInferiorStopOk();
- //notifyInferiorIll();
- //showStatusMessage(tr("Executable failed: %1")
- // .arg(QString::fromLocal8Bit(msg)));
- //shutdown();
- //showMessageBox(QMessageBox::Critical,
- // tr("Executable failed"), QString::fromLocal8Bit(msg));
- } else if (msg.contains("Cannot insert breakpoint")) {
- // For breakpoints set by address to non-existent addresses we
- // might get something like "6^error,msg="Warning:\nCannot insert
- // breakpoint 3.\nError accessing memory address 0x34592327:
- // Input/output error.\nCannot insert breakpoint 4.\nError
- // accessing memory address 0x34592335: Input/output error.\n".
- // This should not stop us from proceeding.
- // Most notably, that happens after a "6^running" and "*running"
- // We are probably sitting at _start and can't proceed as
- // long as the breakpoints are enabled.
- // FIXME: Should we silently disable the offending breakpoints?
- showMessage(_("APPLYING WORKAROUND #5"));
- showMessageBox(QMessageBox::Critical,
- tr("Setting breakpoints failed"), QString::fromLocal8Bit(msg));
- QTC_CHECK(state() == InferiorRunOk);
- notifyInferiorSpontaneousStop();
- notifyEngineIll();
- } else if (isGdbConnectionError(msg)) {
- notifyInferiorExited();
- } else {
- // Windows: Some DLL or some function not found. Report
- // the exception now in a box.
- if (msg.startsWith("During startup program exited with"))
- notifyInferiorExited();
- QString logMsg;
- if (!m_lastWinException.isEmpty())
- logMsg = m_lastWinException + QLatin1Char('\n');
- logMsg += QString::fromLocal8Bit(msg);
- showMessageBox(QMessageBox::Critical, tr("Executable Failed"), logMsg);
- showStatusMessage(tr("Executable failed: %1").arg(logMsg));
- }
- }
- return;
- }
- GdbCommand cmd = m_cookieForToken.take(token);
- if (debuggerCore()->boolSetting(LogTimeStamps)) {
- showMessage(_("Response time: %1: %2 s")
- .arg(_(cmd.command))
- .arg(cmd.postTime.msecsTo(QTime::currentTime()) / 1000.),
- LogTime);
- }
- if (response->token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
- //showMessage(_("### SKIPPING OLD RESULT") + response.toString());
- return;
- }
- response->cookie = cmd.cookie;
- bool isExpectedResult =
- (response->resultClass == GdbResultError) // Can always happen.
- || (response->resultClass == GdbResultRunning && (cmd.flags & RunRequest))
- || (response->resultClass == GdbResultExit && (cmd.flags & ExitRequest))
- || (response->resultClass == GdbResultDone);
- // GdbResultDone can almost "always" happen. Known examples are:
- // (response->resultClass == GdbResultDone && cmd.command == "continue")
- // Happens with some incarnations of gdb 6.8 for "jump to line"
- // (response->resultClass == GdbResultDone && cmd.command.startsWith("jump"))
- // (response->resultClass == GdbResultDone && cmd.command.startsWith("detach"))
- // Happens when stepping finishes very quickly and issues *stopped and ^done
- // instead of ^running and *stopped
- // (response->resultClass == GdbResultDone && (cmd.flags & RunRequest));
- if (!isExpectedResult) {
- const DebuggerStartParameters &sp = startParameters();
- Abi abi = sp.toolChainAbi;
- if (abi.os() == Abi::WindowsOS
- && cmd.command.startsWith("attach")
- && (sp.startMode == AttachExternal || sp.useTerminal))
- {
- // Ignore spurious 'running' responses to 'attach'.
- } else {
- QByteArray rsp = GdbResponse::stringFromResultClass(response->resultClass);
- rsp = "UNEXPECTED RESPONSE '" + rsp + "' TO COMMAND '" + cmd.command + "'";
- qWarning() << rsp << " AT " __FILE__ ":" STRINGIFY(__LINE__);
- showMessage(_(rsp));
- }
- }
- if (!(cmd.flags & Discardable))
- --m_nonDiscardableCount;
- if (cmd.callback)
- (this->*cmd.callback)(*response);
- if (cmd.flags & RebuildBreakpointModel) {
- --m_pendingBreakpointRequests;
- PENDING_DEBUG(" BREAKPOINT" << cmd.command << "=>" << cmd.callbackName
- << "DECREMENTS PENDING TO" << m_uncompleted.size());
- if (m_pendingBreakpointRequests <= 0) {
- PENDING_DEBUG("\n\n ... AND TRIGGERS BREAKPOINT MODEL UPDATE\n");
- attemptBreakpointSynchronization();
- }
- } else {
- PENDING_DEBUG(" OTHER (OUT):" << cmd.command << "=>" << cmd.callbackName
- << "LEAVES PENDING WATCH AT" << m_uncompleted.size()
- << "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests);
- }
- // 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() == InferiorRunOk && !m_commandsToRunOnTemporaryBreak.isEmpty())
- interruptInferiorTemporarily();
- // Continue only if there are no commands wire anymore, so this will
- // be fully synchronous.
- // 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()) {
- showMessage(_("ALL COMMANDS DONE; INVOKING CALLBACK"));
- CommandsDoneCallback cont = m_commandsDoneCallback;
- m_commandsDoneCallback = 0;
- (this->*cont)();
- } else {
- PENDING_DEBUG("MISSING TOKENS: " << m_cookieForToken.keys());
- }
- if (m_cookieForToken.isEmpty())
- m_commandTimer.stop();
- }
- bool GdbEngine::acceptsDebuggerCommands() const
- {
- return state() == InferiorStopOk
- || state() == InferiorUnrunnable;
- }
- void GdbEngine::executeDebuggerCommand(const QString &command, DebuggerLanguages languages)
- {
- if (!(languages & CppLanguage))
- return;
- QTC_CHECK(acceptsDebuggerCommands());
- GdbCommand cmd;
- cmd.command = command.toLatin1();
- flushCommand(cmd);
- }
- // This is called from CoreAdapter and AttachAdapter.
- void GdbEngine::updateAll()
- {
- if (hasPython())
- updateAllPython();
- else
- updateAllClassic();
- }
- void GdbEngine::handleQuerySources(const GdbResponse &response)
- {
- m_sourcesListUpdating = false;
- if (response.resultClass == GdbResultDone) {
- QMap<QString, QString> oldShortToFull = m_shortToFullName;
- m_shortToFullName.clear();
- m_fullToShortName.clear();
- // "^done,files=[{file="../../../../bin/dumper/dumper.cpp",
- // fullname="/data5/dev/ide/main/bin/dumper/dumper.cpp"},
- GdbMi files = response.data.findChild("files");
- foreach (const GdbMi &item, files.children()) {
- GdbMi fileName = item.findChild("file");
- if (fileName.data().endsWith("<built-in>"))
- continue;
- GdbMi fullName = item.findChild("fullname");
- QString file = QSt…
Large files files are truncated, but you can click here to view the full file