gdbengine.cpp 141 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2 3 4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11 12 13 14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18 19 20 21 22 23
** 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.
24
**
25
** If you are unsure which license is appropriate for your use, please
26
** contact the sales department at http://www.qtsoftware.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
con's avatar
con committed
29

30 31
#define QT_NO_CAST_FROM_ASCII

con's avatar
con committed
32
#include "gdbengine.h"
33
#include "gdboptionspage.h"
con's avatar
con committed
34

35
#include "watchutils.h"
36
#include "debuggeractions.h"
con's avatar
con committed
37 38
#include "debuggerconstants.h"
#include "debuggermanager.h"
39
#include "debuggertooltip.h"
con's avatar
con committed
40 41 42 43 44 45 46 47 48
#include "gdbmi.h"
#include "procinterrupt.h"

#include "disassemblerhandler.h"
#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "stackhandler.h"
#include "watchhandler.h"
49
#include "sourcefileswindow.h"
con's avatar
con committed
50

51
#include "debuggerdialogs.h"
con's avatar
con committed
52

hjk's avatar
hjk committed
53
#include <utils/qtcassert.h>
54
#include <texteditor/itexteditor.h>
55
#include <coreplugin/icore.h>
hjk's avatar
hjk committed
56

con's avatar
con committed
57 58 59 60 61
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QTime>
#include <QtCore/QTimer>
62
#include <QtCore/QTextStream>
con's avatar
con committed
63 64

#include <QtGui/QAction>
65
#include <QtGui/QApplication>
con's avatar
con committed
66 67 68
#include <QtGui/QLabel>
#include <QtGui/QMainWindow>
#include <QtGui/QMessageBox>
69 70
#include <QtGui/QDialogButtonBox>
#include <QtGui/QPushButton>
71
#ifdef Q_OS_WIN
72
#    include "shared/sharedlibraryinjector.h"
73
#endif
con's avatar
con committed
74

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
75
#ifdef Q_OS_UNIX
con's avatar
con committed
76 77 78
#include <unistd.h>
#include <dlfcn.h>
#endif
hjk's avatar
hjk committed
79
#include <ctype.h>
con's avatar
con committed
80 81 82 83 84 85 86 87 88 89 90 91 92

using namespace Debugger;
using namespace Debugger::Internal;
using namespace Debugger::Constants;

Q_DECLARE_METATYPE(Debugger::Internal::GdbMi);

//#define DEBUG_PENDING  1
//#define DEBUG_SUBITEM  1

#if DEBUG_PENDING
#   define PENDING_DEBUG(s) qDebug() << s
#else
Roberto Raggi's avatar
Roberto Raggi committed
93
#   define PENDING_DEBUG(s)
con's avatar
con committed
94 95
#endif

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
96 97
#define STRINGIFY_INTERNAL(x) #x
#define STRINGIFY(x) STRINGIFY_INTERNAL(x)
98 99
#define CB(callback) &GdbEngine::callback, STRINGIFY(callback)

con's avatar
con committed
100 101 102 103 104 105
static int &currentToken()
{
    static int token = 0;
    return token;
}

106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
// reads a MI-encoded item frome the consolestream
static bool parseConsoleStream(const GdbResultRecord &record, GdbMi *contents)
{
    GdbMi output = record.data.findChild("consolestreamoutput");
    QByteArray out = output.data();

    int markerPos = out.indexOf('"') + 1; // position of 'success marker'
    if (markerPos == 0 || out.at(markerPos) == 'f') {  // 't' or 'f'
        // custom dumper produced no output
        return false;
    }

    out = out.mid(markerPos +  1);
    out = out.left(out.lastIndexOf('"'));
    // optimization: dumper output never needs real C unquoting
    out.replace('\\', "");
    out = "dummy={" + out + "}";

    contents->fromString(out);
    //qDebug() << "CONTENTS" << contents->toString(true);
    return contents->isValid();
}

static QByteArray parsePlainConsoleStream(const GdbResultRecord &record)
{
    GdbMi output = record.data.findChild("consolestreamoutput");
    QByteArray out = output.data();
    // FIXME: proper decoding needed
    if (out.endsWith("\\n"))
        out.chop(2);
    while (out.endsWith('\n') || out.endsWith(' '))
        out.chop(1);
    int pos = out.indexOf(" = ");
    return out.mid(pos + 3);
}

