gdbengine.cpp 154 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
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.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"
hjk's avatar
hjk committed
34
#include "trkgdbadapter.h"
con's avatar
con committed
35

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

#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "stackhandler.h"
#include "watchhandler.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
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
61
#include <QtCore/QMetaObject>
con's avatar
con committed
62 63
#include <QtCore/QTime>
#include <QtCore/QTimer>
64
#include <QtCore/QTextStream>
con's avatar
con committed
65 66

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

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
77
#ifdef Q_OS_UNIX
con's avatar
con committed
78 79 80
#include <unistd.h>
#include <dlfcn.h>
#endif
hjk's avatar
hjk committed
81
#include <ctype.h>
con's avatar
con committed
82

83 84
namespace Debugger {
namespace Internal {
con's avatar
con committed
85 86 87
using namespace Debugger::Constants;

//#define DEBUG_PENDING  1
hjk's avatar
hjk committed
88
//#define DEBUG_SUBITEM  1
con's avatar
con committed
89 90 91 92

#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);
}

hjk's avatar
hjk committed
142 143
///////////////////////////////////////////////////////////////////////
//
hjk's avatar
hjk committed
144
// PlainGdbAdapter
hjk's avatar
hjk committed
145 146 147
//
///////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
148
void PlainGdbAdapter::attach()
hjk's avatar
hjk committed
149
{
hjk's avatar
hjk committed
150
    QFileInfo fi(m_engine->startParameters().executable);
hjk's avatar
hjk committed
151
    QString fileName = fi.absoluteFilePath();
hjk's avatar
hjk committed
152
    m_engine->postCommand(_("-file-exec-and-symbols ") + fileName,
hjk's avatar
hjk committed
153 154 155
        &GdbEngine::handleFileExecAndSymbols, "handleFileExecAndSymbols");
}

hjk's avatar
hjk committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
void PlainGdbAdapter::interruptInferior()
{
    if (m_engine->manager()->startMode() == StartRemote) {
        m_engine->postCommand(_("-exec-interrupt"));
        return;
    }

    const qint64 attachedPID = m_engine->inferiorPid();
    if (attachedPID <= 0) {
        m_engine->debugMessage(
            _("TRYING TO INTERRUPT INFERIOR BEFORE PID WAS OBTAINED"));
        return;
    }

    if (!interruptProcess(attachedPID))
        m_engine->debugMessage(_("CANNOT INTERRUPT %1").arg(attachedPID));
}

con's avatar
con committed
174 175 176 177 178 179
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
180
GdbEngine::GdbEngine(DebuggerManager *parent, AbstractGdbAdapter *gdbAdapter) :
181
#ifdef Q_OS_WIN // Do injection loading with MinGW (call loading does not work with 64bit)
182
    m_dumperInjectionLoad(true),
183
#else
184
    m_dumperInjectionLoad(false),
185
#endif
hjk's avatar
hjk committed
186
    m_manager(parent),
187
    qq(parent->engineInterface())
con's avatar
con committed
188
{
hjk's avatar
hjk committed
189
    m_gdbAdapter = gdbAdapter;
hjk's avatar
hjk committed
190
    m_gdbAdapter->setEngine(this);
191
    m_stubProc.setMode(Core::Utils::ConsoleProcess::Debug);
192 193 194
#ifdef Q_OS_UNIX
    m_stubProc.setSettings(Core::ICore::instance()->settings());
#endif
195 196
    initializeVariables();
    initializeConnections();
con's avatar
con committed
197 198 199 200
}

GdbEngine::~GdbEngine()
{
hjk's avatar
hjk committed
201
    // prevent sending error messages afterwards
hjk's avatar
hjk committed
202 203
    m_gdbAdapter->disconnect(this);
    delete m_gdbAdapter;
con's avatar
con committed
204 205
}

206
void GdbEngine::initializeConnections()
con's avatar
con committed
207 208
{
    // Gdb Process interaction
hjk's avatar
hjk committed
209
    connect(m_gdbAdapter, SIGNAL(error(QProcess::ProcessError)),
210
        this, SLOT(gdbProcError(QProcess::ProcessError)));
hjk's avatar
hjk committed
211
    connect(m_gdbAdapter, SIGNAL(readyReadStandardOutput()),
212
        this, SLOT(readGdbStandardOutput()));
hjk's avatar
hjk committed
213
    connect(m_gdbAdapter, SIGNAL(readyReadStandardError()),
214
        this, SLOT(readGdbStandardError()));
hjk's avatar
hjk committed
215
    connect(m_gdbAdapter, SIGNAL(finished(int, QProcess::ExitStatus)),
hjk's avatar
hjk committed
216
        m_manager, SLOT(exitDebugger()));
hjk's avatar
hjk committed
217
    connect(m_gdbAdapter, SIGNAL(started()),
hjk's avatar
hjk committed
218
        this, SLOT(startDebugger2()));
219 220 221 222 223 224

    connect(&m_stubProc, SIGNAL(processError(QString)),
        this, SLOT(stubError(QString)));
    connect(&m_stubProc, SIGNAL(processStarted()),
        this, SLOT(stubStarted()));
    connect(&m_stubProc, SIGNAL(wrapperStopped()),
hjk's avatar
hjk committed
225
        m_manager, SLOT(exitDebugger()));
226 227 228

    connect(&m_uploadProc, SIGNAL(error(QProcess::ProcessError)),
        this, SLOT(uploadProcError(QProcess::ProcessError)));
229 230 231 232
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
233

con's avatar
con committed
234
    // Output
235
    connect(&m_outputCollector, SIGNAL(byteDelivery(QByteArray)),
236
        this, SLOT(readDebugeeOutput(QByteArray)));
con's avatar
con committed
237

238
    connect(this, SIGNAL(gdbOutputAvailable(int,QString)),
hjk's avatar
hjk committed
239
        m_manager, SLOT(showDebuggerOutput(int,QString)),
con's avatar
con committed
240
        Qt::QueuedConnection);
241
    connect(this, SIGNAL(gdbInputAvailable(int,QString)),
hjk's avatar
hjk committed
242
        m_manager, SLOT(showDebuggerInput(int,QString)),
con's avatar
con committed
243
        Qt::QueuedConnection);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
244
    connect(this, SIGNAL(applicationOutputAvailable(QString)),
hjk's avatar
hjk committed
245
        m_manager, SLOT(showApplicationOutput(QString)),
con's avatar
con committed
246
        Qt::QueuedConnection);
247

248
    // FIXME: These trigger even if the engine is not active
249 250 251 252 253 254
    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
255 256
}

257 258
void GdbEngine::initializeVariables()
{
259
    m_debuggingHelperState = DebuggingHelperUninitialized;
260
    m_gdbVersion = 100;
hjk's avatar
hjk committed
261
    m_gdbBuildVersion = -1;
262 263 264 265 266 267 268 269 270

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

    m_modulesListOutdated = true;
    m_oldestAcceptableToken = -1;
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingRequests = 0;
271
    m_autoContinue = false;
272
    m_waitingForFirstBreakpointToBeHit = false;
hjk's avatar
hjk committed
273
    m_commandsToRunOnTemporaryBreak.clear();
274
    m_cookieForToken.clear();
275 276 277 278 279 280 281 282 283 284
    m_customOutputForToken.clear();

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

    m_inbuffer.clear();

    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
285
    m_dumperHelper.clear();
286 287 288 289

    // FIXME: unhandled:
    //m_outputCodecState = QTextCodec::ConverterState();
    //OutputCollector m_outputCollector;
hjk's avatar
hjk committed
290
    //QProcess m_gdbAdapter;
291 292
    //QProcess m_uploadProc;
    //Core::Utils::ConsoleProcess m_stubProc;
293 294
}

con's avatar
con committed
295 296 297
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
298
    bool kill = true;
con's avatar
con committed
299 300
    switch (error) {
        case QProcess::FailedToStart:
301
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
302
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
303
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
304
                "permissions to invoke the program.")
hjk's avatar
hjk committed
305
                .arg(theDebuggerStringSetting(GdbLocation));
306
            emitStartFailed();
con's avatar
con committed
307 308
            break;
        case QProcess::Crashed:
309
            kill = false;
con's avatar
con committed
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
            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().");
    }

