gdbengine.cpp 187 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
con's avatar
con committed
29 30

#include "gdbengine.h"
31

32 33
#include "attachgdbadapter.h"
#include "coregdbadapter.h"
34
#include "gdbplainengine.h"
35
#include "termgdbadapter.h"
ck's avatar
ck committed
36
#include "remotegdbserveradapter.h"
37
#include "gdboptionspage.h"
con's avatar
con committed
38

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
#include <debugger/debuggerstartparameters.h>
#include <debugger/debuggerinternalconstants.h>
#include <debugger/debuggerruncontrolfactory.h>
#include <debugger/disassemblerlines.h>

#include <debugger/debuggeractions.h>
#include <debugger/debuggercore.h>
#include <debugger/debuggermainwindow.h>
#include <debugger/debuggerplugin.h>
#include <debugger/debuggerprotocol.h>
#include <debugger/debuggerstringutils.h>
#include <debugger/debuggertooltipmanager.h>
#include <debugger/disassembleragent.h>
#include <debugger/memoryagent.h>
#include <debugger/sourceutils.h>

#include <debugger/breakhandler.h>
#include <debugger/moduleshandler.h>
#include <debugger/registerhandler.h>
#include <debugger/sourcefileshandler.h>
#include <debugger/stackhandler.h>
#include <debugger/threadshandler.h>
#include <debugger/debuggersourcepathmappingwidget.h>
#include <debugger/logwindow.h>
#include <debugger/procinterrupt.h>
#include <debugger/shared/hostutils.h>
con's avatar
con committed
65

66
#include <coreplugin/icore.h>
67
#include <projectexplorer/devicesupport/deviceprocess.h>
68
#include <projectexplorer/itaskhandler.h>
69
#include <projectexplorer/taskhub.h>
70
#include <texteditor/itexteditor.h>
71
#include <utils/hostosinfo.h>
72 73
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
hjk's avatar
hjk committed
74
#include <utils/savedaction.h>
hjk's avatar
hjk committed
75

76
#include <QDirIterator>
77
#include <QTemporaryFile>
con's avatar
con committed
78

79 80
#include <QMessageBox>
#include <QPushButton>
con's avatar
con committed
81

hjk's avatar
hjk committed
82
using namespace ProjectExplorer;
83
using namespace Utils;
hjk's avatar
hjk committed
84