con's avatar
con committed
142 143 144 145 146 147
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

148 149
GdbEngine::GdbEngine(DebuggerManager *parent) :
#ifdef Q_OS_WIN // Do injection loading with MinGW (call loading does not work with 64bit)
150
    m_dumperInjectionLoad(true),
151
#else
152
    m_dumperInjectionLoad(false),
153
#endif
154 155
    q(parent),
    qq(parent->engineInterface())
con's avatar
con committed
156
{
157
    m_stubProc.setMode(Core::Utils::ConsoleProcess::Debug);
158 159 160
#ifdef Q_OS_UNIX
    m_stubProc.setSettings(Core::ICore::instance()->settings());
#endif
161 162
    initializeVariables();
    initializeConnections();
con's avatar
con committed
163 164 165 166
}

GdbEngine::~GdbEngine()
{
hjk's avatar
hjk committed
167 168
    // prevent sending error messages afterwards
    m_gdbProc.disconnect(this);
con's avatar
con committed
169 170
}

171
void GdbEngine::initializeConnections()
con's avatar
con committed
172 173
{
    // Gdb Process interaction
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
    connect(&m_gdbProc, SIGNAL(error(QProcess::ProcessError)),
        this, SLOT(gdbProcError(QProcess::ProcessError)));
    connect(&m_gdbProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readGdbStandardOutput()));
    connect(&m_gdbProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readGdbStandardError()));
    connect(&m_gdbProc, SIGNAL(finished(int, QProcess::ExitStatus)),
        q, SLOT(exitDebugger()));

    connect(&m_stubProc, SIGNAL(processError(QString)),
        this, SLOT(stubError(QString)));
    connect(&m_stubProc, SIGNAL(processStarted()),
        this, SLOT(stubStarted()));
    connect(&m_stubProc, SIGNAL(wrapperStopped()),
        q, SLOT(exitDebugger()));

    connect(&m_uploadProc, SIGNAL(error(QProcess::ProcessError)),
        this, SLOT(uploadProcError(QProcess::ProcessError)));
192 193 194 195
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
196

con's avatar
con committed
197
    // Output
198
    connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
199
        this, SLOT(readDebugeeOutput(QByteArray)));
con's avatar
con committed
200

201 202
    connect(this, SIGNAL(gdbOutputAvailable(int,QString)),
        q, SLOT(showDebuggerOutput(int,QString)),
con's avatar
con committed
203
        Qt::QueuedConnection);
204 205
    connect(this, SIGNAL(gdbInputAvailable(int,QString)),
        q, SLOT(showDebuggerInput(int,QString)),
con's avatar
con committed
206
        Qt::QueuedConnection);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
207 208
    connect(this, SIGNAL(applicationOutputAvailable(QString)),
        q, SLOT(showApplicationOutput(QString)),
con's avatar
con committed
209
        Qt::QueuedConnection);
210

211
    // FIXME: These trigger even if the engine is not active
212 213 214 215 216 217
    connect(theDebuggerAction(UseDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setUseDebuggingHelpers(QVariant)));
    connect(theDebuggerAction(DebugDebuggingHelpers), SIGNAL(valueChanged(QVariant)),
        this, SLOT(setDebugDebuggingHelpers(QVariant)));
    connect(theDebuggerAction(RecheckDebuggingHelpers), SIGNAL(triggered()),
        this, SLOT(recheckDebuggingHelperAvailability()));
con's avatar
con committed
218 219
}

