gdbengine.cpp 142 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 "asyncwatchmodel.h"
50
#include "sourcefileswindow.h"
con's avatar
con committed
51

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

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

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

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

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

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
94
#   define PENDING_DEBUG(s)
con's avatar
con committed
95 96
#endif

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

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

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 142
// 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
143 144 145 146 147 148
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

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

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

173
void GdbEngine::initializeConnections()
con's avatar
con committed
174
{
175 176
    connect(qq->watchHandler(), SIGNAL(watcherInserted(WatchData)), &m_models, SLOT(insertWatcher(WatchData)));
    connect(&m_models, SIGNAL(watchDataUpdateNeeded(WatchData)), this, SLOT(updateWatchData(WatchData)));
con's avatar
con committed
177
    // Gdb Process interaction
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    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)));
196 197 198 199
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
200

con's avatar
con committed
201
    // Output
202
    connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
203
        this, SLOT(readDebugeeOutput(QByteArray)));
con's avatar
con committed
204

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

215
    // FIXME: These trigger even if the engine is not active
216 217 218 219 220 221
    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
222 223
}

224 225
void GdbEngine::initializeVariables()
{
226
    m_debuggingHelperState = DebuggingHelperUninitialized;
227
    m_gdbVersion = 100;
hjk's avatar
hjk committed
228
    m_gdbBuildVersion = -1;
229 230 231 232 233 234 235 236 237

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

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

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

    m_inbuffer.clear();

    m_address.clear();
    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
253
    m_dumperHelper.clear();
254 255 256 257 258 259 260

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

con's avatar
con committed
263 264 265
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
266
    bool kill = true;
con's avatar
con committed
267 268
    switch (error) {
        case QProcess::FailedToStart:
269
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
270
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
271
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
272
                "permissions to invoke the program.")
hjk's avatar
hjk committed
273
                .arg(theDebuggerStringSetting(GdbLocation));
con's avatar
con committed
274 275
            break;
        case QProcess::Crashed:
276
            kill = false;
con's avatar
con committed
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
            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().");
    }

299
    q->showStatusMessage(msg);
con's avatar
con committed
300 301
    QMessageBox::critical(q->mainWindow(), tr("Error"), msg);
    // act as if it was closed by the core
302 303
    if (kill)
        q->exitDebugger();
con's avatar
con committed
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 339 340 341 342
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);
}

343 344 345
void GdbEngine::readUploadStandardOutput()
{
    QByteArray ba = m_uploadProc.readAllStandardOutput();
346
    gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(ba, ba.length()));
347 348 349 350 351
}

void GdbEngine::readUploadStandardError()
{
    QByteArray ba = m_uploadProc.readAllStandardError();
352
    gdbOutputAvailable(LogError, QString::fromLocal8Bit(ba, ba.length()));
353 354
}

con's avatar
con committed
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
#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

371 372 373 374 375 376
void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
    emit applicationOutputAvailable(m_outputCodec->toUnicode(
            data.constData(), data.length(), &m_outputCodecState));
}

hjk's avatar
hjk committed
377 378
void GdbEngine::debugMessage(const QString &msg)
{
379
    emit gdbOutputAvailable(LogDebug, msg);
hjk's avatar
hjk committed
380 381
}

382
void GdbEngine::handleResponse(const QByteArray &buff)
con's avatar
con committed
383 384 385
{
    static QTime lastTime;

386
    if (theDebuggerBoolSetting(LogTimeStamps))
387
        emit gdbOutputAvailable(LogTime, currentTime());
388
    emit gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(buff, buff.length()));
con's avatar
con committed
389 390 391 392 393

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

    lastTime = QTime::currentTime();

403 404
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
405

406 407 408
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
409

410 411 412 413
    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
414
            break;
415 416 417
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
418
        //qDebug() << "found token" << token;
419
    }
con's avatar
con committed
420

421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
    // 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
436

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

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

521
        case '@': {
522 523
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingTargetStreamOutput += data;
524
            break;
con's avatar
con committed
525 526
        }

527 528 529 530 531 532
        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
533
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
534 535 536
            break;
        }

537 538
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
539

540
            record.token = token;
con's avatar
con committed
541

542 543 544
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
545

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
546
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
547 548 549 550 551 552 553 554 555 556 557 558
            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
559

560 561
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
562 563 564 565 566 567 568 569 570
                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
571 572
                }
            }
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597

            //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
598 599 600 601
        }
    }
}

602
void GdbEngine::handleStubAttached(const GdbResultRecord &, const QVariant &)
603 604 605
{
    qq->notifyInferiorStopped();
    handleAqcuiredInferior();
606
    m_autoContinue = true;
607 608 609 610
}

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

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

con's avatar
con committed
621 622
void GdbEngine::readGdbStandardError()
{
623
    qWarning() << "Unexpected gdb stderr:" << m_gdbProc.readAllStandardError();
con's avatar
con committed
624 625 626 627
}