85 86
namespace Debugger {
namespace Internal {
con's avatar
con committed
87

88 89 90
enum { debugPending = 0 };

#define PENDING_DEBUG(s) do { if (debugPending) qDebug() << s; } while (0)
con's avatar
con committed
91

92 93
#define CB(callback) &GdbEngine::callback, STRINGIFY(callback)

94

95
QByteArray GdbEngine::tooltipIName(const QString &exp)
96
{
97
    return "tooltip." + exp.toLatin1().toHex();
98 99
}

hjk's avatar
hjk committed
100
static bool stateAcceptsGdbCommands(DebuggerState state)
hjk's avatar
hjk committed
101
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
102
    switch (state) {
hjk's avatar
hjk committed
103
    case EngineSetupRequested:
104 105
    case EngineSetupOk:
    case EngineSetupFailed:
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
106
    case InferiorUnrunnable:
hjk's avatar
hjk committed
107
    case InferiorSetupRequested:
hjk's avatar
hjk committed
108
    case InferiorSetupFailed:
hjk's avatar
hjk committed
109 110 111 112 113 114 115 116
    case EngineRunRequested:
    case InferiorRunRequested:
    case InferiorRunOk:
    case InferiorStopRequested:
    case InferiorStopOk:
    case InferiorShutdownRequested:
    case EngineShutdownRequested:
    case InferiorShutdownOk:
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
117 118 119 120
    case InferiorShutdownFailed:
        return true;
    case DebuggerNotReady:
    case InferiorStopFailed:
121
    case InferiorSetupOk:
hjk's avatar
hjk committed
122
    case EngineRunFailed:
hjk's avatar
hjk committed
123
    case InferiorExitOk:
hjk's avatar
hjk committed
124 125 126 127
    case InferiorRunFailed:
    case EngineShutdownOk:
    case EngineShutdownFailed:
    case DebuggerFinished:
128
        return false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
129 130 131
    }
    return false;
}
hjk's avatar
hjk committed
132

con's avatar
con committed
133 134 135 136 137 138
static int &currentToken()
{
    static int token = 0;
    return token;
}

hjk's avatar
hjk committed
139
static QByteArray parsePlainConsoleStream(const GdbResponse &response)
140
{
141
    QByteArray out = response.consoleStreamOutput;
142 143 144 145 146 147 148 149 150
    // 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);
}

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
///////////////////////////////////////////////////////////////////////
//
// Debuginfo Taskhandler
//
///////////////////////////////////////////////////////////////////////

class DebugInfoTask
{
public:
    QString command;
};

class DebugInfoTaskHandler : public  ProjectExplorer::ITaskHandler
{
public:
hjk's avatar
hjk committed
166 167
    explicit DebugInfoTaskHandler(GdbEngine *engine)
        : m_engine(engine)
168 169
    {}

hjk's avatar
hjk committed
170
    bool canHandle(const Task &task) const
171 172 173 174 175 176 177 178 179 180 181 182 183 184
    {
        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;
    }

hjk's avatar
hjk committed
185
    QAction *createAction(QObject *parent) const
186
    {
hjk's avatar
hjk committed
187
        QAction *action = new QAction(DebuggerPlugin::tr("Install &Debug Information"), parent);
188
        action->setToolTip(DebuggerPlugin::tr("Tries to install missing debug information."));
189 190 191 192 193 194 195 196
        return action;
    }

private:
    GdbEngine *m_engine;
    QHash<unsigned, DebugInfoTask> m_debugInfoTasks;
};

con's avatar
con committed
197 198 199 200 201 202
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

203 204
GdbEngine::GdbEngine(const DebuggerStartParameters &startParameters)
  : DebuggerEngine(startParameters)
con's avatar
con committed
205
{
206
    setObjectName(_("GdbEngine"));
207

208
    m_busy = false;
209 210
    m_debuggingHelperState = DebuggingHelperUninitialized;
    m_gdbVersion = 100;
211
    m_isQnxGdb = false;
212 213 214
    m_registerNamesListed = false;
    m_sourcesListUpdating = false;
    m_oldestAcceptableToken = -1;
215
    m_nonDiscardableCount = 0;
216 217 218
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingBreakpointRequests = 0;
    m_commandsDoneCallback = 0;
219
    m_stackNeeded = false;
220
    m_preparedForQmlBreak = false;
221
    m_terminalTrap = startParameters.useTerminal;
222
    m_fullStartDone = false;
223
    m_systemDumpersLoaded = false;
224
    m_gdbProc = new GdbProcess(this);
225

226
    m_debugInfoTaskHandler = new DebugInfoTaskHandler(this);
hjk's avatar
hjk committed
227
    //ExtensionSystem::PluginManager::addObject(m_debugInfoTaskHandler);
228

229 230 231
    m_commandTimer.setSingleShot(true);
    connect(&m_commandTimer, SIGNAL(timeout()), SLOT(commandTimeout()));

hjk's avatar
hjk committed
232
    connect(debuggerCore()->action(AutoDerefPointers), SIGNAL(valueChanged(QVariant)),
233
            SLOT(reloadLocals()));
hjk's avatar
hjk committed
234
    connect(debuggerCore()->action(CreateFullBacktrace), SIGNAL(triggered()),
235
            SLOT(createFullBacktrace()));
236 237 238 239
    connect(debuggerCore()->action(UseDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
            SLOT(reloadLocals()));
    connect(debuggerCore()->action(UseDynamicType), SIGNAL(valueChanged(QVariant)),
            SLOT(reloadLocals()));
240 241
    connect(debuggerCore()->action(IntelFlavor), SIGNAL(valueChanged(QVariant)),
            SLOT(reloadDisassembly()));
ck's avatar
ck committed
242 243
}

con's avatar
con committed
244 245
GdbEngine::~GdbEngine()
{
hjk's avatar
hjk committed
246
    //ExtensionSystem::PluginManager::removeObject(m_debugInfoTaskHandler);
247 248 249
    delete m_debugInfoTaskHandler;
    m_debugInfoTaskHandler = 0;

250
    // Prevent sending error messages afterwards.
251 252 253 254 255 256
    disconnect();
}

DebuggerStartMode GdbEngine::startMode() const
{
    return startParameters().startMode;
hjk's avatar
hjk committed
257 258
}

259
QString GdbEngine::errorMessage(QProcess::ProcessError error)
con's avatar
con committed
260 261 262
{
    switch (error) {
        case QProcess::FailedToStart:
Jarek Kobus's avatar
Jarek Kobus committed
263
            return tr("The gdb process failed to start. Either the "
264
                "invoked program \"%1\" is missing, or you may have insufficient "
265
                "permissions to invoke the program.\n%2")
266
                .arg(m_gdb, m_gdbProc->errorString());
con's avatar
con committed
267
        case QProcess::Crashed:
268 269 270 271 272
            if (targetState() == DebuggerFinished)
                return tr("The gdb process crashed some time after starting "
                    "successfully.");
            else
                return tr("The gdb process was ended forcefully");
con's avatar
con committed
273
        case QProcess::Timedout:
274
            return tr("The last waitFor...() function timed out. "
con's avatar
con committed
275 276 277
                "The state of QProcess is unchanged, and you can try calling "
                "waitFor...() again.");
        case QProcess::WriteError:
278
            return tr("An error occurred when attempting to write "
Jarek Kobus's avatar
Jarek Kobus committed
279
                "to the gdb process. For example, the process may not be running, "
con's avatar
con committed
280 281
                "or it may have closed its input channel.");
        case QProcess::ReadError:
282
            return tr("An error occurred when attempting to read from "
Jarek Kobus's avatar
Jarek Kobus committed
283
                "the gdb process. For example, the process may not be running.");
con's avatar
con committed
284
        default:
285
            return tr("An unknown error in the gdb process occurred.");
con's avatar
con committed
286 287 288 289 290 291 292
    }
}

#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
293
    Q_UNUSED(to)
con's avatar
con committed
294 295 296 297 298 299 300 301 302 303 304
    // 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

305 306
// Parse "~:gdb: unknown target exception 0xc0000139 at 0x77bef04e\n"
// and return an exception message
307
static inline QString msgWinException(const QByteArray &data, unsigned *exCodeIn = 0)
308
{
309 310
    if (exCodeIn)
        *exCodeIn = 0;
311 312 313 314 315 316
    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);
317 318
    if (exCodeIn)
        *exCodeIn = exCode;
319 320 321
    const quint64 address = data.mid(addressPos).trimmed().toULongLong(0, 0);
    QString rc;
    QTextStream str(&rc);
322
    str << GdbEngine::tr("An exception was triggered:") << ' ';
323 324 325 326 327
    formatWindowsException(exCode, address, 0, 0, 0, str);
    str << '.';
    return rc;
}

328 329
void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
330 331
    QString msg = m_outputCodec->toUnicode(data.constData(), data.length(),
        &m_outputCodecState);
332
    showMessage(msg, AppOutput);
333 334
}