220 221
void GdbEngine::initializeVariables()
{
222
    m_debuggingHelperState = DebuggingHelperUninitialized;
223
    m_gdbVersion = 100;
hjk's avatar
hjk committed
224
    m_gdbBuildVersion = -1;
225 226 227 228 229 230 231 232 233

    m_fullToShortName.clear();
    m_shortToFullName.clear();
    m_varToType.clear();

    m_modulesListOutdated = true;
    m_oldestAcceptableToken = -1;
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingRequests = 0;
234
    m_autoContinue = false;
235
    m_waitingForFirstBreakpointToBeHit = false;
hjk's avatar
hjk committed
236
    m_commandsToRunOnTemporaryBreak.clear();
237
    m_cookieForToken.clear();
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
    m_customOutputForToken.clear();

    m_pendingConsoleStreamOutput.clear();
    m_pendingTargetStreamOutput.clear();
    m_pendingLogStreamOutput.clear();

    m_inbuffer.clear();

    m_address.clear();
    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
    m_dumperHelper = QtDumperHelper();

    // FIXME: unhandled:
    //m_outputCodecState = QTextCodec::ConverterState();
    //OutputCollector m_outputCollector;
    //QProcess m_gdbProc;
    //QProcess m_uploadProc;
    //Core::Utils::ConsoleProcess m_stubProc;
257 258
}

con's avatar
con committed
259 260 261
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
262
    bool kill = true;
con's avatar
con committed
263 264
    switch (error) {
        case QProcess::FailedToStart:
265
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
266
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
267
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
268
                "permissions to invoke the program.")
hjk's avatar
hjk committed
269
                .arg(theDebuggerStringSetting(GdbLocation));
con's avatar
con committed
270 271
            break;
        case QProcess::Crashed:
272
            kill = false;
con's avatar
con committed
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
            msg = tr("The Gdb process crashed some time after starting "
                "successfully.");
            break;
        case QProcess::Timedout:
            msg = tr("The last waitFor...() function timed out. "
                "The state of QProcess is unchanged, and you can try calling "
                "waitFor...() again.");
            break;
        case QProcess::WriteError:
            msg = 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.");
            break;
        case QProcess::ReadError:
            msg = tr("An error occurred when attempting to read from "
                "the Gdb process. For example, the process may not be running.");
            break;
        default:
            msg = tr("An unknown error in the Gdb process occurred. "
                "This is the default return value of error().");
    }

295
    q->showStatusMessage(msg);
con's avatar
con committed
296 297
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
    // act as if it was closed by the core
298 299
    if (kill)
        q->exitDebugger();
con's avatar
con committed
300 301
}

302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
void GdbEngine::uploadProcError(QProcess::ProcessError error)
{
    QString msg;
    switch (error) {
        case QProcess::FailedToStart:
            msg = tr("The upload process failed to start. Either the "
                "invoked script '%1' is missing, or you may have insufficient "
                "permissions to invoke the program.")
                .arg(theDebuggerStringSetting(GdbLocation));
            break;
        case QProcess::Crashed:
            msg = tr("The upload process crashed some time after starting "
                "successfully.");
            break;
        case QProcess::Timedout:
            msg = tr("The last waitFor...() function timed out. "
                "The state of QProcess is unchanged, and you can try calling "
                "waitFor...() again.");
            break;
        case QProcess::WriteError:
            msg = tr("An error occurred when attempting to write "
                "to the upload process. For example, the process may not be running, "
                "or it may have closed its input channel.");
            break;
        case QProcess::ReadError:
            msg = tr("An error occurred when attempting to read from "
                "the upload process. For example, the process may not be running.");
            break;
        default:
            msg = tr("An unknown error in the upload process occurred. "
                "This is the default return value of error().");
    }

    q->showStatusMessage(msg);
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
}

339 340 341
void GdbEngine::readUploadStandardOutput()
{
    QByteArray ba = m_uploadProc.readAllStandardOutput();
342
    gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(ba, ba.length()));
343 344 345 346 347
}

void GdbEngine::readUploadStandardError()
{
    QByteArray ba = m_uploadProc.readAllStandardError();
348
    gdbOutputAvailable(LogError, QString::fromLocal8Bit(ba, ba.length()));
349 350
}

con's avatar
con committed
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
#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

367 368 369 370 371 372
void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
    emit applicationOutputAvailable(m_outputCodec->toUnicode(
            data.constData(), data.length(), &m_outputCodecState));
}

hjk's avatar
hjk committed
373 374
void GdbEngine::debugMessage(const QString &msg)
{
375
    emit gdbOutputAvailable(LogDebug, msg);
hjk's avatar
hjk committed
376 377
}

