gdbengine.cpp 151 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"
con's avatar
con committed
34

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

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

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

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

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

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

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

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

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

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

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

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

105
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
// reads a MI-encoded item frome the consolestream
static bool parseConsoleStream(const GdbResultRecord &record, GdbMi *contents)
{
    GdbMi output = record.data.findChild("consolestreamoutput");
    QByteArray out = output.data();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    m_modulesListOutdated = true;
    m_oldestAcceptableToken = -1;
    m_outputCodec = QTextCodec::codecForLocale();
    m_pendingRequests = 0;
235
    m_autoContinue = false;
236
    m_waitingForFirstBreakpointToBeHit = false;
237
    m_commandsToRunOnTemporaryBreak.clear();
238
    m_cookieForToken.clear();
239
240
241
242
243
244
245
246
247
248
    m_customOutputForToken.clear();

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

    m_inbuffer.clear();

    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
249
    m_dumperHelper.clear();
250
251
252
253
254
255
256

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

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

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

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

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

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

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

con's avatar
con committed
351
352
353
354
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
355
    Q_UNUSED(to)
con's avatar
con committed
356
357
358
359
360
361
362
363
364
365
366
    // note that qDebug cuts off output after a certain size... (bug?)
    qDebug("\n>>>>> %s\n%s\n====\n%s\n<<<<<\n",
        qPrintable(currentTime()),
        qPrintable(QString(ba).trimmed()),
        qPrintable(to.trimmed()));
    //qDebug() << "";
    //qDebug() << qPrintable(currentTime())
    //    << " Reading response:  " << QString(ba).trimmed() << "\n";
}
#endif

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

523
524
525
526
527
528
        case '&': {
            QByteArray data = GdbMi::parseCString(from, to);
            m_pendingLogStreamOutput += data;
            // On Windows, the contents seem to depend on the debugger
            // version and/or OS version used.
            if (data.startsWith("warning:"))
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
529
                qq->showApplicationOutput(_(data.mid(9))); // cut "warning: "
con's avatar
con committed
530
531
532
            break;
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void GdbEngine::interruptInferior()
{
654
    qq->notifyInferiorStopRequested();
655

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
    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();
            QMessageBox::critical(q->mainWindow(), tr("Error"),
                tr("Executable failed:\n") + QString::fromLocal8Bit(msg));
            q->showStatusMessage(tr("Process failed to start."));
            exitDebugger();
            //qq->notifyInferiorStopped();
            //qq->notifyInferiorExited();
        }
        return;
    }

783
    GdbCommand cmd = m_cookieForToken.take(token);
784
785
786
787
788
    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
789

790
    if (record.token < m_oldestAcceptableToken && (cmd.flags & Discardable)) {
791
        //qDebug() << "### SKIPPING OLD RESULT" << record.toString();
792
        //QMessageBox::information(q->mainWindow(), tr("Skipped"), "xxx");
con's avatar
con committed
793
794
795
796
        return;
    }

#if 0
797
    qDebug() << "# handleOutput,"
hjk's avatar
hjk committed
798
        << "cmd name:" << cmd.callbackName
799
        << " cmd synchronized:" << cmd.synchronized
con's avatar
con committed
800
801
802
803
804
        << "\n record: " << record.toString();
#endif

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

805
806
    if (cmd.callback)
        (this->*(cmd.callback))(record, cmd.cookie);
con's avatar
con committed
807

808
    if (cmd.flags & RebuildModel) {
con's avatar
con committed
809
        --m_pendingRequests;
hjk's avatar
hjk committed
810
        PENDING_DEBUG("   TYPE " << cmd.callbackName << " DECREMENTS PENDING TO: "
con's avatar
con committed
811
            << m_pendingRequests << cmd.command);
812
        if (m_pendingRequests <= 0) {
hjk's avatar
hjk committed
813
814
            PENDING_DEBUG("\n\n ....  AND TRIGGERS MODEL UPDATE\n");
            rebuildModel();
815
        }
con's avatar
con committed
816
    } else {
hjk's avatar
hjk committed
817
        PENDING_DEBUG("   UNKNOWN TYPE " << cmd.callbackName << " LEAVES PENDING AT: "
con's avatar
con committed
818
819
            << m_pendingRequests << cmd.command);
    }