hjk's avatar
hjk committed
335 336 337 338 339 340
static bool isNameChar(char c)
{
    // could be 'stopped' or 'shlibs-added'
    return (c >= 'a' && c <= 'z') || c == '-';
}

341 342 343 344 345 346 347 348
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;
349 350 351
    const bool beginFits = pos == 0 || message.at(pos - 1) == '\n';
    const bool endFits = pos + size == s || message.at(pos + size) == '\n';
    return beginFits && endFits;
352 353
}

354 355 356 357 358 359 360 361
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
362 363 364 365 366 367 368 369

    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);
370 371
}

372
void GdbEngine::handleResponse(const QByteArray &buff)
con's avatar
con committed
373
{
374
    showMessage(QString::fromLocal8Bit(buff, buff.length()), LogOutput);
con's avatar
con committed
375

376 377
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
378

379 380 381
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
382

383
    int token = -1;
384
    // Token is a sequence of numbers.
385 386
    for (inner = from; inner != to; ++inner)
        if (*inner < '0' || *inner > '9')
con's avatar
con committed
387
            break;
388 389 390 391
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
    }
con's avatar
con committed
392

393
    // Next char decides kind of response.
394 395 396 397 398 399 400 401 402 403 404 405
    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;
            }
con's avatar
con committed
406