378
void GdbEngine::handleResponse(const QByteArray &buff)
con's avatar
con committed
379 380 381
{
    static QTime lastTime;

382 383 384
    if (theDebuggerBoolSetting(LogTimeStamps))
        emit gdbOutputAvailable(LogDebug, currentTime());
    emit gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(buff, buff.length()));
con's avatar
con committed
385 386 387 388 389

#if 0
    qDebug() // << "#### start response handling #### "
        << currentTime()
        << lastTime.msecsTo(QTime::currentTime()) << "ms,"
390 391
        << "buf:" << buff.left(1500) << "..."
        //<< "buf:" << buff
392
        << "size:" << buff.size();
con's avatar
con committed
393
#else
394
    //qDebug() << "buf:" << buff;
con's avatar
con committed
395 396 397 398
#endif

    lastTime = QTime::currentTime();

399 400
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
401

402 403 404
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
405

406 407 408 409
    int token = -1;
    // token is a sequence of numbers
    for (inner = from; inner != to; ++inner)
        if (*inner < '0' || *inner > '9')
con's avatar
con committed
410
            break;
411 412 413
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
414
        //qDebug() << "found token" << token;
415
    }
con's avatar
con committed
416

417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
    // next char decides kind of record
    const char c = *from++;
    //qDebug() << "CODE:" << c;
    switch (c) {
        case '*':
        case '+':
        case '=': {
            QByteArray asyncClass;
            for (; from != to; ++from) {
                const char c = *from;
                if (!isNameChar(c))
                    break;
                asyncClass += *from;
            }
            //qDebug() << "ASYNCCLASS" << asyncClass;
con's avatar
con committed
432

433 434 435
            GdbMi record;
            while (from != to) {
                GdbMi data;
hjk's avatar
hjk committed
436
                if (*from != ',') {
hjk's avatar
hjk committed
437 438
                    // happens on archer where we get 
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb) 
439
                    record.m_type = GdbMi::Tuple;
hjk's avatar
hjk committed
440 441 442 443 444
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
445
                    //qDebug() << "parsed response:" << data.toString();
hjk's avatar
hjk committed
446 447
                    record.m_children += data;
                    record.m_type = GdbMi::Tuple;
con's avatar
con committed
448 449
                }
            }
450 451 452 453
            if (asyncClass == "stopped") {
                handleAsyncOutput(record);
            } else if (asyncClass == "running") {
                // Archer has 'thread-id="all"' here
454 455 456 457
            } 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",
458
                // symbols-loaded="0"
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
459
                QByteArray id = record.findChild("id").data();
460
                if (!id.isEmpty())
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
461
                    q->showStatusMessage(tr("Library %1 loaded.").arg(_(id)));
hjk's avatar
hjk committed
462 463 464 465
            } 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"
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
466 467
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Library %1 unloaded.").arg(_(id)));
468 469
            } else if (asyncClass == "thread-group-created") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
470 471
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread group %1 created.").arg(_(id)));
472 473
            } else if (asyncClass == "thread-created") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
474 475
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread %1 created.").arg(_(id)));
476 477
            } else if (asyncClass == "thread-group-exited") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
478 479
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread group %1 exited.").arg(_(id)));
480 481
            } else if (asyncClass == "thread-exited") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
482 483
                QByteArray id = record.findChild("id").data();
                QByteArray groupid = record.findChild("group-id").data();
484
                q->showStatusMessage(tr("Thread %1 in group %2 exited.")
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
485
                    .arg(_(id)).arg(_(groupid)));
hjk's avatar
hjk committed
486
            } else if (asyncClass == "thread-selected") {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
487 488
                QByteArray id = record.findChild("id").data();
                q->showStatusMessage(tr("Thread %1 selected.").arg(_(id)));
hjk's avatar
hjk committed
489
                //"{id="2"}" 
490
            #if defined(Q_OS_MAC)
491 492 493 494 495 496 497 498 499 500 501
            } else if (asyncClass == "shlibs-updated") {
                // MAC announces updated libs
            } else if (asyncClass == "shlibs-added") {
                // MAC 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=""}}
            #endif
            } else {
502
                qDebug() << "IGNORED ASYNC OUTPUT"
503
                    << asyncClass << record.toString();
504
            }
