gdbengine.cpp 190 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
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
** 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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
con's avatar
con committed
25 26

#include "gdbengine.h"
27

28
#include <debugger/debuggerinternalconstants.h>
29
#include <debugger/debuggerruncontrol.h>
30 31 32 33 34 35 36 37 38 39 40
#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/debuggertooltipmanager.h>
#include <debugger/disassembleragent.h>
#include <debugger/memoryagent.h>
#include <debugger/sourceutils.h>
41
#include <debugger/terminal.h>
42 43 44 45 46 47 48 49 50 51 52

#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
53

54
#include <coreplugin/icore.h>
55
#include <coreplugin/messagebox.h>
56

57
#include <projectexplorer/devicesupport/deviceprocess.h>
58
#include <projectexplorer/itaskhandler.h>
59
#include <projectexplorer/projectexplorer.h>
60
#include <projectexplorer/taskhub.h>
hjk's avatar
hjk committed
61

62
#include <app/app_version.h>
63
#include <utils/algorithm.h>
64
#include <utils/hostosinfo.h>
hjk's avatar
hjk committed
65
#include <utils/macroexpander.h>
66 67
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
hjk's avatar
hjk committed
68
#include <utils/savedaction.h>
69 70
#include <utils/synchronousprocess.h>
#include <utils/temporarydirectory.h>
71
#include <utils/temporaryfile.h>
hjk's avatar
hjk committed
72

73
#include <QDirIterator>
74
#include <QMessageBox>
75
#include <QProcess>
76
#include <QPushButton>
77
#include <QJsonArray>
con's avatar
con committed
78

hjk's avatar
hjk committed
79
using namespace Core;
hjk's avatar
hjk committed
80
using namespace ProjectExplorer;
81
using namespace Utils;
hjk's avatar
hjk committed
82