hjk's avatar
hjk committed
407
            GdbMi result;
408 409
            while (from != to) {
                GdbMi data;
hjk's avatar
hjk committed
410
                if (*from != ',') {
411 412
                    // happens on archer where we get
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb)
hjk's avatar
hjk committed
413
                    result.m_type = GdbMi::Tuple;
hjk's avatar
hjk committed
414 415 416 417 418
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
hjk's avatar
hjk committed
419 420 421
                    //qDebug() << "parsed result:" << data.toString();
                    result.m_children += data;
                    result.m_type = GdbMi::Tuple;
con's avatar
con committed
422 423
                }
            }
424
            if (asyncClass == "stopped") {
425
                handleStopResponse(result);
426 427
                m_pendingLogStreamOutput.clear();
                m_pendingConsoleStreamOutput.clear();
428
            } else if (asyncClass == "running") {
429
                GdbMi threads = result["thread-id"];
hjk's avatar
hjk committed
430
                threadsHandler()->notifyRunning(threads.data());
431 432 433 434
                if (state() == InferiorRunOk || state() == InferiorSetupRequested) {
                    // We get multiple *running after thread creation and in Windows terminals.
                    showMessage(QString::fromLatin1("NOTE: INFERIOR STILL RUNNING IN STATE %1.").
                                arg(QLatin1String(DebuggerEngine::stateName(state()))));
435 436 437 438 439
                } else if (Utils::HostOsInfo::isWindowsHost() && (state() == InferiorStopRequested
                               || state() == InferiorShutdownRequested)) {
                    // FIXME: Breakpoints on Windows are exceptions which are thrown in newly
                    // created threads so we have to filter out the running threads messages when
                    // we request a stop.
440 441 442
                } else {
                    notifyInferiorRunOk();
                }
443 444 445 446
            } 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",
447
                // symbols-loaded="0"
hjk's avatar
hjk committed
448 449 450 451 452

                // 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"
453
                QByteArray id = result["id"].data();
454
                if (!id.isEmpty())
455
                    showStatusMessage(tr("Library %1 loaded").arg(_(id)), 1000);
456
                progressPing();
hjk's avatar
hjk committed
457 458 459
                Module module;
                module.startAddress = 0;
                module.endAddress = 0;
460 461
                module.hostPath = _(result["host-name"].data());
                module.modulePath = _(result["target-name"].data());
hjk's avatar
hjk committed
462
                module.moduleName = QFileInfo(module.hostPath).baseName();
463
                modulesHandler()->updateModule(module);
hjk's avatar
hjk committed
464 465 466 467
            } 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"
468
                QByteArray id = result["id"].data();
469
                progressPing();
470
                showStatusMessage(tr("Library %1 unloaded").arg(_(id)), 1000);