505 506
            break;
        }
507

508
        case '~': {
509 510 511
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingConsoleStreamOutput += data;
            if (data.startsWith("Reading symbols from ")) {
512
                q->showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))));
513
            }
514 515
            break;
        }
516

517
        case '@': {
518 519
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingTargetStreamOutput += data;
520
            break;
con's avatar
con committed
521 522
        }

523 524 525 526 527 528
        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:"))
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
529
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
530 531 532
            break;
        }

533 534
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
535

536
            record.token = token;
con's avatar
con committed
537

538 539 540
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
541

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
542
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
543 544 545 546 547 548 549 550 551 552 553 554
            if (resultClass == "done")
                record.resultClass = GdbResultDone;
            else if (resultClass == "running")
                record.resultClass = GdbResultRunning;
            else if (resultClass == "connected")
                record.resultClass = GdbResultConnected;
            else if (resultClass == "error")
                record.resultClass = GdbResultError;
            else if (resultClass == "exit")
                record.resultClass = GdbResultExit;
            else
                record.resultClass = GdbResultUnknown;
con's avatar
con committed
555

556 557
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
558 559 560 561 562 563 564 565 566
                if (*from == ',') {
                    ++from;
                    record.data.parseTuple_helper(from, to);
                    record.data.m_type = GdbMi::Tuple;
                    record.data.m_name = "data";
                } else {
                    // Archer has this
                    record.data.m_type = GdbMi::Tuple;
                    record.data.m_name = "data";
con's avatar
con committed
567 568
                }
            }
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593

            //qDebug() << "\nLOG STREAM:" + m_pendingLogStreamOutput;
            //qDebug() << "\nTARGET STREAM:" + m_pendingTargetStreamOutput;
            //qDebug() << "\nCONSOLE STREAM:" + m_pendingConsoleStreamOutput;
            record.data.setStreamOutput("logstreamoutput",
                m_pendingLogStreamOutput);
            record.data.setStreamOutput("targetstreamoutput",
                m_pendingTargetStreamOutput);
            record.data.setStreamOutput("consolestreamoutput",
                m_pendingConsoleStreamOutput);
            QByteArray custom = m_customOutputForToken[token];
            if (!custom.isEmpty())
                record.data.setStreamOutput("customvaluecontents",
                    '{' + custom + '}');
            //m_customOutputForToken.remove(token);
            m_pendingLogStreamOutput.clear();
            m_pendingTargetStreamOutput.clear();
            m_pendingConsoleStreamOutput.clear();

            handleResultRecord(record);
            break;
        }
        default: {
            qDebug() << "UNKNOWN RESPONSE TYPE" << c;
            break;
con's avatar
con committed
594 595 596 597
        }
    }
}

598
void GdbEngine::handleStubAttached(const GdbResultRecord &, const QVariant &)
599 600 601
{
    qq->notifyInferiorStopped();
    handleAqcuiredInferior();
602
    m_autoContinue = true;
603 604 605 606
}

void GdbEngine::stubStarted()
{
607 608 609
    const qint64 attachedPID = m_stubProc.applicationPID();
    qq->notifyInferiorPidChanged(attachedPID);
    postCommand(_("attach %1").arg(attachedPID), CB(handleStubAttached));
610 611 612 613 614 615 616
}

void GdbEngine::stubError(const QString &msg)
{
    QMessageBox::critical(q->mainWindow(), tr("Debugger Error"), msg);
}

con's avatar
con committed
617 618
void GdbEngine::readGdbStandardError()
{
619
    qWarning() << "Unexpected gdb stderr:" << m_gdbProc.readAllStandardError();
con's avatar
con committed
620 621 622 623
}