hjk's avatar
hjk committed
332 333
    showStatusMessage(msg);
    QMessageBox::critical(mainWindow(), tr("Error"), msg);
con's avatar
con committed
334
    // act as if it was closed by the core
335
    if (kill)
hjk's avatar
hjk committed
336
        m_manager->exitDebugger();
con's avatar
con committed
337 338
}

339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
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().");
    }

hjk's avatar
hjk committed
372 373
    showStatusMessage(msg);
    QMessageBox::critical(mainWindow(), tr("Error"), msg);
374 375
}

376 377 378
void GdbEngine::readUploadStandardOutput()
{
    QByteArray ba = m_uploadProc.readAllStandardOutput();
379
    gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(ba, ba.length()));
380 381 382 383 384
}

void GdbEngine::readUploadStandardError()
{
    QByteArray ba = m_uploadProc.readAllStandardError();
385
    gdbOutputAvailable(LogError, QString::fromLocal8Bit(ba, ba.length()));
386 387
}

con's avatar
con committed
388 389 390 391
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
392
    Q_UNUSED(to)
con's avatar
con committed
393 394 395 396 397 398 399 400 401 402 403
    // 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

404 405 406 407 408 409
void GdbEngine::readDebugeeOutput(const QByteArray &data)
{
    emit applicationOutputAvailable(m_outputCodec->toUnicode(
            data.constData(), data.length(), &m_outputCodecState));
}