83 84
namespace Debugger {
namespace Internal {
con's avatar
con committed
85

86 87 88
enum { debugPending = 0 };

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

90
#define CB(callback) [this](const DebuggerResponse &r) { callback(r); }
91

92
#define CHECK_STATE(s) do { checkState(s, __FILE__, __LINE__); } while (0)
93

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

con's avatar
con committed
126 127 128 129 130 131
static int &currentToken()
{
    static int token = 0;
    return token;
}

132
static bool isMostlyHarmlessMessage(const QStringRef &msg)
133 134 135
{
    return msg == "warning: GDB: Failed to set controlling terminal: "
                  "Inappropriate ioctl for device\\n"
136 137
        || msg == "warning: GDB: Failed to set controlling terminal: "
                  "Invalid argument\\n";
138 139
}

140 141 142 143 144
static QString mainFunction(const DebuggerRunParameters &rp)
{
    return QLatin1String(rp.toolChainAbi.os() == Abi::WindowsOS && !rp.useTerminal ? "qMain" : "main");
}

145 146 147 148 149 150 151 152 153 154 155 156
///////////////////////////////////////////////////////////////////////
//
// Debuginfo Taskhandler
//
///////////////////////////////////////////////////////////////////////

class DebugInfoTask
{
public:
    QString command;
};

hjk's avatar
hjk committed
157
class DebugInfoTaskHandler : public ITaskHandler
158 159
{
public:
hjk's avatar
hjk committed
160 161
    explicit DebugInfoTaskHandler(GdbEngine *engine)
        : m_engine(engine)
162 163
    {}

164
    bool canHandle(const Task &task) const override
165 166 167 168
    {
        return m_debugInfoTasks.contains(task.taskId);
    }

169
    void handle(const Task &task) override
170 171 172 173 174 175 176 177 178
    {
        m_engine->requestDebugInformation(m_debugInfoTasks.value(task.taskId));
    }

    void addTask(unsigned id, const DebugInfoTask &task)
    {
        m_debugInfoTasks[id] = task;
    }

179
    QAction *createAction(QObject *parent) const override
180
    {
hjk's avatar
hjk committed
181
        QAction *action = new QAction(DebuggerPlugin::tr("Install &Debug Information"), parent);
182
        action->setToolTip(DebuggerPlugin::tr("Tries to install missing debug information."));
183 184 185 186 187 188 189 190
        return action;
    }

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

con's avatar
con committed
191 192 193 194 195 196
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

197 198
GdbEngine::GdbEngine(bool useTerminal, DebuggerStartMode startMode)
    : m_startMode(startMode), m_useTerminal(useTerminal), m_terminalTrap(useTerminal)
con's avatar
con committed
199
{
hjk's avatar
hjk committed
200
    setObjectName("GdbEngine");
201

202 203
    m_gdbOutputCodec = QTextCodec::codecForLocale();
    m_inferiorOutputCodec = QTextCodec::codecForLocale();
204

205
    m_debugInfoTaskHandler = new DebugInfoTaskHandler(this);
hjk's avatar
hjk committed
206
    //ExtensionSystem::PluginManager::addObject(m_debugInfoTaskHandler);
207

208
    m_commandTimer.setSingleShot(true);
hjk's avatar
hjk committed
209 210 211 212 213 214 215 216 217 218 219
    connect(&m_commandTimer, &QTimer::timeout,
            this, &GdbEngine::commandTimeout);

    connect(action(AutoDerefPointers), &SavedAction::valueChanged,
            this, &GdbEngine::reloadLocals);
    connect(action(CreateFullBacktrace), &QAction::triggered,
            this, &GdbEngine::createFullBacktrace);
    connect(action(UseDebuggingHelpers), &SavedAction::valueChanged,
            this, &GdbEngine::reloadLocals);
    connect(action(UseDynamicType), &SavedAction::valueChanged,
            this, &GdbEngine::reloadLocals);
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236

    // Output
    connect(&m_outputCollector, &OutputCollector::byteDelivery,
            this, &GdbEngine::readDebuggeeOutput);

    if (isTermEngine()) {
        if (HostOsInfo::isWindowsHost()) {
            // Windows up to xp needs a workaround for attaching to freshly started processes. see proc_stub_win
            if (QSysInfo::WindowsVersion >= QSysInfo::WV_VISTA)
                m_stubProc.setMode(ConsoleProcess::Suspend);
            else
                m_stubProc.setMode(ConsoleProcess::Debug);
        } else {
            m_stubProc.setMode(ConsoleProcess::Debug);
            m_stubProc.setSettings(ICore::settings());
        }
    }
ck's avatar
ck committed
237 238
}

con's avatar
con committed
239 240
GdbEngine::~GdbEngine()
{
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
    if (isTermEngine())
        m_stubProc.disconnect(); // Avoid spurious state transitions from late exiting stub

    if (isCoreEngine()) {
        if (m_coreUnpackProcess) {
            m_coreUnpackProcess->blockSignals(true);
            m_coreUnpackProcess->terminate();
            m_coreUnpackProcess->deleteLater();
            m_coreUnpackProcess = nullptr;
            if (m_tempCoreFile.isOpen())
                m_tempCoreFile.close();
        }
        if (!m_tempCoreName.isEmpty()) {
            QFile tmpFile(m_tempCoreName);
            tmpFile.remove();
        }
    }

hjk's avatar
hjk committed
259
    //ExtensionSystem::PluginManager::removeObject(m_debugInfoTaskHandler);
260 261 262
    delete m_debugInfoTaskHandler;
    m_debugInfoTaskHandler = 0;

263
    // Prevent sending error messages afterwards.
264 265 266
    disconnect();
}

267 268 269 270 271
QString GdbEngine::failedToStartMessage()
{
    return tr("The gdb process failed to start.");
}

272 273
// Parse "~:gdb: unknown target exception 0xc0000139 at 0x77bef04e\n"
// and return an exception message
274
static QString msgWinException(const QString &data, unsigned *exCodeIn = 0)
275
{
276 277
    if (exCodeIn)
        *exCodeIn = 0;
278 279 280 281 282 283
    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);
284 285
    if (exCodeIn)
        *exCodeIn = exCode;
286 287 288
    const quint64 address = data.mid(addressPos).trimmed().toULongLong(0, 0);
    QString rc;
    QTextStream str(&rc);
289
    str << GdbEngine::tr("An exception was triggered:") << ' ';
290 291 292 293 294
    formatWindowsException(exCode, address, 0, 0, 0, str);
    str << '.';
    return rc;
}

hjk's avatar
hjk committed
295
static bool isNameChar(int c)
hjk's avatar
hjk committed
296 297 298 299 300
{
    // could be 'stopped' or 'shlibs-added'
    return (c >= 'a' && c <= 'z') || c == '-';
}

hjk's avatar
hjk committed
301
static bool contains(const QString &message, const QString &pattern, int size)
302 303 304 305 306 307 308
{
    const int s = message.size();
    if (s < size)
        return false;
    const int pos = message.indexOf(pattern);
    if (pos == -1)
        return false;
309 310 311
    const bool beginFits = pos == 0 || message.at(pos - 1) == '\n';
    const bool endFits = pos + size == s || message.at(pos + size) == '\n';
    return beginFits && endFits;
312 313
}

hjk's avatar
hjk committed
314
static bool isGdbConnectionError(const QString &message)
315 316 317 318 319 320 321
{
    // 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
322 323 324 325 326 327 328 329

    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);
330 331
}

hjk's avatar
hjk committed
332
void GdbEngine::handleResponse(const QString &buff)
con's avatar
con committed
333
{
hjk's avatar
hjk committed
334
    showMessage(buff, LogOutput);
con's avatar
con committed
335

336 337
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
338

hjk's avatar
hjk committed
339 340 341
    const QChar *from = buff.constData();
    const QChar *to = from + buff.size();
    const QChar *inner;
con's avatar
con committed
342

343
    int token = -1;
344
    // Token is a sequence of numbers.
345 346
    for (inner = from; inner != to; ++inner)
        if (*inner < '0' || *inner > '9')
con's avatar
con committed
347
            break;
348
    if (from != inner) {
hjk's avatar
hjk committed
349
        token = QString(from, inner - from).toInt();
350 351
        from = inner;
    }
con's avatar
con committed
352

353
    // Next char decides kind of response.
hjk's avatar
hjk committed
354 355
    const QChar c = *from++;
    switch (c.unicode()) {
356 357 358
        case '*':
        case '+':
        case '=': {
hjk's avatar
hjk committed
359
            QString asyncClass;
360
            for (; from != to; ++from) {
hjk's avatar
hjk committed
361 362
                const QChar c = *from;
                if (!isNameChar(c.unicode()))
363 364 365
                    break;
                asyncClass += *from;
            }
con's avatar
con committed
366

hjk's avatar
hjk committed
367
            GdbMi result;
368 369
            while (from != to) {
                GdbMi data;
hjk's avatar
hjk committed
370
                if (*from != ',') {
371 372
                    // happens on archer where we get
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb)
hjk's avatar
hjk committed
373
                    result.m_type = GdbMi::Tuple;
hjk's avatar
hjk committed
374 375 376 377 378
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
hjk's avatar
hjk committed
379
                    //qDebug() << "parsed result:" << data.toString();
380
                    result.m_children.push_back(data);
hjk's avatar
hjk committed
381
                    result.m_type = GdbMi::Tuple;
con's avatar
con committed
382 383
                }
            }
384
            handleAsyncOutput(asyncClass, result);
385 386
            break;
        }
387

388
        case '~': {
hjk's avatar
hjk committed
389
            QString data = GdbMi::parseCString(from, to);
390
            if (data.startsWith("bridgemessage={")) {
391
                // It's already logged.
392 393
                break;
            }
394 395 396
            if (data.startsWith("interpreterresult={")) {
                GdbMi allData;
                allData.fromStringMultiple(data);
397 398
                DebuggerResponse response;
                response.resultClass = ResultDone;
399 400
                response.data = allData["interpreterresult"];
                response.token = allData["token"].toInt();
401 402 403
                handleResultRecord(&response);
                break;
            }
404 405 406
            if (data.startsWith("interpreterasync={")) {
                GdbMi allData;
                allData.fromStringMultiple(data);
hjk's avatar
hjk committed
407
                QString asyncClass = allData["asyncclass"].data();
408 409 410 411
                if (asyncClass == "breakpointmodified")
                    handleInterpreterBreakpointModified(allData["interpreterasync"]);
                break;
            }
412
            m_pendingConsoleStreamOutput += data;
413 414

            // Show some messages to give the impression something happens.
415
            if (data.startsWith("Reading symbols from ")) {
hjk's avatar
hjk committed
416
                showStatusMessage(tr("Reading %1...").arg(data.mid(21)), 1000);
417
                progressPing();
418 419 420
            } else if (data.startsWith("[New ") || data.startsWith("[Thread ")) {
                if (data.endsWith('\n'))
                    data.chop(1);
421
                progressPing();
hjk's avatar
hjk committed
422
                showStatusMessage(data, 1000);
423 424 425 426
            } 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
427 428
                unsigned exCode;
                m_lastWinException = msgWinException(data, &exCode);
429
                showMessage(m_lastWinException, LogMisc);
430
                const Task::TaskType type = isFatalWinException(exCode) ? Task::Error : Task::Warning;
431
                TaskHub::addTask(type, m_lastWinException, Constants::TASK_CATEGORY_DEBUGGER_RUNTIME);
432
            }
433 434
            break;
        }
435

436
        case '@': {
hjk's avatar
hjk committed
437
            QString data = GdbMi::parseCString(from, to);
438
            QString msg = data.left(data.size() - 1);
439
            showMessage(msg, AppOutput);
440
            break;
con's avatar
con committed
441 442
        }

443
        case '&': {
hjk's avatar
hjk committed
444
            QString data = GdbMi::parseCString(from, to);
445 446
            // On Windows, the contents seem to depend on the debugger
            // version and/or OS version used.
447
            if (data.startsWith("warning:"))
hjk's avatar
hjk committed
448
                showMessage(data.mid(9), AppStuff); // Cut "warning: "
449 450

            m_pendingLogStreamOutput += data;
451

452
            if (isGdbConnectionError(data)) {
453
                notifyInferiorExited();
454
                break;
455
            }
456

hjk's avatar
hjk committed
457
            if (boolSetting(IdentifyDebugInfoPackages)) {
458 459 460
                // 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 ")) {
hjk's avatar
hjk committed
461
                    m_lastMissingDebugInfo = data.mid(32);
462
                } else if (data.startsWith("Try: zypper")) {
hjk's avatar
hjk committed
463
                    QString cmd = data.mid(4);
464 465 466 467

                    Task task(Task::Warning,
                        tr("Missing debug information for %1\nTry: %2")
                            .arg(m_lastMissingDebugInfo).arg(cmd),
hjk's avatar
hjk committed
468
                        FileName(), 0, Debugger::Constants::TASK_CATEGORY_DEBUGGER_DEBUGINFO);
469

Nicolas Arnaud-Cormos's avatar
Nicolas Arnaud-Cormos committed
470
                    TaskHub::addTask(task);
471 472 473 474 475

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

con's avatar
con committed
478 479 480
            break;
        }

481
        case '^': {
482
            DebuggerResponse response;
con's avatar
con committed
483

hjk's avatar
hjk committed
484
            response.token = token;
con's avatar
con committed
485

486 487 488
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
489

hjk's avatar
hjk committed
490
            QString resultClass = QString::fromRawData(from, inner - from);
491
            if (resultClass == "done")
492
                response.resultClass = ResultDone;
493
            else if (resultClass == "running")
494
                response.resultClass = ResultRunning;
495
            else if (resultClass == "connected")
496
                response.resultClass = ResultConnected;
497
            else if (resultClass == "error")
498
                response.resultClass = ResultError;
499
            else if (resultClass == "exit")
500
                response.resultClass = ResultExit;
501
            else
502
                response.resultClass = ResultUnknown;
con's avatar
con committed
503

504 505
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
506 507
                if (*from == ',') {
                    ++from;
hjk's avatar
hjk committed
508 509 510
                    response.data.parseTuple_helper(from, to);
                    response.data.m_type = GdbMi::Tuple;
                    response.data.m_name = "data";
hjk's avatar
hjk committed
511
                } else {
512
                    // Archer has this.
hjk's avatar
hjk committed
513 514
                    response.data.m_type = GdbMi::Tuple;
                    response.data.m_name = "data";
con's avatar
con committed
515 516
                }
            }
517

518 519
            response.logStreamOutput = m_pendingLogStreamOutput;
            response.consoleStreamOutput =  m_pendingConsoleStreamOutput;
520 521 522
            m_pendingLogStreamOutput.clear();
            m_pendingConsoleStreamOutput.clear();

523 524 525
            if (response.data.data().isEmpty())
                response.data.fromString(response.consoleStreamOutput);

526
            handleResultRecord(&response);
527 528 529
            break;
        }
        default: {
530
            qDebug() << "UNKNOWN RESPONSE TYPE '" << c << "'. REST: " << from;
531
            break;
con's avatar
con committed
532 533 534 535
        }
    }
}

