gdbengine.cpp 152 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
        q, SLOT(exitDebugger()));
hjk's avatar
hjk committed
183
184
    connect(m_gdbProc, SIGNAL(started()),
        this, SLOT(startDebugger2()));
185
186
187
188
189
190
191
192
193
194

    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)));
195
196
197
198
    connect(&m_uploadProc, SIGNAL(readyReadStandardOutput()),
        this, SLOT(readUploadStandardOutput()));
    connect(&m_uploadProc, SIGNAL(readyReadStandardError()),
        this, SLOT(readUploadStandardError()));
199

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

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

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

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

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

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

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

    m_inbuffer.clear();

    m_currentFunctionArgs.clear();
    m_currentFrame.clear();
251
    m_dumperHelper.clear();
252
253
254
255
256
257
258

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

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

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

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

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

con's avatar
con committed
353
354
355
356
#if 0
static void dump(const char *first, const char *middle, const QString & to)
{
    QByteArray ba(first, middle - first);
357
    Q_UNUSED(to)
con's avatar
con committed
358
359
360
361
362
363
364
365
366
367
368
    // 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

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

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

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

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

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

    lastTime = QTime::currentTime();

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

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

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

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

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

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

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

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

535
536
        case '^': {
            GdbResultRecord record;
con's avatar
con committed
537

538
            record.token = token;
con's avatar
con committed
539

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

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

558
559
            from = inner;
            if (from != to) {
hjk's avatar
hjk committed
560
561
562
563
564
565
566
567
568
                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
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
594
595

            //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
596
597
598
599
        }
    }
}

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

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

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

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

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

629
    m_inbuffer.append(m_gdbProc->readAllStandardOutput());
con's avatar
con committed
630

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

void GdbEngine::interruptInferior()
{
656
    qq->notifyInferiorStopRequested();
657

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
    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;
    }

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

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

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

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

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

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

    // 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
832
833
834
835
}

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

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

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

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

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

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

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

936
#if 0
con's avatar
con committed
937
938
939
940
941
942
943
944
945
946
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();
947
    q->showStatusMessage(tr("Jumped. Stopped."));
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
948
    QByteArray output = record.data.findChild("logstreamoutput").data();
949
    if (output.isEmpty())
con's avatar
con committed
950
        return;
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
951
952
953
954
955
956
957
958
959
    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
960
}
961
#endif
con's avatar
con committed
962

963
void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record, const QVariant &)
con's avatar
con committed
964
965
966
967
968
969
970
971
{
    // 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();
972
    q->showStatusMessage(tr("Run to Function finished. Stopped."));
con's avatar
con committed
973
    GdbMi frame = record.data.findChild("frame");
974
975
976
977
978
    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
979
980
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
981
static bool isExitedReason(const QByteArray &reason)
con's avatar
con committed
982
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
983
984
985
986
    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
987
988
}

Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
989
static bool isStoppedReason(const QByteArray &reason)
con's avatar
con committed
990
{
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
991
992
993
994
995
996
997
    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"
998
        #ifdef Q_OS_MAC
con's avatar
con committed
999
        || reason.isEmpty()
1000
        #endif
con's avatar
con committed
1001
1002
1003
    ;
}

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

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

1020
    tryLoadDebuggingHelpers();
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
1021

1022
    #ifndef Q_OS_MAC