471 472
            } else if (asyncClass == "thread-group-added") {
                // 7.1-symbianelf has "{id="i1"}"
473 474 475
            } else if (asyncClass == "thread-group-created"
                    || asyncClass == "thread-group-started") {
                // Archer had only "{id="28902"}" at some point of 6.8.x.
hjk's avatar
hjk committed
476 477
                // *-started seems to be standard in 7.1, but in early
                // 7.0.x, there was a *-created instead.
478
                progressPing();
ck's avatar
ck committed
479
                // 7.1.50 has thread-group-started,id="i1",pid="3529"
480
                QByteArray id = result["id"].data();
481
                showStatusMessage(tr("Thread group %1 created").arg(_(id)), 1000);
ck's avatar
ck committed
482 483
                int pid = id.toInt();
                if (!pid) {
484
                    id = result["pid"].data();
ck's avatar
ck committed
485 486 487 488
                    pid = id.toInt();
                }
                if (pid)
                    notifyInferiorPid(pid);
489
                handleThreadGroupCreated(result);
490
            } else if (asyncClass == "thread-created") {
491
                //"{id="1",group-id="28902"}"
492
                QByteArray id = result["id"].data();
493
                showStatusMessage(tr("Thread %1 created").arg(_(id)), 1000);
hjk's avatar
hjk committed
494 495
                ThreadData thread;
                thread.id = ThreadId(id.toLong());
496
                thread.groupId = result["group-id"].data();
hjk's avatar
hjk committed
497
                threadsHandler()->updateThread(thread);
498
            } else if (asyncClass == "thread-group-exited") {
499
                // Archer has "{id="28902"}"
500
                QByteArray id = result["id"].data();
501
                showStatusMessage(tr("Thread group %1 exited").arg(_(id)), 1000);
502
                handleThreadGroupExited(result);
503
            } else if (asyncClass == "thread-exited") {
504
                //"{id="1",group-id="28902"}"
505 506
                QByteArray id = result["id"].data();
                QByteArray groupid = result["group-id"].data();
507
                showStatusMessage(tr("Thread %1 in group %2 exited")
hjk's avatar
hjk committed
508
                    .arg(_(id)).arg(_(groupid)), 1000);
hjk's avatar
hjk committed
509
                threadsHandler()->removeThread(ThreadId(id.toLong()));
hjk's avatar
hjk committed
510
            } else if (asyncClass == "thread-selected") {
511
                QByteArray id = result["id"].data();
512
                showStatusMessage(tr("Thread %1 selected").arg(_(id)), 1000);
513
                //"{id="2"}"
514 515
            } else if (asyncClass == "breakpoint-modified") {
                // New in FSF gdb since 2011-04-27.
hjk's avatar
hjk committed
516 517 518 519 520 521
                // "{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",
522
                // fullname="/data/...line="135"},{number="3.2"...}}.."
hjk's avatar
hjk committed
523 524 525 526 527 528 529 530

                // 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);
Robert Loehning's avatar
Robert Loehning committed
531
                ba.remove(pos1, pos3 - pos1 + 1);
hjk's avatar
hjk committed
532 533 534
                result = GdbMi();
                result.fromString(ba);
                BreakHandler *handler = breakHandler();
535
                BreakpointModelId id;
hjk's avatar
hjk committed
536 537
                BreakpointResponse br;
                foreach (const GdbMi &bkpt, result.children()) {
538
                    const QByteArray nr = bkpt["number"].data();
539
                    BreakpointResponseId rid(nr);
540
                    if (!isHiddenBreakpoint(rid)) {
541 542 543 544 545 546
                        if (nr.contains('.')) {
                            // A sub-breakpoint.
                            BreakpointResponse sub;
                            updateResponse(sub, bkpt);
                            sub.id = rid;
                            sub.type = br.type;
547
                            handler->insertSubBreakpoint(id, sub);
548 549
                        } else {
                            // A primary breakpoint.
550 551 552 553 554
                            id = handler->findBreakpointByResponseId(rid);
                            //qDebug() << "NR: " << nr << "RID: " << rid
                            //    << "ID: " << id;
                            //BreakpointModelId id =
                            //    handler->findBreakpointByResponseId(rid);
555 556
                            br = handler->response(id);
                            updateResponse(br, bkpt);
557
                            handler->setResponse(id, br);
558
                        }
hjk's avatar
hjk committed
559 560
                    }
                }
561 562 563
            } else if (asyncClass == "breakpoint-created") {
                // "{bkpt={number="1",type="breakpoint",disp="del",enabled="y",
                //  addr="<PENDING>",pending="main",times="0",
564 565
                //  original-location="main"}}" -- or --
                // {bkpt={number="2",type="hw watchpoint",disp="keep",enabled="y",
566
                // what="*0xbfffed48",times="0",original-location="*0xbfffed48"}}
567
                BreakHandler *handler = breakHandler();
568 569
                foreach (const GdbMi &bkpt, result.children()) {
                    BreakpointResponse br;
570
                    br.type = BreakpointByFileAndLine;
571
                    updateResponse(br, bkpt);
572
                    handler->handleAlienBreakpoint(br, this);
573 574 575 576 577
                }
            } else if (asyncClass == "breakpoint-deleted") {
                // "breakpoint-deleted" "{id="1"}"
                // New in FSF gdb since 2011-04-27.
                BreakHandler *handler = breakHandler();
578
                QByteArray nr = result["id"].data();
579 580
                BreakpointResponseId rid(nr);
                BreakpointModelId id = handler->findBreakpointByResponseId(rid);
581 582 583 584 585 586 587
                if (id.isValid()) {
                    // This also triggers when a temporary breakpoint is hit.
                    // We do not really want that, as this loses all information.
                    // FIXME: Use a special marker for this case?
                    if (!handler->isOneShot(id))
                        handler->removeAlienBreakpoint(id);
                }
588 589 590
            } else if (asyncClass == "cmd-param-changed") {
                // New since 2012-08-09
                //  "{param="debug remote",value="1"}"
591 592 593
            } else if (asyncClass == "memory-changed") {
                // New since 2013
                //   "{thread-group="i1",addr="0x0918a7a8",len="0x10"}"
594
            } else {
595
                qDebug() << "IGNORED ASYNC OUTPUT"
hjk's avatar
hjk committed
596
                    << asyncClass << result.toString();
597
            }