void GdbEngine::readGdbStandardOutput()
{
624 625
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
626

627
    m_inbuffer.append(m_gdbProc.readAllStandardOutput());
con's avatar
con committed
628

629 630
    while (newstart < m_inbuffer.size()) {
        int start = newstart;
631
        int end = m_inbuffer.indexOf('\n', scan);
632 633 634 635 636
        if (end < 0) {
            m_inbuffer.remove(0, start);
            return;
        }
        newstart = end + 1;
637
        scan = newstart;
638 639
        if (end == start)
            continue;
640
        #if defined(Q_OS_WIN)
641 642 643 644 645
        if (m_inbuffer.at(end - 1) == '\r') {
            --end;
            if (end == start)
                continue;
        }
646
        #endif
647
        handleResponse(QByteArray::fromRawData(m_inbuffer.constData() + start, end - start));
con's avatar
con committed
648
    }
649
    m_inbuffer.clear();
con's avatar
con committed
650 651 652 653
}

void GdbEngine::interruptInferior()
{
hjk's avatar
hjk committed
654
    qq->notifyInferiorStopRequested();
655

hjk's avatar
hjk committed
656
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
657
        debugMessage(_("TRYING TO INTERRUPT INFERIOR WITHOUT RUNNING GDB"));
hjk's avatar
hjk committed
658
        qq->notifyInferiorExited();
con's avatar
con committed
659
        return;
hjk's avatar
hjk committed
660
    }
con's avatar
con committed
661

662
    if (q->startMode() == StartRemote) {
663
        postCommand(_("-exec-interrupt"));
664 665 666
        return;
    }

667 668
    const qint64 attachedPID = q->inferiorPid();
    if (attachedPID <= 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
669
        debugMessage(_("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED"));
con's avatar
con committed
670 671 672
        return;
    }

673 674
    if (!interruptProcess(attachedPID))
        debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
con's avatar
con committed
675 676 677 678
}

void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
{
679
    const qint64 pid = pid0.toLongLong();
con's avatar
con committed
680
    if (pid == 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
681
        debugMessage(_("Cannot parse PID from %1").arg(pid0));
con's avatar
con committed
682 683
        return;
    }
684
    if (pid == q->inferiorPid())
con's avatar
con committed
685
        return;
686 687
    debugMessage(_("FOUND PID %1").arg(pid));    

Roberto Raggi's avatar
Roberto Raggi committed
688
    qq->notifyInferiorPidChanged(pid);
689 690
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
691 692
}

693
void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
694 695
                            const char *callbackName, const QVariant &cookie)
{
696
    postCommand(command, NoFlags, callback, callbackName, cookie);
697 698
}

699
void GdbEngine::postCommand(const QString &command, GdbCommandFlags flags,
700 701
                            GdbCommandCallback callback, const char *callbackName,
                            const QVariant &cookie)
con's avatar
con committed
702 703
{
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
704
        debugMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: ") + command);
con's avatar
con committed
705 706 707
        return;
    }

708
    if (flags & RebuildModel) {
con's avatar
con committed
709
        ++m_pendingRequests;
710
        PENDING_DEBUG("   CALLBACK" << callbackName << "INCREMENTS PENDING TO:"
con's avatar
con committed
711 712
            << m_pendingRequests << command);
    } else {
713
        PENDING_DEBUG("   UNKNOWN CALLBACK" << callbackName << "LEAVES PENDING AT:"
con's avatar
con committed
714 715 716
            << m_pendingRequests << command);
    }

717
    GdbCommand cmd;
con's avatar
con committed
718
    cmd.command = command;
719 720 721
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
722 723
    cmd.cookie = cookie;

