gdbengine.cpp 156 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
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);
151
    m_engine->postCommand(_("-file-exec-and-symbols \"%1\"").arg(fi.absoluteFilePath()),
hjk's avatar
hjk committed
152 153 154
        &GdbEngine::handleFileExecAndSymbols, "handleFileExecAndSymbols");
}

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

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

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

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

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

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

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

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

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

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

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

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

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

    m_inbuffer.clear();

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

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

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

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

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

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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void GdbEngine::interruptInferior()
{
hjk's avatar
hjk committed
690
    debugMessage(_("GDBENGINE INTERRUPT INFERIOR: %1").arg(m_gdbAdapter->state()));
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
void GdbEngine::flushCommand(GdbCommand &cmd)
{
765 766 767 768 769 770
    if (m_gdbAdapter->state() != QProcess::Running) {
        emit gdbInputAvailable(LogInput, cmd.command);
        debugMessage(_("GDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: ") + cmd.command);
        return;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

914
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
915
{
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
    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;
        }
933
    }