820
821
822
823
824
825
826
827
828
829

    // This is somewhat inefficient, as it makes the last command synchronous.
    // An optimization would be requesting the continue immediately when the
    // event loop is entered, and let individual commands have a flag to suppress
    // that behavior.
    if (m_cookieForToken.isEmpty() && m_autoContinue) {
        m_autoContinue = false;
        continueInferior();
        q->showStatusMessage(tr("Continuing after temporary stop."));
    }
con's avatar
con committed
830
831
832
833
}

void GdbEngine::executeDebuggerCommand(const QString &command)
{
834
    if (m_gdbProc->state() == QProcess::NotRunning) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
835
        debugMessage(_("NO GDB PROCESS RUNNING, PLAIN CMD IGNORED: ") + command);
con's avatar
con committed
836
837
838
        return;
    }

839
    m_gdbProc->write(command.toLocal8Bit() + "\r\n");
con's avatar
con committed
840
841
}

842
void GdbEngine::handleTargetCore(const GdbResultRecord &, const QVariant &)
843
844
{
    qq->notifyInferiorStopped();
845
846
    q->showStatusMessage(tr("Core file loaded."));
    q->resetLocation();
847
    tryLoadDebuggingHelpers();
848
849
    qq->stackHandler()->setCurrentIndex(0);
    updateLocals(); // Quick shot
850
    reloadStack();
851
    if (supportsThreads())
852
        postCommand(_("-thread-list-ids"), WatchUpdate, CB(handleStackListThreads), 0);
853
    qq->reloadRegisters();
854
855
}

856
void GdbEngine::handleQuerySources(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
857
858
{
    if (record.resultClass == GdbResultDone) {
859
        QMap<QString, QString> oldShortToFull = m_shortToFullName;
con's avatar
con committed
860
861
862
863
864
865
        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
866
            QString fileName = QString::fromLocal8Bit(item.findChild("file").data());
con's avatar
con committed
867
            GdbMi fullName = item.findChild("fullname");
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
868
            QString full = QString::fromLocal8Bit(fullName.data());
con's avatar
con committed
869
870
871
872
            #ifdef Q_OS_WIN
            full = QDir::cleanPath(full);
            #endif
            if (fullName.isValid() && QFileInfo(full).isReadable()) {
873
                //qDebug() << "STORING 2:" << fileName << full;
con's avatar
con committed
874
875
876
877
                m_shortToFullName[fileName] = full;
                m_fullToShortName[full] = fileName;
            }
        }
878
879
        if (m_shortToFullName != oldShortToFull)
            qq->sourceFileWindow()->setSourceFiles(m_shortToFullName);
con's avatar
con committed
880
881
882
    }
}

883
void GdbEngine::handleInfoThreads(const GdbResultRecord &record, const QVariant &)
884
{
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
    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;
        }
902
    }
903
904
905
906
907
    // check "* 3 Thread ..."
    QRegExp re(_("^\\*? +\\d+ +[Tt]hread (\\d+)\\.0x.* in"));
    Q_ASSERT(re.isValid());
    if (re.indexIn(data) != -1)
        maybeHandleInferiorPidChanged(re.cap(1));
908
909
}