724
    if ((flags & NeedsStop) && q->status() != DebuggerInferiorStopped
hjk's avatar
hjk committed
725 726 727
            && q->status() != DebuggerProcessStartingUp) {
        // queue the commands that we cannot send at once
        QTC_ASSERT(q->status() == DebuggerInferiorRunning,
728
            qDebug() << "STATUS:" << q->status());
hjk's avatar
hjk committed
729
        q->showStatusMessage(tr("Stopping temporarily."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
730
        debugMessage(_("QUEUING COMMAND ") + cmd.command);
hjk's avatar
hjk committed
731 732 733
        m_commandsToRunOnTemporaryBreak.append(cmd);
        interruptInferior();
    } else if (!command.isEmpty()) {
734
        flushCommand(cmd);
con's avatar
con committed
735 736 737
    }
}

738 739 740 741 742
void GdbEngine::flushCommand(GdbCommand &cmd)
{
    ++currentToken();
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
743
    if (cmd.flags & EmbedToken)
744 745 746 747 748
        cmd.command = cmd.command.arg(currentToken());

    m_gdbProc.write(cmd.command.toLatin1() + "\r\n");
    //emit gdbInputAvailable(QString(), "         " +  currentTime());
    //emit gdbInputAvailable(QString(), "[" + currentTime() + "]    " + cmd.command);
749
    emit gdbInputAvailable(LogInput, cmd.command);
750 751
}

con's avatar
con committed
752 753
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
754 755
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
756 757 758 759 760 761 762
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

    int token = record.token;
    if (token == -1)
        return;

763
    GdbCommand cmd = m_cookieForToken.take(token);
con's avatar
con committed
764

765
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
766
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
con's avatar
con committed
767 768 769 770 771
        //QMessageBox::information(m_mainWindow, tr("Skipped"), "xxx");
        return;
    }

#if 0
772
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
773
        << "cmd name:" << cmd.callbackName
774
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
775 776 777 778 779
        << "\n record: " << record.toString();
#endif

    // << "\n data: " << record.data.toString(true);

780 781
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
782

783
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
784
        --m_pendingRequests;
hjk's avatar
hjk committed
785
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
786
            << m_pendingRequests << cmd.command);
787
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
788 789
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
790
        }
con's avatar
con committed
791
    } else {
hjk's avatar
hjk committed
792
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
793 794
            << m_pendingRequests << cmd.command);
    }
795 796 797 798 799 800 801 802 803 804

    // 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_cookieForToken.isEmpty() && m_autoContinue) {
        m_autoContinue = false;
        continueInferior();
        q->showStatusMessage(tr("Continuing after temporary stop."));
    }
con's avatar
con committed
805 806 807 808 809
}

void GdbEngine::executeDebuggerCommand(const QString &command)
{
    if (m_gdbProc.state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
810
        debugMessage(_("NO GDB PROCESS RUNNING, PLAIN CMD IGNORED: ") + command);
con's avatar
con committed
811 812 813
        return;
    }

814
    m_gdbProc.write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
815 816
}

817
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
818 819
{
    qq->notifyInferiorStopped();
820 821
    q->showStatusMessage(tr("Core file loaded."));
    q->resetLocation();
822
    tryLoadDebuggingHelpers();
823 824
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
825
    reloadStack();
826
    if (supportsThreads())
827
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
828
    qq->reloadRegisters();
829 830
}

831
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
832 833
{
    if (record.resultClass == GdbResultDone) {
834
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
835 836 837 838 839 840
        m_shortToFullName.clear();
        m_fullToShortName.clear();
        // "^done,files=[{file="../../../../bin/gdbmacros/gdbmacros.cpp",
        // fullname="/data5/dev/ide/main/bin/gdbmacros/gdbmacros.cpp"},
        GdbMi files = record.data.findChild("files");
        foreach (const GdbMi &item, files.children()) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
841
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
842
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
843
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
844 845 846 847
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
848
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
849 850 851 852
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
853 854
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
855 856 857
    }
}

858
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
859
{
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
    if (record.resultClass != GdbResultDone)
        return;
    // FIXME: use something more robust
    // WIN:     [New thread 3380.0x2bc]
    //          * 3 Thread 2312.0x4d0  0x7c91120f in ?? ()
    // LINUX:   * 1 Thread 0x7f466273c6f0 (LWP 21455)  0x0000000000404542 in ...
    const QString data = _(record.data.findChild("consolestreamoutput").data());
    if (data.isEmpty())
        return;
    // check "[New thread 3380.0x2bc]"
    if (data.startsWith(QLatin1Char('['))) {
        QRegExp ren(_("^\\[New thread (\\d+)\\.0x.*"));
        Q_ASSERT(ren.isValid());
        if (ren.indexIn(data) != -1) {
            maybeHandleInferiorPidChanged(ren.cap(1));
            return;
        }
877
    }
878 879 880 881 882
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
883 884
}