hjk's avatar
hjk committed
536
void GdbEngine::handleAsyncOutput(const QString &asyncClass, const GdbMi &result)
537 538 539
{
    if (asyncClass == "stopped") {
        if (m_inUpdateLocals) {
hjk's avatar
hjk committed
540
            showMessage("UNEXPECTED *stopped NOTIFICATION IGNORED", LogWarning);
541 542 543 544 545 546 547
        } else {
            handleStopResponse(result);
            m_pendingLogStreamOutput.clear();
            m_pendingConsoleStreamOutput.clear();
        }
    } else if (asyncClass == "running") {
        if (m_inUpdateLocals) {
hjk's avatar
hjk committed
548
            showMessage("UNEXPECTED *running NOTIFICATION IGNORED", LogWarning);
549 550 551
        } else {
            GdbMi threads = result["thread-id"];
            threadsHandler()->notifyRunning(threads.data());
552 553 554 555
            if (runParameters().toolChainAbi.os() == Abi::WindowsOS) {
                // NOTE: Each created thread spits out a *running message. We completely ignore them
                // on Windows, and handle only numbered responses

556 557 558
                // 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.
559 560
            } else if (state() == InferiorRunOk || state() == InferiorSetupRequested) {
                // We get multiple *running after thread creation and in Windows terminals.
hjk's avatar
hjk committed
561 562
                showMessage(QString("NOTE: INFERIOR STILL RUNNING IN STATE %1.").
                            arg(DebuggerEngine::stateName(state())));
563 564 565 566 567 568 569 570 571 572 573 574 575 576
            } else {
                notifyInferiorRunOk();
            }
        }
    } 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"
hjk's avatar
hjk committed
577
        QString id = result["id"].data();
578
        if (!id.isEmpty())
hjk's avatar
hjk committed
579
            showStatusMessage(tr("Library %1 loaded").arg(id), 1000);
580 581 582 583
        progressPing();
        Module module;
        module.startAddress = 0;
        module.endAddress = 0;
hjk's avatar
hjk committed
584 585
        module.hostPath = result["host-name"].data();
        module.modulePath = result["target-name"].data();
586 587 588 589 590 591
        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"
hjk's avatar
hjk committed
592
        QString id = result["id"].data();
593
        progressPing();
hjk's avatar
hjk committed
594
        showStatusMessage(tr("Library %1 unloaded").arg(id), 1000);
595 596
    } else if (asyncClass == "thread-group-added") {
        // 7.1-symbianelf has "{id="i1"}"
597
    } else if (asyncClass == "thread-group-started") {
598 599 600 601 602
        // 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"
hjk's avatar
hjk committed
603 604
        QString id = result["id"].data();
        showStatusMessage(tr("Thread group %1 created").arg(id), 1000);
605
        notifyInferiorPid(result["pid"].toProcessHandle());
606 607 608
        handleThreadGroupCreated(result);
    } else if (asyncClass == "thread-created") {
        //"{id="1",group-id="28902"}"
hjk's avatar
hjk committed
609 610
        QString id = result["id"].data();
        showStatusMessage(tr("Thread %1 created").arg(id), 1000);
611 612 613 614 615 616
        ThreadData thread;
        thread.id = ThreadId(id.toLong());
        thread.groupId = result["group-id"].data();
        threadsHandler()->updateThread(thread);
    } else if (asyncClass == "thread-group-exited") {
        // Archer has "{id="28902"}"
hjk's avatar
hjk committed
617 618
        QString id = result["id"].data();
        showStatusMessage(tr("Thread group %1 exited").arg(id), 1000);
619 620 621
        handleThreadGroupExited(result);
    } else if (asyncClass == "thread-exited") {
        //"{id="1",group-id="28902"}"
hjk's avatar
hjk committed
622 623
        QString id = result["id"].data();
        QString groupid = result["group-id"].data();
624
        showStatusMessage(tr("Thread %1 in group %2 exited")
hjk's avatar
hjk committed
625
                          .arg(id).arg(groupid), 1000);
626 627
        threadsHandler()->removeThread(ThreadId(id.toLong()));
    } else if (asyncClass == "thread-selected") {
hjk's avatar
hjk committed
628 629
        QString id = result["id"].data();
        showStatusMessage(tr("Thread %1 selected").arg(id), 1000);
630 631 632 633 634 635 636 637 638 639 640 641 642
        //"{id="2"}"
    } 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.