hjk's avatar
hjk committed
410 411
void GdbEngine::debugMessage(const QString &msg)
{
412
    emit gdbOutputAvailable(LogDebug, msg);
hjk's avatar
hjk committed
413 414
}

415
void GdbEngine::handleResponse(const QByteArray &buff)
con's avatar
con committed
416 417 418
{
    static QTime lastTime;

419
    if (theDebuggerBoolSetting(LogTimeStamps))
420
        emit gdbOutputAvailable(LogTime, currentTime());
421
    emit gdbOutputAvailable(LogOutput, QString::fromLocal8Bit(buff, buff.length()));
con's avatar
con committed
422 423 424 425 426

#if 0
    qDebug() // << "#### start response handling #### "
        << currentTime()
        << lastTime.msecsTo(QTime::currentTime()) << "ms,"
427 428
        << "buf:" << buff.left(1500) << "..."
        //<< "buf:" << buff
429
        << "size:" << buff.size();
con's avatar
con committed
430
#else
431
    //qDebug() << "buf:" << buff;
con's avatar
con committed
432 433 434 435
#endif

    lastTime = QTime::currentTime();

436 437
    if (buff.isEmpty() || buff == "(gdb) ")
        return;
con's avatar
con committed
438

439 440 441
    const char *from = buff.constData();
    const char *to = from + buff.size();
    const char *inner;
con's avatar
con committed
442

443 444 445 446
    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
447
            break;
448 449 450
    if (from != inner) {
        token = QByteArray(from, inner - from).toInt();
        from = inner;
451
        //qDebug() << "found token" << token;
452
    }
con's avatar
con committed
453

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
    // 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
469

470 471 472
            GdbMi record;
            while (from != to) {
                GdbMi data;
hjk's avatar
hjk committed
473
                if (*from != ',') {
hjk's avatar
hjk committed
474 475
                    // happens on archer where we get 
                    // 23^running <NL> *running,thread-id="all" <NL> (gdb) 
476
                    record.m_type = GdbMi::Tuple;
hjk's avatar
hjk committed
477 478 479 480 481
                    break;
                }
                ++from; // skip ','
                data.parseResultOrValue(from, to);
                if (data.isValid()) {
482
                    //qDebug() << "parsed response:" << data.toString();
hjk's avatar
hjk committed
483 484
                    record.m_children += data;
                    record.m_type = GdbMi::Tuple;
con's avatar
con committed
485 486
                }
            }
487 488 489 490
            if (asyncClass == "stopped") {
                handleAsyncOutput(record);
            } else if (asyncClass == "running") {
                // Archer has 'thread-id="all"' here
491 492 493 494
            } 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",
495
                // symbols-loaded="0"
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
496
                QByteArray id = record.findChild("id").data();
497
                if (!id.isEmpty())
hjk's avatar
hjk committed
498
                    showStatusMessage(tr("Library %1 loaded.").arg(_(id)));
hjk's avatar
hjk committed
499 500 501 502
            } 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