598 599
            break;
        }
600

601
        case '~': {
602 603
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingConsoleStreamOutput += data;
604 605

            // Parse pid from noise.
606
            if (!inferiorPid()) {
607 608 609 610
                // 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]*"));
hjk's avatar
hjk committed
611 612 613
                // Mac: [Switching to process 9294 local thread 0x2e03] or
                // [Switching to process 31773]
                static QRegExp re3(_("Switching to process ([0-9]+)"));
614 615 616 617 618
                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));
619 620
                else if (re3.indexIn(_(data)) != -1)
                    maybeHandleInferiorPidChanged(re3.cap(1));
621
            }
622 623

            // Show some messages to give the impression something happens.
624
            if (data.startsWith("Reading symbols from ")) {
625
                showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))), 1000);
626
                progressPing();
627 628 629
            } else if (data.startsWith("[New ") || data.startsWith("[Thread ")) {
                if (data.endsWith('\n'))
                    data.chop(1);
630
                progressPing();
631
                showStatusMessage(_(data), 1000);
632 633 634 635
            } 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
636 637
                unsigned exCode;
                m_lastWinException = msgWinException(data, &exCode);
638
                showMessage(m_lastWinException, LogMisc);
639
                const Task::TaskType type = isFatalWinException(exCode) ? Task::Error : Task::Warning;
640
                TaskHub::addTask(type, m_lastWinException, Constants::TASK_CATEGORY_DEBUGGER_RUNTIME);
641
            }
642 643 644 645

            if (data.startsWith("QMLBP:")) {
                int pos1 = 6;
                int pos2 = data.indexOf(' ', pos1);
646
                m_qmlBreakpointResponseId2 = BreakpointResponseId(data.mid(pos1, pos2 - pos1));
647 648 649 650
                //qDebug() << "FOUND QMLBP: " << m_qmlBreakpointNumbers[2];
                //qDebug() << "CURRENT: " << m_qmlBreakpointNumbers;
            }

651 652
            break;
        }