hjk's avatar
hjk committed
643
        QString ba = result.toString();
644 645 646 647 648 649 650 651 652 653 654
        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);
        GdbMi res;
        res.fromString(ba);
        BreakHandler *handler = breakHandler();
        Breakpoint bp;
        BreakpointResponse br;
        foreach (const GdbMi &bkpt, res.children()) {
hjk's avatar
hjk committed
655
            const QString nr = bkpt["number"].data();
656
            BreakpointResponseId rid(nr);
657 658 659 660 661 662 663 664 665 666 667 668 669
            if (nr.contains('.')) {
                // A sub-breakpoint.
                BreakpointResponse sub;
                updateResponse(sub, bkpt);
                sub.id = rid;
                sub.type = br.type;
                bp.insertSubBreakpoint(sub);
            } else {
                // A primary breakpoint.
                bp = handler->findBreakpointByResponseId(rid);
                br = bp.response();
                updateResponse(br, bkpt);
                bp.setResponse(br);
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
            }
        }
    } 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.
hjk's avatar
hjk committed
688
        QString nr = result["id"].data();
689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
        BreakpointResponseId rid(nr);
        if (Breakpoint bp = breakHandler()->findBreakpointByResponseId(rid)) {
            // 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 (!bp.isOneShot()) ... is not sufficient.
            // It keeps temporary "Jump" breakpoints alive.
            bp.removeAlienBreakpoint();
        }
    } else if (asyncClass == "cmd-param-changed") {
        // New since 2012-08-09
        //  "{param="debug remote",value="1"}"
    } else if (asyncClass == "memory-changed") {
        // New since 2013
        //   "{thread-group="i1",addr="0x0918a7a8",len="0x10"}"
    } else if (asyncClass == "tsv-created") {
        // New since 2013-02-06
    } else if (asyncClass == "tsv-modified") {
        // New since 2013-02-06
    } else {
        qDebug() << "IGNORED ASYNC OUTPUT"
                 << asyncClass << result.toString();
    }
}