void GdbEngine::readGdbStandardOutput()
{
628 629
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
630

631
    m_inbuffer.append(m_gdbProc.readAllStandardOutput());
con's avatar
con committed
632

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

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

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

666
    if (q->startMode() == StartRemote) {
667
        postCommand(_("-exec-interrupt"));
668 669 670
        return;
    }

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

677 678
    if (!interruptProcess(attachedPID))
        debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
con's avatar
con committed
679 680 681 682
}

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

Roberto Raggi's avatar
Roberto Raggi committed
692
    qq->notifyInferiorPidChanged(pid);
693 694
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
695 696
}

697
void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
698 699
                            const char *callbackName, const QVariant &cookie)
{
700
    postCommand(command, NoFlags, callback, callbackName, cookie);
701 702
}

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

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

721
    GdbCommand cmd;
con's avatar
con committed
722
    cmd.command = command;
723 724 725
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
726 727
    cmd.cookie = cookie;

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

742 743 744
void GdbEngine::flushCommand(GdbCommand &cmd)
{
    ++currentToken();
745
    cmd.postTime = QTime::currentTime();
746 747
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
748
    if (cmd.flags & EmbedToken)
749 750 751 752 753
        cmd.command = cmd.command.arg(currentToken());

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

con's avatar
con committed
757 758
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
759 760
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
761 762 763 764 765 766 767
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

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

768
    GdbCommand cmd = m_cookieForToken.take(token);
769 770 771 772 773
    if (theDebuggerBoolSetting(LogTimeStamps)) {
        emit gdbOutputAvailable(LogTime, _("Response time: %1: %2 s")
            .arg(cmd.command)
            .arg(cmd.postTime.msecsTo(QTime::currentTime()) / 1000.));
    }
con's avatar
con committed
774

775
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
776
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
con's avatar
con committed
777 778 779 780 781
        //QMessageBox::information(m_mainWindow, tr("Skipped"), "xxx");
        return;
    }

#if 0
782
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
783
        << "cmd name:" << cmd.callbackName
784
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
785 786 787 788 789
        << "\n record: " << record.toString();
#endif

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

790 791
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
792

793
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
794
        --m_pendingRequests;
hjk's avatar
hjk committed
795
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
796
            << m_pendingRequests << cmd.command);
797
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
798 799
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
800
        }
con's avatar
con committed
801
    } else {
hjk's avatar
hjk committed
802
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
803 804
            << m_pendingRequests << cmd.command);
    }
805 806 807 808 809 810 811 812 813 814

    // 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
815 816 817 818 819
}

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

824
    m_gdbProc.write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
825 826
}

827
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
828 829
{
    qq->notifyInferiorStopped();
830 831
    q->showStatusMessage(tr("Core file loaded."));
    q->resetLocation();
832
    tryLoadDebuggingHelpers();
833 834
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
835
    reloadStack();
836
    if (supportsThreads())
837
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
838
    qq->reloadRegisters();
839 840
}

841
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
842 843
{
    if (record.resultClass == GdbResultDone) {
844
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
845 846 847 848 849 850
        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
851
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
852
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
853
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
854 855 856 857
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
858
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
859 860 861 862
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
863 864
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
865 866 867
    }
}

868
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
869
{
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
    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;
        }
887
    }
888 889 890 891 892
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
893 894
}

895
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
896 897
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
898
        #ifdef Q_OS_MAC
con's avatar
con committed
899
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer  
Oswald Buddenhagen committed
900
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
901
        #else
con's avatar
con committed
902
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
903 904
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
905 906 907 908 909 910
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

911
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
912 913 914
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
915
        handleModulesList(record, cookie);
con's avatar
con committed
916 917 918
    }
}

919
#if 0
con's avatar
con committed
920 921 922 923 924 925 926 927 928 929
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();
930
    q->showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
931
    QByteArray output = record.data.findChild("logstreamoutput").data();
932
    if (output.isEmpty())
con's avatar
con committed
933
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
934 935 936 937 938 939 940 941 942
    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
943
}
944
#endif
con's avatar
con committed
945

946
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
947 948 949 950 951 952 953 954
{
    // 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();
955
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
956
    GdbMi frame = record.data.findChild("frame");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
957
    QString file = QString::fromLocal8Bit(frame.findChild("fullname").data());
con's avatar
con committed
958
    int line = frame.findChild("line").data().toInt();
959 960
    qDebug() << "HIT:" << file << line << "IN" << frame.toString()
        << "--" << record.toString();
con's avatar
con committed
961 962 963
    q->gotoLocation(file, line, true);
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
964
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
965
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
966 967 968 969
    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
970 971
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
972
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
973
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
974 975 976 977 978 979 980
    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"
981
        #ifdef Q_OS_MAC
con's avatar
con committed
982
        || reason.isEmpty()
983
        #endif
con's avatar
con committed
984 985 986
    ;
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
987 988
void GdbEngine::handleAqcuiredInferior()
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
989
    #ifdef Q_OS_WIN
990
    postCommand(_("info thread"), CB(handleInfoThreads));