503
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
504
                showStatusMessage(tr("Library %1 unloaded.").arg(_(id)));
505 506
            } else if (asyncClass == "thread-group-created") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
507
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
508
                showStatusMessage(tr("Thread group %1 created.").arg(_(id)));
509 510
            } else if (asyncClass == "thread-created") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
511
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
512
                showStatusMessage(tr("Thread %1 created.").arg(_(id)));
513 514
            } else if (asyncClass == "thread-group-exited") {
                // Archer has "{id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
515
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
516
                showStatusMessage(tr("Thread group %1 exited.").arg(_(id)));
517 518
            } else if (asyncClass == "thread-exited") {
                //"{id="1",group-id="28902"}" 
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
519 520
                QByteArray id = record.findChild("id").data();
                QByteArray groupid = record.findChild("group-id").data();
hjk's avatar
hjk committed
521
                showStatusMessage(tr("Thread %1 in group %2 exited.")
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
522
                    .arg(_(id)).arg(_(groupid)));
hjk's avatar
hjk committed
523
            } else if (asyncClass == "thread-selected") {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
524
                QByteArray id = record.findChild("id").data();
hjk's avatar
hjk committed
525
                showStatusMessage(tr("Thread %1 selected.").arg(_(id)));
hjk's avatar
hjk committed
526
                //"{id="2"}" 
527
            #if defined(Q_OS_MAC)
528 529 530 531 532 533 534 535 536 537 538
            } 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 {
539
                qDebug() << "IGNORED ASYNC OUTPUT"
540
                    << asyncClass << record.toString();
541
            }
542 543
            break;
        }
544

545
        case '~': {
546 547 548
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingConsoleStreamOutput += data;
            if (data.startsWith("Reading symbols from ")) {
hjk's avatar
hjk committed
549
                showStatusMessage(tr("Reading %1...").arg(_(data.mid(21))));
550
            }
551 552
            break;
        }
553

554
        case '@': {
555 556
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingTargetStreamOutput += data;
557
            break;
con's avatar
con committed
558 559
        }

560 561 562 563 564 565
        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
566
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
567 568 569
            break;
        }

570 571
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
572

573
            record.token = token;
con's avatar
con committed
574

575 576 577
            for (inner = from; inner != to; ++inner)
                if (*inner < 'a' || *inner > 'z')
                    break;
con's avatar
con committed
578

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
579
            QByteArray resultClass = QByteArray::fromRawData(from, inner - from);
580 581 582 583 584 585 586 587 588 589 590 591
            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
592

593 594
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
595 596 597 598 599 600 601 602 603
                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
604 605
                }
            }
606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630

            //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
631 632 633 634
        }
    }
}

635
void GdbEngine::handleStubAttached(const GdbResultRecord &, const QVariant &)
636 637 638
{
    qq->notifyInferiorStopped();
    handleAqcuiredInferior();
639
    m_autoContinue = true;
640 641 642 643
}

void GdbEngine::stubStarted()
{
644 645 646
    const qint64 attachedPID = m_stubProc.applicationPID();
    qq->notifyInferiorPidChanged(attachedPID);
    postCommand(_("attach %1").arg(attachedPID), CB(handleStubAttached));
647 648 649 650
}

void GdbEngine::stubError(const QString &msg)
{
hjk's avatar
hjk committed
651
    QMessageBox::critical(mainWindow(), tr("Debugger Error"), msg);
652 653
}

con's avatar
con committed
654 655
void GdbEngine::readGdbStandardError()
{
hjk's avatar
hjk committed
656
    qWarning() << "Unexpected gdb stderr:" << m_gdbAdapter->readAllStandardError();
con's avatar
con committed
657 658 659 660
}