con's avatar
con committed
714 715
void GdbEngine::readGdbStandardError()
{
hjk's avatar
hjk committed
716 717
    QString err = QString::fromUtf8(m_gdbProc.readAllStandardError());
    showMessage("UNEXPECTED GDB STDERR: " + err);
718 719
    if (err == "Undefined command: \"bb\".  Try \"help\".\n")
        return;
720 721
    if (err.startsWith("BFD: reopening"))
        return;
Jarek Kobus's avatar
Jarek Kobus committed
722
    qWarning() << "Unexpected GDB stderr:" << err;
con's avatar
con committed
723 724
}

hjk's avatar
hjk committed
725 726
void GdbEngine::readDebuggeeOutput(const QByteArray &ba)
{
727 728 729 730 731 732 733
    const QString msg = m_inferiorOutputCodec->toUnicode(ba.constData(), ba.size(),
                                                         &m_inferiorOutputCodecState);

    if (msg.startsWith("&\"") && isMostlyHarmlessMessage(msg.midRef(2, msg.size() - 4)))
        showMessage("Mostly harmless terminal warning suppressed.", LogWarning);
    else
        showMessage(msg, AppStuff);
hjk's avatar
hjk committed
734 735
}

con's avatar
con committed
736 737
void GdbEngine::readGdbStandardOutput()
{
738
    m_commandTimer.start(); // Restart timer.
739

740 741
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
742

743
    QByteArray out = m_gdbProc.readAllStandardOutput();
hjk's avatar
hjk committed
744
    m_inbuffer.append(out);
con's avatar
con committed
745

746
    // This can trigger when a dialog starts a nested event loop.
747 748 749
    if (m_busy)
        return;

750 751
    while (newstart < m_inbuffer.size()) {
        int start = newstart;
752
        int end = m_inbuffer.indexOf('\n', scan);
753 754 755 756 757
        if (end < 0) {
            m_inbuffer.remove(0, start);
            return;
        }
        newstart = end + 1;
758
        scan = newstart;
759 760 761 762 763 764 765
        if (end == start)
            continue;
        if (m_inbuffer.at(end - 1) == '\r') {
            --end;
            if (end == start)
                continue;
        }
766
        m_busy = true;
hjk's avatar
hjk committed
767

768 769
        QString msg = m_gdbOutputCodec->toUnicode(m_inbuffer.constData() + start, end - start,
                                                  &m_gdbOutputCodecState);
hjk's avatar
hjk committed
770 771

        handleResponse(msg);
772
        m_busy = false;
con's avatar
con committed
773
    }
774
    m_inbuffer.clear();
con's avatar
con committed
775 776 777 778
}