910
void GdbEngine::handleInfoProc(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
911
912
{
    if (record.resultClass == GdbResultDone) {
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
913
        #ifdef Q_OS_MAC
con's avatar
con committed
914
        //^done,process-id="85075"
Oswald Buddenhagen's avatar
nicer    
Oswald Buddenhagen committed
915
        maybeHandleInferiorPidChanged(_(record.data.findChild("process-id").data()));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
916
        #else
con's avatar
con committed
917
        // FIXME: use something more robust
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
918
919
        QRegExp re(__("process (\\d+)"));
        QString data = __(record.data.findChild("consolestreamoutput").data());
con's avatar
con committed
920
921
922
923
924
925
        if (re.indexIn(data) != -1)
            maybeHandleInferiorPidChanged(re.cap(1));
        #endif
    }
}

926
void GdbEngine::handleInfoShared(const GdbResultRecord &record, const QVariant &cookie)
con's avatar
con committed
927
928
929
{
    if (record.resultClass == GdbResultDone) {
        // let the modules handler do the parsing
930
        handleModulesList(record, cookie);
con's avatar
con committed
931
932
933
    }
}

934
#if 0
con's avatar
con committed
935
936
937
938
939
940
941
942
943
944
void GdbEngine::handleExecJumpToLine(const GdbResultRecord &record)
{
    // FIXME: remove this special case as soon as 'jump'
    // is supported by MI
    // "&"jump /home/apoenitz/dev/work/test1/test1.cpp:242"
    // ~"Continuing at 0x4058f3."
    // ~"run1 (argc=1, argv=0x7fffb213a478) at test1.cpp:242"
    // ~"242\t x *= 2;"
    //109^done"
    qq->notifyInferiorStopped();
945
    q->showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
946
    QByteArray output = record.data.findChild("logstreamoutput").data();
947
    if (output.isEmpty())
con's avatar
con committed
948
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
949
950
951
952
953
954
955
956
957
    int idx1 = output.indexOf(' ') + 1;
    if (idx1 > 0) {
        int idx2 = output.indexOf(':', idx1);
        if (idx2 > 0) {
            QString file = QString::fromLocal8Bit(output.mid(idx1, idx2 - idx1));
            int line = output.mid(idx2 + 1).toInt();
            q->gotoLocation(file, line, true);
        }
    }
con's avatar
con committed
958
}
959
#endif
con's avatar
con committed
960

961
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
962
963
964
965
966
967
968
969
{
    // FIXME: remove this special case as soon as there's a real
    // reason given when the temporary breakpoint is hit.
    // reight now we get:
    // 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4",
    // func="foo",args=[{name="str",value="@0x7fff0f450460"}],
    // file="main.cpp",fullname="/tmp/g/main.cpp",line="37"}
    qq->notifyInferiorStopped();
970
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
971
    GdbMi frame = record.data.findChild("frame");
972
973
974
975
976
    StackFrame f;
    f.file = QString::fromLocal8Bit(frame.findChild("fullname").data());
    f.line = frame.findChild("line").data().toInt();
    f.address = _(frame.findChild("addr").data());
    q->gotoLocation(f, true);
con's avatar
con committed
977
978
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
979
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
980
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
981
982
983
984
    return reason == "exited-normally"   // inferior exited normally
        || reason == "exited-signalled"  // inferior exited because of a signal
        //|| reason == "signal-received" // inferior received signal
        || reason == "exited";           // inferior exited
con's avatar
con committed
985
986
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
987
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
988
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
989
990
991
992
993
994
995
    return reason == "function-finished"  // -exec-finish
        || reason == "signal-received"  // handled as "isExitedReason"
        || reason == "breakpoint-hit"     // -exec-continue
        || reason == "end-stepping-range" // -exec-next, -exec-step
        || reason == "location-reached"   // -exec-until
        || reason == "access-watchpoint-trigger"
        || reason == "read-watchpoint-trigger"
996
        #ifdef Q_OS_MAC
con's avatar
con committed
997
        || reason.isEmpty()
998
        #endif
con's avatar
con committed
999
1000
1001
    ;
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1002
1003
void GdbEngine::handleAqcuiredInferior()
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1004
    #ifdef Q_OS_WIN
1005
    postCommand(_("info thread"), CB(handleInfoThreads));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1006
    #elif defined(Q_OS_MAC)
1007
    postCommand(_("info pid"), NeedsStop, CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1008
1009
    #else
    postCommand(_("info proc"), CB(handleInfoProc));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1010
    #endif
1011
    if (theDebuggerBoolSetting(ListSourceFiles))
1012
        reloadSourceFiles();
1013

1014
    // Reverse debugging. FIXME: Should only be used when available.
1015
1016
    //if (theDebuggerBoolSetting(EnableReverseDebugging))
    //    postCommand(_("target record"));
1017

1018
    tryLoadDebuggingHelpers();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1019

1020
    #ifndef Q_OS_MAC
1021
    // intentionally after tryLoadDebuggingHelpers(),
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1022
    // otherwise we'd interupt solib loading.
hjk's avatar
hjk committed