653

654
        case '@': {
655
            readDebugeeOutput(GdbMi::parseCString(from, to));
656
            break;
con's avatar
con committed
657 658
        }

659 660 661 662 663 664
        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:"))
665
                showMessage(_(data.mid(9)), AppStuff); // Cut "warning: "
666

667
            if (isGdbConnectionError(data)) {
668
                notifyInferiorExited();
669
                break;
670
            }
671

672 673 674 675 676 677 678 679 680 681 682 683 684
            if (debuggerCore()->boolSetting(IdentifyDebugInfoPackages)) {
                // 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(Debugger::Constants::TASK_CATEGORY_DEBUGGER_DEBUGINFO));

Nicolas Arnaud-Cormos's avatar
Nicolas Arnaud-Cormos committed
685
                    TaskHub::addTask(task);
686 687 688 689 690

                    DebugInfoTask dit;
                    dit.command = cmd;
                    m_debugInfoTaskHandler->addTask(task.taskId, dit);
                }
691 692
            }

con's avatar
con committed
693 694 695
            break;
        }

696
        case '^': {
hjk's avatar
hjk committed
697
            GdbResponse response;
con's avatar
con committed
698

hjk's avatar
hjk committed
699
            response.token = token;
con's avatar
con committed
700

701 702 703
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
704

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
705
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
706
            if (resultClass == "done")
hjk's avatar
hjk committed
707
                response.resultClass = GdbResultDone;
708
            else if (resultClass == "running")
hjk's avatar
hjk committed
709
                response.resultClass = GdbResultRunning;
710
            else if (resultClass == "connected")
hjk's avatar
hjk committed
711
                response.resultClass = GdbResultConnected;
712
            else if (resultClass == "error")
hjk's avatar
hjk committed
713
                response.resultClass = GdbResultError;
714
            else if (resultClass == "exit")
hjk's avatar
hjk committed
715
                response.resultClass = GdbResultExit;
716
            else
hjk's avatar
hjk committed
717
                response.resultClass = GdbResultUnknown;
con's avatar
con committed
718

719 720
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
721 722
                if (*from == ',') {
                    ++from;
hjk's avatar
hjk committed
723 724 725
                    response.data.parseTuple_helper(from, to);
                    response.data.m_type = GdbMi::Tuple;
                    response.data.m_name = "data";
hjk's avatar
hjk committed
726
                } else {
727
                    // Archer has this.
hjk's avatar
hjk committed
728 729
                    response.data.m_type = GdbMi::Tuple;
                    response.data.m_name = "data";
con's avatar
con committed
730 731
                }
            }
732 733 734

            //qDebug() << "\nLOG STREAM:" + m_pendingLogStreamOutput;
            //qDebug() << "\nCONSOLE STREAM:" + m_pendingConsoleStreamOutput;
735 736
            response.logStreamOutput = m_pendingLogStreamOutput;
            response.consoleStreamOutput =  m_pendingConsoleStreamOutput;
737 738 739
            m_pendingLogStreamOutput.clear();
            m_pendingConsoleStreamOutput.clear();

740
            handleResultRecord(&response);
741 742 743
            break;
        }
        default: {
744
            qDebug() << "UNKNOWN RESPONSE TYPE '" << c << "'. REST: " << from;
745
            break;
con's avatar
con committed
746 747 748 749 750 751
        }
    }
}

void GdbEngine::readGdbStandardError()
{
752
    QByteArray err = m_gdbProc->readAllStandardError();
753
    showMessage(_("UNEXPECTED GDB STDERR: " + err));
754 755
    if (err == "Undefined command: \"bb\".  Try \"help\".\n")
        return;
756 757
    if (err.startsWith("BFD: reopening"))
        return;
Jarek Kobus's avatar
Jarek Kobus committed
758
    qWarning() << "Unexpected GDB stderr:" << err;
con's avatar
con committed
759 760 761 762
}