void GdbEngine::interruptInferior()
{
779 780 781
    // A core never runs, so this cannot be called.
    QTC_ASSERT(!isCoreEngine(), return);

782
    CHECK_STATE(InferiorStopRequested);
783

784 785 786
    if (terminal()->sendInterrupt())
        return;

787
    if (usesExecInterrupt()) {
788
        runCommand({"-exec-interrupt"});
789 790
    } else {
        showStatusMessage(tr("Stop requested..."), 5000);
hjk's avatar
hjk committed
791
        showMessage("TRYING TO INTERRUPT INFERIOR");
hjk's avatar
hjk committed
792
        if (HostOsInfo::isWindowsHost() && !m_isQnxGdb) {
793
            QTC_ASSERT(state() == InferiorStopRequested, qDebug() << state(); notifyInferiorStopFailed());
794 795 796 797 798 799 800 801 802 803 804 805 806 807
            DeviceProcessSignalOperation::Ptr signalOperation = runTool()->device()->signalOperation();
            QTC_ASSERT(signalOperation, notifyInferiorStopFailed(); return);
            connect(signalOperation.data(), &DeviceProcessSignalOperation::finished,
                    this, [this, signalOperation](const QString &error) {
                        if (error.isEmpty()) {
                            showMessage("Interrupted " + QString::number(inferiorPid()));
                            notifyInferiorStopOk();
                        } else {
                            showMessage(error, LogError);
                            notifyInferiorStopFailed();
                        }
                    });
            signalOperation->setDebuggerCommand(runParameters().debugger.executable);
            signalOperation->interruptProcess(inferiorPid());
808 809 810 811 812 813
        } else {
            interruptInferior2();
        }
    }
}