void GdbEngine::readGdbStandardOutput()
{
661 662
    int newstart = 0;
    int scan = m_inbuffer.size();
con's avatar
con committed
663

hjk's avatar
hjk committed
664
    m_inbuffer.append(m_gdbAdapter->readAllStandardOutput());
con's avatar
con committed
665

666 667
    while (newstart < m_inbuffer.size()) {
        int start = newstart;
668
        int end = m_inbuffer.indexOf('\n', scan);
669 670 671 672 673
        if (end < 0) {
            m_inbuffer.remove(0, start);
            return;
        }
        newstart = end + 1;
674
        scan = newstart;
675 676
        if (end == start)
            continue;
677
        #if defined(Q_OS_WIN)
678 679 680 681 682
        if (m_inbuffer.at(end - 1) == '\r') {
            --end;
            if (end == start)
                continue;
        }
683
        #endif
684
        handleResponse(QByteArray::fromRawData(m_inbuffer.constData() + start, end - start));
con's avatar
con committed
685
    }
686
    m_inbuffer.clear();
con's avatar
con committed
687 688 689 690
}

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

hjk's avatar
hjk committed
693
    if (m_gdbAdapter->state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
694
        debugMessage(_("TRYING TO INTERRUPT INFERIOR WITHOUT RUNNING GDB"));
hjk's avatar
hjk committed
695
        qq->notifyInferiorExited();
con's avatar
con committed
696
        return;
hjk's avatar
hjk committed
697
    }
con's avatar
con committed
698

hjk's avatar
hjk committed
699
    m_gdbAdapter->interruptInferior();
con's avatar
con committed
700 701 702 703
}

void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0)
{
704
    const qint64 pid = pid0.toLongLong();
con's avatar
con committed
705
    if (pid == 0) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
706
        debugMessage(_("Cannot parse PID from %1").arg(pid0));
con's avatar
con committed
707 708
        return;
    }
hjk's avatar
hjk committed
709
    if (pid == inferiorPid())
con's avatar
con committed
710
        return;
711 712
    debugMessage(_("FOUND PID %1").arg(pid));    

Roberto Raggi's avatar
Roberto Raggi committed
713
    qq->notifyInferiorPidChanged(pid);
714 715
    if (m_dumperInjectionLoad)
        tryLoadDebuggingHelpers();
con's avatar
con committed
716 717
}

718
void GdbEngine::postCommand(const QString &command, GdbCommandCallback callback,
719 720
                            const char *callbackName, const QVariant &cookie)
{
721
    postCommand(command, NoFlags, callback, callbackName, cookie);
722 723
}

724
void GdbEngine::postCommand(const QString &command, GdbCommandFlags flags,
725 726
                            GdbCommandCallback callback, const char *callbackName,
                            const QVariant &cookie)