885
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
886 887
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
888
        #ifdef Q_OS_MAC
con's avatar
con committed
889
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer  
Oswald Buddenhagen committed
890
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
891
        #else
con's avatar
con committed
892
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
893 894
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
895 896 897 898 899 900
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

901
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
902 903 904
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
905
        handleModulesList(record, cookie);
con's avatar
con committed
906 907 908
    }
}

909
#if 0
con's avatar
con committed
910 911 912 913 914 915 916 917 918 919
void GdbEngine::handleExecJumpToLine(const GdbResultRecord &record)
{
    // FIXME: remove this special case as soon as 'jump'
    // is supported by MI
    // "&"jump /home/apoenitz/dev/work/test1/test1.cpp:242"
    // ~"Continuing at 0x4058f3."
    // ~"run1 (argc=1, argv=0x7fffb213a478) at test1.cpp:242"
    // ~"242\t x *= 2;"
    //109^done"
    qq->notifyInferiorStopped();
920
    q->showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
921
    QByteArray output = record.data.findChild("logstreamoutput").data();
922
    if (output.isEmpty())
con's avatar
con committed
923
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
924 925 926 927 928 929 930 931 932
    int idx1 = output.indexOf(' ') + 1;
    if (idx1 > 0) {
        int idx2 = output.indexOf(':', idx1);
        if (idx2 > 0) {
            QString file = QString::fromLocal8Bit(output.mid(idx1, idx2 - idx1));
            int line = output.mid(idx2 + 1).toInt();
            q->gotoLocation(file, line, true);
        }
    }
con's avatar
con committed
933
}
934
#endif
con's avatar
con committed
935

936
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
937 938 939 940 941 942 943 944
{
    // FIXME: remove this special case as soon as there's a real
    // reason given when the temporary breakpoint is hit.
    // reight now we get:
    // 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4",
    // func="foo",args=[{name="str",value="@0x7fff0f450460"}],
    // file="main.cpp",fullname="/tmp/g/main.cpp",line="37"}
    qq->notifyInferiorStopped();
945
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
946
    GdbMi frame = record.data.findChild("frame");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
947
    QString file = QString::fromLocal8Bit(frame.findChild("fullname").data());
con's avatar
con committed
948
    int line = frame.findChild("line").data().toInt();
949 950
    qDebug() << "HIT:" << file << line << "IN" << frame.toString()
        << "--" << record.toString();
con's avatar
con committed
951 952 953
    q->gotoLocation(file, line, true);
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
954
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
955
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
956 957 958 959
    return reason == "exited-normally"   // inferior exited normally
        || reason == "exited-signalled"  // inferior exited because of a signal
        //|| reason == "signal-received" // inferior received signal
        || reason == "exited";           // inferior exited
con's avatar
con committed
960 961
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
962
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
963
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
964 965 966 967 968 969 970
    return reason == "function-finished"  // -exec-finish
        || reason == "signal-received"  // handled as "isExitedReason"
        || reason == "breakpoint-hit"     // -exec-continue
        || reason == "end-stepping-range" // -exec-next, -exec-step
        || reason == "location-reached"   // -exec-until
        || reason == "access-watchpoint-trigger"
        || reason == "read-watchpoint-trigger"
971
        #ifdef Q_OS_MAC
con's avatar
con committed
972
        || reason.isEmpty()
973
        #endif
con's avatar
con committed
974 975 976
    ;
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
977 978
void GdbEngine::handleAqcuiredInferior()
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
979
    #ifdef Q_OS_WIN
980
    postCommand(_("info thread"), CB(handleInfoThreads));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
981
    #elif defined(Q_OS_MAC)
982
    postCommand(_("info pid"), NeedsStop, CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
983 984
    #else
    postCommand(_("info proc"), CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
985
    #endif
986
    if (theDebuggerBoolSetting(ListSourceFiles))
987
        reloadSourceFiles();
988

989
    // Reverse debugging. FIXME: Should only be used when available.
990 991
    //if (theDebuggerBoolSetting(EnableReverseDebugging))
    //    postCommand(_("target record"));
992

993
    tryLoadDebuggingHelpers();