void GdbEngine::readGdbStandardOutput()
{
763
    m_commandTimer.start(); // Restart timer.
764

765 766
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
767

768
    QByteArray out = m_gdbProc->readAllStandardOutput();
hjk's avatar
hjk committed
769
    m_inbuffer.append(out);
con's avatar
con committed
770

771
    // This can trigger when a dialog starts a nested event loop.
772 773 774
    if (m_busy)
        return;

775 776
    while (newstart < m_inbuffer.size()) {
        int start = newstart;
777
        int end = m_inbuffer.indexOf('\n', scan);
778 779 780 781 782
        if (end < 0) {
            m_inbuffer.remove(0, start);
            return;
        }
        newstart = end + 1;
783
        scan = newstart;
784 785 786 787 788 789 790
        if (end == start)
            continue;
        if (m_inbuffer.at(end - 1) == '\r') {
            --end;
            if (end == start)
                continue;
        }
791
        m_busy = true;
hjk's avatar
hjk committed
792 793
        QByteArray ba = QByteArray::fromRawData(m_inbuffer.constData() + start, end - start);
        handleResponse(ba);
794
        m_busy = false;
con's avatar
con committed
795
    }
796
    m_inbuffer.clear();
con's avatar
con committed
797 798 799 800
}

void GdbEngine::interruptInferior()
{
hjk's avatar
hjk committed
801 802
    QTC_ASSERT(state() == InferiorStopRequested,
        qDebug() << "INTERRUPT INFERIOR: " << state(); return);
803

804
    if (usesExecInterrupt()) {
hjk's avatar
hjk committed
805
        postCommand("-exec-interrupt", Immediate);
806 807 808
    } else {
        showStatusMessage(tr("Stop requested..."), 5000);
        showMessage(_("TRYING TO INTERRUPT INFERIOR"));
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
        if (Utils::HostOsInfo::isWindowsHost() && !m_isQnxGdb) {
            QTC_ASSERT(state() == InferiorStopRequested, qDebug() << state(); notifyInferiorStopFailed());
            QTC_ASSERT(!m_signalOperation, notifyInferiorStopFailed());
            m_signalOperation = startParameters().device->signalOperation();
            QTC_ASSERT(m_signalOperation, notifyInferiorStopFailed());
            connect(m_signalOperation.data(), SIGNAL(finished(QString)),
                    SLOT(handleInterruptDeviceInferior(QString)));

            m_signalOperation->setDebuggerCommand(startParameters().debuggerCommand);
            m_signalOperation->interruptProcess(inferiorPid());
        } else {
            interruptInferior2();
        }
    }
}

void GdbEngine::handleInterruptDeviceInferior(const QString &error)
{
    if (error.isEmpty()) {
        showMessage(QLatin1String("Interrupted ") + QString::number(inferiorPid()));
        notifyInferiorStopOk();
    } else {
        showMessage(error, LogError);
        notifyInferiorStopFailed();
833
    }
834 835
    m_signalOperation->disconnect(this);
    m_signalOperation.clear();
con's avatar
con committed
836 837
}

838 839
void GdbEngine::interruptInferiorTemporarily()
{
840
    foreach (const GdbCommand &cmd, m_commandsToRunOnTemporaryBreak) {
841
        if (cmd.flags & LosesChild) {
hjk's avatar
hjk committed
842
            notifyInferiorIll();
843
            return;
844
        }
845
    }
846
    requestInterruptInferior();
847 848
}

con's avatar
con committed
849 850
void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
{
851
    const qint64 pid = pid0.toLongLong();
con's avatar
con committed
852
    if (pid == 0) {
853
        showMessage(_("Cannot parse PID from %1").arg(pid0));
con's avatar
con committed
854 855
        return;
    }
856
    if (pid == inferiorPid())
con's avatar
con committed
857
        return;
858

859 860
    showMessage(_("FOUND PID %1").arg(pid));
    notifyInferiorPid(pid);
con's avatar
con committed
861 862
}

863
void GdbEngine::postCommand(const QByteArray &command, GdbCommandCallback callback,