con's avatar
con committed
727
{
hjk's avatar
hjk committed
728
    if (m_gdbAdapter->state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
729
        debugMessage(_("NO GDB PROCESS RUNNING, CMD IGNORED: ") + command);
con's avatar
con committed
730 731 732
        return;
    }

733
    if (flags & RebuildModel) {
con's avatar
con committed
734
        ++m_pendingRequests;
735
        PENDING_DEBUG("   CALLBACK" << callbackName << "INCREMENTS PENDING TO:"
con's avatar
con committed
736 737
            << m_pendingRequests << command);
    } else {
738
        PENDING_DEBUG("   UNKNOWN CALLBACK" << callbackName << "LEAVES PENDING AT:"
con's avatar
con committed
739 740 741
            << m_pendingRequests << command);
    }

742
    GdbCommand cmd;
con's avatar
con committed
743
    cmd.command = command;
744 745 746
    cmd.flags = flags;
    cmd.callback = callback;
    cmd.callbackName = callbackName;
con's avatar
con committed
747 748
    cmd.cookie = cookie;

hjk's avatar
hjk committed
749 750
    if ((flags & NeedsStop) && status() != DebuggerInferiorStopped
            && status() != DebuggerProcessStartingUp) {
hjk's avatar
hjk committed
751
        // queue the commands that we cannot send at once
hjk's avatar
hjk committed
752 753 754
        QTC_ASSERT(status() == DebuggerInferiorRunning,
            qDebug() << "STATUS:" << status());
        showStatusMessage(tr("Stopping temporarily."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
755
        debugMessage(_("QUEUING COMMAND ") + cmd.command);
hjk's avatar
hjk committed
756 757 758
        m_commandsToRunOnTemporaryBreak.append(cmd);
        interruptInferior();
    } else if (!command.isEmpty()) {
759
        flushCommand(cmd);
con's avatar
con committed
760 761 762
    }
}

763 764 765
void GdbEngine::flushCommand(GdbCommand &cmd)
{
    ++currentToken();
766
    cmd.postTime = QTime::currentTime();
767 768
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
769
    if (cmd.flags & EmbedToken)
770 771
        cmd.command = cmd.command.arg(currentToken());

772
    emit gdbInputAvailable(LogInput, cmd.command);
773
    executeDebuggerCommand(cmd.command);
774 775
}

con's avatar
con committed
776 777
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
778 779
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
780 781 782 783 784 785 786
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

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

787 788 789 790 791 792 793 794 795
    if (!m_cookieForToken.contains(token)) {
        // In theory this should not happen, in practice it does.
        debugMessage(_("COOKIE FOR TOKEN %1 ALREADY EATEN. "
            "TWO RESPONSES FOR ONE COMMAND?").arg(token));
        // handle a case known to occur on Linux/gdb 6.8 when debugging moc
        // with helpers enabled. In this case we get a second response with
        // msg="Cannot find new threads: generic error"
        if (record.resultClass == GdbResultError) {
            QByteArray msg = record.data.findChild("msg").data();
hjk's avatar
hjk committed
796
            QMessageBox::critical(mainWindow(), tr("Error"),
797
                tr("Executable failed:\n") + QString::fromLocal8Bit(msg));
hjk's avatar
hjk committed
798
            showStatusMessage(tr("Process failed to start."));
799 800 801 802 803 804 805
            exitDebugger();
            //qq->notifyInferiorStopped();
            //qq->notifyInferiorExited();
        }
        return;
    }

806
    GdbCommand cmd = m_cookieForToken.take(token);
807 808 809 810 811
    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
812

813
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
814
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
hjk's avatar
hjk committed
815
        //QMessageBox::information(mainWindow(), tr("Skipped"), "xxx");
con's avatar
con committed
816 817 818 819
        return;
    }

#if 0
820
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
821
        << "cmd name:" << cmd.callbackName
822
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
823 824 825 826 827
        << "\n record: " << record.toString();
#endif

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

828 829
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
830

831
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
832
        --m_pendingRequests;
hjk's avatar
hjk committed
833
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
834
            << m_pendingRequests << cmd.command);
835
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
836 837
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
838
        }
con's avatar
con committed
839
    } else {
hjk's avatar
hjk committed
840
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
841 842
            << m_pendingRequests << cmd.command);
    }
843 844 845 846 847 848 849 850

    // 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();
hjk's avatar
hjk committed
851
        showStatusMessage(tr("Continuing after temporary stop."));
852
    }
con's avatar
con committed
853 854 855 856
}

void GdbEngine::executeDebuggerCommand(const QString &command)
{
hjk's avatar
hjk committed
857
    if (m_gdbAdapter->state() != QProcess::Running) {
858
        debugMessage(_("GDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: ") + command);
con's avatar
con committed
859 860 861
        return;
    }

hjk's avatar
hjk committed
862
    m_gdbAdapter->write(command.toLatin1() + "\r\n");
con's avatar
con committed
863 864
}

865
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
866 867
{
    qq->notifyInferiorStopped();
hjk's avatar
hjk committed
868 869
    showStatusMessage(tr("Core file loaded."));
    m_manager->resetLocation();
870
    tryLoadDebuggingHelpers();
871 872
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
873
    reloadStack();
874
    if (supportsThreads())
875
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
876
    qq->reloadRegisters();
877 878
}

879
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
880 881
{
    if (record.resultClass == GdbResultDone) {
882
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
883 884 885 886 887 888
        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
889
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
890
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
891
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
892 893 894 895
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
896
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
897 898 899 900
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
901 902
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
903 904 905
    }
}

906
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
907
{
908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
    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;
        }
925
    }
926 927 928 929 930
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
931 932
}

933
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
934 935
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
936
        #ifdef Q_OS_MAC
con's avatar
con committed
937
        //^done,process-id="85075"
Oswald Buddenhagen's avatar