814
void GdbEngine::runCommand(const DebuggerCommand &command)
hjk's avatar
hjk committed
815
{
816 817
    const int token = ++currentToken();

818
    DebuggerCommand cmd = command;
hjk's avatar
hjk committed
819

820 821 822 823 824 825
    if (cmd.function.isEmpty()) {
        showMessage(QString("EMPTY FUNCTION RUN, TOKEN: %1 ARGS: %2")
                        .arg(token).arg(cmd.args.toString()));
        QTC_ASSERT(false, return);
    }

hjk's avatar
hjk committed
826
    if (!stateAcceptsGdbCommands(state())) {
hjk's avatar
hjk committed
827 828
        showMessage(QString("NO GDB PROCESS RUNNING, CMD IGNORED: %1 %2")
            .arg(cmd.function).arg(state()));
con's avatar
con committed
829 830 831
        return;
    }

hjk's avatar
hjk committed
832
    if (cmd.flags & RebuildBreakpointModel) {
833
        ++m_pendingBreakpointRequests;
Andre Hartmann's avatar
Andre Hartmann committed
834
        PENDING_DEBUG("   BREAKPOINT MODEL:" << cmd.function
835
                      << "INCREMENTS PENDING TO" << m_pendingBreakpointRequests);
con's avatar
con committed
836
    } else {
837
        PENDING_DEBUG("   OTHER (IN):" << cmd.function
838
                      << "LEAVES PENDING BREAKPOINT AT" << m_pendingBreakpointRequests);
con's avatar
con committed
839 840
    }

hjk's avatar
hjk committed
841
    if (cmd.flags & (NeedsTemporaryStop|NeedsFullStop)) {
hjk's avatar
hjk committed
842
        showMessage("RUNNING NEEDS-STOP COMMAND " + cmd.function);
hjk's avatar
hjk committed
843 844
        const bool wantContinue = bool(cmd.flags & NeedsTemporaryStop);
        cmd.flags &= ~(NeedsTemporaryStop|NeedsFullStop);
845
        if (state() == InferiorStopRequested) {
hjk's avatar
hjk committed
846
            if (cmd.flags & LosesChild) {
847 848
                notifyInferiorIll();
                return;
849
            }
hjk's avatar
hjk committed
850
            showMessage("CHILD ALREADY BEING INTERRUPTED. STILL HOPING.");
851 852
            // Calling shutdown() here breaks all situations where two
            // NeedsStop commands are issued in quick succession.
hjk's avatar
hjk committed
853 854 855 856
            m_onStop.append(cmd, wantContinue);
            return;
        }
        if (state() == InferiorRunOk) {
857
            showStatusMessage(tr("Stopping temporarily"), 1000);
hjk's avatar
hjk committed
858
            m_onStop.append(cmd, wantContinue);
859
            requestInterruptInferior();
hjk's avatar
hjk committed
860
            return;
hjk's avatar
hjk committed
861
        }
hjk's avatar
hjk committed
862 863 864 865 866 867
        showMessage("UNSAFE STATE FOR QUEUED COMMAND. EXECUTING IMMEDIATELY");
    }

    if (!(cmd.flags & Discardable))
        ++m_nonDiscardableCount;

868
    bool isPythonCommand = true;
869
    if ((cmd.flags & NativeCommand) || cmd.function.contains('-') || cmd.function.contains(' '))
870 871
        isPythonCommand = false;
    if (isPythonCommand) {
hjk's avatar
hjk committed
872 873
        cmd.arg("token", token);
        cmd.function = "python theDumper." + cmd.function + "(" + cmd.argsToPython() + ")";
con's avatar
con committed
874 875
    }

876
    QTC_ASSERT(m_gdbProc.state() == QProcess::Running, return);
877

878
    cmd.postTime = QTime::currentTime().msecsSinceStartOfDay();
879
    m_commandForToken[token] = cmd;
hjk's avatar
hjk committed
880 881
    m_flagsForToken[token] = cmd.flags;
    if (cmd.flags & ConsoleCommand)
882
        cmd.function = "-interpreter-exec console \"" + cmd.function + '"';
hjk's avatar
hjk committed
883 884
    cmd.function = QString::number(token) + cmd.function;
    showMessage(cmd.function, LogInput);
885

886 887
    if (m_scheduledTestResponses.contains(token)) {
        // Fake response for test cases.
hjk's avatar
hjk committed
888 889
        QString buffer = m_scheduledTestResponses.value(token);
        buffer.replace("@TOKEN@", QString::number(token));
890
        m_scheduledTestResponses.remove(token);
hjk's avatar
hjk committed
891 892
        showMessage(QString("FAKING TEST RESPONSE (TOKEN: %2, RESPONSE: %3)")
                    .arg(token).arg(buffer));
893
        QMetaObject::invokeMethod(this, "handleResponse",
hjk's avatar
hjk committed
894
            Q_ARG(QString, buffer));
895
    } else {
896
        m_gdbProc.write(cmd.function.toUtf8() + "\r\n");
hjk's avatar
hjk committed
897
        if (command.flags & NeedsFlush)
898
            m_gdbProc.write("p 0\r\n");
899

900 901 902 903 904 905 906
        // 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.
907
        if (!cmd.function.endsWith("-gdb-exit"))
908
            m_commandTimer.start();
909

910 911 912
        //if (cmd.flags & LosesChild)
        //    notifyInferiorIll();
    }
913 914
}

915 916
int GdbEngine::commandTimeoutTime() const
{
hjk's avatar
hjk committed
917
    int time = action(GdbWatchdogTimeout)->value().toInt();
918
    return 1000 * qMax(40, time);
919 920
}

921 922
void GdbEngine::commandTimeout()
{
923
    QList<int> keys = m_commandForToken.keys();
Nikita Baryshnikov's avatar