gdbengine.cpp 155 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"
34 35
#include "trkoptions.h"
#include "trkoptionspage.h"
hjk's avatar
hjk committed
36
#include "trkgdbadapter.h"
con's avatar
con committed
37

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

#include "breakhandler.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "stackhandler.h"
#include "watchhandler.h"
52
#include "sourcefileswindow.h"
con's avatar
con committed
53

54
#include "debuggerdialogs.h"
con's avatar
con committed
55

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

con's avatar
con committed
60 61 62
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
63
#include <QtCore/QMetaObject>
con's avatar
con committed
64 65
#include <QtCore/QTime>
#include <QtCore/QTimer>
66
#include <QtCore/QTextStream>
con's avatar
con committed
67 68

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

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

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

//#define DEBUG_PENDING  1
90
//#define DEBUG_SUBITEM  1
con's avatar
con committed
91 92 93 94

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

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

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

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 143
// 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
144 145
///////////////////////////////////////////////////////////////////////
//
hjk's avatar
hjk committed
146
// PlainGdbAdapter
hjk's avatar
hjk committed
147 148 149
//
///////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
150
void PlainGdbAdapter::attach()
hjk's avatar
hjk committed
151
{
hjk's avatar
hjk committed
152
    QFileInfo fi(m_engine->startParameters().executable);
153
    m_engine->postCommand(_("-file-exec-and-symbols \"%1\"").arg(fi.absoluteFilePath()),
hjk's avatar
hjk committed
154 155 156
        &GdbEngine::handleFileExecAndSymbols, "handleFileExecAndSymbols");
}

hjk's avatar
hjk committed
157 158
void PlainGdbAdapter::interruptInferior()
{
hjk's avatar
hjk committed
159
    if (m_engine->startMode() == StartRemote) {
hjk's avatar
hjk committed
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
        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
175 176 177 178 179 180
///////////////////////////////////////////////////////////////////////
//
// GdbEngine
//
///////////////////////////////////////////////////////////////////////

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

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

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

    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
226
        m_manager, SLOT(exitDebugger()));
227 228 229

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

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

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

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

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

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

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

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

    m_inbuffer.clear();

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

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

con's avatar
con committed
296 297 298
void GdbEngine::gdbProcError(QProcess::ProcessError error)
{
    QString msg;
299
    bool kill = true;
con's avatar
con committed
300 301
    switch (error) {
        case QProcess::FailedToStart:
302
            kill = false;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
303
            msg = tr("The Gdb process failed to start. Either the "
con's avatar
con committed
304
                "invoked program '%1' is missing, or you may have insufficient "
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
305
                "permissions to invoke the program.")
hjk's avatar
hjk committed
306
                .arg(theDebuggerStringSetting(GdbLocation));
307
            emitStartFailed();
con's avatar
con committed
308 309
            break;
        case QProcess::Crashed:
310
            kill = false;
con's avatar
con committed
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
            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
333 334
    showStatusMessage(msg);
    QMessageBox::critical(mainWindow(), tr("Error"), msg);
con's avatar
con committed
335
    // act as if it was closed by the core
336
    if (kill)
hjk's avatar
hjk committed
337
        m_manager->exitDebugger();
con's avatar
con committed
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 372
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
373 374
    showStatusMessage(msg);
    QMessageBox::critical(mainWindow(), tr("Error"), msg);
375 376
}

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

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

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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

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

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

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

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

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

594 595
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
596 597 598 599 600 601 602 603 604
                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
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 631

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

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

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

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

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

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

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

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

void GdbEngine::interruptInferior()
{
hjk's avatar
hjk committed
692
    debugMessage(_("GDBENGINE INTERRUPT INFERIOR: %1").arg(m_gdbAdapter->state()));
hjk's avatar
hjk committed
693
    qq->notifyInferiorStopRequested();
694

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

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

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

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

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

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

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

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

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

765 766
void GdbEngine::flushCommand(GdbCommand &cmd)
{
767 768 769 770 771 772
    if (m_gdbAdapter->state() != QProcess::Running) {
        emit gdbInputAvailable(LogInput, cmd.command);
        debugMessage(_("GDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: ") + cmd.command);
        return;
    }

773
    ++currentToken();
774
    cmd.postTime = QTime::currentTime();
775 776
    m_cookieForToken[currentToken()] = cmd;
    cmd.command = QString::number(currentToken()) + cmd.command;
777
    if (cmd.flags & EmbedToken)
778
        cmd.command = cmd.command.arg(currentToken());
779
    emit gdbInputAvailable(LogInput, cmd.command);
780 781

    m_gdbAdapter->write(cmd.command.toLatin1() + "\r\n");
782 783
}

con's avatar
con committed
784 785
void GdbEngine::handleResultRecord(const GdbResultRecord &record)
{
786 787
    //qDebug() << "TOKEN:" << record.token
    //    << " ACCEPTABLE:" << m_oldestAcceptableToken;
con's avatar
con committed
788 789 790 791 792 793 794
    //qDebug() << "";
    //qDebug() << "\nRESULT" << record.token << record.toString();

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

795 796 797 798 799 800 801 802 803
    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
804
            QMessageBox::critical(mainWindow(), tr("Error"),
805
                tr("Executable failed:\n") + QString::fromLocal8Bit(msg));
hjk's avatar
hjk committed
806
            showStatusMessage(tr("Process failed to start."));
807 808 809 810 811 812 813
            exitDebugger();
            //qq->notifyInferiorStopped();
            //qq->notifyInferiorExited();
        }
        return;
    }

814
    GdbCommand cmd = m_cookieForToken.take(token);
815 816 817 818 819
    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
820

821
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
822
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
hjk's avatar
hjk committed
823
        //QMessageBox::information(mainWindow(), tr("Skipped"), "xxx");
con's avatar
con committed
824 825 826 827
        return;
    }

#if 0
828
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
829
        << "cmd name:" << cmd.callbackName
830
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
831 832 833 834 835
        << "\n record: " << record.toString();
#endif

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

836 837
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
838

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

852 853
    // Continue only if there are no commands wire anymore, so this will
    // be fully synchroneous.
854 855 856 857 858 859 860
    // 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
861
        showStatusMessage(tr("Continuing after temporary stop."));
862
    }
con's avatar
con committed
863 864 865 866
}

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

hjk's avatar
hjk committed
872
    m_gdbAdapter->write(command.toLatin1() + "\r\n");
con's avatar
con committed
873 874
}

875
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
876 877
{
    qq->notifyInferiorStopped();
hjk's avatar
hjk committed
878 879
    showStatusMessage(tr("Core file loaded."));
    m_manager->resetLocation();
880
    tryLoadDebuggingHelpers();
881 882
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
883
    reloadStack();
884
    if (supportsThreads())
885
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
886
    qq->reloadRegisters();
887 888
}

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

916
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
917
{
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
    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;
        }
935
    }
936 937 938 939 940
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
941 942
}

943
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
944 945
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
946
        #ifdef Q_OS_MAC
con's avatar
con committed
947
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer  
Oswald Buddenhagen committed
948
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
949
        #else
con's avatar
con committed
950
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
951 952
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
953 954 955 956 957 958
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

959
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
960 961 962
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
963
        handleModulesList(record, cookie);
con's avatar
con committed
964 965 966
    }
}